Skip to main content

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:

  1. Relationship-Based Access Control (ReBAC) - "Who has access to what based on relationships" - currently handled by permissions-service via SpiceDB.
  2. Attribute-Based Access Control (ABAC) - "Who has access based on attributes and conditions" - e.g., time of day, IP address, resource status.
  3. 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-service for 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): PermissionsChecker defined in the domain layer
  • Adapter: PermissionsServiceAdapter in the infrastructure layer that makes HTTP calls to permissions-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:

RequirementCedar 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 OPAPolicyStore later if needed alongside CedarPolicyStore.

Negative

  • Single point of failure: The policy-service becomes 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-service directly for pure ReBAC.
  • New services should use policy-service for all authorization.
  • Gradual migration of existing services to policy-service as they add ABAC requirements.