# =============================================================================
# domain-manager-policy.yaml -- Keystone policy override (SCS Domain Manager)
# Charmed OpenStack Caracal 2024.1 -- OLD-STYLE-DEFAULTS aligned
# =============================================================================
# PROVENANCE (do not strip):
#  - Domain-manager BRANCHES: adapted from the canonical SCS reference
#    SovereignCloudStack/standards, scs-0302-w1-domain-manager-implementation-notes
#    (the 2024.1-and-below transitional policy implementation).
#  - FALLTHROUGH defaults: reproduced VERBATIM from THIS deployment's live policy
#    (`oslopolicy-policy-generator --namespace keystone` on keystone/leader,
#    captured 2026-06-24). The cloud runs OLD-STYLE policy (enforce_scope and
#    enforce_new_defaults are forced FALSE by the Canonical charm), so each
#    overridden rule reproduces the live OLD-STYLE default and PREPENDS the
#    manager branch. NO new-style (system_scope:all) rules are introduced.
#
# WHY NO base_* BLOCK (improvement over the SCS template):
#  This overlay does NOT redefine keystone's helper rules (cloud_admin,
#  admin_and_matching_*, owner, domain_admin_for_grants, admin_on_*_filter, ...).
#  Those remain keystone CODE DEFAULTS and are referenced by name. Each overridden
#  rule = (manager branch) OR (the live default string, verbatim). This is
#  behavior-preserving by construction for every non-manager actor, avoids copying
#  helper bodies (which would risk drift), and preserves cloud_admin's baked-in
#  admin domain/project IDs untouched. The `or rule:admin_required` tail from the
#  SCS NEW-STYLE template is deliberately OMITTED -- the live defaults gate on
#  cloud_admin (narrower than admin_required); appending admin_required would
#  WIDEN access beyond the current default.
#
# DELIVERY (use-policyd-override already true):
#    zip overrides.zip domain-manager-policy.yaml
#    juju attach-resource keystone policyd-override=overrides.zip
#  -> /etc/keystone/policy.d/domain-manager-policy.yaml ; keystone restarts.
#  juju status: "PO:" = applied; "PO (broken):" = YAML unparseable (atomic discard).
#
# *** HARD WARNINGS ***
#  1. Validation is YAML-ONLY. A misspelled rule key PASSES and silently no-ops.
#     Acceptance MUST be BEHAVIORAL (see ACCEPTANCE). Same trap as D-046.
#  2. 2024.1 ONLY. Native Domain Manager persona ships in 2024.2 (Dalmatian); on
#     any upgrade past 2024.1 this overlay MUST be removed/reconciled (it would
#     conflict with the native persona). [D-051]
#  3. THREE rules are marked [PENDING-LIVE-READ]: identity:list_users,
#     identity:list_projects, identity:list_groups did NOT appear as explicit
#     lines in the live dump (they fall through to "default": "rule:admin_required").
#     Their fallthrough below is the conservative "rule:admin_required" placeholder.
#     CONFIRM with the short read before shipping; replace if the live default differs.
#
# THIS-DEPLOY ADAPTATIONS vs verbatim SCS:
#  - is_domain_managed_role = member + load-balancer_member ONLY. admin NEVER in
#    the list (anti-escalation). load-balancer_member INCLUDED (Magnum apiserver LB).
#    reader/manager intentionally omitted (least privilege; tenants cannot propagate
#    domain-manager rights).
#
# ACCEPTANCE (behavioral -- gate G3; never trust "PO:" alone):
#  As a domain-scoped `manager` in domain D:
#    PASS user/project create within D; role add member|load-balancer_member within D;
#         user list / project list / group list within D; domain list; role list.
#    DENY role add admin (not a managed role); any create/list in a different domain.
#  As cloud admin: ALL prior admin operations still succeed (cloud_admin reproduced).
# =============================================================================

# --- Domain Manager rules (NEW rules defined by this overlay) ---

# A domain manager is a user holding the `manager` role (assigned in domain scope).
"is_domain_manager": "role:manager"

# Roles a domain manager may assign/revoke. *** admin MUST NOT appear here. ***
# member + load-balancer_member (the latter for the Magnum apiserver load balancer).
"is_domain_managed_role": "'member':%(target.role.name)s or 'load-balancer_member':%(target.role.name)s"

