Files
homelab/homelab-rbac.md
T
rock d146f7e3af iam: add memory scope + service accounts to Authentik
- Add 'memory' scope property mapping with claims:
  - memory_projects: project-level access filter
  - memory_visibility: public/private visibility
  - memory_role: admin/user/portfolio-agent
- Add capability groups: llm-users, memory-users, memory-writers
- Add service account provisioning for portfolio-agent, memory-agent
- Service accounts use client_credentials flow
- Add homelab-rbac.md design doc (Authentik-centric model)
2026-09-03 16:27:39 -07:00

17 KiB

Homelab RBAC Design

Authentik as Universal Identity Provider — Single source of truth for all identity, groups, and permissions. Services validate JWTs and trust claims; no separate auth systems.

Design Principles

  1. Authentik = Policy Decision Point — All access decisions computed in Authentik, embedded in JWT claims
  2. Services = Policy Enforcement Points — Validate JWT signature via JWKS, trust claims blindly
  3. Zero Service-Side RBAC — No local user/role tables, no permission logic in services
  4. Claims Are The API — Each service defines what claims it needs; Authentik provides them

Architecture

┌─────────────────────────────────────────────────────────────────────────────┐
│                              Clients                                         │
│   ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────┐     │
│   │ Browser  │  │   CLI    │  │  Mobile  │  │  Agent   │  │  CI/CD   │     │
│   └────┬─────┘  └────┬─────┘  └────┬─────┘  └────┬─────┘  └────┬─────┘     │
└────────┼─────────────┼─────────────┼─────────────┼─────────────┼────────────┘
         │             │             │             │             │
         │  auth_code  │  device     │  device     │  client     │  client
         │  + PKCE     │  code       │  code       │  credentials│  credentials
         │             │             │             │             │
         └─────────────┴─────────────┴─────────────┴─────────────┴─────────────┐
                                                                               │
                                                                               ▼
┌─────────────────────────────────────────────────────────────────────────────────┐
│                    AUTHENTIK (Policy Decision Point)                             │
│                                                                                  │
│  ┌─────────────────┐  ┌─────────────────┐  ┌───────────────────────────────┐   │
│  │      Users      │  │      Groups     │  │      User Attributes          │   │
│  │                 │  │                 │  │                               │   │
│  │  rock (human)   │  │  homelab-admins │  │  memory_projects: ["*"]       │   │
│  │  portfolio-sa   │  │  llm-admins     │  │  memory_visibility: private   │   │
│  │  (svc account)  │  │  memory-admins  │  │  minio_policy: consoleAdmin   │   │
│  └─────────────────┘  └─────────────────┘  └───────────────────────────────┘   │
│                                    │                                            │
│                                    ▼                                            │
│  ┌──────────────────────────────────────────────────────────────────────────┐  │
│  │              Property Mappings (Scope → Claim)                            │  │
│  │                                                                           │  │
│  │  scope: groups      → claim: groups (group names list)                   │  │
│  │  scope: permissions → claim: permissions (computed from groups)          │  │
│  │  scope: memory      → claims: memory_projects, memory_visibility,        │  │
│  │                              memory_role (from user attrs + groups)      │  │
│  │  scope: minio       → claim: policy (MinIO policy name)                  │  │
│  └──────────────────────────────────────────────────────────────────────────┘  │
│                                    │                                            │
│                                    ▼                                            │
│  ┌──────────────────────────────────────────────────────────────────────────┐  │
│  │                         JWT Token (signed)                                │  │
│  │  {                                                                        │  │
│  │    "sub": "abc123",                                                       │  │
│  │    "groups": ["homelab-admins", "memory-admins"],                        │  │
│  │    "permissions": ["*"],                                                  │  │
│  │    "memory_projects": ["*"],                                              │  │
│  │    "memory_visibility": "private",                                        │  │
│  │    "memory_role": "admin",                                                │  │
│  │    "policy": "consoleAdmin"                                               │  │
│  │  }                                                                        │  │
│  └──────────────────────────────────────────────────────────────────────────┘  │
└────────────────────────────────────────┬────────────────────────────────────────┘
                                         │
                         JWT (Authorization: Bearer)
                                         │
          ┌──────────────────────────────┼──────────────────────────────┐
          │                              │                              │
          ▼                              ▼                              ▼
