Authentication is the first gate in the API server pipeline: "Who are you?" Kubernetes doesn't manage user accounts itself — it delegates authentication to external systems (certificates, tokens, OIDC providers). Understanding the authentication methods is critical for CKS and for securing production clusters.
1. Two Types of Identity
Kubernetes distinguishes between two kinds of identities:
| Human Users | Service Accounts | |
|---|---|---|
| Managed by | External system (OIDC, certs, LDAP) | Kubernetes API (objects in etcd) |
| Exists as K8s object? | ❌ No (K8s has no "User" resource) | ✅ Yes (kind: ServiceAccount) |
| Namespaced? | No (cluster-wide identity) | Yes (per-namespace) |
| Used by | kubectl users, CI/CD pipelines, admins | Pods, controllers, operators |
| Authentication | Client certs, OIDC tokens, static tokens | Projected SA tokens (JWT) |
kubectl create user command. Users are established by external authenticators (X.509 cert CN, OIDC sub claim, etc.). The API server verifies the credential and extracts the username/groups — but never stores them. This is fundamentally different from ServiceAccounts which ARE K8s objects.
The Authentication Flow
The API server runs multiple authenticators. Each request is tried against all configured plugins. The first one that succeeds establishes the identity. If all fail → 401 Unauthorized.
2. X.509 Client Certificates
The most common authentication for cluster components (kubelet, scheduler, controller-manager) and kubeadm-created admin users. The API server trusts any certificate signed by its configured CA.
How It Works
- Client presents a TLS client certificate during the HTTPS handshake
- API server validates the cert against its
--client-ca-file(the cluster CA) - Username is extracted from the certificate's Common Name (CN)
- Groups are extracted from the certificate's Organization (O) fields
# Certificate with CN=jane, O=developers, O=admins # → Authenticated as user "jane" with groups ["developers", "admins"]
Creating a User Certificate (CKA Exam Pattern)
# 1. Generate private key:
openssl genrsa -out jane.key 2048
# 2. Create Certificate Signing Request (CSR):
openssl req -new -key jane.key -out jane.csr \
-subj "/CN=jane/O=developers"
# ↑ username ↑ group
# 3. Submit CSR to Kubernetes for signing:
cat <<EOF | kubectl apply -f -
apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
metadata:
name: jane-csr
spec:
request: $(cat jane.csr | base64 | tr -d '\n')
signerName: kubernetes.io/kube-apiserver-client
usages: ["client auth"]
EOF
# 4. Approve the CSR:
kubectl certificate approve jane-csr
# 5. Get the signed certificate:
kubectl get csr jane-csr -o jsonpath='{.status.certificate}' | base64 -d > jane.crt
# 6. Add to kubeconfig:
kubectl config set-credentials jane \
--client-certificate=jane.crt \
--client-key=jane.key
kubectl config set-context jane-ctx \
--cluster=kubernetes --user=jane
signerName: kubernetes.io/kube-apiserver-client and usages: ["client auth"].
3. OIDC (OpenID Connect)
The recommended method for human users in production. Integrates with identity providers (Google, Azure AD, Okta, Keycloak, Dex) for SSO with centralized user management.
How It Works
- User authenticates with the OIDC provider (login page, MFA)
- Provider returns an ID token (JWT) containing user claims
- kubectl sends the JWT as a bearer token to the API server
- API server validates the JWT signature against the provider's public keys
- Username/groups extracted from configured JWT claims
# API server configuration for OIDC: kube-apiserver \ --oidc-issuer-url=https://accounts.google.com \ --oidc-client-id=my-k8s-cluster \ --oidc-username-claim=email \ --oidc-groups-claim=groups \ --oidc-username-prefix="oidc:" \ --oidc-groups-prefix="oidc:"
OIDC vs Certificates
| Aspect | X.509 Certificates | OIDC |
|---|---|---|
| Revocation | ❌ Can't revoke (must rotate CA) | ✅ Disable user in provider → immediate |
| Short-lived | Typically long-lived (1 year) | ✅ Tokens expire (hours, configurable) |
| MFA | ❌ No (just key possession) | ✅ Provider enforces MFA |
| Centralized | ❌ Per-cluster CA | ✅ One provider for all clusters |
| Setup complexity | Low (built-in) | Medium (need OIDC provider) |
| Best for | Component auth, bootstrap, kubeadm admin | Human users in production |
kubelogin and oidc-login handle the token refresh flow transparently in kubectl.
4. ServiceAccount Tokens
ServiceAccounts are for Pods and automated systems — not humans. Every namespace has a default ServiceAccount. Pods use SA tokens to authenticate with the API server.
Modern Bound Tokens (K8s 1.22+)
# Automatically projected into every Pod:
/var/run/secrets/kubernetes.io/serviceaccount/token
# This is a short-lived JWT (bound token):
# - Expires after 1 hour (auto-rotated by kubelet)
# - Bound to the specific Pod (invalidated on Pod deletion)
# - Audience-bound (valid only for the API server)
# Decoded token payload:
{
"iss": "https://kubernetes.default.svc",
"sub": "system:serviceaccount:default:my-app-sa",
"aud": ["https://kubernetes.default.svc"],
"exp": 1705334400,
"iat": 1705330800,
"kubernetes.io": {
"namespace": "default",
"pod": {"name": "web-abc", "uid": "..."},
"serviceaccount": {"name": "my-app-sa", "uid": "..."}
}
}
Creating Long-Lived Tokens (When Needed)
# For external systems that need a static token (CI/CD, monitoring):
apiVersion: v1
kind: Secret
metadata:
name: ci-token
annotations:
kubernetes.io/service-account.name: ci-sa
type: kubernetes.io/service-account-token
# Controller generates a non-expiring token in this Secret
# Or create a token with specific expiry:
kubectl create token ci-sa --duration=8760h # 1 year
5. Other Authentication Methods
Bootstrap Tokens
# Used during kubeadm join — short-lived tokens for node registration: kubeadm token create --print-join-command # Token format: abcdef.0123456789abcdef (6 chars.16 chars) # Stored as Secrets in kube-system namespace # Default TTL: 24 hours — expire after cluster bootstrap
Webhook Token Authentication
# Delegates authentication to an external service: kube-apiserver --authentication-token-webhook-config-file=/etc/k8s/webhook-config.yaml # API server sends the bearer token to your webhook # Webhook responds with: authenticated=true, user=..., groups=[...] # Use case: custom auth systems, legacy token validation
Static Token File (Avoid in Production)
# API server reads tokens from a CSV file: # kube-apiserver --token-auth-file=/etc/k8s/tokens.csv # Format: token,user,uid,"group1,group2" # ⚠️ Not recommended: no expiry, no revocation, requires API server restart to change
Anonymous Authentication
# If no authenticator matches → request is anonymous: # user: system:anonymous # groups: [system:unauthenticated] # Some paths allow anonymous (healthz, readyz) # Disable for non-public endpoints: --anonymous-auth=false
6. What Each Component Uses
| Component | Auth Method | Identity |
|---|---|---|
| kubelet | X.509 client cert | system:node:<nodename> |
| kube-scheduler | X.509 client cert | system:kube-scheduler |
| kube-controller-manager | X.509 client cert | system:kube-controller-manager |
| kube-proxy | X.509 client cert | system:kube-proxy |
| kubectl (admin) | X.509 client cert (kubeadm) or OIDC | CN from cert or OIDC claim |
| Pods | ServiceAccount token (bound JWT) | system:serviceaccount:<ns>:<name> |
| CI/CD pipelines | SA token or OIDC | Depends on setup |
Summary
| Method | Identity Source | Best For | Revocable? |
|---|---|---|---|
| X.509 Certs | CN=user, O=groups | Components, bootstrap admin | ❌ (only CA rotation) |
| OIDC | JWT claims (email, groups) | Human users (production) | ✅ (disable in IdP) |
| SA Tokens (bound) | Projected JWT (1h, Pod-bound) | Pods, workload identity | ✅ (delete Pod/SA) |
| SA Tokens (static) | Secret-stored JWT (no expiry) | Legacy integrations | ✅ (delete Secret) |
| Bootstrap Tokens | kube-system Secret (24h TTL) | kubeadm node join | ✅ (delete/expire) |
| Webhook | External auth service | Custom auth systems | ✅ (external control) |
📝 Quiz: Authentication Methods
Q1: A user presents a client certificate with CN=alice and O=engineering. What username and groups does the API server extract?
Q2: A user's private key is compromised. They were authenticated via X.509 cert. How do you revoke their access?
Q3: Does Kubernetes have a "User" resource you can create with kubectl create user?
Q4: What's the difference between a legacy SA token (Secret-stored) and a modern bound SA token (projected)?
Bound (modern): Projected into the Pod, expires in ~1h (auto-rotated by kubelet), bound to specific Pod UID (invalidated when Pod is deleted), audience-restricted. Much safer — compromised token is short-lived and useless after Pod deletion.
Q5: The kubelet on worker-1 authenticates to the API server. What identity does it present?
system:node:worker-1. Group: system:nodes. It uses an X.509 client certificate with CN=system:node:worker-1 and O=system:nodes. This identity is used by the Node authorizer to limit kubelet access to only its own node's Pods and Secrets.Q6: Why is OIDC preferred over X.509 certs for human users in production?