Files
command-center/agent/managed-skills/authentik-provision-fleet-user/SKILL.md
T

3.7 KiB

name, description
name description
authentik-provision-fleet-user Provision or audit an Authentik user on Dankclaw (auth.franksdank.cloud): ak shell ORM drive, group set, behavioral login + entitlement verify, docs census record, stale-claim caveat.

Provision an Authentik user on Dankclaw

Source of truth: serverAppConfigs-docs/Infrastructure/Authentik.md ("User provisioning playbook"). Authentik objects are NOT repo-managed — no deploy, no file edits.

  1. Inspect first, mirror the newest human user's groups:
ssh dankclaw 'podman exec -i authentik-server ak shell' <<'EOF' 2>&1 | sed -n '/objects imported/,$p'
from authentik.core.models import User, Group
for u in User.objects.all().order_by("username"):
    print(u.username, u.email, sorted(u.groups.values_list("name", flat=True)))
print(sorted(Group.objects.values_list("name", flat=True)))
EOF
  1. Create (idempotent guard; password goes in the heredoc = stdin, never argv, so it never lands in ps):
from authentik.core.models import User, Group
u = User.objects.filter(username="X").first()
if not u:
    u = User(username="X", email="X@franksdank.cloud", name="X", is_active=True)
    u.set_password("...")
    u.save()
for g in ("Non-Admin Users", "jellyfin_users", "openwebui_users"):  # human-user precedent: ridley
    u.groups.add(Group.objects.get(name=g))
  1. Verify through the exact PasswordStage code path:
from django.contrib.auth import authenticate
assert authenticate(request=None, username="X", password="...")
assert authenticate(request=None, username="X", password="nope") is None
  1. Verify the downstream claims resolve — app_entitlements needs an Application instance (look it up by slug, open-webui), and compare against the precedent user:
from authentik.core.models import Application, User
app = Application.objects.get(slug="open-webui")
for n in ("ridley", "X"):
    u = User.objects.get(username=n)
    print(n, sorted(e.name for e in u.app_entitlements(app)), u.is_superuser, u.path)
  1. Stale-claim check + docs record (repo law: the vault is the source of truth):
ssh dankclaw "podman exec authentik-postgres psql -U authentik -d authentik -tAc \
  \"SELECT count(*) FROM authentik_providers_proxy_proxysession WHERE session_data->'claims'->>'preferred_username' IN ('X')\""

Then commit two doc edits (NEVER write the password into any file): the census sentence in Infrastructure/Authentik.md (human-user template bullet — append the new pks + creation date) and a dated entry in serverAppConfigs-docs/log.md (what was created, groups, how it was verified, proxysession count, downstream lag). ./tests/run.sh must pass before commit (docs completeness + link-integrity seams).

Rules

  • No User.create_user; is_superuser is a group-derived property, not a constructor kwarg (raises TypeError). path defaults to users.
  • Guarded-vhost access = any authenticated user (all proxy providers use implicit-consent); groups are consumed downstream: homepage-admins (Caddy /admin header gate), openwebui_users/_admins (roles claim), jellyfin_users/_admins (LDAP outpost). EXCLUDE admin groups from a plain user; ask before granting homepage-admins.
  • Group add after a login → Caddy RBAC can 403 on the outpost's cached claims; purge that user's rows: DELETE FROM authentik_providers_proxy_proxysession WHERE session_data->'claims'->>'preferred_username' = 'X'; (in the authentik-postgres container) — next visit re-authorizes silently. Groups set BEFORE any login → nothing to purge (count is 0).
  • Jellyfin visibility rides the LDAP outpost cache; expect a lag. OpenWebUI mints a first-time SSO login as role pending.
  • Look up flows by slug, not name. Do not rotate AUTHENTIK_SECRET_KEY to fix claim issues.