┌───────────────────┐      ┌───────────────────┐      ┌───────────────────┐
│    API Gateway    │      │   Memory Service  │      │       MinIO       │
│                   │      │     (Poimen)      │      │                   │
│  1. Validate JWT  │      │  1. Validate JWT  │      │  1. Validate JWT  │
│  2. Read claims:  │      │  2. Read claims:  │      │  2. Read claims:  │
│     - permissions │      │     - memory_*    │      │     - policy      │
│  3. Allow/deny    │      │  3. Filter rows   │      │  3. Map to IAM    │
│                   │      │                   │      │                   │
│  NO local RBAC    │      │  NO local RBAC    │      │  NO local RBAC    │
└───────────────────┘      └───────────────────┘      └───────────────────┘

Authentik Configuration

Groups (in Authentik)

Group Purpose is_superuser
homelab-admins Universal admin, full access to everything true
llm-admins LLM service management false
memory-admins Memory service management false
minio-admins MinIO storage management false
llm-users Can call LLM inference (no admin) false
memory-users Can query memory (no write) false

User Attributes (stored on user object)

Fine-grained scopes stored as user attributes in Authentik:

Attribute Type Example Purpose
memory_projects string[] ["homelab", "portfolio"] or ["*"] Which projects user can access
memory_visibility string "public" or "private" Max visibility level
minio_policy string "portfolio-rw" Override MinIO policy (optional)

Property Mappings (Scopes → Claims)

1. permissions scope (existing)

# Already in authentik-provision.py
GROUP_PERMISSIONS = {
    "homelab-admins": ["*"],
    "llm-admins": ["llm:read", "llm:write", "llm:inference"],
    "llm-users": ["llm:inference"],
    "memory-admins": ["memory:read", "memory:write", "memory:admin"],
    "memory-users": ["memory:read"],
    "minio-admins": ["minio:read", "minio:write"],
    # ...
}
perms = set()
for group in request.user.groups.all():
    perms.update(GROUP_PERMISSIONS.get(group.name, []))
return {"permissions": sorted(perms)}

2. memory scope (NEW - to add)

# Memory service claims - reads from user attributes + group membership
_MEMORY_EXPR = """
# Default scopes
projects = request.user.attributes.get("memory_projects", [])
visibility = request.user.attributes.get("memory_visibility", "public")
role = "user"

# Admin overrides
if request.user.ak_groups.filter(name="homelab-admins").exists():
    projects = ["*"]
    visibility = "private"
    role = "admin"
elif request.user.ak_groups.filter(name="memory-admins").exists():
    visibility = "private"
    role = "admin"

# Service account specific configs
SA_CONFIGS = {
    "portfolio-agent": {
        "projects": ["homelab", "portfolio"],
        "visibility": "public",
        "role": "portfolio-agent"
    },
    "memory-agent": {
        "projects": ["*"],
        "visibility": "private",
        "role": "authenticated-user"
    }
}
if request.user.username in SA_CONFIGS:
    cfg = SA_CONFIGS[request.user.username]
    projects = cfg["projects"]
    visibility = cfg["visibility"]
    role = cfg["role"]

return {
    "memory_projects": projects,
    "memory_visibility": visibility,
    "memory_role": role
}
"""

3. minio scope (existing)

# Already in authentik-provision.py
_POLICY_EXPR = """
# Check for explicit policy override in user attributes
explicit = request.user.attributes.get("minio_policy")
if explicit:
    return {"policy": explicit}

# Group-based defaults
if request.user.ak_groups.filter(name="homelab-admins").exists():
    return {"policy": "consoleAdmin"}
elif request.user.ak_groups.filter(name="minio-admins").exists():
    return {"policy": "readwrite"}
else:
    return {"policy": "readonly"}
"""

Service Claim Contracts

Each service documents what JWT claims it expects. Authentik must provide them.

API Gateway (api.riotpiao.com)

Required Claims:

{
  "permissions": ["llm:inference", "..."]  // Check against required permission
}

Enforcement:

// Gateway just checks if required permission is in claims
if !slices.Contains(claims.Permissions, "llm:inference") {
    return 403
}

Memory Service (memory.riotpiao.com)

Required Claims:

{
  "sub": "user-id",                        // Owner for conversation filtering
  "permissions": ["memory:read", "..."],   // Capability check
  "memory_projects": ["homelab", "..."],   // Project filter (or ["*"])
  "memory_visibility": "public|private",   // Max visibility level
  "memory_role": "admin|user|..."          // Built-in role (optional)
}

Enforcement:

