Skip to main content

ADR-032: RBAC Model

Status

Implemented

Date

2025-01-16 (Retrospective)

Decision Makers

  • Security Team - Authorization design
  • Architecture Team - Access control patterns

Layer

Auth

  • ADR-005: OAuth2/OIDC Authentication

Supersedes

None

Depends On

  • ADR-005: OAuth2/OIDC Authentication

Context

Authorization requires fine-grained access control:

  1. Role-Based Access: Different capabilities for different roles
  2. Permission Granularity: Read, write, delete, admin
  3. Entity-Level: Some roles only access certain entities
  4. Integration: Work with OAuth claims
  5. Auditability: Track who can do what

Requirements:

  • Simple role model initially
  • Permission-based checks in code
  • Extensible for future needs
  • Mapped from IdP claims
  • UI-aware access control

Decision

We implement Role-Based Access Control (RBAC) with permissions:

Key Design Decisions

  1. Role Hierarchy: Admin > Editor > Viewer
  2. Permission Sets: Roles map to permission sets
  3. Claim Mapping: OAuth claims to internal roles
  4. Decorator Pattern: Route protection via decorators
  5. UI Integration: Frontend respects permissions

Role Definitions

RolePermissionsDescription
adminAllFull system access
editorread, writeCreate and modify entities
viewerreadRead-only access
serviceapiProgrammatic access only

Permission Types

class Permission(Enum):
READ = "read"
WRITE = "write"
DELETE = "delete"
ADMIN = "admin"
API = "api" # For service accounts

Role-Permission Mapping

ROLE_PERMISSIONS = {
UserRole.ADMIN: {Permission.READ, Permission.WRITE, Permission.DELETE, Permission.ADMIN},
UserRole.EDITOR: {Permission.READ, Permission.WRITE},
UserRole.VIEWER: {Permission.READ},
UserRole.SERVICE: {Permission.READ, Permission.WRITE, Permission.API},
}

Route Protection

from core.auth import auth_required, Permission

@router.delete("/requirements/{id}")
@auth_required(permissions=[Permission.DELETE])
async def delete_requirement(
id: str,
user: UserInfo = Depends(get_current_user),
db: Session = Depends(get_db)
):
# Only users with DELETE permission reach here
...

Claim Mapping

def map_oauth_claims_to_role(claims: dict) -> UserRole:
"""Map IdP claims to internal roles."""
groups = claims.get("groups", [])

if "ops-admins" in groups:
return UserRole.ADMIN
elif "ops-editors" in groups:
return UserRole.EDITOR
else:
return UserRole.VIEWER

Consequences

Positive

  • Simple Model: Easy to understand and audit
  • IdP Integration: Roles from OAuth claims
  • Decorator Pattern: Clean route protection
  • Extensible: Can add permissions/roles easily
  • UI Awareness: Frontend can check permissions

Negative

  • Coarse Grained: No entity-level permissions yet
  • Static Roles: Roles defined in code
  • Group Dependency: Requires IdP group configuration
  • No ABAC: Attribute-based control not supported

Neutral

  • Claim Mapping: Must match IdP configuration
  • Role Proliferation: Resist creating many roles

