6d224861 came from a --no-checkout worktree whose empty index made the
commit drop every file except two. This restores 05b50d2b's full tree and
correctly adds stacks/stem95su/gdrive-sync.tf + the service-catalog stem95su
entry. Forward-only (parent=6d224861, no force-push); [ci skip] since the
live infra was never applied from the broken commit.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
170 lines
5.8 KiB
Markdown
170 lines
5.8 KiB
Markdown
---
|
|
name: authentik-oidc-kubernetes
|
|
description: |
|
|
Configure Authentik as OIDC provider for Kubernetes API server authentication.
|
|
Use when: (1) setting up OIDC auth for kubectl with Authentik, (2) kube-apiserver
|
|
rejects OIDC tokens with "oidc: email not verified", (3) JWKS endpoint returns
|
|
empty {} despite provider being configured, (4) kubelogin fails with "claim not
|
|
present" for email, (5) redirect_uri mismatch errors during kubelogin browser auth,
|
|
(6) kube-apiserver static pod manifest changes don't take effect after restart.
|
|
Covers all gotchas discovered when integrating Authentik 2025.10.x with Kubernetes
|
|
1.34.x using kubelogin (int128/kubelogin).
|
|
author: Claude Code
|
|
version: 1.0.0
|
|
date: 2026-02-17
|
|
---
|
|
|
|
# Authentik OIDC for Kubernetes API Authentication
|
|
|
|
## Problem
|
|
Setting up Authentik as an OIDC identity provider for Kubernetes kubectl access
|
|
involves multiple non-obvious pitfalls that cause silent failures at different
|
|
stages of the authentication flow.
|
|
|
|
## Context / Trigger Conditions
|
|
- Setting up multi-user kubectl access with OIDC
|
|
- Using Authentik as the identity provider and kubelogin (int128/kubelogin) as the kubectl plugin
|
|
- Any of these errors:
|
|
- `oidc: email not verified`
|
|
- `oidc: parse username claims "email": claim not present`
|
|
- `The request fails due to a missing, invalid, or mismatching redirection URI`
|
|
- JWKS endpoint (`/application/o/<app>/jwks/`) returns `{}`
|
|
- `Unauthorized` after successful browser login
|
|
|
|
## Solution
|
|
|
|
### Gotcha 1: Signing Key Must Be Assigned
|
|
|
|
Authentik's OAuth2 provider does NOT assign a signing key by default. Without it,
|
|
the JWKS endpoint returns `{}` and kube-apiserver can't validate tokens.
|
|
|
|
**Fix:** Assign a signing key (e.g., "authentik Self-signed Certificate") to the
|
|
OAuth2 provider:
|
|
```python
|
|
# Via Django shell (kubectl exec into authentik server pod)
|
|
from authentik.providers.oauth2.models import OAuth2Provider
|
|
from authentik.crypto.models import CertificateKeyPair
|
|
|
|
provider = OAuth2Provider.objects.get(name='kubernetes')
|
|
cert = CertificateKeyPair.objects.filter(name='authentik Self-signed Certificate').first()
|
|
provider.signing_key = cert
|
|
provider.save()
|
|
```
|
|
|
|
Or via API:
|
|
```bash
|
|
curl -X PATCH -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
|
|
"$AUTHENTIK_URL/api/v3/providers/oauth2/<pk>/" \
|
|
-d '{"signing_key": "<certificate-keypair-uuid>"}'
|
|
```
|
|
|
|
### Gotcha 2: Default Email Mapping Sets `email_verified: False`
|
|
|
|
Authentik's built-in email scope mapping hardcodes `email_verified: False`:
|
|
```python
|
|
return {
|
|
"email": request.user.email,
|
|
"email_verified": False # <-- This causes kube-apiserver to reject the token
|
|
}
|
|
```
|
|
|
|
kube-apiserver requires `email_verified: true` by default.
|
|
|
|
**Fix:** Create a custom scope mapping with `email_verified: True` and assign it
|
|
to the provider instead of the default:
|
|
```python
|
|
from authentik.providers.oauth2.models import OAuth2Provider, ScopeMapping
|
|
|
|
# Create custom mapping
|
|
mapping, _ = ScopeMapping.objects.get_or_create(
|
|
name='Kubernetes Email (verified)',
|
|
defaults={
|
|
'scope_name': 'email',
|
|
'expression': 'return {"email": request.user.email, "email_verified": True}'
|
|
}
|
|
)
|
|
|
|
# Replace default email mapping on the provider
|
|
provider = OAuth2Provider.objects.get(name='kubernetes')
|
|
default_email = ScopeMapping.objects.filter(
|
|
managed='goauthentik.io/providers/oauth2/scope-email'
|
|
).first()
|
|
if default_email:
|
|
provider.property_mappings.remove(default_email)
|
|
provider.property_mappings.add(mapping)
|
|
```
|
|
|
|
### Gotcha 3: kubelogin Needs Extra Scopes
|
|
|
|
By default, kubelogin only requests the `openid` scope. The token will lack
|
|
`email` and `groups` claims, causing:
|
|
```
|
|
oidc: parse username claims "email": claim not present
|
|
```
|
|
|
|
**Fix:** Add `--oidc-extra-scope` flags to the kubeconfig exec plugin:
|
|
```yaml
|
|
users:
|
|
- name: oidc-user
|
|
user:
|
|
exec:
|
|
command: kubectl
|
|
args:
|
|
- oidc-login
|
|
- get-token
|
|
- --oidc-issuer-url=https://authentik.example.com/application/o/kubernetes/
|
|
- --oidc-client-id=kubernetes
|
|
- --oidc-extra-scope=email # Required!
|
|
- --oidc-extra-scope=profile
|
|
- --oidc-extra-scope=groups
|
|
```
|
|
|
|
### Gotcha 4: Redirect URIs Must Use Regex Mode
|
|
|
|
kubelogin picks a random available port (tries 8000, 18000, then random).
|
|
Strict redirect URI matching like `http://localhost:8000/callback` will fail
|
|
when kubelogin uses a different port.
|
|
|
|
**Fix:** Use regex matching in the Authentik provider:
|
|
```json
|
|
{
|
|
"redirect_uris": [
|
|
{"matching_mode": "regex", "url": "http://localhost:.*"},
|
|
{"matching_mode": "regex", "url": "http://127\\.0\\.0\\.1:.*"}
|
|
]
|
|
}
|
|
```
|
|
|
|
### Gotcha 5: Property Mappings API Endpoint Changed
|
|
|
|
In Authentik 2025.10.x, scope mappings are at:
|
|
- `propertymappings/provider/scope/` (new, correct)
|
|
- NOT `propertymappings/scope/` (old, returns 405 Method Not Allowed on POST)
|
|
|
|
### Gotcha 6: Static Pod Manifest Changes Need Full Cycle
|
|
|
|
See skill: `kubelet-static-pod-manifest-update` for the full restart procedure.
|
|
|
|
## Verification
|
|
|
|
After all fixes:
|
|
```bash
|
|
# 1. JWKS has a key
|
|
curl -s https://authentik.example.com/application/o/kubernetes/jwks/ | jq '.keys | length'
|
|
# Expected: 1 (or more)
|
|
|
|
# 2. Test auth
|
|
KUBECONFIG=/path/to/oidc-kubeconfig kubectl get namespaces
|
|
# Expected: browser opens, login, namespaces returned
|
|
|
|
# 3. Check API server logs for success
|
|
ssh master "sudo kubectl logs -n kube-system kube-apiserver-* | grep oidc | tail -5"
|
|
# Expected: no "Unable to authenticate" errors
|
|
```
|
|
|
|
## Notes
|
|
- The OAuth2 provider should use `client_type: public` (no client secret needed for kubelogin)
|
|
- Set `sub_mode: user_email` so the OIDC subject matches the RBAC binding
|
|
- Set `include_claims_in_id_token: true` for the token to contain claims directly
|
|
- Use `issuer_mode: per_provider` for a clean issuer URL
|
|
- RBAC ClusterRoleBindings should match on the user's email (the `--oidc-username-claim=email` value)
|