Vapora/adrs/adr-007-cedar-authorization.ncl

80 lines
5.1 KiB
Text
Raw Normal View History

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,
},
}