Skip to main content

0057: Decision Strategy Selection

Date: 2024-12-10

Status: Accepted

Context

The policy-service combines two authorization mechanisms:

  1. ReBAC (Relationship-Based Access Control) via permissions-service - checks graph relationships
  2. ABAC (Attribute-Based Access Control) via Cedar policies - evaluates attributes and conditions

Different use cases require different approaches to combining these decisions:

  • Some resources need BOTH relationship AND policy approval (high security)
  • Some resources only need one or the other (flexibility)
  • Some scenarios should check relationships first, others should check policies first

We need to define the available strategies and how they are selected.

Decision

We will support four decision strategies for combining ReBAC and ABAC decisions, with a default and per-request override capability.

Available Strategies

1. rebac-first (Default)

Behavior:

  1. Check ReBAC (permissions-service) first
  2. If ReBAC allows → ALLOW (skip ABAC)
  3. If ReBAC denies → Check ABAC (Cedar)
  4. Return ABAC result

Use Case: Most common pattern. Relationship-based access is the primary model, with policies as fallback for exceptions.

Example: "User is an editor of this document" (ReBAC allows) OR "User has emergency access policy" (ABAC allows).

ReBAC: ALLOW → Result: ALLOW
ReBAC: DENY → ABAC: ALLOW → Result: ALLOW
ReBAC: DENY → ABAC: DENY → Result: DENY

2. policy-first

Behavior:

  1. Check ABAC (Cedar) first
  2. If ABAC explicitly forbids → DENY (skip ReBAC)
  3. If ABAC explicitly permits → ALLOW (skip ReBAC)
  4. If ABAC has no matching policy → Check ReBAC
  5. Return ReBAC result

Use Case: AWS IAM-style behavior where explicit policies take precedence over relationships.

Example: "Policy explicitly denies access to production" (ABAC denies, even if user is an admin via ReBAC).

ABAC: FORBID → Result: DENY
ABAC: PERMIT → Result: ALLOW
ABAC: NO_MATCH → ReBAC: ALLOW → Result: ALLOW
ABAC: NO_MATCH → ReBAC: DENY → Result: DENY

3. require-both (AND)

Behavior:

  1. Check BOTH ReBAC AND ABAC
  2. If BOTH allow → ALLOW
  3. If EITHER denies → DENY

Use Case: High-security resources requiring multiple layers of authorization.

Example: "User must be a document editor (ReBAC) AND have an active security clearance (ABAC)."

ReBAC: ALLOW + ABAC: ALLOW → Result: ALLOW
ReBAC: ALLOW + ABAC: DENY → Result: DENY
ReBAC: DENY + ABAC: ALLOW → Result: DENY
ReBAC: DENY + ABAC: DENY → Result: DENY

4. require-any (OR)

Behavior:

  1. Check BOTH ReBAC AND ABAC
  2. If EITHER allows → ALLOW
  3. If BOTH deny → DENY

Use Case: Flexible access where multiple paths to authorization exist.

Example: "User is a document viewer (ReBAC) OR user has public access policy (ABAC)."

ReBAC: ALLOW + ABAC: ALLOW → Result: ALLOW
ReBAC: ALLOW + ABAC: DENY → Result: ALLOW
ReBAC: DENY + ABAC: ALLOW → Result: ALLOW
ReBAC: DENY + ABAC: DENY → Result: DENY

Strategy Selection

The strategy is selected via hierarchy with per-request override:

1. Request Parameter (Highest Priority)

POST /authorize
{
"principal": "user:alice",
"action": "document:edit",
"resource": "document:123",
"strategy": "require-both"
}

2. Resource-Type Configuration

Configure default strategy per resource type in service configuration:

strategy_defaults:
default: "rebac-first"
resource_types:
"document:*": "rebac-first"
"invoice:*": "policy-first"
"secret:*": "require-both"

3. Service Default (Lowest Priority)

If no request parameter or resource-type config, use service-wide default: rebac-first.

Decision Hierarchy Summary

┌─────────────────────────────────────────────┐
│ 1. Request `strategy` parameter │ ← Highest priority
├─────────────────────────────────────────────┤
│ 2. Resource-type configuration │
├─────────────────────────────────────────────┤
│ 3. Service default: "rebac-first" │ ← Lowest priority
└─────────────────────────────────────────────┘

API Response

The response includes which strategy was used and the source of decisions:

{
"authorized": true,
"strategy": "rebac-first",
"decision_source": "rebac",
"rebac_result": "allow",
"abac_result": "not_evaluated",
"duration_ms": 3
}

Consequences

Positive

  • Flexibility: Different resources can use different authorization models
  • Transparency: Response includes which strategy was used
  • Override capability: Per-request override for special cases
  • Sensible default: rebac-first works for most use cases
  • Performance: Short-circuit evaluation avoids unnecessary checks

Negative

  • Complexity: Four strategies require documentation and understanding
  • Configuration management: Resource-type configs must be maintained
  • Debugging: Strategy selection adds another variable to debug (mitigated by including strategy in response)

Performance Considerations

StrategyBest CaseWorst Case
rebac-first1 call (ReBAC allows)2 calls
policy-first1 call (ABAC matches)2 calls
require-both2 calls (parallel)2 calls
require-any2 calls (parallel)2 calls

For require-both and require-any, both checks are executed in parallel for performance.