Skip to content

ADR 0010 — IAM RBAC: policies, rollen en security-first enforcement

Status: Accepted (Phase 1–3A, UI redesign, 3B–3D partial)
Date: 2026-06-30
Related: Authentication, Security, ADR 0009, API Keys & Products


Context

MeshFlows identity gebruikt al een scope-gebaseerd RBAC-model:

  • Permissions — string scopes (meshflows:workflow:run:demo, wildcards meshflows:workflow:run:*)
  • Groups — bundels van permissions (in de UI “Roles” genoemd)
  • Users — lid van groepen + optionele directe grants

De dashboard Access-pagina bood een vereenvoudigde vinkjes-UI boven dit model. Dat leidde tot problemen:

  1. Flatten-bugGET retourneerde effectieve rechten (direct ∪ groep), maar PUT schreef die set terug als directe rechten → groepsrechten werden gedupliceerd; afvinken van geërfde rechten had geen effect.
  2. Beperkte UI — platform-, IAM- en design-rechten ontbraken in de vinkjes; groepsrechten waren niet beheerbaar via UI.
  3. Enforcement-inconsistentie — gateway check_scopes deed exacte string-match; orchestrator/dashboard gebruikten wildcard-matching (scope_allowed).
  4. Stale JWT scopes/auth/me las scopes uit het token, niet uit de database → ingetrokken rechten bleven geldig tot token-expiry.

Let op: Gateway workflow policies (YAML: required_headers, oauth_inbound, rate_limit_product, transform) zijn geen IAM-rechten. Die blijven runtime/edge concerns (zie ADR 0009).


Decision

1. Terminologie

Term Implementatie UI-label
Permission / policy Rij in permissions, scope-string Permission
Role Rij in groups + group_permissions Role (groep)
Direct grant user_permissions Extra rechten (override)
Effective direct ∪ from_groups Effectief

2. API-contract (identity)

GET /users/{username}/permissions retourneert een object:

{
  "direct": ["meshflows:runtime:secrets:read"],
  "from_groups": ["meshflows:workflow:run:*"],
  "effective": ["meshflows:runtime:secrets:read", "meshflows:workflow:run:*"]
}

PUT /users/{username}/permissions schrijft alleen direct (vervangt user_permissions); response = zelfde structuur.

GET /groups/{group}/permissions en PUT /groups/{group}/permissions beheren rol-rechten.

POST /permissions blijft beschikbaar om nieuwe scopes te registreren (catalogus).

3. Enforcement — één wildcard-semantiek

Alle services gebruiken scope_allowed(granted, required):

  • meshflows:* → alles
  • exacte match of prefix-wildcard (meshflows:workflow:run:* matcht meshflows:workflow:run:demo)

Toepassing:

  • Identity (_require_admin, API-key scope intersection)
  • Gateway (check_scopes)
  • Dashboard / orchestrator / scheduler (bestaand)

4. Live scopes bij /auth/me

Na JWT-validatie worden scopes en groups opnieuw uit de database geladen. Intrekking van rechten is direct zichtbaar in dashboard en identity-admin (gateway JWT blijft cached tot expiry — acceptabel met korte TTL; optioneel later: gateway introspectie).

5. UI — master-detail IAM (Phase 2–3D)

Fase 1 (afgerond): direct vs inherited vinkjes, flatten-bug opgelost.

Huidige UI (Access-pagina):

  • Tabs: Users | Groups | Policy catalog | Audit log
  • Master-detail met filterbare lijsten (geen dropdowns voor users/groepen)
  • Policy-toggles met kleuraccenten (platform, workflow, secrets, break-glass)
  • Groepslidmaatschap via klikbare chips
  • Per-workflow run/retrigger/schedule met workflow-filter
  • Break-glass (meshflows:*) vraagt bevestiging
  • 2FA optioneel — status in user-detail; admin reset bij verloren device (geen platform-enforce)

Implementatie: engine/services/dashboard/app/static/app.iam-ui.js

6. Security-baseline

  • meshflows:iam:manage vereist voor IAM-mutaties (niet alleen platform:admin).
  • meshflows:* alleen op break-glass accounts; MFA aanbevolen maar niet verplicht platform-breed.
  • API keys: effectieve scopes = intersection key-scopes ∩ user effective, met wildcard-semantiek.
  • Geen deny-ACL in Phase 1 (allow-only).

Consequences

Positief

  • Groepsbeleid blijft bron van waarheid; geen stille duplicatie naar user_permissions.
  • Wildcard-rechten werken consistent op gateway en API keys.
  • Snellere intrekking via live /auth/me.
  • Duidelijk pad naar uitbreidbare permission-catalogus.

Negatief / trade-offs

  • GET /users/.../permissions breaking change (was list[str], wordt object) — alleen dashboard/automation callers; documenteren in wiki.
  • Gateway JWT blijft statisch tot expiry; productie: IDENTITY_JWT_EXP_MINUTES laag houden of GATEWAY_JWT_SCOPE_MODE=live.
  • Scheduler en orchestrator importeren meshflows_rbac (Docker build context engine/).

Implementation checklist (Phase 1)

  • [x] ADR 0010
  • [x] Structured permissions API + GET /groups/{name}/permissions
  • [x] _current_user live scopes from DB
  • [x] Gateway check_scopes + rbac.py
  • [x] API key validation wildcard intersection
  • [x] Dashboard Access UI: direct vs inherited, role editor
  • [x] Tests

Implementation checklist (Phase 2)

  • [x] Shared engine/meshflows_rbac package (identity, gateway, dashboard)
  • [x] IAM audit log table + GET /audit/iam
  • [x] Permission catalog API + dashboard UI
  • [x] Docker build context from engine/ for RBAC services
  • [x] Gateway JWT live introspection (GATEWAY_JWT_SCOPE_MODE=live, cache via /auth/me)

Implementation checklist (Phase 3)

  • [x] Gateway live JWT scopes (3A)
  • [x] Scheduler + orchestrator → meshflows_rbac (3B)
  • [x] Role templates wiki (19-iam-role-templates.md) (3C)
  • [x] IAM UX: master-detail UI, audit action filters, break-glass confirm, admin MFA reset (3D partial)
  • [x] Access-review API + CLI (engine/scripts/iam-access-review.py)
  • [x] Auto-register per-workflow scopes (POST /permissions/sync-workflows, orchestrator on reload)
  • [ ] Break-glass periodic review automation (cron / CI integration left to operators)

See also

  • engine/meshflows_rbac/ — shared scope_allowed, PERMISSION_LABELS
  • engine/services/identity/app/rbac.py — re-export
  • engine/services/identity/app/main.py — IAM tables en endpoints
  • engine/services/dashboard/app/static/app.iam-ui.js — Access UI