tasks ready

This commit is contained in:
2026-05-08 18:01:49 +03:00
parent d8df1fff59
commit bdd376595c
32 changed files with 3243 additions and 229 deletions

View File

@@ -0,0 +1,84 @@
# [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
```python
# 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]