# Appendix C -- Identity / RBAC reference

Authoritative reference for the cloud's identity model: which keystone roles and personas exist,
what each is for, and which accounts should receive which assignments. Governed by D-051 (SCS
Domain Manager persona) as reconciled by D-064 (scs-0302 alignment + create-op templating fix).

The persona is delivered by a keystone policy override (`policies/domain-manager-policy.yaml`)
attached as a `policyd-override` resource. Provenance: SCS standard scs-0302-w1 (Domain Manager).
This is a TRANSITIONAL policy for pre-2024.2 keystone; on any upgrade to 2024.2+ the override MUST
be removed in favour of the native domain-manager persona (see "Removal" below).

Do NOT hand-assign roles ad hoc. Provision accounts per the tables below so the model stays
auditable and the tenant isolation guarantees hold.

---

## C.1 Role / persona catalog

| Role / persona | Keystone rule | What it is for | Typical scope | Notes |
|----------------|---------------|----------------|---------------|-------|
| Admin / admin | `admin_required` (role:Admin); becomes `cloud_admin` when scoped to the admin domain/project | Full cloud authority (as cloud_admin), or domain-scoped admin over a single service domain | admin domain + admin project (cloud_admin); or a service domain (e.g. magnum) | "Admin" and "admin" are the same role (case-insensitive alias). After D-064 an admin-on-a-domain can create users/projects in THAT domain. NOT given to tenants -- an admin role is not safely domain-confinable; tenants get `manager` instead. |
| manager (SCS Domain Manager) | `is_domain_manager` = role:manager, plus the D-051/D-064 override | Domain-confined IAM self-service: create/manage users, projects, groups, and role assignments WITHIN the manager's own domain | a tenant's domain | Requires the policy override attached. Can assign ONLY the roles in `is_domain_managed_role` (below); cannot grant `admin` or `manager` (anti-escalation). |
| member | standard project role | Operate project resources (compute, network, volumes, images) | a project | The baseline tenant working role. |
| load-balancer_member | Octavia project role | Create / manage Octavia load balancers | a project | Required for tenant LBs and for Magnum (the cluster apiserver LB). Must be held by the Magnum trustor on capi-mgmt (D-039). |
| reader | standard read role | Read-only visibility | project or domain | Optional. Part of the Magnum trustor role set on capi-mgmt (D-039). |

Manager-assignable roles (`is_domain_managed_role`): **member** and **load-balancer_member** only.
This is the anti-escalation boundary -- a compromised or careless domain manager cannot mint an
admin or another manager, and cannot reach outside its own domain.

---

## C.2 Account -> role assignment

| Account | Role(s) | Scope | Why / reference |
|---------|---------|-------|-----------------|
| admin (operator super-admin) | Admin | admin domain + admin project (= cloud_admin) | Cloud operator; full authority. Bootstrap identity. |
| admin (as Magnum trustor) | member + load-balancer_member + reader | capi-mgmt project | So the app-cred Magnum mints per cluster carries Octavia authority for the apiserver LB (D-039). These are the frozen trustor roles delegated into each cluster trust. |
| magnum_domain_admin | Admin | magnum domain | Magnum trustee domain admin; creates the per-cluster trustee USER at cluster-create (D-046; Magnum docs). Works via the D-064 create-op fix -- no extra grant needed. Recreated by the `domain-setup` charm action after every teardown/redeploy (D-046). |
| <tenant>-domain-admin | manager | the tenant's domain | SCS Domain Manager persona (D-051/D-064). PASSWORD identity. Operator provisions the domain + this one account; the tenant self-services everything below via CLI/Horizon. |
| <tenant>-cluster | member + load-balancer_member | the tenant's project | D-066 Option-3. PASSWORD identity, trust-capable. SOLE purpose: `coe cluster` lifecycle (mints the Keystone trust -- MUST be password, since keystone blocks trust creation from app-cred tokens; see D-066). Also owns the magnum-internal CAPO child cred the driver mints per cluster (D-039). Manager-created. |
| <tenant>-svc | member + load-balancer_member | the tenant's project | D-066 Option-3. UNRESTRICTED app cred, for tenant-authored non-trust automation (CI/pipelines). NOT used for cluster-create (app-cred trust creation is blocked). Manager-created. |
| <tenant> human users | member (+ load-balancer_member if they use LBs) | the tenant's project(s) | Created/assigned by the tenant's domain-manager. Operator not in the loop. |
| per-cluster trustee | (delegated via trust -- not a direct grant) | -- | Magnum mints at cluster-create, deletes at cluster-delete. Carries the trustor (<tenant>-cluster) roles through the trust (D-039). Never assign roles by hand. |

