0055: Unified Policy Decision Point (PDP) Architecture
Date: 2024-12-10
Status: Accepted
Context
The Citadel platform requires a unified approach to authorization that can handle multiple authorization models:
- Relationship-Based Access Control (ReBAC) - "Who has access to what based on relationships" - currently handled by
permissions-servicevia SpiceDB. - Attribute-Based Access Control (ABAC) - "Who has access based on attributes and conditions" - e.g., time of day, IP address, resource status.
- Policy-Based Access Control (PBAC) - "Who has access based on explicit policy rules" - e.g., AWS IAM-style policies.
Currently, downstream services would need to:
- Call
permissions-servicefor ReBAC checks - Implement their own ABAC logic
- Maintain separate policy evaluation code
This leads to:
- Duplicated authorization logic across services
- Inconsistent policy enforcement
- Difficulty in auditing authorization decisions
- Complex integration patterns for each consuming service
We need a single, unified Policy Decision Point (PDP) that downstream services can query for all authorization decisions.
Decision
We will implement the policy-service as the unified Policy Decision Point (PDP) for the entire platform.
Architecture
┌─────────────────────────────────────────────────────────────────┐
│ Downstream Services │
│ (invoicing, workflow, docs, etc.) │
└───────────────────────────┬─────────────────────────────────────┘
│ POST /authorize
▼
┌─────────────────────────────────────────────────────────────────┐
│ policy-service (PDP) │
│ ┌───────────────────┐ ┌───────────────────────────────────┐ │
│ │ Policy Evaluator │ │ Permissions Client │ │
│ │ (Cedar) │ │ (calls permissions-service) │ │
│ └───────────────────┘ └───────────────────────────────────┘ │
└───────────────────────────┬─────────────────────────────────────┘
│
┌─────────────┴─────────────┐
▼ ▼
┌─────────────────────────┐ ┌───────────────────────────┐
│ permissions-service │ │ Policy Store │
│ (SpiceDB - ReBAC) │ │ (.cedar files) │
└─────────────────────────┘ └───────────────────────────┘
Integration with permissions-service
The policy-service will integrate with permissions-service for ReBAC checks via a well-defined Port/Adapter pattern:
- Port (Interface):
PermissionsCheckerdefined in the domain layer - Adapter:
PermissionsServiceAdapterin the infrastructure layer that makes HTTP calls topermissions-service
This ensures:
- The policy-service is decoupled from the specific implementation of ReBAC
- Easy mocking for unit tests
- Potential for future swapping of the ReBAC engine
Policy Engine: Cedar
We will use Cedar (by AWS) as the policy engine for ABAC/PBAC:
| Requirement | Cedar Support |
|---|---|
| Simple SaaS policies | ✅ Human-readable policy language |
| Complex PaaS rules | ✅ Supports hierarchies, groups, templates |
| AWS IAM-like policies | ✅ Designed for AWS IAM |
| Production-proven | ✅ Used by AWS Verified Permissions |
| Go support | ✅ Via cedar-go bindings |
| Schema validation | ✅ Policies validated against schema |
| Formal verification | ✅ Automated reasoning tools |
Cedar was chosen over alternatives:
- OPA (Open Policy Agent): More flexible but steeper learning curve with Rego language.
- Casbin: Simpler but less suitable for AWS IAM-style policies.
Client SDK
A shared client SDK will be provided in go-commons/policy to simplify integration:
// Simple usage
allowed, err := policyClient.Authorize(ctx, "invoice:create", invoiceID)
// Gin middleware
router.POST("/invoices", policy.RequirePermission("invoice:create"), handler)
Consequences
Positive
- Single source of truth: All authorization decisions go through one service.
- Simplified integration: Downstream services use one API call instead of multiple.
- Consistent enforcement: Policies are evaluated centrally with the same logic.
- Audit trail: All decisions can be logged in one place.
- Flexibility: Supports ReBAC, ABAC, and PBAC in a unified model.
- Extensibility: Can add
OPAPolicyStorelater if needed alongsideCedarPolicyStore.
Negative
- Single point of failure: The
policy-servicebecomes critical infrastructure (mitigated by circuit breakers and fail-open options). - Latency: Adds a network hop for authorization (mitigated by caching and efficient implementation).
- Complexity: The service itself is more complex than a simple single-model approach.
Migration Path
- Existing services can continue calling
permissions-servicedirectly for pure ReBAC. - New services should use
policy-servicefor all authorization. - Gradual migration of existing services to
policy-serviceas they add ABAC requirements.