let d = import "adr-defaults.ncl" in d.make_adr { id = "adr-007", title = "Cedar Policy Engine for Declarative Authorization", status = 'Accepted, date = "2024-11-01", context = "Vapora needs per-stage execution control in the workflow engine and fine-grained access control at the API layer. The markdown ADR describes Cedar in vapora-backend, but as of 2026-03-27, Cedar is implemented in vapora-workflow-engine/src/auth.rs (CedarAuthorizer, loaded from .cedar policy files) — not in the vapora-backend API handlers directly. Cedar policy evaluation happens before workflow stage execution, controlling which principals may trigger which actions on which resources.", decision = "Cedar (via cedar-policy crate) is the policy engine for authorization in vapora-workflow-engine. CedarAuthorizer loads .cedar policy files from a configurable directory at startup. All workflow stage execution requests are evaluated against the loaded policy set before the stage executes. Policy files are version-controlled in the project repo.", rationale = [ { claim = "Declarative policies are auditable and reviewable without reading Rust code", detail = "A Cedar policy file expressing 'Architect role may trigger Deploy actions on any workflow' is readable by non-Rust engineers, auditable by security reviewers, and versionable in Git. An equivalent RBAC check buried in match statements is none of these.", }, { claim = "Cedar's formal verification model prevents policy logic errors", detail = "Cedar policies are formally specified — the evaluator is proven to be sound and complete. Custom RBAC implementations in Rust carry no such guarantees and have historically introduced privilege escalation via logic errors in compound conditions.", }, { claim = "Policy changes do not require recompilation", detail = "CedarAuthorizer loads .cedar files at startup from a configurable directory. Updating authorization rules is a file change + restart, not a code change + deploy. This enables security patches to access control without a full release.", }, ], consequences = { positive = [ "Authorization rules for workflow stage execution are version-controlled separately from Rust logic", "Adding a new stage or role requires only a .cedar policy addition, not a Rust change", "CedarAuthorizer fails closed — if no .cedar files are found, startup fails rather than allowing all requests", "Policy evaluation is synchronous and sub-millisecond — no async overhead in the authorization hot path", ], negative = [ "Cedar policy language has a learning curve for engineers unfamiliar with it", "Entity/action/resource schema must be kept synchronized between Cedar policies and the Rust types they authorize", "The .cedar policy directory path must be configured correctly — misconfiguration causes startup failure", ], }, alternatives_considered = [ { option = "Custom RBAC in Rust (match on roles + permissions)", why_rejected = "Custom RBAC is not auditable, not formally verified, and grows in complexity as role/permission combinations increase. Authorization bugs in custom code have caused data leaks in several production incidents.", }, { option = "Casbin policy engine", why_rejected = "Casbin's Rust implementation is less mature than cedar-policy. Cedar has formal verification backing and is used in production at AWS scale. The correctness guarantee is the primary selection criterion.", }, ], constraints = [ { id = "cedar-authorizer-in-workflow-engine", claim = "vapora-workflow-engine must use CedarAuthorizer for stage execution authorization — no ad-hoc role checks in stage execution code", scope = "vapora-workflow-engine/src/", severity = 'Hard, check = { tag = 'Grep, pattern = "CedarAuthorizer", paths = ["crates/vapora-workflow-engine/src/"], must_be_empty = false }, rationale = "Ad-hoc role checks inside stage execution logic bypass the audit trail and cannot be updated without recompilation.", }, { id = "cedar-policy-files-in-repo", claim = "Cedar .cedar policy files must be version-controlled in the project repo", scope = "vapora (repo root or crates/vapora-workflow-engine/)", severity = 'Soft, check = { tag = 'NuCmd, cmd = "let r = (do { glob '**/*.cedar' } | complete); if $r.exit_code != 0 { exit 1 }; let files = ($r.stdout | lines | where { |l| ($l | str trim | is-not-empty) }); if ($files | is-empty) { exit 1 } else { exit 0 }", expect_exit = 0 }, rationale = "Policy files outside version control cannot be audited, rolled back, or reviewed in PRs. A misconfigured out-of-band policy file is a security incident waiting to happen.", }, ], related_adrs = ["adr-002", "adr-004"], ontology_check = { decision_string = "cedar-policy for workflow stage authorization in vapora-workflow-engine; CedarAuthorizer loads .cedar files at startup; no ad-hoc role checks in stage code", invariants_at_risk = [], verdict = 'Safe, }, }