// Memory service filters results based on claims
fn filter_results(results: Vec<Doc>, claims: &Claims) -> Vec<Doc> {
    results.into_iter().filter(|doc| {
        // Project check
        let project_ok = claims.memory_projects.contains("*") 
            || claims.memory_projects.contains(&doc.project);
        
        // Visibility check
        let visibility_ok = match claims.memory_visibility.as_str() {
            "private" => true,  // Can see everything
            "public" => doc.visibility == "public",
            _ => false,
        };
        
        // Owner check for conversations
        let owner_ok = doc.resource_type != "conversation" 
            || doc.owner == claims.sub;
        
        project_ok && visibility_ok && owner_ok
    }).collect()
}

MinIO (minio.riotpiao.com)

Required Claims:

{
  "policy": "consoleAdmin|readwrite|readonly|<custom>"
}

Enforcement:

  • MinIO natively reads policy claim via MINIO_IDENTITY_OPENID_CLAIM_NAME=policy
  • Looks up policy by name in MinIO's IAM policy store
  • Built-in: consoleAdmin, readwrite, readonly, writeonly, diagnostics
  • Custom policies created via mc admin policy create

Service Accounts

Service accounts are Authentik users with type: service_account and client credentials grant.

Creating a Service Account

# In authentik-provision.py, add to SERVICES dict:

"portfolio-agent": {
    "type": "service_account",           # No redirect URIs
    "grant_types": ["client_credentials"],
    "client_secret_source": ("portfolio", "portfolio-oidc", "CLIENT_SECRET"),
    "generate_if_missing": True,
    "display_name": "Portfolio Agent",
    "groups": ["llm-users", "memory-users"],  # Group membership
    "attributes": {                           # User attributes
        "memory_projects": ["homelab", "portfolio"],
        "memory_visibility": "public"
    }
}

Service Account Token Flow

# 1. Service requests token with client credentials
curl -X POST https://authentik.riotpiao.com/application/o/token/ \
  -d "grant_type=client_credentials" \
  -d "client_id=portfolio-agent" \
  -d "client_secret=$SECRET" \
  -d "scope=openid permissions memory"

# 2. Authentik returns JWT with claims
{
  "access_token": "eyJ...",
  "token_type": "Bearer",
  "expires_in": 300
}

# 3. Service uses token to call API
curl https://api.riotpiao.com/v1/chat/completions \
  -H "Authorization: Bearer eyJ..."

User Onboarding

Admin Creates User

# 1. Create user via Authentik API
POST /api/v3/core/users/
{
  "username": "alice",
  "email": "[email protected]",
  "groups": ["llm-users", "memory-users"],
  "attributes": {
    "memory_projects": ["homelab"],
    "memory_visibility": "public"
  }
}

# 2. Set password or send invite
POST /api/v3/core/users/{pk}/set_password/
  or
POST /api/v3/stages/invitation/invitations/

Grant Additional Access

# Add to group (grants permissions via GROUP_PERMISSIONS mapping)
PATCH /api/v3/core/users/{pk}/
{
  "groups": ["llm-users", "memory-users", "memory-admins"]  # Added memory-admins
}

# Or update attributes for fine-grained scopes
PATCH /api/v3/core/users/{pk}/
{
  "attributes": {
    "memory_projects": ["homelab", "new-project"],  # Added project
    "memory_visibility": "private"                   # Upgraded visibility
  }
}

Offboard User

# Deactivate (preserves audit trail)
PATCH /api/v3/core/users/{pk}/
{"is_active": false}

# Revoke all tokens immediately
POST /api/v3/core/users/{pk}/revoke_tokens/

Implementation Checklist

Phase 1: Add Memory Claims

  1. Add memory scope property mapping to authentik-provision.py
  2. Add scope to poimen-memory OAuth provider
  3. Update Memory service to read claims instead of local RBAC

Phase 2: Service Accounts

  1. Add service account creation to authentik-provision.py
  2. Create portfolio-agent service account
  3. Test client credentials flow

Phase 3: MinIO Fine-Grained

  1. Create custom MinIO policies (portfolio-rw, memory-rw)
  2. Update minio scope mapping for service accounts
  3. Test bucket-level access

Appendix: Claim Reference

Claim Source Services
sub User ID (hashed) All (owner checks)
groups Group membership ArgoCD, Grafana
permissions Computed from groups API Gateway, Memory
memory_projects User attr + group Memory
memory_visibility User attr + group Memory
memory_role User attr + group Memory
policy User attr + group MinIO
immich_role Group membership Immich