# Grant-scoping helpers (NEW; self-contained token-domain checks, valid old-style).
"is_domain_user_project_grant": "token.domain.id:%(target.user.domain_id)s and token.domain.id:%(target.project.domain_id)s"
"is_domain_group_project_grant": "token.domain.id:%(target.group.domain_id)s and token.domain.id:%(target.project.domain_id)s"
"is_domain_level_user_grant": "token.domain.id:%(target.user.domain_id)s and token.domain.id:%(target.domain.id)s"
"is_domain_level_group_grant": "token.domain.id:%(target.group.domain_id)s and token.domain.id:%(target.domain.id)s"
"domain_manager_grant": "rule:is_domain_manager and (rule:is_domain_user_project_grant or rule:is_domain_group_project_grant or rule:is_domain_level_user_grant or rule:is_domain_level_group_grant)"

# --- Domain / role discovery (manager branch + verbatim live default) ---
"identity:get_domain": "(rule:is_domain_manager and token.domain.id:%(target.domain.id)s) or rule:cloud_admin or rule:admin_and_matching_domain_id or token.project.domain.id:%(target.domain.id)s"
"identity:list_domains": "rule:is_domain_manager or rule:cloud_admin"
"identity:get_role": "(rule:is_domain_manager and rule:is_domain_managed_role) or rule:admin_required"
"identity:list_roles": "rule:is_domain_manager or rule:admin_required"

# --- Users (manager branch + verbatim live default) ---
# [PENDING-LIVE-READ] list_users default not explicit in dump -> conservative admin_required
"identity:list_users": "(rule:is_domain_manager and token.domain.id:%(target.domain_id)s) or rule:cloud_admin or rule:admin_and_matching_domain_id"
"identity:get_user": "(rule:is_domain_manager and token.domain.id:%(target.user.domain_id)s) or rule:cloud_admin or rule:admin_and_matching_target_user_domain_id or rule:owner"
"identity:create_user": "(rule:is_domain_manager and token.domain.id:%(target.user.domain_id)s) or rule:cloud_admin or rule:admin_and_matching_target_user_domain_id"
"identity:update_user": "(rule:is_domain_manager and token.domain.id:%(target.user.domain_id)s) or rule:cloud_admin or rule:admin_and_matching_target_user_domain_id"
"identity:delete_user": "(rule:is_domain_manager and token.domain.id:%(target.user.domain_id)s) or rule:cloud_admin or rule:admin_and_matching_target_user_domain_id"

# --- Projects (manager branch + verbatim live default) ---
# [PENDING-LIVE-READ] list_projects default not explicit in dump -> conservative admin_required
"identity:list_projects": "(rule:is_domain_manager and token.domain.id:%(target.domain_id)s) or rule:cloud_admin or rule:admin_and_matching_domain_id"
"identity:get_project": "(rule:is_domain_manager and token.domain.id:%(target.project.domain_id)s) or rule:cloud_admin or rule:admin_and_matching_target_project_domain_id or project_id:%(target.project.id)s"
"identity:create_project": "(rule:is_domain_manager and token.domain.id:%(target.project.domain_id)s) or rule:cloud_admin or rule:admin_and_matching_target_project_domain_id"
"identity:create_trust": "user_id:%(target.trust.trustor_user_id)s"
"identity:update_project": "(rule:is_domain_manager and token.domain.id:%(target.project.domain_id)s) or rule:cloud_admin or rule:admin_and_matching_target_project_domain_id"
"identity:delete_project": "(rule:is_domain_manager and token.domain.id:%(target.project.domain_id)s) or rule:cloud_admin or rule:admin_and_matching_target_project_domain_id"
"identity:list_user_projects": "(rule:is_domain_manager and token.domain.id:%(target.user.domain_id)s) or rule:owner or rule:admin_and_matching_domain_id"

