OIDC login
Corium does not model the flags of the kube-apiserver. So it leaves
authentication to the
k0s.patch
escape hatch. This page connects an OpenID Connect provider (for example
Keycloak, Authentik, Dex, Okta, or Google) to the API server of a cluster.
kubectl then logs people in through the provider, and nobody shares an admin
certificate.
From 0.3.5, you can apply this to a node that is already in service.
cctl apply reconciles a change under k0s. It renders /etc/k0s/k0s.yaml
again and restarts the control plane. See
ADR 8.
Configure the API server
Add the OIDC flags under k0s.patch in the corium: document of the node:
corium:
role: single
k0s:
patch:
spec:
api:
extraArgs:
oidc-issuer-url: https://id.example.com/realms/main
oidc-client-id: kubernetes
oidc-username-claim: sub
oidc-username-prefix: "-"
oidc-groups-claim: groupsoidc-issuer-url: the issuer of the provider, exactly as it is in theissclaim of the token, with the trailing slash if there is one. The API server must be able to reach the discovery and JWKS endpoints of the issuer.oidc-client-id: the OAuth client that you created for Kubernetes. Theaudof the token must contain it.oidc-username-claim: the claim that becomes the name of the user.oidc-username-prefix: "-"disables theissuer#prefix. Without it, Kubernetes adds that prefix to a claim that is notemail.oidc-groups-claim: the claim that carries group names, for RBAC by group.
Apply the configuration. On a node that has not bootstrapped yet, the agent
applies it at first boot. On a running node, cctl apply reconciles it while
the node runs:
$ cctl apply 192.168.1.51 --file controller.yaml
Reconciled 192.168.1.51:7443.
re-applied k0s
restarted k0scontroller.service
Grant access with RBAC
Authentication is not authorization. A user who just logged in has no
permissions until RBAC gives them some. Until then, the login succeeds, but
every command returns 403. Bind a group or a single user. A group is the
better choice, because it does not depend on the username of one person:
# everyone in the "platform-admins" group becomes cluster-admin
kubectl create clusterrolebinding oidc-admins \
--clusterrole=cluster-admin --group=platform-adminsA kubeconfig that logs in
Users authenticate with
kubelogin
(kubectl oidc-login). It runs the browser flow and caches the token. The
kubeconfig stores no certificate and no secret:
apiVersion: v1
kind: Config
clusters:
- name: mycluster
cluster:
server: https://<api-server>:6443
certificate-authority-data: <cluster CA>
contexts:
- name: mycluster
context: { cluster: mycluster, user: oidc }
current-context: mycluster
users:
- name: oidc
user:
exec:
apiVersion: client.authentication.k8s.io/v1beta1
command: kubectl
args:
- oidc-login
- get-token
- --oidc-issuer-url=https://id.example.com/realms/main
- --oidc-client-id=kubernetes
- --oidc-extra-scope=email
- --oidc-extra-scope=profilecctl kubeconfig <node> prints the admin kubeconfig. Copy its server and
certificate-authority-data into the clusters block above. Then replace the
users section with the exec block, so that no admin credential stays in the
file.
Gotchas
email_verified. Withoidc-username-claim: email, Kubernetes rejects a token whoseemail_verifiedclaim isfalse. You get a401that looks like a broken login. Mark the email as verified at the provider, or usesubas shown above.Scopes. Request only the scopes that your provider advertises. You find them in
scopes_supportedat<issuer>/.well-known/openid-configuration. An unknown scope can make the login fail completely.Reachability. The API server validates tokens against the JWKS of the provider. So it must be able to reach the issuer, also when the issuer runs in the same cluster.
Decode a token. If a login is refused, decode the token and look at
iss,audand your username claim:kubectl oidc-login get-token --oidc-issuer-url=<issuer> --oidc-client-id=<id> \ | jq -r .status.token | cut -d. -f2 | base64 -d | jq