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:
@@ -129,6 +129,20 @@ roleRef:
|
||||
name: authentik-provisioner
|
||||
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
|
||||
kind: Job
|
||||
metadata:
|
||||
|
||||
@@ -424,6 +424,16 @@ SERVICES = {
|
||||
"launch_url": "https://memory.riotpiao.com",
|
||||
"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 = []
|
||||
@@ -646,7 +656,7 @@ for name, app_pk in app_pks_for_binding:
|
||||
|
||||
# Per-service admin groups are app-scoped (unlike homelab-admins' blanket
|
||||
# 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 /
|
||||
# future k8s RBAC only - nothing to bind here.
|
||||
SERVICE_GROUP_TO_APP_SLUG = {
|
||||
@@ -656,6 +666,7 @@ SERVICE_GROUP_TO_APP_SLUG = {
|
||||
"homarr-admins": "homarr",
|
||||
"paperless-admins": "paperless",
|
||||
"immich-admins": "immich",
|
||||
"llm-admins": "local-llm",
|
||||
}
|
||||
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)
|
||||
@@ -674,6 +685,19 @@ for group_name, app_slug in SERVICE_GROUP_TO_APP_SLUG.items():
|
||||
)
|
||||
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(" groups: homelab-admins (superuser) + " + ", ".join(SERVICE_ADMIN_GROUP_NAMES))
|
||||
print(" user: rock -> homelab-admins + all service admin groups")
|
||||
|
||||
Reference in New Issue
Block a user