# --- Role assignments / grants (manager branch + managed-role gate + verbatim live default) ---
"identity:check_grant": "rule:domain_manager_grant or rule:cloud_admin or rule:domain_admin_for_grants or rule:project_admin_for_grants"
"identity:list_grants": "(rule:is_domain_manager and token.domain.id:%(target.user.domain_id)s) or (rule:is_domain_manager and token.domain.id:%(target.group.domain_id)s) or rule:cloud_admin or rule:domain_admin_for_list_grants or rule:project_admin_for_list_grants"
"identity:create_grant": "(rule:domain_manager_grant and rule:is_domain_managed_role) or rule:cloud_admin or rule:domain_admin_for_grants or rule:project_admin_for_grants"
"identity:revoke_grant": "(rule:domain_manager_grant and rule:is_domain_managed_role) or rule:cloud_admin or rule:domain_admin_for_grants or rule:project_admin_for_grants"
"identity:list_role_assignments": "(rule:is_domain_manager and token.domain.id:%(target.domain_id)s) or rule:cloud_admin or rule:admin_on_domain_filter or rule:admin_on_project_filter"

# --- Groups (manager branch + verbatim live default) ---
# [PENDING-LIVE-READ] list_groups default not explicit in dump -> conservative admin_required
"identity:list_groups": "(rule:is_domain_manager and token.domain.id:%(target.group.domain_id)s) or rule:cloud_admin or rule:admin_and_matching_domain_id"
"identity:get_group": "(rule:is_domain_manager and token.domain.id:%(target.group.domain_id)s) or rule:cloud_admin or rule:admin_and_matching_target_group_domain_id"
"identity:create_group": "(rule:is_domain_manager and token.domain.id:%(target.group.domain_id)s) or rule:cloud_admin or rule:admin_and_matching_target_group_domain_id"
"identity:update_group": "(rule:is_domain_manager and token.domain.id:%(target.group.domain_id)s) or rule:cloud_admin or rule:admin_and_matching_target_group_domain_id"
"identity:delete_group": "(rule:is_domain_manager and token.domain.id:%(target.group.domain_id)s) or rule:cloud_admin or rule:admin_and_matching_target_group_domain_id"
"identity:list_groups_for_user": "(rule:is_domain_manager and token.domain.id:%(target.user.domain_id)s) or rule:owner or rule:admin_and_matching_target_user_domain_id"
"identity:list_users_in_group": "(rule:is_domain_manager and token.domain.id:%(target.group.domain_id)s) or rule:cloud_admin or rule:admin_and_matching_target_group_domain_id"
"identity:remove_user_from_group": "(rule:is_domain_manager and token.domain.id:%(target.group.domain_id)s and token.domain.id:%(target.user.domain_id)s) or rule:cloud_admin or rule:admin_and_matching_target_group_domain_id"
"identity:check_user_in_group": "(rule:is_domain_manager and token.domain.id:%(target.group.domain_id)s and token.domain.id:%(target.user.domain_id)s) or rule:cloud_admin or rule:admin_and_matching_target_group_domain_id"
"identity:add_user_to_group": "(rule:is_domain_manager and token.domain.id:%(target.group.domain_id)s and token.domain.id:%(target.user.domain_id)s) or rule:cloud_admin or rule:admin_and_matching_target_group_domain_id"

# --- Trusts: enumeration hardening (D-073, adopted 2026-07-06) ---
# Live default was "" (empty): ANY authenticated user could enumerate ALL trusts
# cloud-wide (trustor/trustee/project relationships) -- an info disclosure on a
# multi-tenant cloud. Tightened to the modern keystone default VERBATIM (string as
# recorded in the D-064 create_trust RCA evidence). This is the ONE deliberate
# exception to the header's "no new-style rules" invariant: the
# (role:reader and system_scope:all) branch can never evaluate TRUE under this
# cloud's OLD-STYLE enforcement (enforce_scope forced false; no system-scoped
# tokens issued), so the effective behavior HERE is exactly rule:admin_required --
# the dead branch can only narrow, never widen, and keeping the upstream string
# verbatim preserves provenance for the 2024.2+ reconciliation.
# Tooling compatibility (checked 2026-07-06): tenant-offboard.sh and
# tenant-acceptance.sh call `trust list` in the ADMIN context only -> unaffected.
# Siblings get_trust / delete_trust / list_roles_for_trust / get_role_for_trust
# remain UNCHANGED per D-064's evidence-gated scope: live "" but code-guarded in
# keystone (_trustor_trustee_only) -> functional, and no verified shipped-default
# strings exist in repo evidence to copy. [D-073]
"identity:list_trusts": "rule:admin_required or (role:reader and system_scope:all)"

