Vapora/docs/adrs/0010-cedar-authorization.md

242 lines
5.5 KiB
Markdown
Raw Normal View History

# 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**:
```cedar
// 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**:
```rust
// 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**:
```rust
// 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
```bash
# 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](https://docs.cedarpolicy.com/)
- [Cedar GitHub Repository](https://github.com/aws/cedar)
- `/policies/authorization.cedar` (policy definitions)
- `/crates/vapora-backend/src/auth/` (integration code)
---
**Related ADRs**: ADR-009 (Istio), ADR-025 (Multi-Tenancy)