Authentik — IdP, LDAP outpost, and the gidNumber convention
Authentik basics, how the LDAP outpost feeds Slurm/PAM and shared filesystem ownership, the gidNumber=pk+4000 convention, and onboarding tenant users with SSH keys.
help for the full list, or solutions for copy-paste fix recipes.Authentik is an open-source identity provider that runs as a few pods in Kubernetes. We use it as the single source of truth for tenant users: name, email, group memberships, SSH keys, and POSIX attributes (uidNumber, gidNumber). An LDAP outpost projects all that into an LDAP-compatible service that Slurm/PAM/Linux tools can talk to.
This page is the operator's view: what Authentik actually deploys, how the LDAP outpost works, the gidNumber convention that makes shared-filesystem ownership consistent, and the tenant-user onboarding flow.
Component overview
| Component | Role |
|---|---|
authentik-server | Web UI + REST API + OAuth/OIDC/SAML provider endpoints |
authentik-worker | Background tasks (sync, certificate rotation, email) |
authentik-postgres | DB (Postgres). Stateful. |
authentik-redis | Session cache. |
authentik-ldap-outpost | LDAP server pod. Talks to authentik-server's API; exposes :3389 LDAP |
authentik-proxy-outpost | (optional) Forward-auth proxy for putting SSO in front of arbitrary apps |
authentik-radius-outpost | (optional) RADIUS server |
The LDAP outpost is the one we care about most for HPC integration. Slurm's PAM/nslcd setup, Linux's getent, and many older HPC tools speak LDAP natively.
How the LDAP outpost works
The outpost is a Go binary (ldap-outpost) that:
- On startup, registers with authentik-server via a long-lived token.
- Pulls the configured LDAP "Provider" definition: base DN, bind DN, search filter, attribute mapping.
- Listens on
:3389(or :636 for LDAPS). - For every LDAP query (e.g.,
getent passwd alice→(&(objectClass=posixAccount)(uid=alice))), translates to authentik-server's user/group API and returns synthetic LDAP entries.
The outpost has no local state. Every query hits authentik-server. Cache once via the consumer (nslcd does this) — there's no caching at the outpost.
A typical query path:
[ login pod ] sshd → PAM → nslcd → LDAPS:636 ──> [ ldap-outpost pod ]
│
v
HTTP API ──> [ authentik-server ] ──> postgres
The pod-to-pod path stays inside the cluster. External LDAP exposure (for non-K8s hosts that need to bind) goes via a Service of type LoadBalancer or an external proxy.
Sample provider config
Bind DN: cn=ldap-bind,ou=users,dc=auth,dc=example,dc=internal
Base DN: dc=auth,dc=example,dc=internal
UID attribute: uid
Group object class: posixGroup
Member attribute: memberUid
Search filter for users (queried via UI):
(&(objectClass=user)(memberOf=cn=tenant-foo,ou=groups,dc=auth,dc=example,dc=internal))
Slurm/PAM clients then bind as cn=ldap-bind,... and search with this filter.
The gidNumber convention
POSIX UID/GID numbers must be consistent across every host that touches the shared filesystem. If alice has UID 5042 on the login pod but 5043 on the slurmd pod, files she creates show wrong ownership when the other side reads them.
Authentik computes UIDs and GIDs from the user/group object's primary key (pk in the database) plus an offset. The convention we use:
uidNumber = user.pk + 2000
gidNumber = group.pk + 4000
(Some sites use pk + 1000 for users and pk + 3000 for groups; the exact offset is a choice as long as it stays out of system UID range and is consistent.)
This makes the mapping deterministic: a user with pk=42 always has UID 2042. Recreate the user, get a new pk, get a new UID — files they own become orphaned. Don't recreate users.
The mapping is implemented as a property mapping in Authentik:
# Property mapping: ldap_uidNumber
return user.pk + 2000
# Property mapping: ldap_gidNumber
return user.group_set.first().pk + 4000 # primary group
The LDAP outpost uses these to build posixAccount and posixGroup entries.
Onboarding a tenant user
The end-to-end recipe:
-
Create the user in Authentik (UI or API):
- username, email, full name
- assign to tenant's group (
tenant-foo) - upload SSH public key (custom user attribute,
ssh_keys)
-
Verify POSIX attributes are computed — UI shows
uidNumber: 2042,gidNumber: 4099. -
Sync to login pod's authorized_keys. There's typically a small operator (or a CronJob) that:
- Lists users in tenant-foo group
- For each, reads
ssh_keysattribute - Writes to a Secret in the tenant namespace, one entry per user
-
Verify on the login pod:
kubectl -n tenant-foo exec login-0 -- getent passwd alice # alice:x:2042:4099:Alice Researcher:/home/alice:/bin/bash kubectl -n tenant-foo exec login-0 -- ls /etc/ssh/authorized_keys.d/ # alice bob # User can SSH in from outside ssh alice@login.tenant-foo.example.internal -
Add to Slurm accounting (in slurmdbd):
kubectl -n tenant-foo exec login-0 -- sacctmgr add user alice Account=tenant-foo
If any step fails, the next step won't work — sacctmgr requires the user to resolve via getent first; getent requires the LDAP outpost to be reachable; and so on.
Group memberships and propagation
Authentik supports nested groups. A common pattern:
tenant-foo (parent)
├── tenant-foo-admins
│ └── alice (admin user)
└── tenant-foo-users
├── bob
└── carol
The LDAP outpost flattens nesting by default in memberOf queries. Slurm's account-vs-user mapping uses Slurm's own coarseness (one account per tenant); finer-grained admin distinctions are handled at the SSH/sudo layer inside the login pod.
When you add a user to a group in Authentik, the change is immediately visible to the LDAP outpost (it doesn't cache). nslcd on the login pod, however, does cache (default 600s). If a user shows up but Slurm says "Invalid account", flush the nslcd cache:
kubectl -n tenant-foo exec login-0 -- nscd -i passwd
kubectl -n tenant-foo exec login-0 -- nscd -i group
# or just restart nslcd
kubectl -n tenant-foo exec login-0 -- pkill -HUP nslcd
Authentik for K8s admin SSO
Beyond LDAP for tenants, Authentik provides OIDC for human admins logging into:
- ArgoCD UI
- Grafana
- Internal dashboards
- (sometimes) kubectl via
kubelogin
OIDC providers are configured per-application in the UI. Each gets a client-id/secret and a redirect URI. The application validates tokens against Authentik's well-known endpoint:
https://auth.example.internal/application/o/argocd/.well-known/openid-configuration
Group membership comes through as a claim (groups) in the ID token. Apps can use it for RBAC: "users in platform-admins get cluster-admin, others read-only".
Common operational issues
LDAP outpost pod crashloops:
- Token mismatch — outpost's auth token doesn't match what authentik-server expects. Regenerate via UI ("Outposts" → outpost → token).
- Network policy blocking outpost → server. Check
kubectl describe pod.
getent passwd alice returns nothing on login pod:
- nslcd not running or LDAP unreachable from the pod
- LDAP base DN wrong in
/etc/nsswitch.confand/etc/openldap/ldap.conf - User exists but isn't in the tenant's group (and the search filter requires group membership)
File ownership inconsistent across hosts:
- gidNumber convention mismatch — a host is computing GIDs from a different field or offset. All consumers must read from the same LDAP outpost with the same property mapping.
Authentik server returns 500 on user save:
- Postgres connection broken (check
kubectl -n auth logs authentik-postgres-0) - Disk full on Postgres PV
- Schema migration in progress (after a major version bump)
SSO login redirects in a loop:
- Cookie domain wrong; clear cookies and check that the SP and IdP share a common cookie scope.
- ID token signature mismatch — Authentik's signing key changed and the consumer cached the old one.
Operations on Authentik itself
# Create a recovery key (when admin password is lost)
kubectl -n auth exec deploy/authentik-server -- ak create_recovery_key 24h
# Outputs a one-time URL valid for 24 hours
# Re-render flows from blueprints (after a Git apply)
kubectl -n auth rollout restart deploy/authentik-worker
# Tail outpost logs
kubectl -n auth logs -f deploy/authentik-ldap-outpost-default
Backup and recovery
The Postgres database is the only piece of state you need to back up. Standard pg_dump on a schedule:
kubectl -n auth exec authentik-postgres-0 -- \
pg_dump -U authentik authentik | gzip > authentik-$(date +%F).sql.gz
Restore: scale authentik-server down, pg_restore, scale back up.
See also
- SUNK login pods — where Authentik LDAP feeds into PAM
- SUNK intro — Slurm's PAM/LDAP integration
- ArgoCD — common consumer of Authentik OIDC
External:
- goauthentik.io
- LDAP Outpost docs: goauthentik.io/docs/outposts/integrations/ldap
- POSIX attribute property mappings: goauthentik.io/docs/property-mappings/expression