## Keyboard shortcuts

Press `←` or `→` to navigate between chapters

Press `S` or `/` to search in the book

Press `?` to show this help

Press `Esc` to hide this help

# 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

1. Declarative Policies: Separar políticas de autorización de lógica de código
2. Auditable: Políticas versionables en Git, fácil de revisar
3. AWS Proven: Usado internamente en AWS, production-proven
4. Type Safe: Schemas para resources y principals
5. 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)