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.