Files
ss-tools/docs/adr/ADR-0005-auth-rbac.md
2026-05-08 18:01:49 +03:00

5.2 KiB
Raw Blame History

[DEF:ADR-0005:ADR]

@STATUS ACTIVE

@PURPOSE Define the authentication and authorization architecture for ss-tools: local auth, optional ADFS SSO federation, RBAC model, session management, and the security boundary between ss-tools (DevOps privileges) and Superset (BI privileges).

@RELATION DEPENDS_ON -> [ADR-0001:ADR]

@RELATION DEPENDS_ON -> [ADR-0003:ADR]

@RELATION CALLS -> [ADR-0004:ADR]

@RATIONALE ss-tools manages operations that can modify production Superset instances (dashboard migration, backup, deployment). Unauthenticated or underauthorized access to these operations is a critical security risk. A dedicated RBAC system ensures that DevOps privileges (manage deployments) are cleanly separated from BI privileges (view dashboards) and that actions are auditable.

@RATIONALE Local auth (bcrypt + JWT) is the primary path because ss-tools must work in airgapped enterprise deployments where external identity providers are unavailable. ADFS SSO is an optional federation layer for organizations that already have Active Directory.

@RATIONALE RoleBased Access Control (RBAC) was chosen over AttributeBased Access Control (ABAC) because: (a) the system has a small, welldefined set of operations (deploy, backup, migrate, view), (b) RBAC maps naturally to organizational roles (admin, analyst, operator), (c) ABAC adds complexity without proportional benefit for this scope.

@REJECTED Delegating all auth to Superset — rejected because ss-tools must operate when Superset is down, and Superset's auth model is designed for BI users, not DevOps operators with crossenvironment privileges.

@REJECTED OAuth2 social login (Google, GitHub) as primary path — rejected because enterprise deployments require airgapped operation. External OAuth providers are unavailable in offline mode.

@REJECTED Simple API key (no RBAC) — rejected because it cannot express granular permissions (admin vs analyst vs viewer), making auditability and leastprivilege impossible.

Decision

Authentication Flow

┌──────────┐     POST /api/auth/login     ┌──────────────┐
│  User    │ ──────────────────────────────│   FastAPI    │
│ (Browser)│     {username, password}      │   Backend    │
│          │ ◄──────────────────────────── │              │
│          │     {access_token, refresh}   │  bcrypt +    │
│          │                               │  JWT (HS256) │
└──────────┘                               └──────┬───────┘
                                                  │
                                          ┌───────▼───────┐
                                          │  PostgreSQL   │
                                          │  users table  │
                                          │  roles table  │
                                          └───────────────┘

ADFS Federation (Optional)

  • Enabled via config.json: auth.adfs_enabled = true
  • SAML 2.0 flow through python3-saml
  • ADFS users mapped to local roles via adfs_group → ss_tools_role mapping table
  • Federated sessions still receive JWTs (stateless after initial SAML handshake)

RBAC Model

Role Permissions
admin All: manage users, manage roles, manage plugins, deploy, backup, migrate, view dashboards, view logs
analyst View dashboards, run LLM analysis plugins, view reports, view logs (own)
operator Deploy dashboards, run migrations, manage backups, view logs
viewer View dashboards, view reports

Token Design

  • Access token: JWT (HS256), 15minute expiry, contains user_id, roles: list[str]
  • Refresh token: opaque random string (SHA256), 7day expiry, stored hashed in DB
  • Superset API token per environment: AES256GCM encrypted, stored in connection_configs table
  • Plugin execution token: JWT, scoped to a single task_id, 15minute expiry

Security Constraints

  1. Passwords: bcrypt with cost factor 12 (minimum).
  2. Rate limiting: 5 failed login attempts per IP per 15 minutes → temporary IP block.
  3. Token revocation: admin can revoke all sessions for a user (delete refresh tokens).
  4. Audit log: all auth events (login success/failure, role change, token revoke) written to audit_log table.
  5. Enterprise clean mode: local auth only, ADFS disabled, no external network calls for identity.

RBAC Enforcement Pattern

# backend/src/api/dependencies.py
from fastapi import Depends, HTTPException

def require_role(required_role: str):
    def dependency(current_user: User = Depends(get_current_user)):
        if required_role not in current_user.roles:
            raise HTTPException(status_code=403, detail="Insufficient permissions")
        return current_user
    return dependency

# Usage in route:
@router.post("/api/dashboards/deploy")
async def deploy(..., user: User = Depends(require_role("operator"))):
    ...

[/DEF:ADR-0005:ADR]