feat(iam): add local-llm Authentik application with JWT auth

- Add local-llm OAuth2 provider and application to Authentik provisioning
- Configure JWT-compatible OAuth2 provider (client_id: local-llm)
- Generate client secret on first run, stored in llm-serving/local-llm-jwt Secret
- Bind llm-admins group to local-llm application for admin access
- Add RBAC for provisioning job to create secrets in llm-serving namespace
- Output JWT issuer URL and certificate for local-llm token validation
This commit is contained in:
2026-08-30 20:13:41 -07:00
parent 3b26d4435e
commit c4a302c3d1
2 changed files with 39 additions and 1 deletions
@@ -129,6 +129,20 @@ roleRef:
name: authentik-provisioner name: authentik-provisioner
apiGroup: rbac.authorization.k8s.io apiGroup: rbac.authorization.k8s.io
--- ---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: authentik-provisioner
namespace: llm-serving
subjects:
- kind: ServiceAccount
name: authentik-provisioner
namespace: iam
roleRef:
kind: ClusterRole
name: authentik-provisioner
apiGroup: rbac.authorization.k8s.io
---
apiVersion: batch/v1 apiVersion: batch/v1
kind: Job kind: Job
metadata: metadata:
+25 -1
View File
@@ -424,6 +424,16 @@ SERVICES = {
"launch_url": "https://memory.riotpiao.com", "launch_url": "https://memory.riotpiao.com",
"display_name": "Poimen Memory", "display_name": "Poimen Memory",
}, },
"local-llm": {
# JWT auth for local LLM API access - service-to-service, no browser flow.
# Client validates JWT tokens issued by this provider using the public key.
"client_secret_source": ("llm-serving", "local-llm-jwt", "client-secret"),
"generate_if_missing": True,
"extra_secret_literals": {"client-id": "local-llm"},
"redirect_uris": [], # No browser flow, JWT/service-to-service only
"launch_url": "https://llm.riotpiao.com",
"display_name": "Local LLM",
},
} }
app_pks_for_binding = [] app_pks_for_binding = []
@@ -646,7 +656,7 @@ for name, app_pk in app_pks_for_binding:
# Per-service admin groups are app-scoped (unlike homelab-admins' blanket # Per-service admin groups are app-scoped (unlike homelab-admins' blanket
# binding above) - only grants visibility/access to that one application. # binding above) - only grants visibility/access to that one application.
# portainer/kmsvc/temporal/llm-serving have no Authentik Application (no OIDC # portainer/kmsvc/temporal have no Authentik Application (no OIDC
# login integration), so their groups exist for the "permissions" claim / # login integration), so their groups exist for the "permissions" claim /
# future k8s RBAC only - nothing to bind here. # future k8s RBAC only - nothing to bind here.
SERVICE_GROUP_TO_APP_SLUG = { SERVICE_GROUP_TO_APP_SLUG = {
@@ -656,6 +666,7 @@ SERVICE_GROUP_TO_APP_SLUG = {
"homarr-admins": "homarr", "homarr-admins": "homarr",
"paperless-admins": "paperless", "paperless-admins": "paperless",
"immich-admins": "immich", "immich-admins": "immich",
"llm-admins": "local-llm",
} }
for group_name, app_slug in SERVICE_GROUP_TO_APP_SLUG.items(): for group_name, app_slug in SERVICE_GROUP_TO_APP_SLUG.items():
app_pk = next((pk for n, pk in app_pks_for_binding if n == app_slug), None) app_pk = next((pk for n, pk in app_pks_for_binding if n == app_slug), None)
@@ -674,6 +685,19 @@ for group_name, app_slug in SERVICE_GROUP_TO_APP_SLUG.items():
) )
print(f" {app_slug}: {group_name} bound") print(f" {app_slug}: {group_name} bound")
# JWT configuration for local-llm
print("\n[JWT] Fetching Authentik signing key for local-llm...")
status, signing_key_res = api("GET", f"/api/v3/crypto/certificatekeypairs/{SIGNING_KEY_PK}/")
if status == 200:
jwt_cert = signing_key_res.get("certificate", "")
print(f" Public certificate available for JWT validation (base64-encoded below)\n")
import base64
cert_b64 = base64.b64encode(jwt_cert.encode()).decode()
print(f"Save this to local-llm config for JWT token validation:")
print(f" AUTHENTIK_JWT_CERT={cert_b64}")
print(f"\nJWT issuer URL: https://authentik.riotpiao.com/application/o/local-llm/")
print(f"Local-LLM credentials are stored in: kubectl -n llm-serving get secret local-llm-jwt")
print("\nDone. Summary:") print("\nDone. Summary:")
print(" groups: homelab-admins (superuser) + " + ", ".join(SERVICE_ADMIN_GROUP_NAMES)) print(" groups: homelab-admins (superuser) + " + ", ".join(SERVICE_ADMIN_GROUP_NAMES))
print(" user: rock -> homelab-admins + all service admin groups") print(" user: rock -> homelab-admins + all service admin groups")