From f90fece9510146e075dc0090d23bf82d55e117cd Mon Sep 17 00:00:00 2001 From: Artem Goncharov Date: Fri, 29 May 2026 18:01:35 +0200 Subject: [PATCH] feat: Normalize the policy enforcer structure Fix and normalize the structure of the data passed to OPA: - target describes what the user has passed - optional "existing" field passes the current state of the resource - update the ADR to fix the design --- CONTRIBUTING.md | 14 ++- crates/core/src/policy.rs | 8 +- crates/keystone/Cargo.toml | 2 +- crates/keystone/src/api/v3/group/create.rs | 3 +- crates/keystone/src/api/v3/group/delete.rs | 3 +- crates/keystone/src/api/v3/group/list.rs | 3 +- crates/keystone/src/api/v3/group/show.rs | 3 +- crates/keystone/src/api/v3/project/create.rs | 3 +- crates/keystone/src/api/v3/project/delete.rs | 3 +- crates/keystone/src/api/v3/role/create.rs | 3 +- crates/keystone/src/api/v3/role/list.rs | 3 +- crates/keystone/src/api/v3/role/show.rs | 3 +- .../src/api/v3/role_assignment/list.rs | 3 +- crates/keystone/src/api/v3/user/create.rs | 3 +- crates/keystone/src/api/v3/user/delete.rs | 3 +- crates/keystone/src/api/v3/user/groups.rs | 3 +- crates/keystone/src/api/v3/user/list.rs | 3 +- crates/keystone/src/api/v3/user/show.rs | 3 +- .../src/api/v4/token/restriction/create.rs | 5 +- .../src/api/v4/token/restriction/delete.rs | 5 +- .../src/api/v4/token/restriction/list.rs | 6 +- .../src/api/v4/token/restriction/show.rs | 5 +- .../src/api/v4/token/restriction/update.rs | 10 +- .../api/identity_provider/create.rs | 5 +- .../api/identity_provider/delete.rs | 5 +- .../federation/api/identity_provider/list.rs | 6 +- .../federation/api/identity_provider/show.rs | 5 +- .../api/identity_provider/update.rs | 7 +- .../src/federation/api/mapping/create.rs | 5 +- .../src/federation/api/mapping/delete.rs | 5 +- .../src/federation/api/mapping/list.rs | 9 +- .../src/federation/api/mapping/show.rs | 5 +- .../src/federation/api/mapping/update.rs | 7 +- .../src/k8s_auth/api/instance/create.rs | 3 +- .../src/k8s_auth/api/instance/delete.rs | 3 +- .../src/k8s_auth/api/instance/list.rs | 4 +- .../src/k8s_auth/api/instance/show.rs | 3 +- .../src/k8s_auth/api/instance/update.rs | 5 +- .../keystone/src/k8s_auth/api/role/delete.rs | 5 +- crates/keystone/src/k8s_auth/api/role/list.rs | 6 +- crates/keystone/src/k8s_auth/api/role/show.rs | 5 +- .../keystone/src/k8s_auth/api/role/update.rs | 9 +- crates/keystone/src/policy.rs | 11 +- doc/src/adr/0002-open-policy-agent.md | 104 ++++++++++++++++++ policy/assignment/common.rego | 6 +- policy/assignment/list.rego | 13 +++ policy/assignment/list_test.rego | 11 +- policy/auth/project/list.rego | 7 ++ policy/auth/token/revoke.rego | 17 +++ policy/auth/token/show.rego | 17 +++ policy/federation/common.rego | 49 +++++++++ .../identity_provider_create.rego | 50 +++++++++ .../identity_provider_create_test.rego | 16 +++ .../identity_provider_delete.rego | 26 +++-- .../identity_provider_delete_test.rego | 16 +++ .../identity_provider_list.rego | 24 ++-- .../identity_provider_list_test.rego | 15 +++ .../identity_provider_show.rego | 55 +++++++++ .../identity_provider_show_test.rego | 15 +++ .../identity_provider_update.rego | 62 +++++++++++ .../identity_provider_update_test.rego | 16 +++ .../idp/identity_provider_create.rego | 31 ------ .../idp/identity_provider_create_test.rego | 16 --- .../idp/identity_provider_delete_test.rego | 16 --- .../idp/identity_provider_list_test.rego | 15 --- .../idp/identity_provider_show.rego | 38 ------- .../idp/identity_provider_show_test.rego | 15 --- .../idp/identity_provider_update.rego | 33 ------ .../idp/identity_provider_update_test.rego | 16 --- policy/federation/mapping/mapping_create.rego | 35 ++++-- .../mapping/mapping_create_test.rego | 18 +-- policy/federation/mapping/mapping_delete.rego | 26 +++-- .../mapping/mapping_delete_test.rego | 16 +-- policy/federation/mapping/mapping_list.rego | 25 +++-- .../federation/mapping/mapping_list_test.rego | 14 +-- policy/federation/mapping/mapping_show.rego | 43 ++++++-- .../federation/mapping/mapping_show_test.rego | 14 +-- policy/federation/mapping/mapping_update.rego | 51 +++++++-- .../mapping/mapping_update_test.rego | 16 +-- policy/identity.rego | 84 ++++++-------- policy/identity/group/create.rego | 7 ++ policy/identity/group/create_test.rego | 8 +- policy/identity/group/delete.rego | 10 ++ policy/identity/group/delete_test.rego | 8 +- policy/identity/group/list.rego | 8 ++ policy/identity/group/list_test.rego | 8 +- policy/identity/group/show.rego | 10 ++ policy/identity/group/show_test.rego | 8 +- policy/identity/user/create.rego | 12 +- policy/identity/user/create_test.rego | 8 +- policy/identity/user/delete.rego | 13 +++ policy/identity/user/delete_test.rego | 8 +- policy/identity/user/list.rego | 9 ++ policy/identity/user/list_test.rego | 8 +- policy/identity/user/show.rego | 13 +++ policy/identity/user/show_test.rego | 8 +- policy/k8s_auth/instance/create.rego | 12 +- policy/k8s_auth/instance/create_test.rego | 10 +- policy/k8s_auth/instance/delete.rego | 8 +- policy/k8s_auth/instance/delete_test.rego | 10 +- policy/k8s_auth/instance/list.rego | 10 +- policy/k8s_auth/instance/list_test.rego | 8 +- policy/k8s_auth/instance/show.rego | 15 ++- policy/k8s_auth/instance/show_test.rego | 12 +- policy/k8s_auth/instance/update.rego | 20 +++- policy/k8s_auth/instance/update_test.rego | 10 +- policy/k8s_auth/role/create.rego | 21 +++- policy/k8s_auth/role/delete.rego | 8 +- policy/k8s_auth/role/delete_test.rego | 10 +- policy/k8s_auth/role/list.rego | 11 +- policy/k8s_auth/role/list_test.rego | 8 +- policy/k8s_auth/role/show.rego | 17 ++- policy/k8s_auth/role/show_test.rego | 12 +- policy/k8s_auth/role/update.rego | 23 +++- policy/k8s_auth/role/update_test.rego | 10 +- policy/project/create.rego | 12 +- policy/role/create.rego | 9 ++ policy/role/create_test.rego | 4 +- policy/role/list.rego | 7 ++ policy/role/list_test.rego | 6 +- policy/role/show.rego | 10 ++ policy/token/common.rego | 25 +++++ policy/token/restriction/create.rego | 24 +++- policy/token/restriction/create_test.rego | 18 +-- policy/token/restriction/delete.rego | 21 ++-- policy/token/restriction/delete_test.rego | 18 +-- policy/token/restriction/list.rego | 19 +++- policy/token/restriction/list_test.rego | 8 +- policy/token/restriction/show.rego | 21 +++- policy/token/restriction/show_test.rego | 8 +- policy/token/restriction/update.rego | 30 +++-- policy/token/restriction/update_test.rego | 18 +-- policy/user/passkey/register/finish.rego | 12 +- policy/user/passkey/register/finish_test.rego | 8 +- policy/user/passkey/register/start.rego | 12 +- policy/user/passkey/register/start_test.rego | 8 +- 136 files changed, 1334 insertions(+), 599 deletions(-) create mode 100644 policy/federation/common.rego create mode 100644 policy/federation/identity_provider/identity_provider_create.rego create mode 100644 policy/federation/identity_provider/identity_provider_create_test.rego rename policy/federation/{idp => identity_provider}/identity_provider_delete.rego (60%) create mode 100644 policy/federation/identity_provider/identity_provider_delete_test.rego rename policy/federation/{idp => identity_provider}/identity_provider_list.rego (59%) create mode 100644 policy/federation/identity_provider/identity_provider_list_test.rego create mode 100644 policy/federation/identity_provider/identity_provider_show.rego create mode 100644 policy/federation/identity_provider/identity_provider_show_test.rego create mode 100644 policy/federation/identity_provider/identity_provider_update.rego create mode 100644 policy/federation/identity_provider/identity_provider_update_test.rego delete mode 100644 policy/federation/idp/identity_provider_create.rego delete mode 100644 policy/federation/idp/identity_provider_create_test.rego delete mode 100644 policy/federation/idp/identity_provider_delete_test.rego delete mode 100644 policy/federation/idp/identity_provider_list_test.rego delete mode 100644 policy/federation/idp/identity_provider_show.rego delete mode 100644 policy/federation/idp/identity_provider_show_test.rego delete mode 100644 policy/federation/idp/identity_provider_update.rego delete mode 100644 policy/federation/idp/identity_provider_update_test.rego create mode 100644 policy/token/common.rego diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 080143e2..be147ad5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -36,7 +36,11 @@ Here are some important resources: the error propagation. - **Async/Await**: The project is heavily asynchronous, built on top of `tokio`. - **Policy Enforcement**: Uses Open Policy Agent (OPA) logic, with `.rego` files - located in the `policy/` directory. + located in the `policy/` directory. The policy name passed to + `state.policy_enforcer.enforce()` corresponds to the policy's `package` + identifier with dots replaced by slashes. Policy documentation must include + the original Rust structure name (e.g., `UserCreate`) to facilitate future + updates. - Pass by reference when receiver is not supposed to take ownership. - Code should be reasonably commented. @@ -81,6 +85,14 @@ Here are some important resources: - for authentication handlers: - at least one successful unittest. - Policy Enforcement rules (`state.policy_enforcement.enforce`): + - The policy name corresponds to the Rego `package` identifier (e.g., + `identity.user.show` is found in `policy/identity/user/show.rego`) and + invoked from the API handler as `identity/user/show`. + - Input structures follow ADR-0002: + - Create: `input.target` = payload, `input.existing` = `null`. + - Update: `input.target` = patch, `input.existing` = stored resource. + - Show/Delete: `input.target` = stored resource, `input.existing` = `null`. + - List: `input.target` = query parameters, `input.existing` = `null`. - For create operation the new object is passed to enforcer before the creation. - For remove operation first the current state is fetched, it is then passed diff --git a/crates/core/src/policy.rs b/crates/core/src/policy.rs index b5c8cf54..b328d796 100644 --- a/crates/core/src/policy.rs +++ b/crates/core/src/policy.rs @@ -105,8 +105,10 @@ pub trait PolicyEnforcer: Send + Sync { /// # Parameters /// - `policy_name`: The name of the policy to enforce. /// - `credentials`: The credentials of the user requesting the action. - /// - `target`: The target resource of the action. - /// - `update`: Optional update data for the resource. + /// - `target`: The object the action is acting upon (new object for create, + /// patch for update, query params for list, `Value::Null` for show/delete). + /// - `existing`: The existing/stored object before the action (for update + /// operations), or `None` for create/list/show/delete. /// /// # Returns /// - `Ok(PolicyEvaluationResult)` if the policy was evaluated successfully. @@ -116,7 +118,7 @@ pub trait PolicyEnforcer: Send + Sync { policy_name: &'static str, credentials: &ValidatedSecurityContext, target: Value, - update: Option, + existing: Option, ) -> Result; /// Performs a health check of the policy enforcer. diff --git a/crates/keystone/Cargo.toml b/crates/keystone/Cargo.toml index bd7e0e85..845be59c 100644 --- a/crates/keystone/Cargo.toml +++ b/crates/keystone/Cargo.toml @@ -61,7 +61,7 @@ spiffe = { workspace = true, features = ["x509-source"] } spiffe-rustls = "0.6" spiffe-rustls-tokio = "0.3" thiserror.workspace = true -tokio = { workspace = true, features = ["fs", "macros", "signal", "rt-multi-thread"] } +tokio = { workspace = true, features = ["fs", "macros", "process", "signal", "rt-multi-thread"] } tokio-rustls.workspace = true tokio-util.workspace = true tonic = { workspace = true, features = ["server", "tls-aws-lc" ] } diff --git a/crates/keystone/src/api/v3/group/create.rs b/crates/keystone/src/api/v3/group/create.rs index e1cd93f1..525904ce 100644 --- a/crates/keystone/src/api/v3/group/create.rs +++ b/crates/keystone/src/api/v3/group/create.rs @@ -12,6 +12,7 @@ // // SPDX-License-Identifier: Apache-2.0 use axum::{Json, debug_handler, extract::State, http::StatusCode, response::IntoResponse}; +use serde_json::json; use validator::Validate; use super::types::{Group, GroupCreateRequest, GroupResponse}; @@ -42,7 +43,7 @@ pub async fn create( .enforce( "identity/group/create", &user_auth, - serde_json::to_value(&req.group)?, + json!({"group": req.group}), None, ) .await?; diff --git a/crates/keystone/src/api/v3/group/delete.rs b/crates/keystone/src/api/v3/group/delete.rs index 7742a127..2338fa04 100644 --- a/crates/keystone/src/api/v3/group/delete.rs +++ b/crates/keystone/src/api/v3/group/delete.rs @@ -16,6 +16,7 @@ use axum::{ http::StatusCode, response::IntoResponse, }; +use serde_json::json; use crate::api::auth::Auth; use crate::api::error::KeystoneApiError; @@ -50,7 +51,7 @@ pub async fn delete( .enforce( "identity/group/delete", &user_auth, - serde_json::to_value(¤t)?, + json!({"group": current}), None, ) .await?; diff --git a/crates/keystone/src/api/v3/group/list.rs b/crates/keystone/src/api/v3/group/list.rs index fa84e80f..248ca95a 100644 --- a/crates/keystone/src/api/v3/group/list.rs +++ b/crates/keystone/src/api/v3/group/list.rs @@ -17,6 +17,7 @@ use axum::{ http::StatusCode, response::IntoResponse, }; +use serde_json::json; use validator::Validate; use super::types::{Group, GroupList, GroupListParameters}; @@ -48,7 +49,7 @@ pub async fn list( .enforce( "identity/group/list", &user_auth, - serde_json::to_value(&query)?, + json!({"group": query}), None, ) .await?; diff --git a/crates/keystone/src/api/v3/group/show.rs b/crates/keystone/src/api/v3/group/show.rs index d312a04c..ec94830d 100644 --- a/crates/keystone/src/api/v3/group/show.rs +++ b/crates/keystone/src/api/v3/group/show.rs @@ -17,6 +17,7 @@ use axum::{ http::StatusCode, response::IntoResponse, }; +use serde_json::json; use super::types::{Group, GroupResponse}; use crate::api::auth::Auth; @@ -51,7 +52,7 @@ pub async fn show( .enforce( "identity/group/show", &user_auth, - serde_json::to_value(¤t)?, + json!({"group": current}), None, ) .await?; diff --git a/crates/keystone/src/api/v3/project/create.rs b/crates/keystone/src/api/v3/project/create.rs index 05f4764a..8fb61f72 100644 --- a/crates/keystone/src/api/v3/project/create.rs +++ b/crates/keystone/src/api/v3/project/create.rs @@ -17,6 +17,7 @@ use axum::{ http::StatusCode, response::IntoResponse, }; +use serde_json::json; use validator::Validate; use super::types::{ProjectCreateRequest, ProjectResponse}; @@ -53,7 +54,7 @@ pub(super) async fn create( .enforce( "identity/project/create", &user_auth, - serde_json::to_value(&payload.project)?, + json!({"project": payload.project}), None, ) .await?; diff --git a/crates/keystone/src/api/v3/project/delete.rs b/crates/keystone/src/api/v3/project/delete.rs index c74ad825..29c1891a 100644 --- a/crates/keystone/src/api/v3/project/delete.rs +++ b/crates/keystone/src/api/v3/project/delete.rs @@ -17,6 +17,7 @@ use axum::{ http::StatusCode, response::IntoResponse, }; +use serde_json::json; use crate::api::auth::Auth; use crate::api::error::KeystoneApiError; @@ -51,7 +52,7 @@ pub async fn remove( .enforce( "identity/project/delete", &user_auth, - serde_json::to_value(¤t)?, + json!({"project": current}), None, ) .await?; diff --git a/crates/keystone/src/api/v3/role/create.rs b/crates/keystone/src/api/v3/role/create.rs index 2f5cf914..4bb0ebbe 100644 --- a/crates/keystone/src/api/v3/role/create.rs +++ b/crates/keystone/src/api/v3/role/create.rs @@ -17,6 +17,7 @@ use axum::{ http::StatusCode, response::IntoResponse, }; +use serde_json::json; use validator::Validate; use super::types::{RoleCreateRequest, RoleResponse}; @@ -50,7 +51,7 @@ pub(super) async fn create( .enforce( "identity/role/create", &user_auth, - serde_json::to_value(&payload.role)?, + json!({"role": payload.role}), None, ) .await?; diff --git a/crates/keystone/src/api/v3/role/list.rs b/crates/keystone/src/api/v3/role/list.rs index 81451e33..7684807b 100644 --- a/crates/keystone/src/api/v3/role/list.rs +++ b/crates/keystone/src/api/v3/role/list.rs @@ -18,6 +18,7 @@ use axum::{ http::StatusCode, response::IntoResponse, }; +use serde_json::json; use super::types::{Role, RoleList, RoleListParameters}; use crate::api::auth::Auth; @@ -48,7 +49,7 @@ pub(super) async fn list( .enforce( "identity/role/list", &user_auth, - serde_json::to_value(&query)?, + json!({"role": query}), None, ) .await?; diff --git a/crates/keystone/src/api/v3/role/show.rs b/crates/keystone/src/api/v3/role/show.rs index 44b2c480..d83f7c81 100644 --- a/crates/keystone/src/api/v3/role/show.rs +++ b/crates/keystone/src/api/v3/role/show.rs @@ -18,6 +18,7 @@ use axum::{ http::StatusCode, response::IntoResponse, }; +use serde_json::json; use openstack_keystone_api_types::v3::role::{Role, RoleResponse}; @@ -55,7 +56,7 @@ pub(super) async fn show( .enforce( "identity/role/show", &user_auth, - serde_json::to_value(¤t)?, + json!({"role": current}), None, ) .await?; diff --git a/crates/keystone/src/api/v3/role_assignment/list.rs b/crates/keystone/src/api/v3/role_assignment/list.rs index c5208098..178ce38a 100644 --- a/crates/keystone/src/api/v3/role_assignment/list.rs +++ b/crates/keystone/src/api/v3/role_assignment/list.rs @@ -18,6 +18,7 @@ use axum::{ http::StatusCode, response::IntoResponse, }; +use serde_json::json; use crate::api::auth::Auth; use crate::api::error::KeystoneApiError; @@ -54,7 +55,7 @@ pub(super) async fn list( .enforce( "identity/assignment/list", &user_auth, - serde_json::to_value(&query)?, + json!({"assignment": query}), None, ) .await?; diff --git a/crates/keystone/src/api/v3/user/create.rs b/crates/keystone/src/api/v3/user/create.rs index b051d131..a4499994 100644 --- a/crates/keystone/src/api/v3/user/create.rs +++ b/crates/keystone/src/api/v3/user/create.rs @@ -18,6 +18,7 @@ use axum::{ http::StatusCode, response::IntoResponse, }; +use serde_json::json; use validator::Validate; use super::types::{User, UserCreateRequest, UserListParameters, UserResponse}; @@ -50,7 +51,7 @@ pub(super) async fn create( .enforce( "identity/user/create", &user_auth, - serde_json::to_value(&req.user)?, + json!({"user": req.user}), None, ) .await?; diff --git a/crates/keystone/src/api/v3/user/delete.rs b/crates/keystone/src/api/v3/user/delete.rs index c37314a6..20670694 100644 --- a/crates/keystone/src/api/v3/user/delete.rs +++ b/crates/keystone/src/api/v3/user/delete.rs @@ -17,6 +17,7 @@ use axum::{ http::StatusCode, response::IntoResponse, }; +use serde_json::json; use crate::api::auth::Auth; use crate::api::error::KeystoneApiError; @@ -52,7 +53,7 @@ pub(super) async fn delete( .enforce( "identity/user/delete", &user_auth, - serde_json::to_value(¤t)?, + json!({"user": current}), None, ) .await?; diff --git a/crates/keystone/src/api/v3/user/groups.rs b/crates/keystone/src/api/v3/user/groups.rs index 3cc43cd8..f4f09940 100644 --- a/crates/keystone/src/api/v3/user/groups.rs +++ b/crates/keystone/src/api/v3/user/groups.rs @@ -18,6 +18,7 @@ use axum::{ http::StatusCode, response::IntoResponse, }; +use serde_json::json; use crate::api::auth::Auth; use crate::api::error::KeystoneApiError; @@ -63,7 +64,7 @@ pub(super) async fn groups( .enforce( "identity/user/show", &user_auth, - serde_json::to_value(¤t)?, + json!({"user": current}), None, ) .await?; diff --git a/crates/keystone/src/api/v3/user/list.rs b/crates/keystone/src/api/v3/user/list.rs index b9d2b40b..41493a09 100644 --- a/crates/keystone/src/api/v3/user/list.rs +++ b/crates/keystone/src/api/v3/user/list.rs @@ -18,6 +18,7 @@ use axum::{ http::StatusCode, response::IntoResponse, }; +use serde_json::json; use validator::Validate; use super::types::{User, UserList, UserListParameters}; @@ -50,7 +51,7 @@ pub(super) async fn list( .enforce( "identity/user/list", &user_auth, - serde_json::to_value(&query)?, + json!({"user": query}), None, ) .await?; diff --git a/crates/keystone/src/api/v3/user/show.rs b/crates/keystone/src/api/v3/user/show.rs index 8a7c5d2d..96fa7b6f 100644 --- a/crates/keystone/src/api/v3/user/show.rs +++ b/crates/keystone/src/api/v3/user/show.rs @@ -18,6 +18,7 @@ use axum::{ http::StatusCode, response::IntoResponse, }; +use serde_json::json; use super::types::{User, UserResponse}; use crate::api::auth::Auth; @@ -53,7 +54,7 @@ pub(super) async fn show( .enforce( "identity/user/show", &user_auth, - serde_json::to_value(¤t)?, + json!({"user": current}), None, ) .await?; diff --git a/crates/keystone/src/api/v4/token/restriction/create.rs b/crates/keystone/src/api/v4/token/restriction/create.rs index 4ad1340e..620d1082 100644 --- a/crates/keystone/src/api/v4/token/restriction/create.rs +++ b/crates/keystone/src/api/v4/token/restriction/create.rs @@ -14,6 +14,7 @@ //! Token restriction: create. use axum::{Json, debug_handler, extract::State, http::StatusCode, response::IntoResponse}; +use serde_json::json; use validator::Validate; use crate::api::auth::Auth; @@ -54,9 +55,9 @@ pub(super) async fn create( state .policy_enforcer .enforce( - "identity/token_restriction/create", + "identity/token/token_restriction/create", &user_auth, - serde_json::to_value(&req.restriction)?, + json!({"restriction": req.restriction}), None, ) .await?; diff --git a/crates/keystone/src/api/v4/token/restriction/delete.rs b/crates/keystone/src/api/v4/token/restriction/delete.rs index 03a707ab..c281992e 100644 --- a/crates/keystone/src/api/v4/token/restriction/delete.rs +++ b/crates/keystone/src/api/v4/token/restriction/delete.rs @@ -19,6 +19,7 @@ use axum::{ http::StatusCode, response::IntoResponse, }; +use serde_json::json; use crate::api::auth::Auth; use crate::api::error::KeystoneApiError; @@ -62,9 +63,9 @@ pub(super) async fn remove( state .policy_enforcer .enforce( - "identity/token_restriction/delete", + "identity/token/token_restriction/delete", &user_auth, - serde_json::to_value(¤t)?, + json!({"restriction": current}), None, ) .await?; diff --git a/crates/keystone/src/api/v4/token/restriction/list.rs b/crates/keystone/src/api/v4/token/restriction/list.rs index 367f111c..054c3bcf 100644 --- a/crates/keystone/src/api/v4/token/restriction/list.rs +++ b/crates/keystone/src/api/v4/token/restriction/list.rs @@ -19,7 +19,7 @@ use axum::{ http::StatusCode, response::IntoResponse, }; -use serde_json::to_value; +use serde_json::json; use openstack_keystone_core_types::token::TokenRestrictionListParameters as ProviderTokenRestrictionListParameters; @@ -60,9 +60,9 @@ pub(super) async fn list( state .policy_enforcer .enforce( - "identity/token_restriction/list", + "identity/token/token_restriction/list", &user_auth, - to_value(&query)?, + json!({"restriction": query}), None, ) .await?; diff --git a/crates/keystone/src/api/v4/token/restriction/show.rs b/crates/keystone/src/api/v4/token/restriction/show.rs index 7007a48b..1f24ce27 100644 --- a/crates/keystone/src/api/v4/token/restriction/show.rs +++ b/crates/keystone/src/api/v4/token/restriction/show.rs @@ -19,6 +19,7 @@ use axum::{ http::StatusCode, response::IntoResponse, }; +use serde_json::json; use crate::api::auth::Auth; use crate::api::error::KeystoneApiError; @@ -69,9 +70,9 @@ pub(super) async fn show( state .policy_enforcer .enforce( - "identity/token_restriction/show", + "identity/token/token_restriction/show", &user_auth, - serde_json::to_value(¤t)?, + json!({"restriction": current}), None, ) .await?; diff --git a/crates/keystone/src/api/v4/token/restriction/update.rs b/crates/keystone/src/api/v4/token/restriction/update.rs index 44226a73..28df9fd3 100644 --- a/crates/keystone/src/api/v4/token/restriction/update.rs +++ b/crates/keystone/src/api/v4/token/restriction/update.rs @@ -19,6 +19,7 @@ use axum::{ http::StatusCode, response::IntoResponse, }; +use serde_json::json; use validator::Validate; use crate::api::auth::Auth; @@ -60,20 +61,21 @@ pub(super) async fn update( Json(req): Json, ) -> Result { req.validate()?; - // Fetch the current resource to pass current object into the policy evaluation + // Fetch the current resource to pass it as existing object into the policy evaluation let current = state .provider .get_token_provider() .get_token_restriction(&state, &id, false) .await?; + let existing_restriction = current.as_ref().map(|c| json!({"restriction": c})); state .policy_enforcer .enforce( - "identity/token_restriction/update", + "identity/token/token_restriction/update", &user_auth, - serde_json::to_value(¤t)?, - Some(serde_json::to_value(&req.restriction)?), + json!({"restriction": req.restriction}), + existing_restriction, ) .await?; diff --git a/crates/keystone/src/federation/api/identity_provider/create.rs b/crates/keystone/src/federation/api/identity_provider/create.rs index 10905061..835a6a59 100644 --- a/crates/keystone/src/federation/api/identity_provider/create.rs +++ b/crates/keystone/src/federation/api/identity_provider/create.rs @@ -14,6 +14,7 @@ //! Identity providers: create IDP. use axum::{Json, debug_handler, extract::State, http::StatusCode, response::IntoResponse}; +use serde_json::json; use validator::Validate; use crate::api::auth::Auth; @@ -53,9 +54,9 @@ pub(super) async fn create( state .policy_enforcer .enforce( - "identity/identity_provider_create", + "identity/federation/identity_provider/create", &user_auth, - serde_json::to_value(&req.identity_provider)?, + json!({"identity_provider": req.identity_provider}), None, ) .await?; diff --git a/crates/keystone/src/federation/api/identity_provider/delete.rs b/crates/keystone/src/federation/api/identity_provider/delete.rs index d12a01ec..ab4d5593 100644 --- a/crates/keystone/src/federation/api/identity_provider/delete.rs +++ b/crates/keystone/src/federation/api/identity_provider/delete.rs @@ -18,6 +18,7 @@ use axum::{ http::StatusCode, response::IntoResponse, }; +use serde_json::json; use crate::api::auth::Auth; use crate::api::error::KeystoneApiError; @@ -64,9 +65,9 @@ pub(super) async fn remove( state .policy_enforcer .enforce( - "identity/identity_provider_delete", + "identity/federation/identity_provider/delete", &user_auth, - serde_json::to_value(¤t)?, + json!({"identity_provider": current}), None, ) .await?; diff --git a/crates/keystone/src/federation/api/identity_provider/list.rs b/crates/keystone/src/federation/api/identity_provider/list.rs index 1db1ebe8..7f63e621 100644 --- a/crates/keystone/src/federation/api/identity_provider/list.rs +++ b/crates/keystone/src/federation/api/identity_provider/list.rs @@ -19,7 +19,7 @@ use axum::{ http::StatusCode, response::IntoResponse, }; -use serde_json::to_value; +use serde_json::json; use std::collections::HashSet; use validator::Validate; @@ -66,9 +66,9 @@ pub(super) async fn list( let res = state .policy_enforcer .enforce( - "identity/identity_provider_list", + "identity/federation/identity_provider/list", &user_auth, - to_value(&query)?, + json!({"identity_provider": query}), None, ) .await?; diff --git a/crates/keystone/src/federation/api/identity_provider/show.rs b/crates/keystone/src/federation/api/identity_provider/show.rs index 4c78b78a..5cac8b21 100644 --- a/crates/keystone/src/federation/api/identity_provider/show.rs +++ b/crates/keystone/src/federation/api/identity_provider/show.rs @@ -19,6 +19,7 @@ use axum::{ http::StatusCode, response::IntoResponse, }; +use serde_json::json; use crate::api::auth::Auth; use crate::api::error::KeystoneApiError; @@ -68,9 +69,9 @@ pub(super) async fn show( state .policy_enforcer .enforce( - "identity/identity_provider_show", + "identity/federation/identity_provider/show", &user_auth, - serde_json::to_value(¤t)?, + json!({"identity_provider": current}), None, ) .await?; diff --git a/crates/keystone/src/federation/api/identity_provider/update.rs b/crates/keystone/src/federation/api/identity_provider/update.rs index c95eba50..afc157b8 100644 --- a/crates/keystone/src/federation/api/identity_provider/update.rs +++ b/crates/keystone/src/federation/api/identity_provider/update.rs @@ -19,6 +19,7 @@ use axum::{ http::StatusCode, response::IntoResponse, }; +use serde_json::json; use validator::Validate; use crate::api::auth::Auth; @@ -66,10 +67,10 @@ pub(super) async fn update( state .policy_enforcer .enforce( - "identity/identity_provider_update", + "identity/federation/identity_provider/update", &user_auth, - serde_json::to_value(¤t)?, - Some(serde_json::to_value(&req.identity_provider)?), + json!({"identity_provider": current}), + Some(json!({"identity_provider": req.identity_provider})), ) .await?; diff --git a/crates/keystone/src/federation/api/mapping/create.rs b/crates/keystone/src/federation/api/mapping/create.rs index 65aafa97..9469d311 100644 --- a/crates/keystone/src/federation/api/mapping/create.rs +++ b/crates/keystone/src/federation/api/mapping/create.rs @@ -14,6 +14,7 @@ //! Federation attribute mapping: create. use axum::{Json, debug_handler, extract::State, http::StatusCode, response::IntoResponse}; +use serde_json::json; use validator::Validate; use crate::api::auth::Auth; @@ -43,9 +44,9 @@ pub(super) async fn create( state .policy_enforcer .enforce( - "identity/mapping_create", + "identity/federation/mapping/create", &user_auth, - serde_json::to_value(&req.mapping)?, + json!({"mapping": req.mapping}), None, ) .await?; diff --git a/crates/keystone/src/federation/api/mapping/delete.rs b/crates/keystone/src/federation/api/mapping/delete.rs index a96fee41..0bf5ded7 100644 --- a/crates/keystone/src/federation/api/mapping/delete.rs +++ b/crates/keystone/src/federation/api/mapping/delete.rs @@ -18,6 +18,7 @@ use axum::{ http::StatusCode, response::IntoResponse, }; +use serde_json::json; use crate::api::auth::Auth; use crate::api::error::KeystoneApiError; @@ -59,9 +60,9 @@ pub(super) async fn remove( state .policy_enforcer .enforce( - "identity/mapping_delete", + "identity/federation/mapping/delete", &user_auth, - serde_json::to_value(¤t)?, + json!({"mapping": current}), None, ) .await?; diff --git a/crates/keystone/src/federation/api/mapping/list.rs b/crates/keystone/src/federation/api/mapping/list.rs index c60ef44f..ee688701 100644 --- a/crates/keystone/src/federation/api/mapping/list.rs +++ b/crates/keystone/src/federation/api/mapping/list.rs @@ -19,7 +19,7 @@ use axum::{ http::StatusCode, response::IntoResponse, }; -use serde_json::to_value; +use serde_json::json; use crate::api::{KeystoneApiError, auth::Auth, common::build_pagination_links}; use crate::federation::{FederationApi, api::types::*}; @@ -59,7 +59,12 @@ pub(super) async fn list( ) -> Result { state .policy_enforcer - .enforce("identity/mapping_list", &user_auth, to_value(&query)?, None) + .enforce( + "identity/federation/mapping/list", + &user_auth, + json!({"mapping": query}), + None, + ) .await?; let mappings: Vec = state diff --git a/crates/keystone/src/federation/api/mapping/show.rs b/crates/keystone/src/federation/api/mapping/show.rs index 359fee84..d7fd55ae 100644 --- a/crates/keystone/src/federation/api/mapping/show.rs +++ b/crates/keystone/src/federation/api/mapping/show.rs @@ -19,6 +19,7 @@ use axum::{ http::StatusCode, response::IntoResponse, }; +use serde_json::json; use crate::api::auth::Auth; use crate::api::error::KeystoneApiError; @@ -68,9 +69,9 @@ pub(super) async fn show( state .policy_enforcer .enforce( - "identity/mapping_show", + "identity/federation/mapping/show", &user_auth, - serde_json::to_value(¤t)?, + json!({"mapping": current}), None, ) .await?; diff --git a/crates/keystone/src/federation/api/mapping/update.rs b/crates/keystone/src/federation/api/mapping/update.rs index 58f1c327..58119f70 100644 --- a/crates/keystone/src/federation/api/mapping/update.rs +++ b/crates/keystone/src/federation/api/mapping/update.rs @@ -19,6 +19,7 @@ use axum::{ http::StatusCode, response::IntoResponse, }; +use serde_json::json; use validator::Validate; use crate::api::auth::Auth; @@ -65,10 +66,10 @@ pub(super) async fn update( state .policy_enforcer .enforce( - "identity/mapping_update", + "identity/federation/mapping/update", &user_auth, - serde_json::to_value(¤t)?, - Some(serde_json::to_value(&req.mapping)?), + json!({"mapping": current}), + Some(json!({"mapping": req.mapping})), ) .await?; diff --git a/crates/keystone/src/k8s_auth/api/instance/create.rs b/crates/keystone/src/k8s_auth/api/instance/create.rs index 966741ab..d2f9fd6b 100644 --- a/crates/keystone/src/k8s_auth/api/instance/create.rs +++ b/crates/keystone/src/k8s_auth/api/instance/create.rs @@ -14,6 +14,7 @@ //! K8s auth instance: create. use axum::{Json, debug_handler, extract::State, http::StatusCode, response::IntoResponse}; +use serde_json::json; use validator::Validate; use openstack_keystone_api_types::k8s_auth::{ @@ -56,7 +57,7 @@ pub(super) async fn create( .enforce( "identity/k8s_auth/instance/create", &user_auth, - serde_json::to_value(&req.instance)?, + json!({"instance": req.instance}), None, ) .await?; diff --git a/crates/keystone/src/k8s_auth/api/instance/delete.rs b/crates/keystone/src/k8s_auth/api/instance/delete.rs index 7195bef8..e49586d6 100644 --- a/crates/keystone/src/k8s_auth/api/instance/delete.rs +++ b/crates/keystone/src/k8s_auth/api/instance/delete.rs @@ -18,6 +18,7 @@ use axum::{ http::StatusCode, response::IntoResponse, }; +use serde_json::json; use crate::api::auth::Auth; use crate::api::error::KeystoneApiError; @@ -63,7 +64,7 @@ pub(super) async fn remove( .enforce( "identity/k8s_auth/instance/delete", &user_auth, - serde_json::to_value(¤t)?, + json!({"instance": current}), None, ) .await?; diff --git a/crates/keystone/src/k8s_auth/api/instance/list.rs b/crates/keystone/src/k8s_auth/api/instance/list.rs index d89f975f..845115d7 100644 --- a/crates/keystone/src/k8s_auth/api/instance/list.rs +++ b/crates/keystone/src/k8s_auth/api/instance/list.rs @@ -19,7 +19,7 @@ use axum::{ http::StatusCode, response::IntoResponse, }; -use serde_json::to_value; +use serde_json::json; use validator::Validate; use openstack_keystone_api_types::k8s_auth::*; @@ -60,7 +60,7 @@ pub(super) async fn list( .enforce( "identity/k8s_auth/instance/list", &user_auth, - to_value(&query)?, + json!({"instance": query}), None, ) .await?; diff --git a/crates/keystone/src/k8s_auth/api/instance/show.rs b/crates/keystone/src/k8s_auth/api/instance/show.rs index c3c56530..ecef5dcc 100644 --- a/crates/keystone/src/k8s_auth/api/instance/show.rs +++ b/crates/keystone/src/k8s_auth/api/instance/show.rs @@ -19,6 +19,7 @@ use axum::{ http::StatusCode, response::IntoResponse, }; +use serde_json::json; use openstack_keystone_api_types::k8s_auth::*; @@ -72,7 +73,7 @@ pub(super) async fn show( .enforce( "identity/k8s_auth/instance/show", &user_auth, - serde_json::to_value(¤t)?, + json!({"instance": current}), None, ) .await?; diff --git a/crates/keystone/src/k8s_auth/api/instance/update.rs b/crates/keystone/src/k8s_auth/api/instance/update.rs index 8721f43b..98c8ed0e 100644 --- a/crates/keystone/src/k8s_auth/api/instance/update.rs +++ b/crates/keystone/src/k8s_auth/api/instance/update.rs @@ -19,6 +19,7 @@ use axum::{ http::StatusCode, response::IntoResponse, }; +use serde_json::json; use validator::Validate; use openstack_keystone_api_types::k8s_auth::*; @@ -70,8 +71,8 @@ pub(super) async fn update( .enforce( "identity/k8s_auth/instance/update", &user_auth, - serde_json::to_value(¤t)?, - Some(serde_json::to_value(&req.instance)?), + json!({"instance": current}), + Some(json!({"instance": req.instance})), ) .await?; diff --git a/crates/keystone/src/k8s_auth/api/role/delete.rs b/crates/keystone/src/k8s_auth/api/role/delete.rs index 81227475..1a57bd42 100644 --- a/crates/keystone/src/k8s_auth/api/role/delete.rs +++ b/crates/keystone/src/k8s_auth/api/role/delete.rs @@ -18,6 +18,7 @@ use axum::{ http::StatusCode, response::IntoResponse, }; +use serde_json::json; use crate::api::auth::Auth; use crate::api::error::KeystoneApiError; @@ -59,7 +60,7 @@ pub(super) async fn remove_nested( .enforce( "identity/k8s_auth/role/delete", &user_auth, - serde_json::to_value(¤t)?, + json!({"role": current}), None, ) .await?; @@ -115,7 +116,7 @@ pub(super) async fn remove( .enforce( "identity/k8s_auth/role/delete", &user_auth, - serde_json::to_value(¤t)?, + json!({"role": current}), None, ) .await?; diff --git a/crates/keystone/src/k8s_auth/api/role/list.rs b/crates/keystone/src/k8s_auth/api/role/list.rs index eb240d12..538e4eef 100644 --- a/crates/keystone/src/k8s_auth/api/role/list.rs +++ b/crates/keystone/src/k8s_auth/api/role/list.rs @@ -19,7 +19,7 @@ use axum::{ http::StatusCode, response::IntoResponse, }; -use serde_json::to_value; +use serde_json::json; use validator::Validate; use openstack_keystone_api_types::k8s_auth::*; @@ -67,7 +67,7 @@ pub(super) async fn list_nested( .enforce( "identity/k8s_auth/role/list", &user_auth, - to_value(&query)?, + json!({"role": query}), None, ) .await?; @@ -136,7 +136,7 @@ pub(super) async fn list( .enforce( "identity/k8s_auth/role/list", &user_auth, - to_value(&query)?, + json!({"role": query}), None, ) .await?; diff --git a/crates/keystone/src/k8s_auth/api/role/show.rs b/crates/keystone/src/k8s_auth/api/role/show.rs index 7ee5624b..d4560ba9 100644 --- a/crates/keystone/src/k8s_auth/api/role/show.rs +++ b/crates/keystone/src/k8s_auth/api/role/show.rs @@ -19,6 +19,7 @@ use axum::{ http::StatusCode, response::IntoResponse, }; +use serde_json::json; use openstack_keystone_api_types::k8s_auth::*; @@ -71,7 +72,7 @@ pub(super) async fn show_nested( .enforce( "identity/k8s_auth/role/show", &user_auth, - serde_json::to_value(¤t)?, + json!({"role": current}), None, ) .await?; @@ -129,7 +130,7 @@ pub(super) async fn show( .enforce( "identity/k8s_auth/role/show", &user_auth, - serde_json::to_value(¤t)?, + json!({"role": current}), None, ) .await?; diff --git a/crates/keystone/src/k8s_auth/api/role/update.rs b/crates/keystone/src/k8s_auth/api/role/update.rs index e7c2f3b6..47513790 100644 --- a/crates/keystone/src/k8s_auth/api/role/update.rs +++ b/crates/keystone/src/k8s_auth/api/role/update.rs @@ -19,6 +19,7 @@ use axum::{ http::StatusCode, response::IntoResponse, }; +use serde_json::json; use validator::Validate; use openstack_keystone_api_types::k8s_auth::{ @@ -67,8 +68,8 @@ pub(super) async fn update_nested( .enforce( "identity/k8s_auth/role/update", &user_auth, - serde_json::to_value(¤t)?, - Some(serde_json::to_value(&req.role)?), + json!({"role": current}), + Some(json!({"role": req.role})), ) .await?; @@ -125,8 +126,8 @@ pub(super) async fn update( .enforce( "identity/k8s_auth/role/update", &user_auth, - serde_json::to_value(¤t)?, - Some(serde_json::to_value(&req.role)?), + json!({"role": current}), + Some(json!({"role": req.role})), ) .await?; diff --git a/crates/keystone/src/policy.rs b/crates/keystone/src/policy.rs index 94af4400..f9b484dc 100644 --- a/crates/keystone/src/policy.rs +++ b/crates/keystone/src/policy.rs @@ -93,8 +93,11 @@ impl PolicyEnforcer for HttpPolicyEnforcer { /// # Parameters /// * `policy_name` - The name of the policy to evaluate. /// * `credentials` - The SecurityContext of the request. - /// * `target` - The target resource for the policy evaluation. - /// * `update` - Optional update data for the policy evaluation. + /// * `target` - The object the action is acting upon (new object for + /// create, patch for update, query params for list, `Value::Null` for + /// show/delete). + /// * `existing` - The existing/stored object before the action (for update + /// operations), or `None`. /// /// # Returns /// A `Result` containing the `PolicyEvaluationResult`, or a `PolicyError`. @@ -103,7 +106,7 @@ impl PolicyEnforcer for HttpPolicyEnforcer { policy_name: &'static str, credentials: &ValidatedSecurityContext, target: Value, - update: Option, + existing: Option, ) -> Result { let start = SystemTime::now(); // Convert SecurityContext into Credentials object that is passed to OPA @@ -111,7 +114,7 @@ impl PolicyEnforcer for HttpPolicyEnforcer { let input = json!({ "credentials": creds, "target": target, - "update": update, + "existing": existing.unwrap_or(Value::Null), }); let span = tracing::Span::current(); diff --git a/doc/src/adr/0002-open-policy-agent.md b/doc/src/adr/0002-open-policy-agent.md index c815953e..a8037986 100644 --- a/doc/src/adr/0002-open-policy-agent.md +++ b/doc/src/adr/0002-open-policy-agent.md @@ -42,3 +42,107 @@ Engine. - When covering existing functionality of the python Keystone policies SHOULD be converted as is and do not introduce a changed flow. + +## Standardized Policy Input Structure + +The `PolicyEnforcer` interface is standardized with the following signature: + +```rust +async fn enforce( + &self, + policy_name: &'static str, + credentials: &ValidatedSecurityContext, + target: Value, + existing: Option, +) -> Result; +``` + +The OPA input document structure is: + +```json +{ + "credentials": { "user_id": "...", "roles": [...], "domain_id": "...", ... }, + "target": { + "": + }, + "existing": { + "": + } +} +``` + +The `` key matches the REST resource type: `user`, `group`, `role`, +`project`, `instance`, `idp`, `mapping`, `restriction`, `assignment`, etc. +This prevents field name collisions between policies and ensures each resource's +data is properly isolated. + +### Field Semantics Per Operation + +The `` key matches the REST resource type: +- `user`, `group`, `role`, `project`, `instance`, `idp`, `mapping`, `restriction`, `assignment`, etc. +- This isolates data and prevents field name collisions between different resource schemas. + +Policies read fields as `input.target.user.domain_id`, +`input.existing.restriction.user_id`, `input.target.instance.name`, etc. + +Examples: +- Create user: `{"target": {"user": request_payload}}` +- Update restriction: `{"target": {"restriction": patch}, "existing": {"restriction": stored}}` +- Show group: `{"target": {"group": stored_object}}` +- List instances: `{"target": {"instance": query_params}}` + +### Implementation Details + +The handler-side contract for `enforce()`: + +- **Create**: Pass `serde_json::to_value(request_object)?` as target, `None` as + existing +- **Update**: Pass `serde_json::to_value(patch)?` as target, + `Option::from(stored_object).map(serde_json::to_value)` as existing +- **Show**: Pass `serde_json::to_value(stored_object)?` as target, `None` as + existing +- **Delete**: Pass `serde_json::to_value(stored_object)?` as target, `None` as + existing +- **List**: Pass `serde_json::to_value(query_params)?` as target, `None` as + existing + +### Policy Evaluation Guidelines + +Ownership predicates that need to work across create/show/delete/update should +resolve the `domain_id` from either target or existing: + +```rego +# Resolve domain_id from target or existing depending on operation +resource_domain_id := input.target.domain_id if { + input.target.domain_id +} +resource_domain_id := input.existing.domain_id if { + input.existing.domain_id +} + +own_resource if { + resource_domain_id != null + resource_domain_id == input.credentials.domain_id +} +``` + +Validation rules (checking user-provided data for referential integrity, e.g., +that domain/project/role IDs exist) should read from `input.target` for both +create and update operations, as `target` carries the user's request in both +cases. + +### Notes + +- The `input.existing` field is `Value::Null` when passed as `None` from the + handler, not an absent key. Policies can check `input.existing == null`. + +- The `input.target` field is never `null` except deliberately (e.g., when no + target object is relevant). For operations where the object is the existing + stored resource, `target` carries that object. + +- Policy tests (`*_test.rego`) should use the same input structure as handlers: + - Create tests: `"target": { "binding": { ... } }` + - Update tests: + `"target": { "binding": { ... } }, "existing": { "binding": { ... } }` + - Show/Delete tests: `"target": { "binding": { ... } }` + diff --git a/policy/assignment/common.rego b/policy/assignment/common.rego index b8f9d778..18e40b94 100644 --- a/policy/assignment/common.rego +++ b/policy/assignment/common.rego @@ -24,11 +24,11 @@ project_role_domain_matches if { # description: Ensure that if a domain_id is explicitly provided, it matches the scope domain_matches_scope if { - input.target.domain_id == null + input.target.assignment.domain_id == null } domain_matches_scope if { - input.target.domain_id == input.credentials.domain_id + input.target.assignment.domain_id == input.credentials.domain_id } # description: Ensure that if a project is provided, its domain matches the scope @@ -73,5 +73,5 @@ all_filters_match_scope if { # This prevents "listing all" by requiring that the target domain # is explicitly the one the user is managed in. is_scoped_to_token_domain if { - input.target.domain_id == input.credentials.domain_id + input.target.assignment.domain_id == input.credentials.domain_id } diff --git a/policy/assignment/list.rego b/policy/assignment/list.rego index 87172d33..b3ec2e18 100644 --- a/policy/assignment/list.rego +++ b/policy/assignment/list.rego @@ -6,6 +6,19 @@ package identity.assignment.list import data.identity import data.identity.assignment +# List role assignments. +# +# The `input.target.assignment` contains query parameters (RoleAssignmentListParameters): +# domain_id: string (optional) Filters the response by a domain ID. +# group_id: string (optional) Filters the response by a group ID. +# effective: bool (optional) Returns the effective assignments. +# project_id: string (optional) Filters the response by a project ID. +# role_id: string (optional) Filters the response by a role ID. +# user_id: string (optional) Filters the response by a user ID. +# include_names: bool (optional) Include names of entities. +# +# The `input.existing` is null +# default allow := false # METADATA diff --git a/policy/assignment/list_test.rego b/policy/assignment/list_test.rego index df70c923..bd37a691 100644 --- a/policy/assignment/list_test.rego +++ b/policy/assignment/list_test.rego @@ -7,27 +7,24 @@ test_allowed if { list.allow with input as {"credentials": {"roles": ["reader"], "system_scope": "all"}} list.allow with input as { "credentials": {"roles": ["admin"], "domain_id": "domain_a"}, - "target": {"domain_id": "domain_b"}, + "target": {"assignment": {"domain_id": "domain_b"}}, } list.allow with input as { "credentials": {"roles": ["manager"], "domain_id": "domain_a"}, - "target": {"domain_id": "domain_a"}, + "target": {"assignment": {"domain_id": "domain_a"}}, } list.allow with input as { "credentials": {"roles": ["manager"], "domain_id": "domain_a"}, - "target": { - "domain_id": "domain_a", - "project": {"domain_id": "domain_a"}, - }, + "target": {"assignment": {"domain_id": "domain_a"}}, } } test_forbidden if { not list.allow with input as { "credentials": {"roles": ["manager"], "domain_id": "domain_a"}, - "target": {"domain_id": "domain_b"}, + "target": {"assignment": {"domain_id": "domain_b"}}, } not list.allow with input as { diff --git a/policy/auth/project/list.rego b/policy/auth/project/list.rego index 69e881b0..1e580ef3 100644 --- a/policy/auth/project/list.rego +++ b/policy/auth/project/list.rego @@ -4,6 +4,13 @@ package identity.auth.project.list import data.identity +# List projects the authentication have access to. +# +# The `input.target.project` contains query parameters (ProjectListParameters): +# (none) +# +# The `input.existing` is null +# default allow := false allow if { diff --git a/policy/auth/token/revoke.rego b/policy/auth/token/revoke.rego index fc512d3d..60db3825 100644 --- a/policy/auth/token/revoke.rego +++ b/policy/auth/token/revoke.rego @@ -4,6 +4,23 @@ package identity.auth.token.revoke import data.identity +# Revoke authentication tokens. +# +# The `input.target.token` is the stored token object (Token): +# audit_ids: array A list of one or two audit IDs. +# methods: array The authentication methods. +# expires_at: datetime The date and time when the token expires. +# issued_at: datetime The date and time when the token was issued. +# user: object A user object. +# domain: object (optional) A domain object. +# project: object (optional) A project object. +# trust: object (optional) A trust object. +# roles: array (optional) A list of role objects. +# system: object (optional) A system object. +# catalog: object (optional) A catalog object. +# +# The `input.existing` is null +# default allow := false allow if { diff --git a/policy/auth/token/show.rego b/policy/auth/token/show.rego index 5c6a8eab..469ed24b 100644 --- a/policy/auth/token/show.rego +++ b/policy/auth/token/show.rego @@ -4,6 +4,23 @@ package identity.auth.token.show import data.identity +# View authentication token details. +# +# The `input.target.token` is the stored token object (Token): +# audit_ids: array A list of one or two audit IDs. +# methods: array The authentication methods. +# expires_at: datetime The date and time when the token expires. +# issued_at: datetime The date and time when the token was issued. +# user: object A user object. +# domain: object (optional) A domain object. +# project: object (optional) A project object. +# trust: object (optional) A trust object. +# roles: array (optional) A list of role objects. +# system: object (optional) A system object. +# catalog: object (optional) A catalog object. +# +# The `input.existing` is null +# default allow := false allow if { diff --git a/policy/federation/common.rego b/policy/federation/common.rego new file mode 100644 index 00000000..18ceed0d --- /dev/null +++ b/policy/federation/common.rego @@ -0,0 +1,49 @@ +# METADATA +# description: Common policies for federation management +package identity.federation + +import data.identity + +any_domain_id := input.target.identity_provider.domain_id if { + input.target.identity_provider.domain_id +} + +any_domain_id := input.target.mapping.domain_id if { + input.target.mapping.domain_id +} + +global_idp if { + not input.target.identity_provider.domain_id +} + +global_idp if { + input.target.identity_provider.domain_id == null +} + +own_idp if { + input.target.identity_provider.domain_id != null + input.target.identity_provider.domain_id == input.credentials.domain_id +} + +foreign_idp if { + input.target.identity_provider.domain_id != null + input.target.identity_provider.domain_id != input.credentials.domain_id +} + +global_mapping if { + not input.target.mapping.domain_id +} + +global_mapping if { + input.target.mapping.domain_id == null +} + +own_mapping if { + input.target.mapping.domain_id != null + input.target.mapping.domain_id == input.credentials.domain_id +} + +foreign_mapping if { + input.target.mapping.domain_id != null + input.target.mapping.domain_id != input.credentials.domain_id +} diff --git a/policy/federation/identity_provider/identity_provider_create.rego b/policy/federation/identity_provider/identity_provider_create.rego new file mode 100644 index 00000000..b6d3c491 --- /dev/null +++ b/policy/federation/identity_provider/identity_provider_create.rego @@ -0,0 +1,50 @@ +# METADATA +# description: Policy for creating identity providers +package identity.federation.identity_provider.create + +import data.identity.federation as common_federation + +# Create identity provider. +# +# The `input.target.identity_provider` is the new IDP object (IdentityProviderCreate): +# name: string Identity provider name. +# domain_id: string (optional) The ID of the domain this identity provider belongs to. +# enabled: bool Identity provider `enabled` property. +# oidc_discovery_url: string (optional) OIDC discovery endpoint. +# oidc_client_id: string (optional) The oidc `client_id`. +# oidc_client_secret: string (optional) The oidc `client_secret`. +# oidc_response_mode: string (optional) The oidc response mode. +# oidc_response_types: array (optional) List of supported response types. +# jwks_url: string (optional) URL to fetch JsonWebKeySet. +# jwt_validation_pubkeys: array (optional) List of the jwt validation public keys. +# bound_issuer: string (optional) The bound issuer. +# default_mapping_name: string (optional) Default attribute mapping name. +# provider_config: object (optional) Additional special provider specific configuration. +# +# The `input.existing` is null +# +default allow := false + +allow if { + "admin" in input.credentials.roles +} + +allow if { + common_federation.own_idp + "manager" in input.credentials.roles +} + +violation contains {"field": "domain_id", "msg": "creating identity provider for other domain requires `admin` role."} if { + common_federation.foreign_idp + not "admin" in input.credentials.roles +} + +violation contains {"field": "role", "msg": "creating global identity provider requires `admin` role."} if { + common_federation.global_idp + not "admin" in input.credentials.roles +} + +violation contains {"field": "role", "msg": "creating identity provider requires `manager` role."} if { + common_federation.own_idp + not "member" in input.credentials.roles +} diff --git a/policy/federation/identity_provider/identity_provider_create_test.rego b/policy/federation/identity_provider/identity_provider_create_test.rego new file mode 100644 index 00000000..9bfa536a --- /dev/null +++ b/policy/federation/identity_provider/identity_provider_create_test.rego @@ -0,0 +1,16 @@ +package test_identity_provider_create + +import data.identity.federation.identity_provider.create + +test_allowed if { + create.allow with input as {"credentials": {"roles": ["admin"]}} + create.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"identity_provider": {"domain_id": "domain"}}} + create.allow with input as {"credentials": {"roles": ["admin"]}, "target": {"identity_provider": {"domain_id": null}}} +} + +test_forbidden if { + not create.allow with input as {"credentials": {"roles": []}} + not create.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain"}, "target": {"identity_provider": {"domain_id": "domain"}}} + not create.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"identity_provider": {"domain_id": "other_domain"}}} + not create.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"identity_provider": {"domain_id": null}}} +} diff --git a/policy/federation/idp/identity_provider_delete.rego b/policy/federation/identity_provider/identity_provider_delete.rego similarity index 60% rename from policy/federation/idp/identity_provider_delete.rego rename to policy/federation/identity_provider/identity_provider_delete.rego index 4b830df9..8b7d1e2a 100644 --- a/policy/federation/idp/identity_provider_delete.rego +++ b/policy/federation/identity_provider/identity_provider_delete.rego @@ -1,11 +1,17 @@ # METADATA # description: Policy for deleting identity providers -package identity.identity_provider_delete - -import data.identity - -# Show identity provider. - +package identity.federation.identity_provider.delete + +import data.identity.federation as common_federation + +# Delete identity provider. +# +# The `input.target.identity_provider` is the stored IDP object (IdentityProvider): +# domain_id: string Domain ID +# id: string IDP ID +# +# The `input.existing` is null +# default allow := false allow if { @@ -13,21 +19,21 @@ allow if { } allow if { - identity.own_idp + common_federation.own_idp "manager" in input.credentials.roles } violation contains {"field": "domain_id", "msg": "deleting the global identity provider requires `admin` role."} if { - identity.global_idp + common_federation.global_idp not "admin" in input.credentials.roles } violation contains {"field": "role", "msg": "deleting the identity provider owned by the other domain requires `admin` role."} if { - identity.foreign_idp + common_federation.foreign_idp not "admin" in input.credentials.roles } violation contains {"field": "role", "msg": "deleting the identity provider requires `manager` role."} if { - identity.own_idp + common_federation.own_idp not "manager" in input.credentials.roles } diff --git a/policy/federation/identity_provider/identity_provider_delete_test.rego b/policy/federation/identity_provider/identity_provider_delete_test.rego new file mode 100644 index 00000000..ba9cc84b --- /dev/null +++ b/policy/federation/identity_provider/identity_provider_delete_test.rego @@ -0,0 +1,16 @@ +package test_identity_provider_delete + +import data.identity.federation.identity_provider.delete + +test_allowed if { + delete.allow with input as {"credentials": {"roles": ["admin"]}} + delete.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"identity_provider": {"domain_id": "domain"}}} + delete.allow with input as {"credentials": {"roles": ["admin"]}, "target": {"identity_provider": {"domain_id": null}}} +} + +test_forbidden if { + not delete.allow with input as {"credentials": {"roles": []}} + not delete.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain"}, "target": {"identity_provider": {"domain_id": "domain"}}} + not delete.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"identity_provider": {"domain_id": "other_domain"}}} + not delete.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"identity_provider": {"domain_id": null}}} +} diff --git a/policy/federation/idp/identity_provider_list.rego b/policy/federation/identity_provider/identity_provider_list.rego similarity index 59% rename from policy/federation/idp/identity_provider_list.rego rename to policy/federation/identity_provider/identity_provider_list.rego index 194ddc5e..0d1c03a2 100644 --- a/policy/federation/idp/identity_provider_list.rego +++ b/policy/federation/identity_provider/identity_provider_list.rego @@ -1,11 +1,19 @@ # METADATA # description: Policy for listing identity providers -package identity.identity_provider_list +package identity.federation.identity_provider.list -import data.identity +import data.identity.federation as common_federation # List identity providers - +# +# The `input.target.identity_provider` contains query parameters (IdentityProviderListParameters): +# name: string (optional) Filters the response by IDP name. +# domain_id: string (optional) Filters the response by a domain ID. +# limit: integer (optional) Limit number of entries. +# marker: string (optional) Page marker. +# +# The `input.existing` is null +# default allow := false default can_see_other_domain_resources := false @@ -15,12 +23,12 @@ can_see_other_domain_resources if { } allow if { - identity.own_idp + common_federation.own_idp "reader" in input.credentials.roles } allow if { - identity.global_idp + common_federation.global_idp "reader" in input.credentials.roles } @@ -29,16 +37,16 @@ allow if { } violation contains {"field": "domain_id", "msg": "listing federated identity providers owned by other domain requires `admin` role."} if { - identity.foreign_identity_provider + common_federation.foreign_idp not "admin" in input.credentials.roles } violation contains {"field": "role", "msg": "listing federated identity providers owned by the domain requires `reader` role."} if { - identity.own_idp + common_federation.own_idp not "reader" in input.credentials.roles } violation contains {"field": "role", "msg": "listing global federated identity providers requires `reader` role."} if { - identity.global_idp + common_federation.global_idp not "reader" in input.credentials.roles } diff --git a/policy/federation/identity_provider/identity_provider_list_test.rego b/policy/federation/identity_provider/identity_provider_list_test.rego new file mode 100644 index 00000000..1999626c --- /dev/null +++ b/policy/federation/identity_provider/identity_provider_list_test.rego @@ -0,0 +1,15 @@ +package test_identity_provider_list + +import data.identity.federation.identity_provider.list + +test_allowed if { + list.allow with input as {"credentials": {"roles": ["admin"]}} + list.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain"}, "target": {"identity_provider": {"domain_id": "domain"}}} + list.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain"}, "target": {"identity_provider": {"domain_id": null}}} +} + +test_forbidden if { + not list.allow with input as {"credentials": {"roles": []}} + not list.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain"}, "target": {"identity_provider": {"domain_id": "other_domain"}}} + not list.allow with input as {"credentials": {"roles": ["member"], "domain_id": "domain"}, "target": {"identity_provider": {"domain_id": "other_domain"}}} +} diff --git a/policy/federation/identity_provider/identity_provider_show.rego b/policy/federation/identity_provider/identity_provider_show.rego new file mode 100644 index 00000000..586c3ad0 --- /dev/null +++ b/policy/federation/identity_provider/identity_provider_show.rego @@ -0,0 +1,55 @@ +# METADATA +# description: Policy for viewing identity provider details +package identity.federation.identity_provider.show + +import data.identity.federation as common_federation + +# Show identity provider. +# +# The `input.target.identity_provider` is the stored IDP object (IdentityProvider): +# id: string The ID of the federated identity provider. +# name: string The Name of the federated identity provider. +# domain_id: string (optional) The ID of the domain this identity provider belongs to. +# enabled: bool Identity provider `enabled` property. +# oidc_discovery_url: string (optional) OIDC discovery endpoint. +# oidc_client_id: string (optional) The oidc `client_id`. +# oidc_response_mode: string (optional) The oidc response mode. +# oidc_response_types: array (optional) List of supported response types. +# jwks_url: string (optional) URL to fetch JsonWebKeySet. +# jwt_validation_pubkeys: array (optional) List of the jwt validation public keys. +# bound_issuer: string (optional) The bound issuer. +# default_mapping_name: string (optional) Default attribute mapping name. +# provider_config: object (optional) Additional provider configuration. +# +# The `input.existing` is null +# +default allow := false + +allow if { + "admin" in input.credentials.roles +} + +allow if { + common_federation.own_idp + "reader" in input.credentials.roles +} + +allow if { + common_federation.global_idp + "reader" in input.credentials.roles +} + +violation contains {"field": "domain_id", "msg": "fetching identity provider details owned by other domain requires `admin` role."} if { + common_federation.foreign_idp + not "admin" in input.credentials.roles +} + +violation contains {"field": "role", "msg": "fetching own identity provider details requires `reader`."} if { + common_federation.own_idp + not "reader" in input.credentials.roles +} + +violation contains {"field": "role", "msg": "fetching global identity provider details requires `reader`."} if { + common_federation.global_idp + not "reader" in input.credentials.roles +} diff --git a/policy/federation/identity_provider/identity_provider_show_test.rego b/policy/federation/identity_provider/identity_provider_show_test.rego new file mode 100644 index 00000000..2e6cbc79 --- /dev/null +++ b/policy/federation/identity_provider/identity_provider_show_test.rego @@ -0,0 +1,15 @@ +package test_identity_provider_show + +import data.identity.federation.identity_provider.show + +test_allowed if { + show.allow with input as {"credentials": {"roles": ["admin"]}} + show.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain"}, "target": {"identity_provider": {"domain_id": "domain"}}} + show.allow with input as {"credentials": {"roles": ["reader"]}, "target": {"identity_provider": {"domain_id": null}}} +} + +test_forbidden if { + not show.allow with input as {"credentials": {"roles": []}} + not show.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain"}, "target": {"identity_provider": {"domain_id": "other_domain"}}} + not show.allow with input as {"credentials": {"roles": ["member"], "domain_id": "domain"}, "target": {"identity_provider": {"domain_id": "other_domain"}}} +} diff --git a/policy/federation/identity_provider/identity_provider_update.rego b/policy/federation/identity_provider/identity_provider_update.rego new file mode 100644 index 00000000..3b6c60e7 --- /dev/null +++ b/policy/federation/identity_provider/identity_provider_update.rego @@ -0,0 +1,62 @@ +# METADATA +# description: Policy for updating identity providers +package identity.federation.identity_provider.update + +import data.identity.federation as common_federation + +# Update identity provider. +# +# The `input.target.identity_provider` is the update patch (IdentityProviderUpdate): +# name: string (optional) The new name. +# enabled: bool (optional) Identity provider `enabled` property. +# oidc_discovery_url: string (optional) The new OIDC discovery endpoint. +# oidc_client_id: string (optional) The new oidc `client_id`. +# oidc_client_secret: string (optional) The new oidc `client_secret`. +# oidc_response_mode: string (optional) The new oidc response mode. +# oidc_response_types: array (optional) The new oidc response types. +# jwks_url: string (optional) New URL to fetch JsonWebKeySet. +# jwt_validation_pubkeys: array (optional) The list of the jwt validation public keys. +# bound_issuer: string (optional) The new bound issuer. +# default_mapping_name: string (optional) New default attribute mapping name. +# provider_config: object (optional) New additional provider configuration. +# +# The `input.existing.identity_provider` is the stored IDP object (IdentityProvider): +# id: string The ID of the federated identity provider. +# name: string The Name of the federated identity provider. +# domain_id: string (optional) The ID of the domain this identity provider belongs to. +# enabled: bool Identity provider `enabled` property. +# oidc_discovery_url: string (optional) OIDC discovery endpoint. +# oidc_client_id: string (optional) The oidc `client_id`. +# oidc_response_mode: string (optional) The oidc response mode. +# oidc_response_types: array (optional) List of supported response types. +# jwks_url: string (optional) URL to fetch JsonWebKeySet. +# jwt_validation_pubkeys: array (optional) List of the jwt validation public keys. +# bound_issuer: string (optional) The bound issuer. +# default_mapping_name: string (optional) Default attribute mapping name. +# provider_config: object (optional) Additional provider configuration. +# +default allow := false + +allow if { + "admin" in input.credentials.roles +} + +allow if { + common_federation.own_idp + "manager" in input.credentials.roles +} + +violation contains {"field": "domain_id", "msg": "updating identity provider for other domain requires `admin` role."} if { + common_federation.foreign_idp + not "admin" in input.credentials.roles +} + +violation contains {"field": "role", "msg": "updating global identity provider requires `admin` role."} if { + common_federation.global_idp + not "admin" in input.credentials.roles +} + +violation contains {"field": "role", "msg": "updating identity provider requires `manager` role."} if { + common_federation.own_idp + not "member" in input.credentials.roles +} diff --git a/policy/federation/identity_provider/identity_provider_update_test.rego b/policy/federation/identity_provider/identity_provider_update_test.rego new file mode 100644 index 00000000..736bb80e --- /dev/null +++ b/policy/federation/identity_provider/identity_provider_update_test.rego @@ -0,0 +1,16 @@ +package test_identity_provider_update + +import data.identity.federation.identity_provider.update + +test_allowed if { + update.allow with input as {"credentials": {"roles": ["admin"]}} + update.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"identity_provider": {"domain_id": "domain"}}} + update.allow with input as {"credentials": {"roles": ["admin"]}, "target": {"identity_provider": {"domain_id": null}}} +} + +test_forbidden if { + not update.allow with input as {"credentials": {"roles": []}} + not update.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain"}, "target": {"identity_provider": {"domain_id": "domain"}}} + not update.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"identity_provider": {"domain_id": "other_domain"}}} + not update.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"identity_provider": {"domain_id": null}}} +} diff --git a/policy/federation/idp/identity_provider_create.rego b/policy/federation/idp/identity_provider_create.rego deleted file mode 100644 index d6ab1b6f..00000000 --- a/policy/federation/idp/identity_provider_create.rego +++ /dev/null @@ -1,31 +0,0 @@ -# METADATA -# description: Policy for creating identity providers -package identity.identity_provider_create - -import data.identity - -default allow := false - -allow if { - "admin" in input.credentials.roles -} - -allow if { - identity.own_idp - "manager" in input.credentials.roles -} - -violation contains {"field": "domain_id", "msg": "creating identity provider for other domain requires `admin` role."} if { - identity.foreign_idp - not "admin" in input.credentials.roles -} - -violation contains {"field": "role", "msg": "creating global identity provider requires `admin` role."} if { - identity.global_idp - not "admin" in input.credentials.roles -} - -violation contains {"field": "role", "msg": "creating identity provider requires `manager` role."} if { - identity.own_idp - not "member" in input.credentials.roles -} diff --git a/policy/federation/idp/identity_provider_create_test.rego b/policy/federation/idp/identity_provider_create_test.rego deleted file mode 100644 index 1fb86c6a..00000000 --- a/policy/federation/idp/identity_provider_create_test.rego +++ /dev/null @@ -1,16 +0,0 @@ -package test_identity_provider_create - -import data.identity.identity_provider_create - -test_allowed if { - identity_provider_create.allow with input as {"credentials": {"roles": ["admin"]}} - identity_provider_create.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"domain_id": "domain"}} - identity_provider_create.allow with input as {"credentials": {"roles": ["admin"]}, "target": {"domain_id": null}} -} - -test_forbidden if { - not identity_provider_create.allow with input as {"credentials": {"roles": []}} - not identity_provider_create.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain"}, "target": {"domain_id": "domain"}} - not identity_provider_create.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"domain_id": "other_domain"}} - not identity_provider_create.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"domain_id": null}} -} diff --git a/policy/federation/idp/identity_provider_delete_test.rego b/policy/federation/idp/identity_provider_delete_test.rego deleted file mode 100644 index fb1b9b35..00000000 --- a/policy/federation/idp/identity_provider_delete_test.rego +++ /dev/null @@ -1,16 +0,0 @@ -package test_identity_provider_delete - -import data.identity.identity_provider_delete - -test_allowed if { - identity_provider_delete.allow with input as {"credentials": {"roles": ["admin"]}} - identity_provider_delete.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"domain_id": "domain"}} - identity_provider_delete.allow with input as {"credentials": {"roles": ["admin"]}, "target": {"domain_id": null}} -} - -test_forbidden if { - not identity_provider_delete.allow with input as {"credentials": {"roles": []}} - not identity_provider_delete.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain"}, "target": {"domain_id": "domain"}} - not identity_provider_delete.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"domain_id": "other_domain"}} - not identity_provider_delete.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"domain_id": null}} -} diff --git a/policy/federation/idp/identity_provider_list_test.rego b/policy/federation/idp/identity_provider_list_test.rego deleted file mode 100644 index dbe682f3..00000000 --- a/policy/federation/idp/identity_provider_list_test.rego +++ /dev/null @@ -1,15 +0,0 @@ -package test_identity_provider_list - -import data.identity.identity_provider_list - -test_allowed if { - identity_provider_list.allow with input as {"credentials": {"roles": ["admin"]}} - identity_provider_list.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain"}, "target": {"domain_id": "domain"}} - identity_provider_list.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain"}, "target": {"domain_id": null}} -} - -test_forbidden if { - not identity_provider_list.allow with input as {"credentials": {"roles": []}} - not identity_provider_list.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain"}, "target": {"domain_id": "other_domain"}} - not identity_provider_list.allow with input as {"credentials": {"roles": ["member"], "domain_id": "domain"}, "target": {"domain_id": "other_domain"}} -} diff --git a/policy/federation/idp/identity_provider_show.rego b/policy/federation/idp/identity_provider_show.rego deleted file mode 100644 index b25b6ebe..00000000 --- a/policy/federation/idp/identity_provider_show.rego +++ /dev/null @@ -1,38 +0,0 @@ -# METADATA -# description: Policy for viewing identity provider details -package identity.identity_provider_show - -import data.identity - -# Show identity provider. - -default allow := false - -allow if { - "admin" in input.credentials.roles -} - -allow if { - identity.own_idp - "reader" in input.credentials.roles -} - -allow if { - identity.global_idp - "reader" in input.credentials.roles -} - -violation contains {"field": "domain_id", "msg": "fetching identity provider details owned by other domain requires `admin` role."} if { - identity.foreign_idp - not "admin" in input.credentials.roles -} - -violation contains {"field": "role", "msg": "fetching own identity provider details requires `reader`."} if { - identity.own_idp - not "reader" in input.credentials.roles -} - -violation contains {"field": "role", "msg": "fetching global identity provider details requires `reader`."} if { - identity.global_idp - not "reader" in input.credentials.roles -} diff --git a/policy/federation/idp/identity_provider_show_test.rego b/policy/federation/idp/identity_provider_show_test.rego deleted file mode 100644 index b4bbd21b..00000000 --- a/policy/federation/idp/identity_provider_show_test.rego +++ /dev/null @@ -1,15 +0,0 @@ -package test_identity_provider_show - -import data.identity.identity_provider_show - -test_allowed if { - identity_provider_show.allow with input as {"credentials": {"roles": ["admin"]}} - identity_provider_show.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain"}, "target": {"domain_id": "domain"}} - identity_provider_show.allow with input as {"credentials": {"roles": ["reader"]}, "target": {"domain_id": null}} -} - -test_forbidden if { - not identity_provider_show.allow with input as {"credentials": {"roles": []}} - not identity_provider_show.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain"}, "target": {"domain_id": "other_domain"}} - not identity_provider_show.allow with input as {"credentials": {"roles": ["member"], "domain_id": "domain"}, "target": {"domain_id": "other_domain"}} -} diff --git a/policy/federation/idp/identity_provider_update.rego b/policy/federation/idp/identity_provider_update.rego deleted file mode 100644 index 78735506..00000000 --- a/policy/federation/idp/identity_provider_update.rego +++ /dev/null @@ -1,33 +0,0 @@ -# METADATA -# description: Policy for updating identity providers -package identity.identity_provider_update - -import data.identity - -# Update identity provider. - -default allow := false - -allow if { - "admin" in input.credentials.roles -} - -allow if { - identity.own_idp - "manager" in input.credentials.roles -} - -violation contains {"field": "domain_id", "msg": "updating identity provider for other domain requires `admin` role."} if { - identity.foreign_idp - not "admin" in input.credentials.roles -} - -violation contains {"field": "role", "msg": "updating global identity provider requires `admin` role."} if { - identity.global_idp - not "admin" in input.credentials.roles -} - -violation contains {"field": "role", "msg": "updating identity provider requires `manager` role."} if { - identity.own_idp - not "member" in input.credentials.roles -} diff --git a/policy/federation/idp/identity_provider_update_test.rego b/policy/federation/idp/identity_provider_update_test.rego deleted file mode 100644 index 3e31f222..00000000 --- a/policy/federation/idp/identity_provider_update_test.rego +++ /dev/null @@ -1,16 +0,0 @@ -package test_identity_provider_update - -import data.identity.identity_provider_update - -test_allowed if { - identity_provider_update.allow with input as {"credentials": {"roles": ["admin"]}} - identity_provider_update.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"domain_id": "domain"}} - identity_provider_update.allow with input as {"credentials": {"roles": ["admin"]}, "target": {"domain_id": null}} -} - -test_forbidden if { - not identity_provider_update.allow with input as {"credentials": {"roles": []}} - not identity_provider_update.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain"}, "target": {"domain_id": "domain"}} - not identity_provider_update.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"domain_id": "other_domain"}} - not identity_provider_update.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"domain_id": null}} -} diff --git a/policy/federation/mapping/mapping_create.rego b/policy/federation/mapping/mapping_create.rego index ddbe307e..d2754a6f 100644 --- a/policy/federation/mapping/mapping_create.rego +++ b/policy/federation/mapping/mapping_create.rego @@ -1,11 +1,32 @@ # METADATA # description: Policy for creating federation mappings -package identity.mapping_create +package identity.federation.mapping.create -import data.identity +import data.identity.federation as common_federation # Create mapping. - +# +# The `input.target.mapping` is the new mapping object (MappingCreate): +# id: string (optional) Attribute mapping ID. +# name: string Attribute mapping name. +# domain_id: string (optional) `domain_id` owning the attribute mapping. +# idp_id: string ID of the federated identity provider. +# type: string (optional) Attribute mapping type ([oidc, jwt]). +# enabled: bool Mapping enabled property. +# allowed_redirect_uris: array (optional) List of allowed redirect urls. +# user_id_claim: string `user_id` claim name. +# user_name_claim: string `user_name` claim name. +# domain_id_claim: string (optional) `domain_id` claim name. +# groups_claim: string (optional) `groups` claim name. +# bound_audiences: array (optional) List of audiences. +# bound_subject: string (optional) Token subject value. +# bound_claims: object Additional claims. +# oidc_scopes: array (optional) List of OIDC scopes. +# token_project_id: string (optional) Fixed project_id for the token. +# token_restriction_id: string (optional) Token restrictions. +# +# The `input.existing` is null +# default allow := false allow if { @@ -13,21 +34,21 @@ allow if { } allow if { - identity.own_mapping + common_federation.own_mapping "manager" in input.credentials.roles } violation contains {"field": "domain_id", "msg": "creating mapping for other domain requires `admin` role."} if { - identity.foreign_mapping + common_federation.foreign_mapping not "admin" in input.credentials.roles } violation contains {"field": "role", "msg": "creating global mapping requires `admin` role."} if { - identity.global_mapping + common_federation.global_mapping not "admin" in input.credentials.roles } violation contains {"field": "role", "msg": "creating mapping requires `manager` role."} if { - identity.own_mapping + common_federation.own_mapping not "member" in input.credentials.roles } diff --git a/policy/federation/mapping/mapping_create_test.rego b/policy/federation/mapping/mapping_create_test.rego index 40696fc9..f33a8576 100644 --- a/policy/federation/mapping/mapping_create_test.rego +++ b/policy/federation/mapping/mapping_create_test.rego @@ -1,16 +1,16 @@ -package test_mapping_create +package test_create -import data.identity.mapping_create +import data.identity.federation.mapping.create test_allowed if { - mapping_create.allow with input as {"credentials": {"roles": ["admin"]}} - mapping_create.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"domain_id": "domain"}} - mapping_create.allow with input as {"credentials": {"roles": ["admin"]}, "target": {"domain_id": null}} + create.allow with input as {"credentials": {"roles": ["admin"]}} + create.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"mapping": {"domain_id": "domain"}}} + create.allow with input as {"credentials": {"roles": ["admin"]}, "target": {"mapping": {"domain_id": null}}} } test_forbidden if { - not mapping_create.allow with input as {"credentials": {"roles": []}} - not mapping_create.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain"}, "target": {"domain_id": "domain"}} - not mapping_create.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"domain_id": "other_domain"}} - not mapping_create.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"domain_id": null}} + not create.allow with input as {"credentials": {"roles": []}} + not create.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain"}, "target": {"mapping": {"domain_id": "domain"}}} + not create.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"mapping": {"domain_id": "other_domain"}}} + not create.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"mapping": {"domain_id": null}}} } diff --git a/policy/federation/mapping/mapping_delete.rego b/policy/federation/mapping/mapping_delete.rego index 18a8e0ce..81e175f7 100644 --- a/policy/federation/mapping/mapping_delete.rego +++ b/policy/federation/mapping/mapping_delete.rego @@ -1,11 +1,17 @@ # METADATA # description: Policy for deleting federation mappings -package identity.mapping_delete - -import data.identity - -# Show mapping. - +package identity.federation.mapping.delete + +import data.identity.federation as common_federation + +# Delete mapping. +# +# The `input.target.mapping` is the stored mapping object (Mapping): +# domain_id: string domain ID +# id: string mapping ID +# +# The `input.existing` is null +# default allow := false allow if { @@ -13,21 +19,21 @@ allow if { } allow if { - identity.own_mapping + common_federation.own_mapping "manager" in input.credentials.roles } violation contains {"field": "domain_id", "msg": "deleting the global mapping requires `admin` role."} if { - identity.global_mapping + common_federation.global_mapping not "admin" in input.credentials.roles } violation contains {"field": "role", "msg": "deleting the mapping owned by the other domain requires `admin` role."} if { - identity.foreign_mapping + common_federation.foreign_mapping not "admin" in input.credentials.roles } violation contains {"field": "role", "msg": "deleting the mapping requires `manager` role."} if { - identity.own_mapping + common_federation.own_mapping not "manager" in input.credentials.roles } diff --git a/policy/federation/mapping/mapping_delete_test.rego b/policy/federation/mapping/mapping_delete_test.rego index 95f8f327..be361e68 100644 --- a/policy/federation/mapping/mapping_delete_test.rego +++ b/policy/federation/mapping/mapping_delete_test.rego @@ -1,16 +1,16 @@ package test_mapping_delete -import data.identity.mapping_delete +import data.identity.federation.mapping.delete test_allowed if { - mapping_delete.allow with input as {"credentials": {"roles": ["admin"]}} - mapping_delete.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"domain_id": "domain"}} - mapping_delete.allow with input as {"credentials": {"roles": ["admin"]}, "target": {"domain_id": null}} + delete.allow with input as {"credentials": {"roles": ["admin"]}} + delete.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"mapping": {"domain_id": "domain"}}} + delete.allow with input as {"credentials": {"roles": ["admin"]}, "target": {"mapping": {"domain_id": null}}} } test_forbidden if { - not mapping_delete.allow with input as {"credentials": {"roles": []}} - not mapping_delete.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain"}, "target": {"domain_id": "domain"}} - not mapping_delete.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"domain_id": "other_domain"}} - not mapping_delete.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"domain_id": null}} + not delete.allow with input as {"credentials": {"roles": []}} + not delete.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain"}, "target": {"mapping": {"domain_id": "domain"}}} + not delete.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"mapping": {"domain_id": "other_domain"}}} + not delete.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"mapping": {"domain_id": null}}} } diff --git a/policy/federation/mapping/mapping_list.rego b/policy/federation/mapping/mapping_list.rego index 6faf0f43..b057255c 100644 --- a/policy/federation/mapping/mapping_list.rego +++ b/policy/federation/mapping/mapping_list.rego @@ -1,20 +1,31 @@ # METADATA # description: Policy for listing federation mappings -package identity.mapping_list +package identity.federation.mapping.list import data.identity +import data.identity.federation as common_federation # List mappings. - +# +# The `input.target.mapping` contains query parameters (MappingListParameters): +# domain_id: string (optional) Filters the response by a domain ID. +# idp_id: string (optional) Filters the response by a idp ID. +# name: string (optional) Filters the response by IDP name. +# limit: integer (optional) Limit number of entries. +# marker: string (optional) Page marker. +# type: string (optional) Filters the response by a mapping type. +# +# The `input.existing` is null +# default allow := false allow if { - identity.own_mapping + common_federation.own_mapping "reader" in input.credentials.roles } allow if { - identity.global_mapping + common_federation.global_mapping "reader" in input.credentials.roles } @@ -23,16 +34,16 @@ allow if { } violation contains {"field": "domain_id", "msg": "listing federated mappings owned by other domain requires `admin` role."} if { - identity.foreign_mapping + common_federation.foreign_mapping not "admin" in input.credentials.roles } violation contains {"field": "role", "msg": "listing federated mappings owned by the domain requires `reader` role."} if { - identity.own_mapping + common_federation.own_mapping not "reader" in input.credentials.roles } violation contains {"field": "role", "msg": "listing global federated mappings requires `reader` role."} if { - identity.global_mapping + common_federation.global_mapping not "reader" in input.credentials.roles } diff --git a/policy/federation/mapping/mapping_list_test.rego b/policy/federation/mapping/mapping_list_test.rego index 4c0f5c0a..f6f310aa 100644 --- a/policy/federation/mapping/mapping_list_test.rego +++ b/policy/federation/mapping/mapping_list_test.rego @@ -1,15 +1,15 @@ package test_mapping_list -import data.identity.mapping_list +import data.identity.federation.mapping.list test_allowed if { - mapping_list.allow with input as {"credentials": {"roles": ["admin"]}} - mapping_list.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain"}, "target": {"domain_id": "domain"}} - mapping_list.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain"}, "target": {"domain_id": null}} + list.allow with input as {"credentials": {"roles": ["admin"]}} + list.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain"}, "target": {"mapping": {"domain_id": "domain"}}} + list.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain"}, "target": {"mapping": {"domain_id": null}}} } test_forbidden if { - not mapping_list.allow with input as {"credentials": {"roles": []}} - not mapping_list.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain"}, "target": {"domain_id": "other_domain"}} - not mapping_list.allow with input as {"credentials": {"roles": ["member"], "domain_id": "domain"}, "target": {"domain_id": "other_domain"}} + not list.allow with input as {"credentials": {"roles": []}} + not list.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain"}, "target": {"mapping": {"domain_id": "other_domain"}}} + not list.allow with input as {"credentials": {"roles": ["member"], "domain_id": "domain"}, "target": {"mapping": {"domain_id": "other_domain"}}} } diff --git a/policy/federation/mapping/mapping_show.rego b/policy/federation/mapping/mapping_show.rego index dd1b13ba..bb5d1b58 100644 --- a/policy/federation/mapping/mapping_show.rego +++ b/policy/federation/mapping/mapping_show.rego @@ -1,11 +1,32 @@ # METADATA # description: Policy for viewing federation mapping details -package identity.mapping_show - -import data.identity - -# Show identity provider. - +package identity.federation.mapping.show + +import data.identity.federation as common_federation + +# Show mapping. +# +# The `input.target.mapping` is the stored mapping object (Mapping): +# id: string Attribute mapping ID. +# name: string Attribute mapping name. +# domain_id: string (optional) `domain_id` owning the attribute mapping. +# idp_id: string ID of the federated identity provider. +# type: string Attribute mapping type ([oidc, jwt]). +# enabled: bool Mapping enabled property. +# allowed_redirect_uris: array (optional) List of allowed redirect urls. +# user_id_claim: string `user_id` claim name. +# user_name_claim: string `user_name` claim name. +# domain_id_claim: string (optional) `domain_id` claim name. +# groups_claim: string (optional) `groups` claim name. +# bound_audiences: array (optional) List of audiences. +# bound_subject: string (optional) Token subject value. +# bound_claims: object Additional claims. +# oidc_scopes: array (optional) List of OIDC scopes. +# token_project_id: string (optional) Fixed project_id for the token. +# token_restriction_id: string (optional) Token restrictions. +# +# The `input.existing` is null +# default allow := false allow if { @@ -13,26 +34,26 @@ allow if { } allow if { - identity.own_mapping + common_federation.own_mapping "reader" in input.credentials.roles } allow if { - identity.global_mapping + common_federation.global_mapping "reader" in input.credentials.roles } violation contains {"field": "domain_id", "msg": "fetching mapping details owned by other domain requires `admin` role."} if { - identity.foreign_mapping + common_federation.foreign_mapping not "admin" in input.credentials.roles } violation contains {"field": "role", "msg": "fetching own mapping details requires `reader`."} if { - identity.own_mapping + common_federation.own_mapping not "reader" in input.credentials.roles } violation contains {"field": "role", "msg": "fetching global mappingdetails requires `reader`."} if { - identity.global_mapping + common_federation.global_mapping not "reader" in input.credentials.roles } diff --git a/policy/federation/mapping/mapping_show_test.rego b/policy/federation/mapping/mapping_show_test.rego index 8964d57d..6b790060 100644 --- a/policy/federation/mapping/mapping_show_test.rego +++ b/policy/federation/mapping/mapping_show_test.rego @@ -1,15 +1,15 @@ package test_mapping_show -import data.identity.mapping_show +import data.identity.federation.mapping.show test_allowed if { - mapping_show.allow with input as {"credentials": {"roles": ["admin"]}} - mapping_show.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain"}, "target": {"domain_id": "domain"}} - mapping_show.allow with input as {"credentials": {"roles": ["reader"]}, "target": {"domain_id": null}} + show.allow with input as {"credentials": {"roles": ["admin"]}} + show.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain"}, "target": {"mapping": {"domain_id": "domain"}}} + show.allow with input as {"credentials": {"roles": ["reader"]}, "target": {"mapping": {"domain_id": null}}} } test_forbidden if { - not mapping_show.allow with input as {"credentials": {"roles": []}} - not mapping_show.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain"}, "target": {"domain_id": "other_domain"}} - not mapping_show.allow with input as {"credentials": {"roles": ["member"], "domain_id": "domain"}, "target": {"domain_id": "other_domain"}} + not show.allow with input as {"credentials": {"roles": []}} + not show.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain"}, "target": {"mapping": {"domain_id": "other_domain"}}} + not show.allow with input as {"credentials": {"roles": ["member"], "domain_id": "domain"}, "target": {"mapping": {"domain_id": "other_domain"}}} } diff --git a/policy/federation/mapping/mapping_update.rego b/policy/federation/mapping/mapping_update.rego index e13b13b5..6662cd80 100644 --- a/policy/federation/mapping/mapping_update.rego +++ b/policy/federation/mapping/mapping_update.rego @@ -1,11 +1,48 @@ # METADATA # description: Policy for updating federation mappings -package identity.mapping_update +package identity.federation.mapping.update -import data.identity +import data.identity.federation as common_federation # Update mapping. - +# +# The `input.target.mapping` is the update patch (MappingUpdate): +# name: string (optional) Attribute mapping name. +# domain_id: string (optional) `domain_id` owning the attribute mapping. +# idp_id: string (optional) ID of the federated identity provider. +# type: string (optional) Attribute mapping type. +# enabled: bool (optional) Mapping enabled property. +# allowed_redirect_uris: array (optional) List of allowed redirect urls. +# user_id_claim: string (optional) `user_id` claim name. +# user_name_claim: string (optional) `user_name` claim name. +# domain_id_claim: string (optional) `domain_id` claim name. +# groups_claim: string (optional) `groups` claim name. +# bound_audiences: array (optional) List of audiences. +# bound_subject: string (optional) Token subject value. +# bound_claims: object (optional) Additional claims. +# oidc_scopes: array (optional) List of OIDC scopes. +# token_project_id: string (optional) Fixed project_id for the token. +# token_restriction_id: string (optional) Token restrictions. +# +# The `input.existing.mapping` is the stored mapping object (Mapping): +# id: string Attribute mapping ID. +# name: string Attribute mapping name. +# domain_id: string (optional) `domain_id` owning the attribute mapping. +# idp_id: string ID of the federated identity provider. +# type: string Attribute mapping type ([oidc, jwt]). +# enabled: bool Mapping enabled property. +# allowed_redirect_uris: array (optional) List of allowed redirect urls. +# user_id_claim: string `user_id` claim name. +# user_name_claim: string `user_name` claim name. +# domain_id_claim: string (optional) `domain_id` claim name. +# groups_claim: string (optional) `groups` claim name. +# bound_audiences: array (optional) List of audiences. +# bound_subject: string (optional) Token subject value. +# bound_claims: object Additional claims. +# oidc_scopes: array (optional) List of OIDC scopes. +# token_project_id: string (optional) Fixed project_id for the token. +# token_restriction_id: string (optional) Token restrictions. +# default allow := false allow if { @@ -13,21 +50,21 @@ allow if { } allow if { - identity.own_mapping + common_federation.own_mapping "manager" in input.credentials.roles } violation contains {"field": "domain_id", "msg": "updating mapping for other domain requires `admin` role."} if { - identity.foreign_mapping + common_federation.foreign_mapping not "admin" in input.credentials.roles } violation contains {"field": "role", "msg": "updating global mapping requires `admin` role."} if { - identity.global_mapping + common_federation.global_mapping not "admin" in input.credentials.roles } violation contains {"field": "role", "msg": "updating mapping requires `manager` role."} if { - identity.own_mapping + common_federation.own_mapping not "member" in input.credentials.roles } diff --git a/policy/federation/mapping/mapping_update_test.rego b/policy/federation/mapping/mapping_update_test.rego index 87a0bd25..1b7fb0a1 100644 --- a/policy/federation/mapping/mapping_update_test.rego +++ b/policy/federation/mapping/mapping_update_test.rego @@ -1,16 +1,16 @@ package test_mapping_update -import data.identity.mapping_update +import data.identity.federation.mapping.update test_allowed if { - mapping_update.allow with input as {"credentials": {"roles": ["admin"]}} - mapping_update.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"domain_id": "domain"}} - mapping_update.allow with input as {"credentials": {"roles": ["admin"]}, "target": {"domain_id": null}} + update.allow with input as {"credentials": {"roles": ["admin"]}} + update.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"mapping": {"domain_id": "domain"}}} + update.allow with input as {"credentials": {"roles": ["admin"]}, "target": {"mapping": {"domain_id": null}}} } test_forbidden if { - not mapping_update.allow with input as {"credentials": {"roles": []}} - not mapping_update.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain"}, "target": {"domain_id": "domain"}} - not mapping_update.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"domain_id": "other_domain"}} - not mapping_update.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"domain_id": null}} + not update.allow with input as {"credentials": {"roles": []}} + not update.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain"}, "target": {"mapping": {"domain_id": "domain"}}} + not update.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"mapping": {"domain_id": "other_domain"}}} + not update.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"mapping": {"domain_id": null}}} } diff --git a/policy/identity.rego b/policy/identity.rego index fabc21cc..5a5a0b92 100644 --- a/policy/identity.rego +++ b/policy/identity.rego @@ -6,78 +6,62 @@ token_subject if { input.credentials.user_id == input.target.token.user_id } -global_idp if { - not input.target.domain_id -} - -global_idp if { - input.target.domain_id == null -} - -own_idp if { - input.target.domain_id != null - input.target.domain_id == input.credentials.domain_id -} - -foreign_idp if { - input.target.domain_id != null - input.target.domain_id != input.credentials.domain_id +global_role if { + input.target.role.domain_id == null } -global_mapping if { - not input.target.domain_id +own_role if { + input.target.role.domain_id != null + input.credentials.domain_id == input.target.role.domain_id } -global_mapping if { - input.target.domain_id == null +# Domain role or the global role. +own_role_or_global_role if { + global_role } -own_mapping if { - input.target.domain_id != null - input.target.domain_id == input.credentials.domain_id +own_role_or_global_role if { + own_role } -foreign_mapping if { - input.target.domain_id != null - input.target.domain_id != input.credentials.domain_id +own_target if { + any_domain_id != null + any_domain_id == input.credentials.domain_id } -foreign_token_restriction if { - input.target.domain_id != null - input.target.domain_id != input.credentials.domain_id +foreign_target if { + any_domain_id != null + any_domain_id != input.credentials.domain_id } -own_token_restriction if { - input.target.domain_id != null - input.target.domain_id == input.credentials.domain_id +# Collect domain_id from any known wrapped resource key. +# Used by own_target / foreign_target / domain_matches_domain_scope. +any_domain_id := input.target.instance.domain_id if { + input.target.instance.domain_id } -global_role if { - input.target.role.domain_id == null +any_domain_id := input.target.user.domain_id if { + input.target.user.domain_id } -own_role if { - input.target.role.domain_id != null - input.credentials.domain_id == input.target.role.domain_id +any_domain_id := input.target.group.domain_id if { + input.target.group.domain_id } -# Domain role or the global role. -own_role_or_global_role if { - global_role +any_domain_id := input.target.restriction.domain_id if { + input.target.restriction.domain_id } -own_role_or_global_role if { - own_role +any_domain_id := input.target.project.domain_id if { + input.target.project.domain_id } -own_target if { - input.target.domain_id != null - input.target.domain_id == input.credentials.domain_id +any_domain_id := input.target.role.domain_id if { + input.target.role.domain_id } -foreign_target if { - input.target.domain_id != null - input.target.domain_id != input.credentials.domain_id +any_domain_id := input.target.token.domain_id if { + input.target.token.domain_id } project_domain_matches_domain_scope if { @@ -86,6 +70,6 @@ project_domain_matches_domain_scope if { } domain_matches_domain_scope if { - input.target.domain_id != null - input.target.domain_id = input.credentials.domain_id + any_domain_id != null + any_domain_id = input.credentials.domain_id } diff --git a/policy/identity/group/create.rego b/policy/identity/group/create.rego index 8f8a9d84..f77ecb08 100644 --- a/policy/identity/group/create.rego +++ b/policy/identity/group/create.rego @@ -6,6 +6,13 @@ import data.identity # Create a new user group # +# The `input.target.group` is the new group object (GroupCreate): +# domain_id: string Group domain ID. +# name: string Group name. +# description: string (optional) Group description. +# +# The `input.existing` is null +# default allow := false allow if { diff --git a/policy/identity/group/create_test.rego b/policy/identity/group/create_test.rego index 469f0adb..4a3591c6 100644 --- a/policy/identity/group/create_test.rego +++ b/policy/identity/group/create_test.rego @@ -4,12 +4,12 @@ import data.identity.group.create test_allowed if { create.allow with input as {"credentials": {"roles": ["admin"]}} - create.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "foo"}, "target": {"domain_id": "foo"}} + create.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "foo"}, "target": {"group": {"domain_id": "foo"}}} } test_forbidden if { not create.allow with input as {"credentials": {"roles": []}} - not create.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "foo"}, "target": {"domain_id": "foo1"}} - not create.allow with input as {"credentials": {"roles": ["manager"]}, "target": {"domain_id": "foo"}} - not create.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "foo"}, "target": {"domain_id": "foo"}} + not create.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "foo"}, "target": {"group": {"domain_id": "foo1"}}} + not create.allow with input as {"credentials": {"roles": ["manager"]}, "target": {"group": {"domain_id": "foo"}}} + not create.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "foo"}, "target": {"group": {"domain_id": "foo"}}} } diff --git a/policy/identity/group/delete.rego b/policy/identity/group/delete.rego index b396650f..6cfed106 100644 --- a/policy/identity/group/delete.rego +++ b/policy/identity/group/delete.rego @@ -4,6 +4,16 @@ package identity.group.delete import data.identity +# Delete identity group. +# +# The `input.target.group` is the stored group object: +# domain_id: string Group domain ID. +# description: string (optional) Group description. +# id: string Group ID. +# name: string Group name. +# +# The `input.existing` is null +# default allow := false allow if { diff --git a/policy/identity/group/delete_test.rego b/policy/identity/group/delete_test.rego index a6117873..1b6ceb2b 100644 --- a/policy/identity/group/delete_test.rego +++ b/policy/identity/group/delete_test.rego @@ -4,12 +4,12 @@ import data.identity.group.delete test_allowed if { delete.allow with input as {"credentials": {"roles": ["admin"]}} - delete.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "foo"}, "target": {"domain_id": "foo"}} + delete.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "foo"}, "target": {"group": {"domain_id": "foo"}}} } test_forbidden if { not delete.allow with input as {"credentials": {"roles": []}} - not delete.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "foo"}, "target": {"domain_id": "foo1"}} - not delete.allow with input as {"credentials": {"roles": ["manager"]}, "target": {"domain_id": "foo"}} - not delete.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "foo"}, "target": {"domain_id": "foo"}} + not delete.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "foo"}, "target": {"group": {"domain_id": "foo1"}}} + not delete.allow with input as {"credentials": {"roles": ["manager"]}, "target": {"group": {"domain_id": "foo"}}} + not delete.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "foo"}, "target": {"group": {"domain_id": "foo"}}} } diff --git a/policy/identity/group/list.rego b/policy/identity/group/list.rego index a155f5e2..8726e115 100644 --- a/policy/identity/group/list.rego +++ b/policy/identity/group/list.rego @@ -4,6 +4,14 @@ package identity.group.list import data.identity +# List identity groups. +# +# The `input.target.group` contains query parameters: +# domain_id: string (optional) Filter users by Domain ID. +# name: string (optional) Filter users by Name. +# +# The `input.existing` is null +# default allow := false allow if { diff --git a/policy/identity/group/list_test.rego b/policy/identity/group/list_test.rego index 24b8c365..d20daa39 100644 --- a/policy/identity/group/list_test.rego +++ b/policy/identity/group/list_test.rego @@ -6,12 +6,12 @@ test_allowed if { list.allow with input as {"credentials": {"roles": ["admin"]}} list.allow with input as {"credentials": {"roles": [], "is_admin": true}} list.allow with input as {"credentials": {"roles": ["reader"], "system": "all"}} - list.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "foo"}, "target": {"domain_id": "foo"}} + list.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "foo"}, "target": {"group": {"domain_id": "foo"}}} } test_forbidden if { not list.allow with input as {"credentials": {"roles": []}} - not list.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "foo"}, "target": {"domain_id": "foo1"}} - not list.allow with input as {"credentials": {"roles": ["manager"]}, "target": {"domain_id": "foo"}} - not list.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "foo"}, "target": {"domain_id": "foo2"}} + not list.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "foo"}, "target": {"group": {"domain_id": "foo1"}}} + not list.allow with input as {"credentials": {"roles": ["manager"]}, "target": {"group": {"domain_id": "foo"}}} + not list.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "foo"}, "target": {"group": {"domain_id": "foo2"}}} } diff --git a/policy/identity/group/show.rego b/policy/identity/group/show.rego index ab54853a..895f1b74 100644 --- a/policy/identity/group/show.rego +++ b/policy/identity/group/show.rego @@ -4,6 +4,16 @@ package identity.group.show import data.identity +# Show identity group. +# +# The `input.target.group` is the stored group object (Group): +# domain_id: string Group domain ID. +# description: string (optional) Group description. +# id: string Group ID. +# name: string Group name. +# +# The `input.existing` is null +# default allow := false allow if { diff --git a/policy/identity/group/show_test.rego b/policy/identity/group/show_test.rego index 3b8b6e82..f8df7e2b 100644 --- a/policy/identity/group/show_test.rego +++ b/policy/identity/group/show_test.rego @@ -4,12 +4,12 @@ import data.identity.group.show test_allowed if { show.allow with input as {"credentials": {"roles": ["admin"]}} - show.allow with input as {"credentials": {"roles": ["manager", "reader"], "domain_id": "foo"}, "target": {"domain_id": "foo"}} + show.allow with input as {"credentials": {"roles": ["manager", "reader"], "domain_id": "foo"}, "target": {"group": {"domain_id": "foo"}}} } test_forbidden if { not show.allow with input as {"credentials": {"roles": []}} - not show.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "foo"}, "target": {"domain_id": "foo1"}} - not show.allow with input as {"credentials": {"roles": ["manager"]}, "target": {"domain_id": "foo"}} - not show.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "foo"}, "target": {"domain_id": "foo2"}} + not show.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "foo"}, "target": {"group": {"domain_id": "foo1"}}} + not show.allow with input as {"credentials": {"roles": ["manager"]}, "target": {"group": {"domain_id": "foo"}}} + not show.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "foo"}, "target": {"group": {"domain_id": "foo2"}}} } diff --git a/policy/identity/user/create.rego b/policy/identity/user/create.rego index f6a8d862..67262b4d 100644 --- a/policy/identity/user/create.rego +++ b/policy/identity/user/create.rego @@ -4,7 +4,17 @@ package identity.user.create import data.identity -# Create a new user group +# Create a new user +# +# The `input.target.user` is the new user object (UserCreate): +# default_project_id: string (optional) The ID of the default project for the user. +# domain_id: string User domain ID. +# enabled: bool If the user is enabled. +# name: string The user name. +# options: object (optional) The resource options for the user. +# password: string (optional) The password for the user. +# +# The `input.existing` is null # default allow := false diff --git a/policy/identity/user/create_test.rego b/policy/identity/user/create_test.rego index a5e91c8f..9601d19d 100644 --- a/policy/identity/user/create_test.rego +++ b/policy/identity/user/create_test.rego @@ -4,12 +4,12 @@ import data.identity.user.create test_allowed if { create.allow with input as {"credentials": {"roles": ["admin"]}} - create.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "foo"}, "target": {"domain_id": "foo"}} + create.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "foo"}, "target": {"user": {"domain_id": "foo"}}} } test_forbidden if { not create.allow with input as {"credentials": {"roles": []}} - not create.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "foo"}, "target": {"domain_id": "foo1"}} - not create.allow with input as {"credentials": {"roles": ["manager"]}, "target": {"domain_id": "foo"}} - not create.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "foo"}, "target": {"domain_id": "foo"}} + not create.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "foo"}, "target": {"user": {"domain_id": "foo1"}}} + not create.allow with input as {"credentials": {"roles": ["manager"]}, "target": {"user": {"domain_id": "foo"}}} + not create.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "foo"}, "target": {"user": {"domain_id": "foo"}}} } diff --git a/policy/identity/user/delete.rego b/policy/identity/user/delete.rego index c5e02ade..641d44ed 100644 --- a/policy/identity/user/delete.rego +++ b/policy/identity/user/delete.rego @@ -4,6 +4,19 @@ package identity.user.delete import data.identity +# Delete user. +# +# The `input.target.user` is the stored user object: +# default_project_id: string (optional) The ID of the default project for the user. +# domain_id: string User domain ID. +# enabled: bool If the user is enabled. +# id: string User ID. +# name: string User name. +# options: object (optional) The resource options for the user. +# password_expires_at: string (optional) The date and time when the password expires. +# +# The `input.existing` is null +# default allow := false allow if { diff --git a/policy/identity/user/delete_test.rego b/policy/identity/user/delete_test.rego index 8a330577..5354fb99 100644 --- a/policy/identity/user/delete_test.rego +++ b/policy/identity/user/delete_test.rego @@ -4,12 +4,12 @@ import data.identity.user.delete test_allowed if { delete.allow with input as {"credentials": {"roles": ["admin"]}} - delete.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "foo"}, "target": {"domain_id": "foo"}} + delete.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "foo"}, "target": {"user": {"domain_id": "foo"}}} } test_forbidden if { not delete.allow with input as {"credentials": {"roles": []}} - not delete.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "foo"}, "target": {"domain_id": "foo1"}} - not delete.allow with input as {"credentials": {"roles": ["manager"]}, "target": {"domain_id": "foo"}} - not delete.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "foo"}, "target": {"domain_id": "foo"}} + not delete.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "foo"}, "target": {"user": {"domain_id": "foo1"}}} + not delete.allow with input as {"credentials": {"roles": ["manager"]}, "target": {"user": {"domain_id": "foo"}}} + not delete.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "foo"}, "target": {"user": {"domain_id": "foo"}}} } diff --git a/policy/identity/user/list.rego b/policy/identity/user/list.rego index f056e07e..f564678a 100644 --- a/policy/identity/user/list.rego +++ b/policy/identity/user/list.rego @@ -4,6 +4,15 @@ package identity.user.list import data.identity +# List users. +# +# The `input.target.user` contains query parameters: +# domain_id: string (optional) Filter users by Domain ID. +# name: string (optional) Filter users by Name. +# unique_id: string (optional) Filter users by the federated unique ID. +# +# The `input.existing` is null +# default allow := false allow if { diff --git a/policy/identity/user/list_test.rego b/policy/identity/user/list_test.rego index 2abdfc51..a8177e20 100644 --- a/policy/identity/user/list_test.rego +++ b/policy/identity/user/list_test.rego @@ -6,12 +6,12 @@ test_allowed if { list.allow with input as {"credentials": {"roles": ["admin"]}} list.allow with input as {"credentials": {"roles": [], "is_admin": true}} list.allow with input as {"credentials": {"roles": ["reader"], "system": "all"}} - list.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "foo"}, "target": {"domain_id": "foo"}} + list.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "foo"}, "target": {"user": {"domain_id": "foo"}}} } test_forbidden if { not list.allow with input as {"credentials": {"roles": []}} - not list.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "foo"}, "target": {"domain_id": "foo1"}} - not list.allow with input as {"credentials": {"roles": ["manager"]}, "target": {"domain_id": "foo"}} - not list.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "foo"}, "target": {"domain_id": "foo2"}} + not list.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "foo"}, "target": {"user": {"domain_id": "foo1"}}} + not list.allow with input as {"credentials": {"roles": ["manager"]}, "target": {"user": {"domain_id": "foo"}}} + not list.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "foo"}, "target": {"user": {"domain_id": "foo2"}}} } diff --git a/policy/identity/user/show.rego b/policy/identity/user/show.rego index ae231f4a..14c62e96 100644 --- a/policy/identity/user/show.rego +++ b/policy/identity/user/show.rego @@ -4,6 +4,19 @@ package identity.user.show import data.identity +# Show user. +# +# The `input.target.user` is the stored user object (User): +# default_project_id: string (optional) The ID of the default project for the user. +# domain_id: string User domain ID. +# enabled: bool If the user is enabled. +# id: string User ID. +# name: string User name. +# options: object (optional) The resource options for the user. +# password_expires_at: string (optional) The date and time when the password expires. +# +# The `input.existing` is null +# default allow := false allow if { diff --git a/policy/identity/user/show_test.rego b/policy/identity/user/show_test.rego index ff04d610..0b23c7ec 100644 --- a/policy/identity/user/show_test.rego +++ b/policy/identity/user/show_test.rego @@ -4,12 +4,12 @@ import data.identity.user.show test_allowed if { show.allow with input as {"credentials": {"roles": ["admin"]}} - show.allow with input as {"credentials": {"roles": ["manager", "reader"], "domain_id": "foo"}, "target": {"domain_id": "foo"}} + show.allow with input as {"credentials": {"roles": ["manager", "reader"], "domain_id": "foo"}, "target": {"user": {"domain_id": "foo"}}} } test_forbidden if { not show.allow with input as {"credentials": {"roles": []}} - not show.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "foo"}, "target": {"domain_id": "foo1"}} - not show.allow with input as {"credentials": {"roles": ["manager"]}, "target": {"domain_id": "foo"}} - not show.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "foo"}, "target": {"domain_id": "foo2"}} + not show.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "foo"}, "target": {"user": {"domain_id": "foo1"}}} + not show.allow with input as {"credentials": {"roles": ["manager"]}, "target": {"user": {"domain_id": "foo"}}} + not show.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "foo"}, "target": {"user": {"domain_id": "foo2"}}} } diff --git a/policy/k8s_auth/instance/create.rego b/policy/k8s_auth/instance/create.rego index cd683c36..886800ae 100644 --- a/policy/k8s_auth/instance/create.rego +++ b/policy/k8s_auth/instance/create.rego @@ -5,7 +5,17 @@ package identity.k8s_auth.instance.create import data.identity # Create k8s auth instance. - +# +# The `input.target.instance` is the new instance object (K8sAuthInstanceCreate): +# ca_cert: string (optional) PEM encoded CA cert. +# disable_local_ca_jwt: bool (optional) Disable defaulting to local CA cert and JWT. +# domain_id: string Domain ID owning the K8s auth instance. +# enabled: bool If the instance is enabled. +# host: string Host of the Kubernetes API server. +# name: string (optional) K8s auth name. +# +# The `input.existing` is null +# default allow := false allow if { diff --git a/policy/k8s_auth/instance/create_test.rego b/policy/k8s_auth/instance/create_test.rego index 27be82f2..bf1c9d4a 100644 --- a/policy/k8s_auth/instance/create_test.rego +++ b/policy/k8s_auth/instance/create_test.rego @@ -4,13 +4,13 @@ import data.identity.k8s_auth.instance.create test_allowed if { create.allow with input as {"credentials": {"roles": ["admin"]}} - create.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"domain_id": "domain"}} - create.allow with input as {"credentials": {"roles": ["admin"]}, "target": {"domain_id": null}} + create.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"instance": {"domain_id": "domain"}}} + create.allow with input as {"credentials": {"roles": ["admin"]}, "target": {"instance": {"domain_id": null}}} } test_forbidden if { not create.allow with input as {"credentials": {"roles": []}} - not create.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain"}, "target": {"domain_id": "domain"}} - not create.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"domain_id": "other_domain"}} - not create.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"domain_id": null}} + not create.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain"}, "target": {"instance": {"domain_id": "domain"}}} + not create.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"instance": {"domain_id": "other_domain"}}} + not create.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"instance": {"domain_id": null}}} } diff --git a/policy/k8s_auth/instance/delete.rego b/policy/k8s_auth/instance/delete.rego index b3952cc4..769ac68a 100644 --- a/policy/k8s_auth/instance/delete.rego +++ b/policy/k8s_auth/instance/delete.rego @@ -5,7 +5,13 @@ package identity.k8s_auth.instance.delete import data.identity # Delete k8s auth instance. - +# +# The `input.target.instance` is the stored instance object (K8sAuthInstance): +# domain_id: string domain ID +# id: string instance ID +# +# The `input.existing` is null +# default allow := false allow if { diff --git a/policy/k8s_auth/instance/delete_test.rego b/policy/k8s_auth/instance/delete_test.rego index c8fb19ae..971b634d 100644 --- a/policy/k8s_auth/instance/delete_test.rego +++ b/policy/k8s_auth/instance/delete_test.rego @@ -4,13 +4,13 @@ import data.identity.k8s_auth.instance.delete test_allowed if { delete.allow with input as {"credentials": {"roles": ["admin"]}} - delete.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"domain_id": "domain"}} - delete.allow with input as {"credentials": {"roles": ["admin"]}, "target": {"domain_id": null}} + delete.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"instance": {"domain_id": "domain"}}} + delete.allow with input as {"credentials": {"roles": ["admin"]}, "target": {"instance": {"domain_id": null}}} } test_forbidden if { not delete.allow with input as {"credentials": {"roles": []}} - not delete.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain"}, "target": {"domain_id": "domain"}} - not delete.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"domain_id": "other_domain"}} - not delete.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"domain_id": null}} + not delete.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain"}, "target": {"instance": {"domain_id": "domain"}}} + not delete.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"instance": {"domain_id": "other_domain"}}} + not delete.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"instance": {"domain_id": null}}} } diff --git a/policy/k8s_auth/instance/list.rego b/policy/k8s_auth/instance/list.rego index 5253d806..a0e2d663 100644 --- a/policy/k8s_auth/instance/list.rego +++ b/policy/k8s_auth/instance/list.rego @@ -5,7 +5,13 @@ package identity.k8s_auth.instance.list import data.identity # List k8s auth instances. - +# +# The `input.target.instance` contains query parameters (K8sAuthInstanceListParameters): +# domain_id: string (optional) Domain id. +# name: string (optional) Name. +# +# The `input.existing` is null +# default allow := false can_see_other_domain_resources if { @@ -24,7 +30,7 @@ allow if { # allow listing when the domain_id is unset. Code is responsible for setting # domain_id to the current one. allow if { - input.target.domain_id == null + input.target.instance.domain_id == null "reader" in input.credentials.roles } diff --git a/policy/k8s_auth/instance/list_test.rego b/policy/k8s_auth/instance/list_test.rego index ce94bd27..fdfb1adc 100644 --- a/policy/k8s_auth/instance/list_test.rego +++ b/policy/k8s_auth/instance/list_test.rego @@ -4,12 +4,12 @@ import data.identity.k8s_auth.instance.list test_allowed if { list.allow with input as {"credentials": {"roles": ["admin"]}} - list.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain"}, "target": {"domain_id": "domain"}} - list.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain"}, "target": {"domain_id": null}} + list.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain"}, "target": {"instance": {"domain_id": "domain"}}} + list.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain"}, "target": {"instance": {"domain_id": null}}} } test_forbidden if { not list.allow with input as {"credentials": {"roles": []}} - not list.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain"}, "target": {"domain_id": "other_domain"}} - not list.allow with input as {"credentials": {"roles": ["member"], "domain_id": "domain"}, "target": {"domain_id": "other_domain"}} + not list.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain"}, "target": {"instance": {"domain_id": "other_domain"}}} + not list.allow with input as {"credentials": {"roles": ["member"], "domain_id": "domain"}, "target": {"instance": {"domain_id": "other_domain"}}} } diff --git a/policy/k8s_auth/instance/show.rego b/policy/k8s_auth/instance/show.rego index e897615f..b8b13843 100644 --- a/policy/k8s_auth/instance/show.rego +++ b/policy/k8s_auth/instance/show.rego @@ -5,7 +5,18 @@ package identity.k8s_auth.instance.show import data.identity # Show k8s auth instance. - +# +# The `input.target.instance` is the stored instance object (K8sAuthInstance): +# ca_cert: string (optional) PEM encoded CA cert. +# disable_local_ca_jwt: bool Disable defaulting to local CA cert and JWT. +# domain_id: string Domain ID owning the K8s auth configuration. +# enabled: bool If the instance is enabled. +# host: string Host of the Kubernetes API server. +# id: string K8s auth configuration ID. +# name: string (optional) K8s auth name. +# +# The `input.existing` is null +# default allow := false allow if { @@ -13,7 +24,7 @@ allow if { } allow if { - identity.own_idp + identity.own_target "reader" in input.credentials.roles } diff --git a/policy/k8s_auth/instance/show_test.rego b/policy/k8s_auth/instance/show_test.rego index 7261d069..2d796e6b 100644 --- a/policy/k8s_auth/instance/show_test.rego +++ b/policy/k8s_auth/instance/show_test.rego @@ -4,14 +4,14 @@ import data.identity.k8s_auth.instance.show test_allowed if { show.allow with input as {"credentials": {"roles": ["admin"]}} - show.allow with input as {"credentials": {"roles": ["manager", "reader"], "domain_id": "domain"}, "target": {"domain_id": "domain"}} - show.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain"}, "target": {"domain_id": "domain"}} - show.allow with input as {"credentials": {"roles": ["admin"]}, "target": {"domain_id": null}} + show.allow with input as {"credentials": {"roles": ["manager", "reader"], "domain_id": "domain"}, "target": {"instance": {"domain_id": "domain"}}} + show.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain"}, "target": {"instance": {"domain_id": "domain"}}} + show.allow with input as {"credentials": {"roles": ["admin"]}, "target": {"instance": {"domain_id": null}}} } test_forbidden if { not show.allow with input as {"credentials": {"roles": []}} - not show.allow with input as {"credentials": {"roles": ["not_reader"], "domain_id": "domain"}, "target": {"domain_id": "domain"}} - not show.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"domain_id": "other_domain"}} - not show.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"domain_id": null}} + not show.allow with input as {"credentials": {"roles": ["not_reader"], "domain_id": "domain"}, "target": {"instance": {"domain_id": "domain"}}} + not show.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"instance": {"domain_id": "other_domain"}}} + not show.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"instance": {"domain_id": null}}} } diff --git a/policy/k8s_auth/instance/update.rego b/policy/k8s_auth/instance/update.rego index a46ee8de..9c4b262e 100644 --- a/policy/k8s_auth/instance/update.rego +++ b/policy/k8s_auth/instance/update.rego @@ -5,7 +5,23 @@ package identity.k8s_auth.instance.update import data.identity # Update k8s auth instance. - +# +# The `input.target.instance` is the update patch (K8sAuthInstanceUpdate): +# ca_cert: string (optional) PEM encoded CA cert. +# disable_local_ca_jwt: bool (optional) Disable defaulting to local CA cert and JWT. +# enabled: bool (optional) If the instance is enabled. +# host: string (optional) Host of the Kubernetes API server. +# name: string (optional) K8s auth name. +# +# The `input.existing.instance` is the stored instance object (K8sAuthInstance): +# ca_cert: string (optional) PEM encoded CA cert. +# disable_local_ca_jwt: bool Disable defaulting to local CA cert and JWT. +# domain_id: string Domain ID owning the K8s auth configuration. +# enabled: bool If the instance is enabled. +# host: string Host of the Kubernetes API server. +# id: string K8s auth configuration ID. +# name: string (optional) K8s auth name. +# default allow := false allow if { @@ -13,7 +29,7 @@ allow if { } allow if { - identity.own_idp + identity.own_target "manager" in input.credentials.roles } diff --git a/policy/k8s_auth/instance/update_test.rego b/policy/k8s_auth/instance/update_test.rego index 8532c588..42c59d6c 100644 --- a/policy/k8s_auth/instance/update_test.rego +++ b/policy/k8s_auth/instance/update_test.rego @@ -4,13 +4,13 @@ import data.identity.k8s_auth.instance.update test_allowed if { update.allow with input as {"credentials": {"roles": ["admin"]}} - update.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"domain_id": "domain"}} - update.allow with input as {"credentials": {"roles": ["admin"]}, "target": {"domain_id": null}} + update.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"instance": {"domain_id": "domain"}}} + update.allow with input as {"credentials": {"roles": ["admin"]}, "target": {"instance": {"domain_id": null}}} } test_forbidden if { not update.allow with input as {"credentials": {"roles": []}} - not update.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain"}, "target": {"domain_id": "domain"}} - not update.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"domain_id": "other_domain"}} - not update.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"domain_id": null}} + not update.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain"}, "target": {"instance": {"domain_id": "domain"}}} + not update.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"instance": {"domain_id": "other_domain"}}} + not update.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"instance": {"domain_id": null}}} } diff --git a/policy/k8s_auth/role/create.rego b/policy/k8s_auth/role/create.rego index 5578dffc..00d49b3f 100644 --- a/policy/k8s_auth/role/create.rego +++ b/policy/k8s_auth/role/create.rego @@ -4,12 +4,21 @@ package identity.k8s_auth.role.create import data.identity -# # Create k8s auth role. - -# # Input: -# * input.target.instance - auth_instance object -# * input.target.role - auth_role object - +# Create k8s auth role. +# +# The `input.target.instance` is the instance context: +# domain_id: string domain ID +# +# The `input.target.role` is the new role object (K8sAuthRoleCreate): +# bound_audience: string (optional) Optional Audience claim to verify in the JWT. +# bound_service_account_names: array List of service account names able to access this role. +# bound_service_account_namespaces: array List of namespaces allowed to access this role. +# enabled: bool If the role is enabled. +# name: string K8s auth role name. +# token_restriction_id: string A token restriction ID. +# +# The `input.existing` is null +# default allow := false own_instance if { diff --git a/policy/k8s_auth/role/delete.rego b/policy/k8s_auth/role/delete.rego index 7b0878d6..9135dd98 100644 --- a/policy/k8s_auth/role/delete.rego +++ b/policy/k8s_auth/role/delete.rego @@ -5,7 +5,13 @@ package identity.k8s_auth.role.delete import data.identity # Delete k8s auth role. - +# +# The `input.target.role` is the stored role object (K8sAuthRole): +# domain_id: string domain ID +# id: string role ID +# +# The `input.existing` is null +# default allow := false allow if { diff --git a/policy/k8s_auth/role/delete_test.rego b/policy/k8s_auth/role/delete_test.rego index 1feb24fc..500818bc 100644 --- a/policy/k8s_auth/role/delete_test.rego +++ b/policy/k8s_auth/role/delete_test.rego @@ -4,13 +4,13 @@ import data.identity.k8s_auth.role.delete test_allowed if { delete.allow with input as {"credentials": {"roles": ["admin"]}} - delete.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"domain_id": "domain"}} - delete.allow with input as {"credentials": {"roles": ["admin"]}, "target": {"domain_id": null}} + delete.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"role": {"domain_id": "domain"}}} + delete.allow with input as {"credentials": {"roles": ["admin"]}, "target": {"role": {"domain_id": null}}} } test_forbidden if { not delete.allow with input as {"credentials": {"roles": []}} - not delete.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain"}, "target": {"domain_id": "domain"}} - not delete.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"domain_id": "other_domain"}} - not delete.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"domain_id": null}} + not delete.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain"}, "target": {"role": {"domain_id": "domain"}}} + not delete.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"role": {"domain_id": "other_domain"}}} + not delete.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"role": {"domain_id": null}}} } diff --git a/policy/k8s_auth/role/list.rego b/policy/k8s_auth/role/list.rego index d6c60c13..452b9400 100644 --- a/policy/k8s_auth/role/list.rego +++ b/policy/k8s_auth/role/list.rego @@ -5,7 +5,14 @@ package identity.k8s_auth.role.list import data.identity # List k8s auth role. - +# +# The `input.target.role` contains query parameters (K8sAuthRoleListParameters): +# auth_instance_id: string (optional) K8s auth instance id. +# domain_id: string (optional) Domain id. +# name: string (optional) Name. +# +# The `input.existing` is null +# default allow := false can_see_other_domain_resources if { @@ -24,7 +31,7 @@ allow if { # allow listing when the domain_id is unset. Code is responsible for setting # domain_id to the current one. allow if { - input.target.domain_id == null + input.target.role.domain_id == null "reader" in input.credentials.roles } diff --git a/policy/k8s_auth/role/list_test.rego b/policy/k8s_auth/role/list_test.rego index 84571fd2..f1f38570 100644 --- a/policy/k8s_auth/role/list_test.rego +++ b/policy/k8s_auth/role/list_test.rego @@ -4,12 +4,12 @@ import data.identity.k8s_auth.role.list test_allowed if { list.allow with input as {"credentials": {"roles": ["admin"]}} - list.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain"}, "target": {"domain_id": "domain"}} - list.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain"}, "target": {"domain_id": null}} + list.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain"}, "target": {"role": {"domain_id": "domain"}}} + list.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain"}, "target": {"role": {"domain_id": null}}} } test_forbidden if { not list.allow with input as {"credentials": {"roles": []}} - not list.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain"}, "target": {"domain_id": "other_domain"}} - not list.allow with input as {"credentials": {"roles": ["member"], "domain_id": "domain"}, "target": {"domain_id": "other_domain"}} + not list.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain"}, "target": {"role": {"domain_id": "other_domain"}}} + not list.allow with input as {"credentials": {"roles": ["member"], "domain_id": "domain"}, "target": {"role": {"domain_id": "other_domain"}}} } diff --git a/policy/k8s_auth/role/show.rego b/policy/k8s_auth/role/show.rego index c802e8b5..84dadb11 100644 --- a/policy/k8s_auth/role/show.rego +++ b/policy/k8s_auth/role/show.rego @@ -5,7 +5,20 @@ package identity.k8s_auth.role.show import data.identity # Show k8s auth role. - +# +# The `input.target.role` is the stored role object (K8sAuthRole): +# auth_instance_id: string ID of the K8s auth instance this role belongs to. +# bound_audience: string (optional) Optional Audience claim to verify in the JWT. +# bound_service_account_names: array List of service account names able to access this role. +# bound_service_account_namespaces: array List of namespaces allowed to access this role. +# domain_id: string Domain ID owning the K8s auth role configuration. +# enabled: bool If the role is enabled. +# id: string K8s auth role ID. +# name: string K8s auth role name. +# token_restriction_id: string A token restriction ID. +# +# The `input.existing` is null +# default allow := false allow if { @@ -13,7 +26,7 @@ allow if { } allow if { - identity.own_idp + identity.own_target "reader" in input.credentials.roles } diff --git a/policy/k8s_auth/role/show_test.rego b/policy/k8s_auth/role/show_test.rego index 5b6ac3b1..11b971b6 100644 --- a/policy/k8s_auth/role/show_test.rego +++ b/policy/k8s_auth/role/show_test.rego @@ -4,14 +4,14 @@ import data.identity.k8s_auth.role.show test_allowed if { show.allow with input as {"credentials": {"roles": ["admin"]}} - show.allow with input as {"credentials": {"roles": ["manager", "reader"], "domain_id": "domain"}, "target": {"domain_id": "domain"}} - show.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain"}, "target": {"domain_id": "domain"}} - show.allow with input as {"credentials": {"roles": ["admin"]}, "target": {"domain_id": null}} + show.allow with input as {"credentials": {"roles": ["manager", "reader"], "domain_id": "domain"}, "target": {"role": {"domain_id": "domain"}}} + show.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain"}, "target": {"role": {"domain_id": "domain"}}} + show.allow with input as {"credentials": {"roles": ["admin"]}, "target": {"role": {"domain_id": null}}} } test_forbidden if { not show.allow with input as {"credentials": {"roles": []}} - not show.allow with input as {"credentials": {"roles": ["not_reader"], "domain_id": "domain"}, "target": {"domain_id": "domain"}} - not show.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"domain_id": "other_domain"}} - not show.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"domain_id": null}} + not show.allow with input as {"credentials": {"roles": ["not_reader"], "domain_id": "domain"}, "target": {"role": {"domain_id": "domain"}}} + not show.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"role": {"domain_id": "other_domain"}}} + not show.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"role": {"domain_id": null}}} } diff --git a/policy/k8s_auth/role/update.rego b/policy/k8s_auth/role/update.rego index e29e4f66..a918b82c 100644 --- a/policy/k8s_auth/role/update.rego +++ b/policy/k8s_auth/role/update.rego @@ -5,7 +5,26 @@ package identity.k8s_auth.role.update import data.identity # Update k8s auth role. - +# +# The `input.target.role` is the update patch (K8sAuthRoleUpdate): +# bound_audience: string (optional) Optional Audience claim to verify in the JWT. +# bound_service_account_names: array (optional) List of service account names able to access this role. +# bound_service_account_namespaces: array (optional) List of namespaces allowed to access this role. +# enabled: bool (optional) If the role is enabled. +# name: string (optional) K8s auth role name. +# token_restriction_id: string (optional) A token restriction ID. +# +# The `input.existing.role` is the stored role object (K8sAuthRole): +# auth_instance_id: string ID of the K8s auth instance this role belongs to. +# bound_audience: string (optional) Optional Audience claim to verify in the JWT. +# bound_service_account_names: array List of service account names able to access this role. +# bound_service_account_namespaces: array List of namespaces allowed to access this role. +# domain_id: string Domain ID owning the K8s auth role configuration. +# enabled: bool If the role is enabled. +# id: string K8s auth role ID. +# name: string K8s auth role name. +# token_restriction_id: string A token restriction ID. +# default allow := false allow if { @@ -13,7 +32,7 @@ allow if { } allow if { - identity.own_idp + identity.own_target "manager" in input.credentials.roles } diff --git a/policy/k8s_auth/role/update_test.rego b/policy/k8s_auth/role/update_test.rego index be4fa3a6..8863c918 100644 --- a/policy/k8s_auth/role/update_test.rego +++ b/policy/k8s_auth/role/update_test.rego @@ -4,13 +4,13 @@ import data.identity.k8s_auth.role.update test_allowed if { update.allow with input as {"credentials": {"roles": ["admin"]}} - update.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"domain_id": "domain"}} - update.allow with input as {"credentials": {"roles": ["admin"]}, "target": {"domain_id": null}} + update.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"role": {"domain_id": "domain"}}} + update.allow with input as {"credentials": {"roles": ["admin"]}, "target": {"role": {"domain_id": null}}} } test_forbidden if { not update.allow with input as {"credentials": {"roles": []}} - not update.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain"}, "target": {"domain_id": "domain"}} - not update.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"domain_id": "other_domain"}} - not update.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"domain_id": null}} + not update.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain"}, "target": {"role": {"domain_id": "domain"}}} + not update.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"role": {"domain_id": "other_domain"}}} + not update.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"role": {"domain_id": null}}} } diff --git a/policy/project/create.rego b/policy/project/create.rego index 39feb572..fe2bdd71 100644 --- a/policy/project/create.rego +++ b/policy/project/create.rego @@ -5,7 +5,17 @@ package identity.project.create import data.identity # Create a new project - +# +# The `input.target.project` is the new project object (ProjectCreate): +# description: string (optional) The description of the project. +# domain_id: string The ID of the domain for the project. +# enabled: bool If set to true, project is enabled. +# is_domain: bool Indicates whether the project also acts as a domain. +# name: string The name of the project. +# parent_id: string (optional) The ID of the parent of the project. +# +# The `input.existing` is null +# default allow := false allow if { diff --git a/policy/role/create.rego b/policy/role/create.rego index 51258738..bb09e62a 100644 --- a/policy/role/create.rego +++ b/policy/role/create.rego @@ -5,6 +5,15 @@ package identity.role.create import data.identity +# Create role. +# +# The `input.target.role` is the new role object (RoleCreate): +# description: string (optional) The role description. +# domain_id: string (optional) The domain ID of the role. +# name: string The role name. +# +# The `input.existing` is null +# default allow := false # METADATA diff --git a/policy/role/create_test.rego b/policy/role/create_test.rego index 39b72fc8..23c8acef 100644 --- a/policy/role/create_test.rego +++ b/policy/role/create_test.rego @@ -3,10 +3,10 @@ package test_role_create import data.identity.role.create test_allowed if { - create.allow with input as {"credentials": {"roles": ["admin"], "domain_id": "domain_a"}, "target": {"domain_id": "domain_a"}} + create.allow with input as {"credentials": {"roles": ["admin"], "domain_id": "domain_a"}, "target": {"role": {"domain_id": "domain_a"}}} } test_forbidden if { not create.allow with input as {"credentials": {"roles": []}} - not create.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain_a"}, "target": {"domain_id": "domain_a"}} + not create.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain_a"}, "target": {"role": {"domain_id": "domain_a"}}} } diff --git a/policy/role/list.rego b/policy/role/list.rego index 2c6f9502..3d72196d 100644 --- a/policy/role/list.rego +++ b/policy/role/list.rego @@ -5,6 +5,13 @@ package identity.role.list import data.identity +# List roles. +# +# The `input.target.role` contains query parameters: +# domain_id: string|null domain ID for filtering +# +# The `input.existing` is null +# default allow := false # METADATA diff --git a/policy/role/list_test.rego b/policy/role/list_test.rego index ef835dfd..352c73f3 100644 --- a/policy/role/list_test.rego +++ b/policy/role/list_test.rego @@ -4,11 +4,11 @@ import data.identity.role.list test_allowed if { #list.allow with input as {"credentials": {"roles": ["admin"]}} - list.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain_a"}, "target": {"domain_id": "domain_a"}} + list.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain_a"}, "target": {"role": {"domain_id": "domain_a"}}} } test_forbidden if { not list.allow with input as {"credentials": {"roles": []}} - not list.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain_a"}, "target": {"domain_id": "domain_b"}} - not list.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain_a"}, "target": {"domain_id": "domain_b"}} + not list.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain_a"}, "target": {"role": {"domain_id": "domain_b"}}} + not list.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain_a"}, "target": {"role": {"domain_id": "domain_b"}}} } diff --git a/policy/role/show.rego b/policy/role/show.rego index 7ec011af..785f89ca 100644 --- a/policy/role/show.rego +++ b/policy/role/show.rego @@ -5,6 +5,16 @@ package identity.role.show import data.identity +# Show role. +# +# The `input.target.role` is the stored role object (Role): +# description: string (optional) Role description. +# domain_id: string (optional) Role domain ID. +# id: string Role ID. +# name: string Role name. +# +# The `input.existing` is null +# default allow := false # METADATA diff --git a/policy/token/common.rego b/policy/token/common.rego new file mode 100644 index 00000000..375b45d8 --- /dev/null +++ b/policy/token/common.rego @@ -0,0 +1,25 @@ +# METADATA +# description: Common policies for token operations +package identity.token + +import data.identity + +foreign_token_restriction if { + token_restriction_domain_id != null + token_restriction_domain_id != input.credentials.domain_id +} + +own_token_restriction if { + token_restriction_domain_id != null + token_restriction_domain_id == input.credentials.domain_id +} + +# Resolve token restriction domain_id from target or existing, depending +# on the operation (create/show/delete vs update). +token_restriction_domain_id := input.target.restriction.domain_id if { + input.target.restriction.domain_id +} + +token_restriction_domain_id := input.existing.restriction.domain_id if { + input.existing.restriction.domain_id +} diff --git a/policy/token/restriction/create.rego b/policy/token/restriction/create.rego index bda64e15..f08c971a 100644 --- a/policy/token/restriction/create.rego +++ b/policy/token/restriction/create.rego @@ -1,9 +1,21 @@ # METADATA # description: Policy for creating token restrictions -package identity.token_restriction.create +package identity.token.token_restriction.create -import data.identity +import data.identity.token +# Create token restriction. +# +# The `input.target.restriction` is the new restriction object (TokenRestrictionCreate): +# allow_renew: bool Allow token renew. +# allow_rescope: bool Allow token rescope. +# domain_id: string Domain ID the token restriction belongs to. +# project_id: string (optional) Project ID that the token must be bound to. +# user_id: string (optional) User ID that the token must be bound to. +# roles: array Bound token roles. +# +# The `input.existing` is null +# default allow := false allow if { @@ -12,16 +24,16 @@ allow if { allow if { "manager" in input.credentials.roles - identity.own_token_restriction + token.own_token_restriction } allow if { "member" in input.credentials.roles - input.target.user_id != null - input.credentials.user_id == input.target.user_id + input.target.restriction.user_id != null + input.credentials.user_id == input.target.restriction.user_id } violation contains {"field": "domain_id", "msg": "creating token restrictions in other domain requires `admin` role."} if { - identity.foreign_token_restriction + token.foreign_token_restriction not "admin" in input.credentials.roles } diff --git a/policy/token/restriction/create_test.rego b/policy/token/restriction/create_test.rego index 25758b2b..63f87153 100644 --- a/policy/token/restriction/create_test.rego +++ b/policy/token/restriction/create_test.rego @@ -1,19 +1,19 @@ package test_token_restriction_create -import data.identity.token_restriction.create +import data.identity.token.token_restriction.create test_allowed if { create.allow with input as {"credentials": {"roles": ["admin"]}} - create.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"domain_id": "domain"}} - create.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain", "user_id": "uid"}, "target": {"domain_id": "domain", "user_id": "uid"}} - create.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain", "user_id": "uid"}, "target": {"domain_id": "domain", "user_id": "uid2"}} - create.allow with input as {"credentials": {"roles": ["member"], "domain_id": "domain", "user_id": "uid"}, "target": {"domain_id": "domain", "user_id": "uid"}} + create.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"restriction": {"domain_id": "domain"}}} + create.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain", "user_id": "uid"}, "target": {"restriction": {"domain_id": "domain", "user_id": "uid"}}} + create.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain", "user_id": "uid"}, "target": {"restriction": {"domain_id": "domain", "user_id": "uid2"}}} + create.allow with input as {"credentials": {"roles": ["member"], "domain_id": "domain", "user_id": "uid"}, "target": {"restriction": {"domain_id": "domain", "user_id": "uid"}}} } test_forbidden if { not create.allow with input as {"credentials": {"roles": []}} - not create.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain"}, "target": {"domain_id": "other_domain"}} - not create.allow with input as {"credentials": {"roles": ["member"], "domain_id": "domain"}, "target": {"domain_id": "other_domain"}} - not create.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"domain_id": "other_domain"}} - not create.allow with input as {"credentials": {"roles": ["member"], "domain_id": "domain", "user_id": "uid1"}, "target": {"domain_id": "domain", "user_id": "uid2"}} + not create.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain"}, "target": {"restriction": {"domain_id": "other_domain"}}} + not create.allow with input as {"credentials": {"roles": ["member"], "domain_id": "domain"}, "target": {"restriction": {"domain_id": "other_domain"}}} + not create.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"restriction": {"domain_id": "other_domain"}}} + not create.allow with input as {"credentials": {"roles": ["member"], "domain_id": "domain", "user_id": "uid1"}, "target": {"restriction": {"domain_id": "domain", "user_id": "uid2"}}} } diff --git a/policy/token/restriction/delete.rego b/policy/token/restriction/delete.rego index 2dd9786e..eba1529f 100644 --- a/policy/token/restriction/delete.rego +++ b/policy/token/restriction/delete.rego @@ -1,11 +1,18 @@ # METADATA # description: Policy for deleting token restrictions -package identity.token_restriction.delete +package identity.token.token_restriction.delete -import data.identity +import data.identity.token # Delete token restriction. - +# +# The `input.target.restriction` is the stored restriction object: +# domain_id: string domain ID +# user_id: string|null user ID +# role_ids: [string] list of role IDs +# +# The `input.existing` is null +# default allow := false allow if { @@ -14,16 +21,16 @@ allow if { allow if { "manager" in input.credentials.roles - identity.own_token_restriction + token.own_token_restriction } allow if { "member" in input.credentials.roles - input.target.user_id != null - input.credentials.user_id == input.target.user_id + input.target.restriction.user_id != null + input.credentials.user_id == input.target.restriction.user_id } violation contains {"field": "domain_id", "msg": "deleting token restrictions in other domain requires `admin` role."} if { - identity.foreign_token_restriction + token.foreign_token_restriction not "admin" in input.credentials.roles } diff --git a/policy/token/restriction/delete_test.rego b/policy/token/restriction/delete_test.rego index 1ca3ba59..98bfff73 100644 --- a/policy/token/restriction/delete_test.rego +++ b/policy/token/restriction/delete_test.rego @@ -1,19 +1,19 @@ package test_token_restriction_delete -import data.identity.token_restriction.delete +import data.identity.token.token_restriction.delete test_allowed if { delete.allow with input as {"credentials": {"roles": ["admin"]}} - delete.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"domain_id": "domain"}} - delete.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain", "user_id": "uid"}, "target": {"domain_id": "domain", "user_id": "uid"}} - delete.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain", "user_id": "uid"}, "target": {"domain_id": "domain", "user_id": "uid2"}} - delete.allow with input as {"credentials": {"roles": ["member"], "domain_id": "domain", "user_id": "uid"}, "target": {"domain_id": "domain", "user_id": "uid"}} + delete.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"restriction": {"domain_id": "domain"}}} + delete.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain", "user_id": "uid"}, "target": {"restriction": {"domain_id": "domain", "user_id": "uid"}}} + delete.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain", "user_id": "uid"}, "target": {"restriction": {"domain_id": "domain", "user_id": "uid2"}}} + delete.allow with input as {"credentials": {"roles": ["member"], "domain_id": "domain", "user_id": "uid"}, "target": {"restriction": {"domain_id": "domain", "user_id": "uid"}}} } test_forbidden if { not delete.allow with input as {"credentials": {"roles": []}} - not delete.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain"}, "target": {"domain_id": "other_domain"}} - not delete.allow with input as {"credentials": {"roles": ["member"], "domain_id": "domain"}, "target": {"domain_id": "other_domain"}} - not delete.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"domain_id": "other_domain"}} - not delete.allow with input as {"credentials": {"roles": ["member"], "domain_id": "domain", "user_id": "uid1"}, "target": {"domain_id": "domain", "user_id": "uid2"}} + not delete.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain"}, "target": {"restriction": {"domain_id": "other_domain"}}} + not delete.allow with input as {"credentials": {"roles": ["member"], "domain_id": "domain"}, "target": {"restriction": {"domain_id": "other_domain"}}} + not delete.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"restriction": {"domain_id": "other_domain"}}} + not delete.allow with input as {"credentials": {"roles": ["member"], "domain_id": "domain", "user_id": "uid1"}, "target": {"restriction": {"domain_id": "domain", "user_id": "uid2"}}} } diff --git a/policy/token/restriction/list.rego b/policy/token/restriction/list.rego index 3f9d117b..c2541530 100644 --- a/policy/token/restriction/list.rego +++ b/policy/token/restriction/list.rego @@ -1,11 +1,18 @@ # METADATA # description: Policy for listing token restrictions -package identity.token_restriction.list +package identity.token.token_restriction.list -import data.identity - -# List token restriction. +import data.identity.token +# List token restrictions. +# +# The `input.target.restriction` contains query parameters (TokenRestrictionListParameters): +# domain_id: string (optional) Domain id. +# user_id: string (optional) User id. +# project_id: string (optional) Project id. +# +# The `input.existing` is null +# default allow := false allow if { @@ -18,10 +25,10 @@ allow if { allow if { "member" in input.credentials.roles - identity.own_token_restriction + token.own_token_restriction } violation contains {"field": "domain_id", "msg": "showing token restrictions requires `admin` role."} if { - identity.foreign_token_restriction + token.foreign_token_restriction not "admin" in input.credentials.roles } diff --git a/policy/token/restriction/list_test.rego b/policy/token/restriction/list_test.rego index d30244b7..c27082be 100644 --- a/policy/token/restriction/list_test.rego +++ b/policy/token/restriction/list_test.rego @@ -1,15 +1,15 @@ package test_token_restriction_list -import data.identity.token_restriction.list +import data.identity.token.token_restriction.list test_allowed if { list.allow with input as {"credentials": {"roles": ["admin"]}} list.allow with input as {"credentials": {"roles": ["manager"]}} - list.allow with input as {"credentials": {"roles": ["member"], "domain_id": "domain"}, "target": {"domain_id": "domain"}} + list.allow with input as {"credentials": {"roles": ["member"], "domain_id": "domain"}, "target": {"restriction": {"domain_id": "domain"}}} } test_forbidden if { not list.allow with input as {"credentials": {"roles": []}} - not list.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain"}, "target": {"domain_id": "other_domain"}} - not list.allow with input as {"credentials": {"roles": ["member"], "domain_id": "domain"}, "target": {"domain_id": "other_domain"}} + not list.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain"}, "target": {"restriction": {"domain_id": "other_domain"}}} + not list.allow with input as {"credentials": {"roles": ["member"], "domain_id": "domain"}, "target": {"restriction": {"domain_id": "other_domain"}}} } diff --git a/policy/token/restriction/show.rego b/policy/token/restriction/show.rego index 05176ee9..a9d2b269 100644 --- a/policy/token/restriction/show.rego +++ b/policy/token/restriction/show.rego @@ -1,11 +1,22 @@ # METADATA # description: Policy for viewing token restriction details -package identity.token_restriction.show +package identity.token.token_restriction.show -import data.identity +import data.identity.token # Show single token restriction. - +# +# The `input.target.restriction` is the stored restriction object (TokenRestriction): +# allow_renew: bool Allow token renew. +# allow_rescope: bool Allow token rescope. +# domain_id: string Domain ID the token restriction belongs to. +# id: string Token restriction ID. +# project_id: string (optional) Project ID that the token must be bound to. +# user_id: string (optional) User ID that the token must be bound to. +# roles: array Bound token roles. +# +# The `input.existing` is null +# default allow := false allow if { @@ -13,11 +24,11 @@ allow if { } allow if { - identity.own_token_restriction + token.own_token_restriction "manager" in input.credentials.roles } violation contains {"field": "domain_id", "msg": "showing token restrictions requires `admin` role."} if { - identity.foreign_token_restriction + token.foreign_token_restriction not "admin" in input.credentials.roles } diff --git a/policy/token/restriction/show_test.rego b/policy/token/restriction/show_test.rego index db4e157f..40f26682 100644 --- a/policy/token/restriction/show_test.rego +++ b/policy/token/restriction/show_test.rego @@ -1,15 +1,15 @@ package test_token_restriction_show -import data.identity.token_restriction.show +import data.identity.token.token_restriction.show test_allowed if { show.allow with input as {"credentials": {"roles": ["admin"]}} - #token_restriction_show.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain"}, "target": {"domain_id": "domain"}} + #token_restriction_show.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain"}, "target": {"restriction": {"domain_id": "domain"}}} #token_restriction_show.allow with input as {"credentials": {"roles": ["reader"]}, "target": {"domain_id": null}} } test_forbidden if { not show.allow with input as {"credentials": {"roles": []}} - not show.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain"}, "target": {"domain_id": "other_domain"}} - not show.allow with input as {"credentials": {"roles": ["member"], "domain_id": "domain"}, "target": {"domain_id": "other_domain"}} + not show.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain"}, "target": {"restriction": {"domain_id": "other_domain"}}} + not show.allow with input as {"credentials": {"roles": ["member"], "domain_id": "domain"}, "target": {"restriction": {"domain_id": "other_domain"}}} } diff --git a/policy/token/restriction/update.rego b/policy/token/restriction/update.rego index 43dcfc7b..b805122c 100644 --- a/policy/token/restriction/update.rego +++ b/policy/token/restriction/update.rego @@ -1,11 +1,27 @@ # METADATA # description: Policy for updating token restrictions -package identity.token_restriction.update +package identity.token.token_restriction.update -import data.identity +import data.identity.token # Update token restriction. - +# +# The `input.target.restriction` is the update patch (TokenRestrictionUpdate): +# allow_renew: bool (optional) Allow token renew. +# allow_rescope: bool (optional) Allow token rescope. +# project_id: string (optional) Project ID that the token must be bound to. +# user_id: string (optional) User ID that the token must be bound to. +# roles: array (optional) Bound token roles. +# +# The `input.existing.restriction` is the stored restriction object (TokenRestriction): +# allow_renew: bool Allow token renew. +# allow_rescope: bool Allow token rescope. +# domain_id: string Domain ID the token restriction belongs to. +# id: string Token restriction ID. +# project_id: string (optional) Project ID that the token must be bound to. +# user_id: string (optional) User ID that the token must be bound to. +# roles: array Bound token roles. +# default allow := false allow if { @@ -14,16 +30,16 @@ allow if { allow if { "manager" in input.credentials.roles - identity.own_token_restriction + token.own_token_restriction } allow if { "member" in input.credentials.roles - input.target.user_id != null - input.credentials.user_id == input.target.user_id + input.existing.restriction.user_id != null + input.credentials.user_id == input.existing.restriction.user_id } violation contains {"field": "domain_id", "msg": "updating token restrictions in other domain requires `admin` role."} if { - identity.foreign_token_restriction + token.foreign_token_restriction not "admin" in input.credentials.roles } diff --git a/policy/token/restriction/update_test.rego b/policy/token/restriction/update_test.rego index 6833a1fe..c2afd678 100644 --- a/policy/token/restriction/update_test.rego +++ b/policy/token/restriction/update_test.rego @@ -1,19 +1,19 @@ package test_token_restriction_update -import data.identity.token_restriction.update +import data.identity.token.token_restriction.update test_allowed if { update.allow with input as {"credentials": {"roles": ["admin"]}} - update.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"domain_id": "domain"}} - update.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain", "user_id": "uid"}, "target": {"domain_id": "domain", "user_id": "uid"}} - update.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain", "user_id": "uid"}, "target": {"domain_id": "domain", "user_id": "uid2"}} - update.allow with input as {"credentials": {"roles": ["member"], "domain_id": "domain", "user_id": "uid"}, "target": {"domain_id": "domain", "user_id": "uid"}} + update.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "existing": {"restriction": {"domain_id": "domain"}}} + update.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain", "user_id": "uid"}, "existing": {"restriction": {"domain_id": "domain", "user_id": "uid"}}} + update.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain", "user_id": "uid"}, "existing": {"restriction": {"domain_id": "domain", "user_id": "uid2"}}} + update.allow with input as {"credentials": {"roles": ["member"], "domain_id": "domain", "user_id": "uid"}, "existing": {"restriction": {"domain_id": "domain", "user_id": "uid"}}} } test_forbidden if { not update.allow with input as {"credentials": {"roles": []}} - not update.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain"}, "target": {"domain_id": "other_domain"}} - not update.allow with input as {"credentials": {"roles": ["member"], "domain_id": "domain"}, "target": {"domain_id": "other_domain"}} - not update.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"domain_id": "other_domain"}} - not update.allow with input as {"credentials": {"roles": ["member"], "domain_id": "domain", "user_id": "uid1"}, "target": {"domain_id": "domain", "user_id": "uid2"}} + not update.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain"}, "existing": {"restriction": {"domain_id": "other_domain"}}} + not update.allow with input as {"credentials": {"roles": ["member"], "domain_id": "domain"}, "existing": {"restriction": {"domain_id": "other_domain"}}} + not update.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "existing": {"restriction": {"domain_id": "other_domain"}}} + not update.allow with input as {"credentials": {"roles": ["member"], "domain_id": "domain", "user_id": "uid1"}, "existing": {"restriction": {"domain_id": "domain", "user_id": "uid2"}}} } diff --git a/policy/user/passkey/register/finish.rego b/policy/user/passkey/register/finish.rego index b7dea51f..6108bff3 100644 --- a/policy/user/passkey/register/finish.rego +++ b/policy/user/passkey/register/finish.rego @@ -4,6 +4,16 @@ package identity.user.passkey.register.finish import data.identity +# Finish registering a passkey for the user +# +# The `input.target.user` is the user object (User): +# domain_id: string domain ID +# +# The `input.target.id` is the user ID: +# id: string user ID +# +# The `input.existing` is null +# default allow := false allow if { @@ -12,7 +22,7 @@ allow if { allow if { "manager" in input.credentials.roles - input.credentials.domain_id == input.target.domain_id + input.credentials.domain_id == input.target.user.domain_id } allow if { diff --git a/policy/user/passkey/register/finish_test.rego b/policy/user/passkey/register/finish_test.rego index 5ab75a16..767f48ad 100644 --- a/policy/user/passkey/register/finish_test.rego +++ b/policy/user/passkey/register/finish_test.rego @@ -4,13 +4,13 @@ import data.identity.user.passkey.register.finish test_allowed if { finish.allow with input as {"credentials": {"roles": ["admin"]}} - finish.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"domain_id": "domain"}} + finish.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"user": {"domain_id": "domain"}}} finish.allow with input as {"credentials": {"user_id": "foo"}, "target": {"id": "foo"}} } test_forbidden if { - not finish.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain"}, "target": {"domain_id": "domain"}} - not finish.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"domain_id": "other_domain"}} - not finish.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"domain_id": null}} + not finish.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain"}, "target": {"user": {"domain_id": "domain"}}} + not finish.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"user": {"domain_id": "other_domain"}}} + not finish.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"user": {"domain_id": null}}} not finish.allow with input as {"credentials": {"user_id": "foo"}, "target": {"id": "bar"}} } diff --git a/policy/user/passkey/register/start.rego b/policy/user/passkey/register/start.rego index ed6f52b3..f7137314 100644 --- a/policy/user/passkey/register/start.rego +++ b/policy/user/passkey/register/start.rego @@ -5,7 +5,15 @@ package identity.user.passkey.register.start import data.identity # Start registering a passkey for the user - +# +# The `input.target.user` is the user object (User): +# domain_id: string domain ID +# +# The `input.target.id` is the user ID: +# id: string user ID +# +# The `input.existing` is null +# default allow := false allow if { @@ -14,7 +22,7 @@ allow if { allow if { "manager" in input.credentials.roles - input.credentials.domain_id == input.target.domain_id + input.credentials.domain_id == input.target.user.domain_id } allow if { diff --git a/policy/user/passkey/register/start_test.rego b/policy/user/passkey/register/start_test.rego index dda50025..d4d44c16 100644 --- a/policy/user/passkey/register/start_test.rego +++ b/policy/user/passkey/register/start_test.rego @@ -4,13 +4,13 @@ import data.identity.user.passkey.register.start test_allowed if { start.allow with input as {"credentials": {"roles": ["admin"]}} - start.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"domain_id": "domain"}} + start.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"user": {"domain_id": "domain"}}} start.allow with input as {"credentials": {"user_id": "foo"}, "target": {"id": "foo"}} } test_forbidden if { - not start.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain"}, "target": {"domain_id": "domain"}} - not start.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"domain_id": "other_domain"}} - not start.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"domain_id": null}} + not start.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain"}, "target": {"user": {"domain_id": "domain"}}} + not start.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"user": {"domain_id": "other_domain"}}} + not start.allow with input as {"credentials": {"roles": ["manager"], "domain_id": "domain"}, "target": {"user": {"domain_id": null}}} not start.allow with input as {"credentials": {"user_id": "foo"}, "target": {"id": "bar"}} }