diff --git a/docs/design-decisions.md b/docs/design-decisions.md index 45452c2..b828737 100644 --- a/docs/design-decisions.md +++ b/docs/design-decisions.md @@ -1159,3 +1159,73 @@ **Related:** D-051 (reconciled here), D-046 (magnum trustee domain), D-039 (per-cluster app-cred roles), D-050 (resolved by supplying the zip), scs-0302-w1 (the authoritative standard), appendix-C (identity/RBAC reference). **Supersedes:** D-051's [LIVE-READ PENDING] gate (discharged). + + +--- + +## D-065: Fix identity:create_trust non-resolving policy template (Magnum multi-tenant cluster blocker) + +**Status:** ADOPTED 2026-07-02. Extends the D-064 defect-class fix to the keystone trust family. +Behavioral acceptance = a tenant service identity clears create_trust and the cluster converges +(pending re-attach + retest at adoption). + +**Context / trigger:** multi-tenant validation (tenant acme). A tenant service identity (acme-svc, +app cred, holding member + load-balancer_member on acme-prod) creating a Magnum cluster failed at +create_trust with identity:create_trust 403 -- AFTER D-064 unblocked create_user (the trustee user +was created). The SAME 403 reproduced (a) for admin via password auth, and (b) via a DIRECT +`openstack trust create` with trustor == caller == self, no magnum in the path. That proves it is +identity-independent and not a magnum bug. + +**Root cause:** the charm-rendered base policy.json defines +identity:create_trust = "user_id:%(trust.trustor_user_id)s" -- the legacy NON-target-prefixed +template that does NOT resolve on Caracal (keystone populates target.trust.trustor_user_id). The +rule evaluates user_id == "" -> always false -> every trust create 403s regardless of caller. Same +defect class as D-064 (create_user's %(user.domain_id)s vs the resolving %(target.user.domain_id)s). +Keystone's OWN shipped default is the target-prefixed form: +"user_id:%(target.trust.trustor_user_id)s". + +**Masking:** create_trust was never reached before D-064 because create_user 403'd first. The +2026-06-09 CREATE_COMPLETE was on the pre-2026-06-11-teardown cloud; this redeploy's create_trust +had never been exercised until D-064 opened the path. + +**Causation proof (verify-before-fix):** with use-policyd-override=false (base policy only), the +direct trust create STILL 403s -> the D-064 override is NOT the cause; the base charm policy owns +the bug. Override re-enabled immediately (PO: restored) -- the cloud was not left without the +manager persona. + +**Decision:** add identity:create_trust = "user_id:%(target.trust.trustor_user_id)s" to the D-064 +override (policies/domain-manager-policy.yaml). Exactly the keystone-shipped default, and +consistent with this cloud's own already-correct sibling rules (list_trusts_for_trustor/trustee). +No admin fallback (matches the shipped default; trusts are self-delegation only). No manager branch +(create_trust is universal self-delegation, not part of the domain-manager persona). + +**Scope (evidence-gated; NOT a blanket family sweep):** create_trust is the ONLY demonstrably +broken trust rule. list_trusts_for_trustor / list_trusts_for_trustee already carry the +target-prefixed form (match shipped). get_trust / delete_trust / list_trusts / list_roles_for_trust +/ get_role_for_trust are live "" (empty) but code-guarded in keystone (_trustor_trustee_only; +delete checks user==trustor or is_admin) -> functional, not broken, not blockers; left unchanged. + +**Second-check clearance:** keystone's create_trust also enforces _require_trustor_has_role_in_project +(trustor must hold each delegated role on the project). Magnum delegates context.roles (the caller's +token roles), which by construction are a subset of what the caller holds on the scoped project -> +passes. acme-svc holds member + load-balancer_member on acme-prod; confirmed clear on retest. + +**Validation:** oslo.policy parses all 38 rules; YAML + ASCII + connector lint clean. + +**PROPOSED / OPEN (separate hardening, NOT actioned here):** live identity:list_trusts = "" lets any +authenticated user enumerate ALL trusts (trustor/trustee/project relationships) cloud-wide -- an +info-disclosure the newer keystone default tightens to +"rule:admin_required or (role:reader and system_scope:all)". This is a security-posture change, not +a functional blocker (create/get/delete are code-guarded), so it is recorded as an open item to rule +on rather than bundled into this blocker fix. If adopted, add list_trusts (and optionally the read +rules) at the shipped defaults in the same override. + +**Roosevelt:** carry the create_trust override with the rest of the D-064 policy. The base-charm +non-resolving-template defect is upstream-worthy -- report against the keystone charm's rendered +policy.json for Caracal (2024.1). Remove on 2024.2+ (native secure-RBAC ships the resolving defaults). + +**Related:** D-064 (same defect class; same override file), D-039 (the trustor roles that satisfy the +trustor-has-role check), D-046 (magnum trustee domain), D-035 (mgmt cluster), appendix-D (trust +model). **Revises:** appendix-D section D.3 -- the role-delegation hypothesis is REFUTED (a +clean-role tenant identity still 403'd; the cause was the non-resolving policy template, not role +delegation). appendix-D to be corrected on finalize. diff --git a/docs/v1-redeploy-changelog.md b/docs/v1-redeploy-changelog.md index b32e51a..db523ff 100644 --- a/docs/v1-redeploy-changelog.md +++ b/docs/v1-redeploy-changelog.md @@ -1306,8 +1306,25 @@ manager can enumerate domain + role names cloud-wide (list_domains/list_roles); remove override on any 2024.2+ upgrade. +## 2026-07-02 -- multi-tenant trust blocker -> D-065 (create_trust template fix) + +Multi-tenant validation (tenant acme, manager persona) reached cluster-create and failed at +identity:create_trust 403 -- AFTER D-064 unblocked create_user (trustee user created). Reproduced +identity-independently: admin (password) and a direct `openstack trust create` with trustor==self +both 403. Root cause: base charm policy.json ships create_trust with the non-resolving +user_id:%(trust.trustor_user_id)s (Caracal populates target.trust.trustor_user_id) -> evaluates +false for everyone. Same defect class as D-064. Causation proven by toggling use-policyd-override=false +(still 403 -> base policy owns it, override exonerated), then re-enabling (PO: restored). + +Fix (D-065): added identity:create_trust = user_id:%(target.trust.trustor_user_id)s (keystone's +shipped default) to the override. create_trust is the ONLY broken trust rule (list_trusts_for_* already +target-prefixed; get/delete/list/roles-for-trust are empty but code-guarded -> left unchanged -- +evidence-gated, not a blanket sweep). oslo.policy parses all 38 rules; ASCII+LF+connector clean. +appendix-D D.3 (role-delegation hypothesis) REFUTED and to be revised. list_trusts="" info-disclosure +recorded PROPOSED/OPEN, not actioned. + ### Next-free numbers -Design decision: D-065. Doc fix: DOCFIX-065. (D-064 ASSIGNED above = reconcile D-051 to scs-0302 +Design decision: D-066. Doc fix: DOCFIX-065. (D-064 ASSIGNED above = reconcile D-051 to scs-0302 + create-op templating fix. DOCFIX-064 RESERVED = phase-08 runbook sweep (image --public; seed retry/timeout + poll hard-gate + post-active property re-verify; image-absent guard; template capi-mgmt scope preamble + flavor floor; 8.1 D-039 role + keypair pre-checks; octavia prereq diff --git a/policies/domain-manager-policy.yaml b/policies/domain-manager-policy.yaml index 893b9df..796995b 100644 --- a/policies/domain-manager-policy.yaml +++ b/policies/domain-manager-policy.yaml @@ -92,6 +92,7 @@ "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" @@ -115,3 +116,4 @@ "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" +