Authentication vs Authorization
“https://kubernetes.io/docs/reference/access-authn-authz/”
These are two different things that get conflated constantly. Authentication answers “who are you?”; Authorization answers “what can you do?“. Kubernetes does them as separate steps, in that order, on every API request. A request that fails authn returns 401; a request that passes authn but fails authz returns 403. Understanding the split is the foundation for RBAC, OIDC, ServiceAccount tokens, and webhook authz.
Table of Contents
- The Split in the Request Flow
- Authentication — who are you?
- The Authentication Chain
- X.509 Client Certificates
- Bearer Tokens (ServiceAccount, OIDC, Bootstrap)
- Bound ServiceAccount Tokens (k8s 1.21+)
- OIDC for Human Users
- Webhook Token Authentication
- The UserInfo Object
- Authorization — what can you do?
- The Authorization Chain
- The Authorizers in Detail (Node, RBAC, ABAC, Webhook)
- The “Deny by Default” Rule
- Anonymous Authentication — the Footgun
- The system:* Groups
- Impersonation (—as, —as-group)
- The Authentication and Authorization in Practice
- Common Patterns
- Operations and Debugging
- Gotchas and Common Mistakes
1. The Split in the Request Flow
On every API request, the apiserver runs:
kubectl apply -f deployment.yaml
↓
HTTPS POST to apiserver
↓
┌─────────────────────────────────┐
│ 1. TLS termination │
│ (transport security) │
└────────────┬────────────────────┘
↓
┌─────────────────────────────────┐
│ 2. Authentication │ "who are you?"
│ x509 / token / webhook / OIDC │ → 401 if fail
│ → UserInfo attached │
└────────────┬────────────────────┘
↓
┌─────────────────────────────────┐
│ 3. Authorization │ "what can you do?"
│ RBAC / Node / ABAC / Webhook │ → 403 if fail
│ → allow / deny │
└────────────┬────────────────────┘
↓
┌─────────────────────────────────┐
│ 4. Admission control │ (see L09)
│ mutating + validating │
└────────────┬────────────────────┘
↓
Object stored in etcd
The two steps are independent. A valid user can be denied. The error message is the only way to tell which step failed.
The error codes:
- 401 Unauthorized — authn failed. The credentials are wrong.
- 403 Forbidden — authz failed. The credentials are fine, but you can’t do this.
Clients should react differently:
- 401 — “your credentials are wrong, fix them and retry.”
- 403 — “your credentials are fine, you can’t do this, talk to an admin.”
2. Authentication — who are you?
The apiserver runs a chain of authenticators in order. The first one that returns “yes” wins. The rest are skipped.
| Authenticator | Source | Common use |
|---|---|---|
| X.509 client certificates | ~/.kube/config client-certificate | kubectl from outside, kubelet, controllers |
| Bearer tokens | ~/.kube/config token, or HTTP Authorization: Bearer | ServiceAccount tokens, OIDC |
| Bootstrap tokens | kube-system bootstrap-token-* Secrets | kubeadm join |
| ServiceAccount tokens | JWT, mounted in Pods at /var/run/secrets/kubernetes.io/serviceaccount/token | In-cluster Pods |
| OpenID Connect (OIDC) | External IdP (Okta, Google, Azure AD) | User SSO |
| Webhook token authentication | External service you run | Custom auth |
| Anonymous | If anonymous-auth=true and no other matched | Healthz, debugging |
The result of authentication is a UserInfo object attached to the request.
3. The Authentication Chain
The chain runs in order. The first authenticator to return “yes” wins. If no authenticator returns “yes”, the request is either:
- 401 Unauthorized — if
--anonymous-auth=false(the safe default). - Authenticated as
system:anonymous— if--anonymous-auth=true.
The order is configured via the apiserver’s --authentication-* flags. The most common:
# standard setup
--client-ca-file=/etc/kubernetes/pki/ca.crt
--oidc-issuer-url=https://accounts.google.com
--oidc-client-id=kubernetes
--oidc-username-claim=email
--oidc-groups-claim=groups
--api-audiences=kubernetes
--service-account-key-file=/etc/kubernetes/pki/sa.pub
--service-account-signing-key-file=/etc/kubernetes/pki/sa.key
--service-account-issuer=https://kubernetes.default.svc.cluster.localThe order:
- X.509 client cert (if
client-ca-fileis set). - Bearer token (ServiceAccount, OIDC, bootstrap).
- Anonymous (if enabled).
The X.509 check is first. If the request has a client cert, it’s verified against the client CA. If the cert is valid, the user is set from the cert’s CN.
The bearer token check is next. The token is verified against the configured auth methods (SA key, OIDC, etc.).
Anonymous is the catch-all. If no other authenticator matched and anonymous is enabled, the user is system:anonymous.
4. X.509 Client Certificates
X.509 client certs are the legacy but still common auth method for:
kubectlusers (theclient-certificateandclient-keyin kubeconfig).- Kubelets (each kubelet has a client cert).
- Controllers (kube-scheduler, kube-controller-manager, etc.).
The cert is verified against --client-ca-file (the cluster CA). The cert’s CN becomes the user, the O becomes the group.
Example:
# generate a client cert signed by the cluster CA
openssl genrsa -out alice.key 2048
openssl req -new -key alice.key -out alice.csr -subj "/CN=alice/O=developers"
openssl x509 -req -in alice.csr -CA /etc/kubernetes/pki/ca.crt -CAkey /etc/kubernetes/pki/ca.key \
-CAcreateserial -out alice.crt -days 365The kubeconfig:
apiVersion: v1
kind: Config
users:
- name: alice
user:
client-certificate: /path/to/alice.crt
client-key: /path/to/alice.keyThe user alice is authenticated. The developers group is set. RBAC matches on these.
Client certs are powerful. The CN of the cert becomes the user; if the CN is kubernetes-admin, the user is cluster-admin. Treat the cert / key as a root password.
5. Bearer Tokens (ServiceAccount, OIDC, Bootstrap)
Bearer tokens are in the HTTP Authorization: Bearer <token> header. The apiserver extracts the token and verifies it.
The token types:
- ServiceAccount token — a JWT signed by the apiserver. Verifiable by any k8s consumer.
- OIDC token — a JWT signed by an external IdP. The apiserver validates via the OIDC issuer’s JWKS.
- Bootstrap token — a short-lived token in
kube-system/bootstrap-token-*Secret. Used forkubeadm join.
5.1 The kubeconfig token
apiVersion: v1
kind: Config
users:
- name: alice
user:
token: <bearer-token>Or:
users:
- name: alice
user:
auth-provider:
name: oidc
config:
id-token: <jwt>
refresh-token: <refresh>5.2 The in-cluster Pod
A Pod in the cluster has its SA token mounted:
# inside the Pod
cat /var/run/secrets/kubernetes.io/serviceaccount/token
# a JWT, valid for the Pod's lifetimeThe Pod uses this token to authenticate to the apiserver.
5.3 The bootstrap token
kubeadm join uses a bootstrap token:
kubeadm token create --print-join-command
# generates: kubeadm join ... --token <token> --discovery-token-ca-cert-hash sha256:...The token is in a Secret in kube-system. It’s short-lived (24h by default). After kubeadm join, the token is consumed.
6. Bound ServiceAccount Tokens (k8s 1.21+)
Legacy SA tokens were long-lived (the lifetime of the Secret). The bound tokens (k8s 1.21+, GA in 1.30) are short-lived and audience-scoped.
A bound token:
- Is a JWT.
- Has a specific
aud(audience) — only valid for the configured audience. - Has a short expiry (~1h by default).
- Is bound to a specific Pod — can’t be reused by another Pod.
The Pod gets a bound token via a projected volume:
apiVersion: v1
kind: Pod
metadata: { name: app }
spec:
serviceAccountName: my-sa
containers:
- name: app
image: app:1.0
volumeMounts:
- name: sa-token
mountPath: /var/run/secrets/kubernetes.io/serviceaccount
readOnly: true
volumes:
- name: sa-token
projected:
sources:
- serviceAccountToken:
path: token
audience: https://my-service.example.com # the audience
expirationSeconds: 3600 # 1 hourThe kubelet requests a bound token from the apiserver. The token is mounted to the Pod. The Pod can use it to authenticate to https://my-service.example.com (or any service that validates the aud).
The audience is critical. A token for https://my-service.example.com is rejected by https://other-service.example.com. This is the security model for service-to-service auth.
The legacy long-lived tokens (via Secrets) are deprecated. Use bound tokens.
7. OIDC for Human Users
“https://kubernetes.io/docs/reference/access-authn-authz/authentication/#openid-connect-tokens”
OIDC is the standard for user SSO. The apiserver is configured to trust an OIDC issuer:
--oidc-issuer-url=https://accounts.google.com
--oidc-client-id=kubernetes
--oidc-username-claim=email
--oidc-groups-claim=groups
--oidc-required-claim=hd=example.com # optional: only users in this domainThe flow:
- User authenticates to the OIDC IdP (Okta, Google, Azure AD).
- The IdP issues a JWT.
- The user passes the JWT to the apiserver (via
kubectlwith a kubeconfig that has the OIDC provider config). - The apiserver validates the JWT against the IdP’s JWKS.
- The apiserver extracts the username (from
email) and groups (fromgroups). - The user is
alice@example.com, the groups aredevelopers,platform, etc. - RBAC matches on these.
For kubectl, the kubelogin plugin handles the OIDC flow. It opens a browser, the user logs in, the plugin gets the token, and kubectl uses it.
7.1 The OIDC claims
The claims the apiserver uses:
iss(issuer) — must match--oidc-issuer-url.sub(subject) — the user’s unique ID.aud(audience) — must include--oidc-client-id.exp(expiry) — must be in the future.--oidc-username-claim— the claim to use as the username (e.g.email,sub,preferred_username).--oidc-groups-claim— the claim to use as the groups (e.g.groups).
7.2 The required-claim filter
--oidc-required-claim=hd=example.com — only users from the example.com G Suite domain are allowed. The hd claim is Google-specific; for Okta, you’d use a different claim.
8. Webhook Token Authentication
“https://kubernetes.io/docs/reference/access-authn-authz/authentication/#webhook-token-authentication”
The apiserver can call out to an external service to validate tokens:
# /etc/kubernetes/authn-webhook-config.yaml
apiVersion: v1
kind: Config
clusters:
- name: my-authn
cluster:
server: https://my-authn-service/authn
certificate-authority: /etc/kubernetes/ca.crt
contexts:
- context:
cluster: my-authn
user: ""
name: default
current-context: default
preferences: {}
users: []The apiserver calls the webhook for each bearer token. The webhook returns:
{
"apiVersion": "authentication.k8s.io/v1beta1",
"kind": "TokenReview",
"status": {
"authenticated": true,
"user": {
"username": "alice@example.com",
"groups": ["developers"]
}
}
}The apiserver extracts the user and groups from the response. The webhook is on the hot path — every API request that uses a token goes through it.
The webhook is used for:
- Custom auth — your own SSO (e.g. a non-OIDC IdP).
- JWT validation — a service that validates JWTs from your IdP.
- Static tokens — a service that looks up tokens in a database.
The webhook’s response is cached (--authentication-token-webhook-cache-ttl, default 5m). The cache reduces load on the webhook.
9. The UserInfo Object
The result of authentication is a UserInfo object attached to the request:
type UserInfo struct {
Username string
UID string
Groups []string
Extra map[string][]string
}Username— a string.system:serviceaccount:default:my-safor a SA.alice@example.comfor OIDC.kubernetes-adminfor a client cert.UID— a unique ID for the user (k8s-internal). Used by RBAC.Groups— a list of groups.system:authenticated,system:serviceaccounts,developers,system:masters, etc.Extra— extra claims. For OIDC, the IdP’s other claims (e.g.extra.email).
The UserInfo is read by RBAC and recorded in the audit log.
10. Authorization — what can you do?
The apiserver has multiple authorizers configured. They’re tried in order. The first one to give a definitive answer wins. If none give a definitive answer, the default is deny.
| Authorizer | Model | Use case |
|---|---|---|
| RBAC (Role-Based Access Control) | Roles + bindings | Most common, default in 1.8+ |
| Node | Special case for kubelets | Node authorizer, not for users |
| ABAC (Attribute-Based) | Policy file with attrs | Legacy, deprecated |
| Webhook | Call out to an external authorizer (OPA, etc.) | Custom policy engines |
RBAC is the default and the only one most clusters use.
11. The Authorization Chain
The chain runs in order. The first authorizer to return a definitive allow or forbid wins. If none return a definitive answer, the request is denied.
--authorization-mode=Node,RBACThe order:
Node— for kubelet requests. The Node authorizer allows kubelets to update their own Node and Pod status.RBAC— for everything else. Matches Roles / ClusterRoles / RoleBindings / ClusterRoleBindings.
Other modes:
--authorization-mode=Node,RBAC,Webhook— adds a webhook authorizer. The webhook is called only if RBAC returns a definitive answer (or to override, depending on config).--authorization-mode=ABAC,RBAC— ABAC is deprecated, don’t use.--authorization-mode=AlwaysAllow— disables all authz. Don’t use.
The chain’s order is critical. If RBAC is before Webhook, the RBAC answer wins for matching requests. The webhook is not called.
12. The Authorizers in Detail (Node, RBAC, ABAC, Webhook)
12.1 Node authorizer
“https://kubernetes.io/docs/reference/access-authn-authz/node/”
A special authorizer for kubelet requests. Allows kubelets to:
- Read their own Node.
- Update their own Node’s status (conditions, addresses).
- Update their own Pod’s status.
- Read most API resources (for
kubectl exec,kubectl logs).
A kubelet is identified by its username system:node:<node-name>. The Node authorizer matches this and allows the relevant actions.
The Node authorizer works with the NodeRestriction admission plugin. NodeRestriction restricts what kubelets can do on the Node / Pod (e.g. only add certain labels / taints).
12.2 RBAC authorizer
“https://kubernetes.io/docs/reference/access-authn-authz/rbac/”
The workhorse. RBAC has:
- Role / ClusterRole — a set of allowed verbs on resources.
- RoleBinding / ClusterRoleBinding — assigns a Role to a subject (User, Group, ServiceAccount).
See RBAC for the full picture.
12.3 ABAC authorizer
“https://kubernetes.io/docs/reference/access-authn-authz/abac/”
Legacy. Uses a policy file with attributes. Deprecated; use RBAC.
ABAC has a few drawbacks:
- Policies are in a file (not API objects).
- No default-deny (you have to deny explicitly).
- No way to update without restarting the apiserver.
- No way to delegate (you have to edit the file).
For new clusters, don’t use ABAC.
12.4 Webhook authorizer
“https://kubernetes.io/docs/reference/access-authn-authz/webhook/”
A custom authorizer. The apiserver calls a webhook for each request:
# /etc/kubernetes/authz-webhook-config.yaml
apiVersion: v1
kind: Config
clusters:
- name: my-authz
cluster:
server: https://my-authz-service/authz
certificate-authority: /etc/kubernetes/ca.crtThe webhook receives a SubjectAccessReview:
{
"apiVersion": "authorization.k8s.io/v1",
"kind": "SubjectAccessReview",
"spec": {
"user": "alice",
"group": ["developers"],
"resourceAttributes": {
"namespace": "default",
"verb": "get",
"resource": "pods"
}
}
}The webhook returns allowed: true or allowed: false.
The webhook is on the hot path. A slow webhook slows down all API requests. Cache aggressively (--authorization-webhook-cache-authorized-ttl=5m).
The failurePolicy (in the SAR spec, not the apiserver) is important:
FailurePolicy: allow— if the webhook fails, the request is allowed.FailurePolicy: deny— if the webhook fails, the request is denied.
For most custom authz, allow is the safe default (don’t break the API when the webhook is down). For security-critical authz, deny.
13. The “Deny by Default” Rule
A request that no authorizer has an opinion on is denied. This is the safe default.
kubectl apply -f foo.yaml
# 403 Forbidden
# (even if you're authenticated as a valid user)
The fix: add a Role / ClusterRole + binding for the user / SA.
This is the principle of least privilege at the authz level. Nothing is allowed by default; every action needs a rule.
14. Anonymous Authentication — the Footgun
--anonymous-auth=true (the k8s default) allows requests with no credentials to be authenticated as system:anonymous.
The system:anonymous user is in the system:unauthenticated group. If RBAC grants these any permission, anyone can use them (no credentials needed).
The standard hardening:
--anonymous-auth=falseThis rejects unauthenticated requests. A request with no credentials is 401 Unauthorized. No system:anonymous user is created.
The trade-off:
- With anonymous on — backward-compatible, some tools (like
kubectl auth can-iwithout a user) work without credentials. But the footgun is real. - With anonymous off — strict. All requests must have credentials.
kubectl auth can-irequires--as <user>or a kubeconfig.
For production, set --anonymous-auth=false. The footgun outweighs the convenience.
15. The system:* Groups
Some groups are automatic and special:
system:authenticated— anyone who authenticates (regardless of who they are) is in this group. Don’t grant it broad permissions.system:unauthenticated— unauthenticated requests (with anonymous auth on). Don’t grant it anything.system:masters— cluster-admin. Anyone in this group has full access. Don’t add users to it.system:serviceaccounts— all ServiceAccounts in all namespaces. Don’t grant it broad permissions.system:serviceaccounts:<namespace>— all SAs in a specific namespace.system:nodes— all kubelets.system:kube-controller-manager,system:kube-scheduler,system:kube-proxy— the control plane components. Don’t grant them extra permissions.
The convention: system: is reserved for k8s-internal groups. You should not create a group with a system: prefix.
16. Impersonation (—as, —as-group)
“https://kubernetes.io/docs/reference/access-authn-authz/authentication/#user-impersonation”
kubectl supports impersonation: act as a different user for one command.
# act as alice
kubectl get pods --as=alice
# act as a member of a group
kubectl get pods --as=alice --as-group=developers
# act as a ServiceAccount
kubectl get pods --as=system:serviceaccount:default:my-saThe apiserver’s user is set to the impersonated user, but the apiserver records both the original user (in user.extra.authentication.kubernetes.io/impersonator) and the impersonated user.
Impersonation is powerful and dangerous. The original user must be allowed to impersonate the target user (via an RBAC rule with impersonate / impersonate verbs on users / groups).
The standard:
# allow alice to impersonate ServiceAccounts
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata: { name: impersonator }
rules:
- apiGroups: [""]
resources: ["users", "groups", "serviceaccounts"]
verbs: ["impersonate"]
- apiGroups: ["authentication.k8s.io"]
resources: ["uids"]
verbs: ["impersonate"]The impersonator ClusterRole is for debugging. Don’t grant it broadly.
17. The Authentication and Authorization in Practice
17.1 kubectl from your laptop
1. kubectl loads your kubeconfig
2. kubectl extracts the client cert + key (or token)
3. kubectl constructs the HTTP request
4. TLS to the apiserver
5. apiserver authenticates the client cert → user "system:user:alice"
6. RBAC checks: does alice have "create deployments" in this namespace?
- Yes: proceed
- No: 403
7. Admission control runs
8. Object stored
9. Response back to kubectl
17.2 In-cluster Pod
1. Pod's code reads /var/run/secrets/kubernetes.io/serviceaccount/token
2. Constructs HTTP request with "Authorization: Bearer ***"
3. TLS to the apiserver (in-cluster)
4. apiserver validates the JWT → user "system:serviceaccount:default:app"
5. RBAC checks: does app have "list pods" in default?
- Yes: proceed
- No: 403
6. Admission control
7. Response
18. Common Patterns
18.1 “I want to give the dev team access to their namespace”
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata: { name: devs, namespace: team-a }
subjects:
- kind: Group
name: "developers" # OIDC group
apiGroup: rbac.authorization.k8s.io
- kind: Group
name: "team-a-admins"
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: edit # built-in "edit" role
apiGroup: rbac.authorization.k8s.ioThe edit ClusterRole gives most read/write permissions in the namespace. The Group developers is from OIDC.
18.2 “I want the app to read its own ConfigMap but no others”
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata: { name: app-reader, namespace: default }
rules:
- apiGroups: [""]
resources: ["configmaps"]
resourceNames: ["app-config"] # ONLY this ConfigMap
verbs: ["get"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata: { name: app-reader, namespace: default }
subjects:
- kind: ServiceAccount
name: app
namespace: default
roleRef:
kind: Role
name: app-reader
apiGroup: rbac.authorization.k8s.ioThe resourceNames field is the trick — it limits the Role to one specific ConfigMap.
18.3 “I want the CI system to deploy to a specific namespace”
apiVersion: v1
kind: ServiceAccount
metadata: { name: ci, namespace: ci }
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata: { name: ci-deploy, namespace: prod }
rules:
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "list", "watch", "update", "patch"]
- apiGroups: [""]
resources: ["pods", "services", "configmaps"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata: { name: ci-deploy, namespace: prod }
subjects:
- kind: ServiceAccount
name: ci
namespace: ci # SA in one ns, bound in another
roleRef:
kind: Role
name: ci-deploy
apiGroup: rbac.authorization.k8s.ioCI’s ServiceAccount can deploy but not create new resources or read secrets.
19. Operations and Debugging
19.1 Common commands
# check your identity
kubectl auth whoami
# output: system:user:alice (or similar)
# can I do this?
kubectl auth can-i create deployments --namespace=default
# can the "app" SA do this?
kubectl auth can-i list pods --as=system:serviceaccount:default:app -n default
# what can this user do in this namespace?
kubectl auth can-i --list --as=alice@example.com -n production
# debug authn
kubectl -n kube-system logs kube-apiserver-<node> | grep -i "authn\|401"
# debug authz
kubectl auth can-i <verb> <resource> --as=<user>19.2 The “401 Unauthorized” case
A request is rejected with 401.
# 1. Is the apiserver's anonymous-auth on?
kubectl -n kube-system get pod kube-apiserver-<node> -o yaml | grep anonymous-auth
# if --anonymous-auth=true, requests with no credentials are accepted as system:anonymous
# 2. Is the token valid?
# (for bearer tokens)
kubectl -n kube-system logs kube-apiserver-<node> | grep "401\|invalid\|expired"
# 3. Is the cert valid?
# (for client certs)
openssl verify -CAfile /etc/kubernetes/pki/ca.crt alice.crt19.3 The “403 Forbidden” case
A request passes authn but fails authz.
# 1. What user is the apiserver seeing?
kubectl auth whoami
# or, in the apiserver log:
kubectl -n kube-system logs kube-apiserver-<node> | grep "user\|forbid"
# 2. Does the user have a RoleBinding?
kubectl get rolebindings -A
kubectl get clusterrolebindings
# 3. What can the user do?
kubectl auth can-i --list --as=<user> -n <ns>
# 4. Is the RBAC correct?
kubectl get role <name> -n <ns> -o yaml
kubectl get rolebinding <name> -n <ns> -o yaml20. Gotchas and Common Mistakes
20.1 The 30+ common mistakes
-
Anonymous auth is on by default (
--anonymous-auth=true). For production, set it to false. The footgun outweighs the convenience. -
ServiceAccount tokens in the cluster are not the same as OIDC tokens. SA tokens are JWTs signed by the apiserver, validated by any k8s-aware consumer. OIDC tokens are signed by an external IdP.
-
Long-lived SA tokens (legacy) are deprecated. The new bound tokens (k8s 1.21+) are short-lived and audience-scoped.
-
The
client-certin~/.kube/configis the cluster-admin key. Anyone with this can do anything. Treat it like a root password. -
kubectl auth can-iis what the apiserver thinks you can do — useful for debugging RBAC, but it’s not a security boundary (the apiserver itself can be compromised). -
No authorizer answer = deny. If RBAC doesn’t have a rule that matches, the request is denied. There’s no implicit “allow if no rule says otherwise”.
-
RoleBinding is namespaced; ClusterRoleBinding is cluster-wide. A ClusterRoleBinding gives the same permissions in every namespace.
-
ABAC is deprecated. Don’t use it for new clusters. Migrate to RBAC if you have an old one.
-
Webhook authorizers are on the hot path. A slow or unavailable webhook blocks all requests. Always have a fallback or
failurePolicy: allow. -
The
system:mastersgroup is cluster-admin. Anyone in this group has full access. Don’t add users to it. -
The
system:anonymousandsystem:unauthenticatedgroups exist. Make sure they’re not granted anything. -
Node authorization is special. It’s used only by the kubelet to update its Node and Pod status. You don’t normally interact with it.
-
ServiceAccounts don’t have cluster-wide permissions by default. They have whatever’s bound in their namespace. ClusterRoleBinding a SA = cluster-wide for that SA.
-
The
system:authenticatedgroup is automatic. Anyone who authenticates is in this group. Don’t grant it broad permissions. -
The 401 vs 403 distinction matters. 401 = authn failed. 403 = authz failed. Clients should react differently.
-
Impersonation requires an RBAC rule. The original user must have
impersonateonusers/groups/serviceaccounts. -
The apiserver records both the original and impersonated user in the audit log and the request’s UserInfo. The
impersonatoris inuser.extra. -
OIDC group claims can be slow. The apiserver validates the token (calls the IdP’s JWKS endpoint, cached). If the IdP is slow, auth is slow.
-
A bootstrap token is short-lived (default 24h). After
kubeadm join, the token is consumed. -
The
--requestheader-client-ca-fileis for the front proxy (API aggregation), not for client certs. -
A RoleBinding subject can be a User, Group, or ServiceAccount. Different
kinds. -
A
ServiceAccountin one namespace can be a subject in a RoleBinding in another. The cross-namespace binding is a useful pattern. -
Bound SA tokens (k8s 1.21+) are the standard. The legacy long-lived SA token Secret is deprecated.
-
The
--service-account-issueris theissclaim of bound tokens. Consumers verify theissto confirm the token is from this cluster. -
The
audclaim of a bound token is the audience. A token forvaultis rejected byconsul. This is the security model for service-to-service auth. -
The
kubernetesaudience is the default. The apiserver issues tokens withaud: kubernetesby default. -
A bound token’s expirationSeconds is per-token, not per-SA. The token is valid for 1h (default). The kubelet requests a new one before expiry.
-
The kubelet’s serving cert is separate from the kubelet’s client cert. Two certs, two roles.
-
The apiserver’s
--bind-addressis the IP it listens on. Set it to a private IP for production. -
The
kubectl auth can-icommand can be slow on large clusters (queries RBAC for every match). Use the namespace-scoped version for performance.
See also
- ServiceAccounts — the in-cluster identity
- RBAC — the authorization model
- Certificates — the X.509 piece
- Cluster Hardening — the apiserver flags