ADR-010: Cedar Policy Engine para Authorization
Status: Accepted | Implemented Date: 2024-11-01 Deciders: Security Architecture Team Technical Story: Implementing declarative RBAC with audit-friendly policies
Decision
Usar Cedar policy engine para autorización declarativa (no custom RBAC, no Casbin).
Rationale
- Declarative Policies: Separar políticas de autorización de lógica de código
- Auditable: Políticas versionables en Git, fácil de revisar
- AWS Proven: Usado internamente en AWS, production-proven
- Type Safe: Schemas para resources y principals
- No Vendor Lock-in: Open source, portable
Alternatives Considered
❌ Custom RBAC Implementation
- Pros: Full control
- Cons: Mantenimiento pesada, fácil de introducir vulnerabilidades
❌ Casbin (Policy Engine)
- Pros: Flexible
- Cons: Menos maduro en Rust ecosystem que Cedar
✅ Cedar (CHOSEN)
- Declarative, auditable, production-proven, AWS-backed
Trade-offs
Pros:
- ✅ Declarative policies separate from code
- ✅ Easy to audit and version control
- ✅ Type-safe schema validation
- ✅ AWS production-proven
- ✅ Support for complex hierarchies (teams, orgs)
Cons:
- ⚠️ Learning curve (new policy language)
- ⚠️ Policies must be pre-compiled for performance
- ⚠️ Smaller community than Casbin
Implementation
Policy Definition:
// policies/authorization.cedar
// Allow owners full access to projects
permit(
principal,
action,
resource
)
when {
principal.role == "owner"
};
// Allow members to create tasks
permit(
principal in [User],
action == Action::"create_task",
resource in [Project]
)
when {
principal.team_id == resource.team_id &&
principal.role in ["owner", "member"]
};
// Deny editing completed tasks
forbid(
principal,
action == Action::"update_task",
resource in [Task]
)
when {
resource.status == "done"
};
// Allow viewing with viewer role
permit(
principal,
action == Action::"read",
resource
)
when {
principal.role == "viewer"
};
Authorization Check in Backend:
#![allow(unused)] fn main() { // crates/vapora-backend/src/api/projects.rs use cedar_policy::{Authorizer, Request, Entity, Entities}; async fn get_project( State(app_state): State<AppState>, Path(project_id): Path<String>, ) -> Result<Json<Project>, ApiError> { let user = get_current_user()?; // Create authorization request let request = Request::new( user.into_entity(), action("read"), resource("project", &project_id), None, )?; // Load policies and entities let policies = app_state.cedar_policies(); let entities = app_state.cedar_entities(); // Authorize let authorizer = Authorizer::new(); let response = authorizer.is_authorized(&request, &policies, &entities)?; match response.decision { Decision::Allow => { let project = app_state .project_service .get_project(&user.tenant_id, &project_id) .await?; Ok(Json(project)) } Decision::Deny => Err(ApiError::Forbidden), } } }
Entity Schema:
#![allow(unused)] fn main() { // crates/vapora-backend/src/auth/entities.rs pub struct User { pub id: String, pub role: UserRole, pub tenant_id: String, } pub struct Project { pub id: String, pub tenant_id: String, pub status: ProjectStatus, } // Convert to Cedar entities impl From<User> for cedar_policy::Entity { fn from(user: User) -> Self { // Serialized to Cedar format } } }
Key Files:
/crates/vapora-backend/src/auth/(Cedar integration)/crates/vapora-backend/src/api/(authorization checks)/policies/authorization.cedar(policy definitions)
Verification
# Validate policy syntax
cedar validate --schema schemas/schema.json --policies policies/authorization.cedar
# Test authorization decision
cedar evaluate \
--schema schemas/schema.json \
--policies policies/authorization.cedar \
--entities entities.json \
--request '{"principal": "User:alice", "action": "Action::read", "resource": "Project:123"}'
# Run authorization tests
cargo test -p vapora-backend test_cedar_authorization
# Test edge cases
cargo test -p vapora-backend test_forbidden_access
cargo test -p vapora-backend test_hierarchical_permissions
Expected Output:
- Policies validate without syntax errors
- Owners have full access
- Members can create tasks in their team
- Viewers can only read
- Completed tasks cannot be edited
- All tests pass
Consequences
Authorization Model
- Three roles: Owner, Member, Viewer
- Hierarchical teams (can nest permissions)
- Resource-scoped access (per project, per task)
- Audit trail of policy decisions
Policy Management
- Policies versioned in Git
- Policy changes require code review
- Centralized policy repository
- No runtime policy compilation (pre-compiled)
Performance
- Policy evaluation cached (policies don't change often)
- Entity resolution cached per request
- Negligible latency overhead (<1ms)
Scaling
- Policies apply across all services
- Cedar policies portable to other services
- Centralized policy management
References
- Cedar Policy Language Documentation
- Cedar GitHub Repository
/policies/authorization.cedar(policy definitions)/crates/vapora-backend/src/auth/(integration code)
Related ADRs: ADR-009 (Istio), ADR-025 (Multi-Tenancy)