Skip to main content
RunBook Academy

KubernetesLVII · AuthenticationAuthentication

OIDC — corporate identity for human users

Advanced⏱ ~17 minkubectlOIDC provider

What you'll learn

  • Configure OIDC integration for the API server
  • Map IdP claims to Kubernetes UserInfo (username, groups)
  • Set up `kubectl` with OIDC for human users
  • Diagnose common OIDC failure modes (token not refreshed, claim mapping wrong)

Prerequisites

Verified against Kubernetes 1.34.x · kubeadm 1.34.x · kubectl 1.34.x · etcd 3.6.x · CoreDNS 1.11.x · containerd 1.7.x / 2.x · 2026-08-16

Not yet marked complete on this device.

OIDC (OpenID Connect) is the right authentication method for human users of a Kubernetes cluster. The API server delegates authentication to a corporate IdP (Okta, Azure AD, Google Workspace, Keycloak, etc.); the IdP issues an ID token; kubectl presents the token as a bearer token to the API server. The cluster never sees the user’s password, the token is short-lived (1 hour by default), and the IdP’s audit log records every cluster access. This lesson covers the configuration, the kubeconfig flow, and the failure modes.

How OIDC works

The user runs kubectl with OIDC. kubectl opens a browser to the IdP’s login page; the user authenticates (password + MFA); the IdP redirects kubectl to a callback URL with an authorization code; kubectl exchanges the code for an ID token; kubectl presents the ID token as a bearer token to the API server; the API server validates the token’s signature, issuer, audience, and expiry against the IdP’s metadata.

sequenceDiagram
    participant U as User (kubectl)
    participant IDP as IdP (Okta, AAD)
    participant AS as API server
    U->>IDP: Browser login + MFA
    IDP->>U: Authorization code
    U->>IDP: Code → ID token
    IDP->>U: ID token (JWT)
    U->>AS: GET /api (Authorization: Bearer ID_JWT)
    AS->>IDP: Fetch JWKS for signature verification
    AS->>AS: Verify issuer, audience, expiry, claims
    AS->>U: 200 OK (RBAC applied)

The ID token is a JWT signed by the IdP with a key published at the issuer’s .well-known/openid-configuration URL. The API server fetches the JWKS (JSON Web Key Set) and verifies the signature; it then reads the claims and maps them to UserInfo.

Configuration

The minimum OIDC configuration:

# AuthenticationConfiguration (mounted into kube-apiserver)
apiVersion: apiserver.config.k8s.io/v1
kind: AuthenticationConfiguration
jwt:
- issuer:
    url: https://idp.example.com
    audiences:
    - kubernetes
  claimMappings:
    username:
      claim: email
      prefix: ""
    groups:
      claim: groups
      prefix: "idp:"
  userValidationRules:
  - expression: 'user.groups.exists(g, g.startsWith("idp:"))'
    message: "user must belong to an idp: group"

The claim mappings:

  • username.claim: email — the email claim becomes the username (alice@example.com).
  • groups.claim: groups — the groups claim is split into individual groups, each prefixed with idp:.
  • The userValidationRules require that the user belong to an idp: group; users without groups are rejected.

The prefix is critical: it prevents a user whose IdP groups contain system:masters from impersonating a Kubernetes built-in group. Without the prefix, a malicious IdP group would be honoured; with the prefix, Kubernetes groups are namespaced under the IdP.

kubeconfig for OIDC

apiVersion: v1
kind: Config
clusters:
- name: prod
  cluster:
    server: https://api.prod.example.com:6443
    certificate-authority: /home/alice/.kube/ca.crt
contexts:
- name: alice-prod
  context:
    user: alice
    cluster: prod
users:
- name: alice
  user:
    auth-provider:
      name: oidc
      config:
        idp-issuer-url: https://idp.example.com
        client-id: kubernetes-cli
        client-secret: <redacted>  # optional, for confidential clients
        id-token: <id_token>      # populated by kubectl
        refresh-token: <refresh>  # for offline access
        idp-certificate-authority: /home/alice/.kube/idp-ca.crt

The auth-provider block tells kubectl to use the built-in OIDC flow: open the browser, authenticate with the IdP, receive the ID token, cache it in ~/.kube/cache/, and refresh it as it approaches expiry.

# First use opens the browser
kubectl --context=alice-prod get pods