IMPORTANT (D-066): the cluster-creator (`<tenant>-cluster`) authenticates by PASSWORD, not app cred.
Keystone blocks trust creation from application-credential tokens regardless of the unrestricted flag
(this build's `_check_application_credential`), and Magnum needs a Keystone trust at cluster-create.
The app-cred identity (`<tenant>-svc`) is for non-trust automation only. See appendix-D.

Provisioning direction: the operator creates a tenant's DOMAIN and its single `manager` account,
then hands off. Everything below the domain (users, projects, member/LB grants) is tenant
self-service. This is the whole point of the persona -- it removes the operator from routine
tenant IAM while keeping a hard domain boundary.

---

## C.3 Attaching the policy override

Prerequisite: keystone deployed with `use-policyd-override=true` (already set in the bundle).
Note D-050: `use-policyd-override=true` with NO resource attached is a silent no-op -- the zip
must actually be supplied, or the override does nothing while reporting healthy.

Pre-attach re-check on the jumphost (from the repo working copy), then attach:

NOTE (DOCFIX-071): deploy-time delivery is now BUNDLE-NATIVE -- keystone carries
`resources: policyd-override: ./policies/overrides.zip`, so a fresh deploy applies the
policy with no manual attach. THIS block is the LIVE-UPDATE path (changing the policy on
a running cloud), and the drift guard: `provider-bundle-check.py` verifies the committed
zip's content matches `policies/domain-manager-policy.yaml`, so rebuild + recommit the
zip whenever the yaml changes.

```
( {
  # 1) validate the file before attaching (YAML + ASCII + connector lint)
  python3 -c 'import yaml,sys; d=yaml.safe_load(open("policies/domain-manager-policy.yaml")); print("YAML OK:",len(d),"rules")' || exit 1
  LC_ALL=C grep -nP "[^\x00-\x7F]" policies/domain-manager-policy.yaml && { echo "NON-ASCII -- STOP"; exit 1; } || echo "ASCII clean"
  grep -nE "\)\s+(rule:|role:|token|project_id|domain_id|user_id|None:)" policies/domain-manager-policy.yaml && { echo "MALFORMED connector -- STOP"; exit 1; } || echo "connector lint clean"

  # 2) package (repo copy, so the committed artifact and the attached one are the SAME
  #    bytes) and attach. $HOME-adjacent path -- attach-resource CANNOT read /tmp on this
  #    jumphost (snap confinement; the previous /tmp form here was the known-broken shape).
  ( cd policies && zip -X -j overrides.zip domain-manager-policy.yaml ) || exit 1
  juju attach-resource -m openstack keystone policyd-override="$PWD/policies/overrides.zip"
} )
```

Gate: `juju status -m openstack keystone` must move from `PO (broken)` to `PO:` and settle
active/idle, with no other charm disturbed. If the charm rejects the file it stays `PO (broken)`.

Rollback (immediate): `juju config -m openstack keystone use-policyd-override=false`
(the override stops applying; keystone reverts to shipped defaults on the next hook).

The charm validates YAML only. It does NOT parse the oslo.policy rule grammar, so a syntactically
malformed rule can pass validation and silently no-op. Always run the oslo.policy parse in the
sandbox before delivering a change to this file.

---

## C.4 Tenant self-service validation (G3)

Run after the override is active. Confirms the persona works AND is properly bounded.

PASS cases (a `manager`-on-domain account, scoped to its own domain, must succeed):
- create a user in its own domain
- create a project in its own domain
- grant `member` and `load-balancer_member` to a user on a project in its own domain

DENY cases (the same account must be refused):
- grant `admin` or `manager` to anyone (anti-escalation)
- create/read/modify anything in a DIFFERENT domain (cross-domain isolation)

Unaffected:
- cloud_admin (the operator admin) retains full authority everywhere

Only when all three groups hold is the persona accepted for that release.

---

## C.5 Known limitations (carried from scs-0302)

- CORRECTION (verified live 2026-07-01): on THIS deployment a domain manager's `domain list`
  returns ONLY its own domain -- keystone scope-filters the result even though the D-064 policy
  authorizes `list_domains` for a manager. So the cloud-wide domain-name enumeration described
  in the SCS worst-case does NOT manifest here; isolation is tighter than the standard warns.
  Role names (`list_roles`) may still be broadly visible (the persona needs to resolve role
  names); this remains names-only and confers no resource access. The original SCS caveat is
  retained below as version/config-dependent, NOT asserted as this cloud's behavior:
- [SCS worst-case, not observed here] A domain manager could enumerate ALL domain names/ids
  (`list_domains`) and ALL role names (`list_roles`) cloud-wide. This is names/ids only --
  no access to other domains' resources --
  and is required for the manager to resolve domains/roles by name. It is inherent to the
  pre-2024.2 transitional policy; upstream RBAC-scoping of domain listing is a pending fix.
- The persona relies on `enforce_scope=False` (old-style policy). It is a bridge, not the
  destination.

## C.6 Removal (on upgrade to keystone 2024.2+)

2024.2 ships a native domain-manager persona and secure-RBAC scope enforcement. On that upgrade:
detach the `policyd-override` resource (or set `use-policyd-override=false`), adopt the native
persona, and retire this file. Leaving the old-style override in place on a secure-RBAC keystone
is unsupported and will conflict. Tracked by D-051/D-064.
