Skip to main content

0056: Policy Scoping and Multi-Tenancy Strategy

Date: 2024-12-10

Status: Accepted

Context

The Citadel platform is a multi-tenant system where multiple organizations (tenants) share the same infrastructure. The policy-service must support policies at different scopes while ensuring strict tenant isolation.

Key requirements:

  1. Platform operators need to define system-wide policies that apply to all tenants
  2. Tenant administrators need to define policies specific to their organization
  3. Default behaviors must exist when no explicit policy matches
  4. Tenant isolation must be enforced at every layer

Decision

We will implement a three-tier policy scoping model with strict multi-tenancy enforcement.

Policy Scopes

1. System Policies

Definition: Platform-wide policies that are immutable by tenants and managed by platform operators.

Characteristics:

  • Stored in /policies/system/*.cedar files
  • Loaded at service startup
  • Cannot be created, modified, or deleted via API by tenants
  • Apply to ALL authorization requests regardless of tenant
  • Examples:
    • Platform security boundaries (e.g., "No access to internal admin resources")
    • Compliance requirements (e.g., "Deny all access outside business hours for SOC2 compliance")
    • Feature gates (e.g., "Only premium tier can access feature X")

Cedar Example:

// System policy: Block access to internal resources from any tenant
forbid(
principal,
action,
resource
) when {
resource.type == "internal" &&
principal.type == "tenant_user"
};

2. Tenant Policies

Definition: Policies scoped to a specific tenant and managed by that tenant's administrators.

Characteristics:

  • Stored in /policies/tenants/{tenant_id}/*.cedar files (MVP: file-based)
  • Future: Stored in database via DatabasePolicyStore adapter
  • Can be created, modified, and deleted by tenant admins via API
  • Only evaluated for requests from that specific tenant
  • Scoped by tenant_id extracted from X-Enriched-Headers
  • Examples:
    • Custom role definitions (e.g., "Marketing team can only view reports")
    • Resource access rules (e.g., "Only managers can approve invoices > $10,000")
    • Time-based restrictions (e.g., "Contractors cannot access after 6 PM")

Cedar Example:

// Tenant policy: Only managers can approve large invoices
permit(
principal,
action == Action::"invoice:approve",
resource
) when {
principal.role == "manager" &&
resource.amount <= 10000
};

forbid(
principal,
action == Action::"invoice:approve",
resource
) when {
principal.role != "manager" &&
resource.amount > 10000
};

3. Default/Fallback Policies

Definition: Policies that apply when no explicit policy (system or tenant) matches the request.

Behavior:

  • Default decision: DENY (secure by default)
  • Can be overridden per-action via explicit default policies
  • Logged for audit when applied

Cedar Example:

// Default policy: Allow read access to own resources
permit(
principal,
action == Action::"read",
resource
) when {
resource.owner == principal.id
};

// Implicit: All other requests are denied by default

Multi-Tenancy Enforcement

All policy operations are scoped by tenant_id extracted from X-Enriched-Headers, set by the API gateway after authentication.

Enforcement Rules

OperationEnforcement
Authorization Checktenant_id from X-Enriched-Headers determines which tenant policies to load
Policy CreationNew policies are scoped to the caller's tenant_id
Policy ListingOnly returns policies for the caller's tenant_id
Policy Update/DeleteOnly allowed for policies within the caller's tenant_id

Header Extraction

X-Enriched-User-ID: user-123
X-Enriched-Tenant-ID: tenant-456
X-Enriched-Roles: admin,user
X-Enriched-Email: user@example.com

The policy-service extracts X-Enriched-Tenant-ID and uses it to:

  1. Filter which tenant policies to load
  2. Populate the context.tenant_id in Cedar evaluation
  3. Enforce that cross-tenant policy access is forbidden

Policy Evaluation Order

┌─────────────────────────────────────┐
│ 1. System Policies (DENY) │ ← Explicit denies take precedence
├─────────────────────────────────────┤
│ 2. Tenant Policies │ ← Tenant-specific rules
├─────────────────────────────────────┤
│ 3. System Policies (PERMIT) │ ← System-wide permits
├─────────────────────────────────────┤
│ 4. Default Policies │ ← Fallback behavior
├─────────────────────────────────────┤
│ 5. Implicit DENY │ ← Secure by default
└─────────────────────────────────────┘

Cedar semantics: In Cedar, forbid policies always override permit policies. This natural behavior supports our security model.

Consequences

Positive

  • Tenant isolation: Strict enforcement prevents cross-tenant policy access
  • Operator control: System policies ensure platform-wide security
  • Tenant flexibility: Tenants can customize policies for their needs
  • Secure defaults: Deny-by-default prevents accidental exposure
  • Auditability: Clear policy hierarchy makes decisions traceable

Negative

  • Policy debugging: Multiple tiers can make it harder to understand why a request was denied (mitigated by detailed decision logging)
  • File management: File-based tenant policies require deployment for changes (mitigated by future DatabasePolicyStore)
  • Header trust: Relies on API gateway setting X-Enriched-Headers correctly (mitigated by gateway validation)

Future Enhancements

  1. Policy inheritance: Allow tenants to extend/override system templates
  2. Policy groups: Bundle related policies for easier management
  3. Policy simulation: "What-if" API to test policy changes before deployment