# Subsequent uses use the cached ID token
kubectl --context=alice-prod get pods  # uses cached token

Claim mappings

The API server maps IdP claims to UserInfo:

IdP claimUserInfoNotes
emailusername: alice@example.comusername.claim
groupsgroups: [idp:dev, idp:oncall]groups.claim
sub(used as fallback)Subject identifier

The IdP’s groups are mapped to Kubernetes groups with the configured prefix. A user in the IdP’s prod-admins group is in idp:prod-admins in Kubernetes. RBAC bindings must reference the prefixed group:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: prod-admins
subjects:
- kind: Group
  name: idp:prod-admins
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: cluster-admin
  apiGroup: rbac.authorization.k8s.io

Token refresh and offline access

The OIDC flow has two variants:

  • Authorization Code with PKCE (default) — kubectl opens the browser, exchanges the code for a token, caches the token and the refresh token, and uses the refresh token to obtain a new ID token before the old one expires. The user is prompted again when the refresh token expires (typically every 24 hours).
  • Device Code Flow — for headless machines, kubectl prints a URL and a code; the user opens the URL on any device, enters the code, and authenticates. The token is returned to kubectl via polling.

CI/CD pipelines that need offline access use the device code flow with a refresh token persisted to disk. The refresh token has a long lifetime (days to weeks) but is revocable via the IdP.

Common failure modes

  1. Wrong claim mapping. The API server expects email but the IdP sends preferred_username. The username is empty or wrong. The fix is to match the claim name to the IdP’s actual claim.
  2. Audience mismatch. The IdP issues a token with aud=kubernetes, but the API server is configured for aud=k8s. The token is rejected. The fix is to match the audience.
  3. Clock skew. The API server’s clock is 5 minutes behind the IdP’s clock. The token is not yet valid (nbf). The fix is NTP.
  4. Missing refresh token. A user with no refresh token (e.g., a confidential client with no offline access) is prompted every hour. The fix is to grant offline access in the IdP.
  5. JWKS endpoint unreachable. The API server cannot fetch the IdP’s signing keys. The token cannot be verified. The fix is to allow the API server to reach the IdP.

Production failure modes

  1. OIDC configured without RBAC bound to IdP groups. A user can authenticate but cannot do anything; the team has shipped OIDC but not the RBAC. The fix is to bind the IdP groups to appropriate Roles.
  2. Single IdP, no fallback. The IdP is unreachable; the entire cluster is unreachable. The fix is to keep a break-glass admin (OIDC + client cert).
  3. No claim validation. A user without any group is authenticated and granted whatever RBAC the system:authenticated group has. The fix is userValidationRules that require a specific group.
  4. Refresh token stored in plaintext. A .kube/cache/ directory that is world-readable exposes refresh tokens. The fix is chmod 700.

Cross-course references

  • The Linux course covers JWT and JWKS — the underlying primitives that OIDC uses.
  • The OPNsense course covers the network-level access controls that complement IdP authentication.

Quiz

Knowledge check · 4 questions

  1. Q1. Why is the `prefix` field on the OIDC `groups` claim mapping a critical security control?

  2. Q2. An OIDC integration that authenticates a user but does not validate that the user belongs to any IdP group will reject the user with `unauthorized` because Kubernetes requires at least one group.

  3. Q3. Your team configured OIDC with `--oidc-username-claim=email`, but the IdP sends `preferred_username`. Users authenticate but the username is empty. `kubectl auth whoami` returns `<unknown>`. What went wrong, and how do you fix it?

    The cluster is configured with `claimMappings.username.claim: email`. The IdP issues tokens with both `email` and `preferred_username`. The audit log shows `user.username: ""` for every request. RBAC bindings reference `alice@example.com`.

  4. Q4. Describe the OIDC authentication flow from `kubectl` to the API server in five steps.

Passing score: 75%. Answers are checked in this browser.

Production discipline

OIDC is the right authentication primitive for human users of a Kubernetes cluster. Every production cluster should be on OIDC; client certs and --token-auth-file are reserved for nodes and bootstrap. The configuration must use a prefix on the group claim, must validate that users belong to a known group, must be backed by RBAC bindings on the prefixed group names, and must have a break-glass admin path (OIDC + client cert) for the IdP-is-down scenario. The audit log is the proof of OIDC’s value: every cluster access is traced to an IdP record, which is the operational benefit the static token file can never provide.