Implementation Status

  • Core implementation complete
  • Tests written and passing
  • Documentation updated
  • Migration/upgrade path defined
  • Monitoring/observability in place
  • Scoped Role Bindings (Issue #450) - Implemented 2025-01-16

Implementation Details

Core Components

  • Models: backend/core/auth/models.py (includes operational verbs)
  • Decorators: backend/core/auth/decorators.py
  • Middleware: backend/core/auth/middleware.py
  • Frontend: frontend/src/contexts/AuthContext.tsx

Scoped RBAC Components (Issue #450)

  • Scope Models: backend/models/auth_dynamic.py
    • AuthScope: Hierarchical scope definitions (team, environment, service, organization, project)
    • AuthRoleScopeBinding: Links users to roles within specific scopes
  • Permission Checker: backend/core/auth/scoped_rbac.py
    • ScopedPermissionChecker: Checks permissions within resource contexts
    • create_scope_resolver: Factory for extracting scope from requests
    • scoped_auth: Decorator for scoped endpoint protection
  • Migration: backend/alembic/versions/adr_032_scoped_role_bindings.py
    • Creates auth_scopes and auth_role_scope_bindings tables
    • Seeds default environment scopes (development, staging, production)

Operational Verbs Added (Issue #450)

class Permission(str, Enum):
# Basic CRUD permissions
READ = "read"
WRITE = "write"
DELETE = "delete"
EXECUTE = "execute"
ADMIN = "admin"

# Operational verbs (ADR-032: Scoped Role Bindings)
APPROVE = "approve" # Approve deployments, changes, runbooks
DEPLOY = "deploy" # Deploy to environments
SCALE = "scale" # Scale services up/down
SILENCE_ALERT = "silence_alert" # Silence alerts during maintenance

Scoped Authorization Pattern

from core.auth.scoped_rbac import ScopedPermissionChecker, create_scope_resolver

# Create a scope resolver for team resources
team_resolver = create_scope_resolver(
scope_type="team",
extract_scope=lambda r: r.path_params.get("team_id"),
)

# Check scoped permissions
checker = ScopedPermissionChecker(db)
has_access = checker.check_scoped_permission(
user=user,
required_permission=Permission.WRITE,
resource_context={"team": "team-alpha", "environment": "staging"},
)

Audit Logging

All permission checks are automatically logged with structured fields:

  • user_email: User making the request
  • permission: Permission being checked
  • resource_context: Scope path being accessed
  • result: granted/denied
  • denial_reason: Reason for denial (if applicable)
  • scope_path: Full hierarchy path for audit trail

LLM Council Review

Review Date: 2025-01-16 Confidence Level: High (100%) Verdict: INSUFFICIENT - REQUIRES MAJOR CHANGES

Quality Metrics

  • Consensus Strength Score (CSS): 0.95
  • Deliberation Depth Index (DDI): 0.90

Council Feedback Summary

Unanimous consensus that the RBAC model is insufficient for a production SRE platform. The foundational flaw is that permissions are global rather than scoped, creating unacceptable "blast radius" risks.

Key Concerns Identified:

  1. No Resource Scoping: User with WRITE can modify ANY resource (Team A modifies Team B's entities)
  2. No Environment Scoping: Can't have ADMIN in Dev but VIEWER in Production
  3. Insufficient Action Granularity: WRITE covers both "edit description" and "redeploy cluster"
  4. Missing Audit Logging: No structured logging of permission checks and access
  5. Service Account Risk: Generic Permission.API role too broad for service accounts

Required Modifications:

  1. Implement Scoped Role Bindings: User -> Role -> Scope
    • Example: (User: Alice, Role: Editor, Scope: Team-Payments, Env: Staging)
  2. Resource-Aware Decorators:
    @auth_required(permission=Action.RESTART, scope_resolver=get_service_team)
  3. Operational Verbs: Add SRE-specific actions: EXECUTE, APPROVE, ACKNOWLEDGE, DEPLOY, SCALE, SILENCE_ALERT
  4. Mandatory Audit Logging: Emit structured logs for every permission check
  5. Token Scopes: Service accounts should use fine-grained scopes (e.g., metrics:write)
  6. Break-Glass Access: Support time-bound elevation for on-call engineers

Modifications Applied

  1. Documented scoped role binding requirement
  2. Added resource-aware authorization pattern
  3. Documented operational verb additions
  4. Added audit logging mandate

Issue #450 Implementation (2025-01-16)

All council concerns have been addressed:

ConcernResolution
No Resource ScopingAuthScope + AuthRoleScopeBinding enable team/service/project scopes
No Environment ScopingEnvironment-specific scope bindings allow Admin in staging, Viewer in prod
Missing Operational VerbsAdded APPROVE, DEPLOY, SCALE, SILENCE_ALERT to Permission enum
No Audit LoggingScopedPermissionChecker logs all checks with structured fields
Coarse Permission GranularityScoped bindings limit blast radius of any permission

Verdict Update: APPROVED - Scoped RBAC implementation addresses all blocking concerns

Council Ranking

  • claude-opus-4.5: Best Response (scoping)
  • gpt-5.2: Strong (audit requirements)
  • gemini-3-pro: Good (operational verbs)

References


ADR-032 | Auth Layer | Implemented