Kubernetes authorization takes place following authentication. Usually, a client making a request must be authenticated (logged in) before its request can be allowed; however, Kubernetes also allows anonymous requests in some circumstances.
For an overview of how authorization fits into the wider context of API access control, read Controlling Access to the Kubernetes API.
Kubernetes authorization of API requests takes place within the API server. The API server evaluates all of the request attributes against all policies, potentially also consulting external services, and then allows or denies the request.
All parts of an API request must be allowed by some authorization mechanism in order to proceed. In other words: access is denied by default.
Access controls and policies that depend on specific fields of specific kinds of objects are handled by admission controllers.
Kubernetes admission control happens after authorization has completed (and, therefore, only when the authorization decision was to allow the request).
When multiple authorization modules are configured, each is checked in sequence. If any authorizer approves or denies a request, that decision is immediately returned and no other authorizer is consulted. If all modules have no opinion on the request, then the request is denied. An overall deny verdict means that the API server rejects the request and responds with an HTTP 403 (Forbidden) status.
Kubernetes v1.37 [alpha](disabled by default)Starting with Kubernetes v1.37, authorizers can return conditional responses in addition to the standard allow, deny, or no opinion verdicts. A conditional response means that the final authorization decision depends on the content of the API request or stored object, rather than just metadata like resource name or namespace.
Conditional authorization enables more fine-grained access control policies that span both the authorization and admission phases. For example, an authorizer can express: "allow user Alice to create PersistentVolumes, but only when spec.storageClassName is 'dev'".
When an authorizer returns a conditional response:
AuthorizationConditionsEnforcer admission
controller calls the authorizer back to evaluate the conditions the authorizer
produced in the authorization phase, producing a final allow or deny decision.Conditional authorization is supported for requests that proceed through admission:
create, update, patch, delete, deletecollection verbspods/exec, pods/portforward)For read requests (get, list, watch) and other operations, authorizers
must return unconditional (Allow, Deny, NoOpinion) decisions only.
Kubernetes reviews only the following API request attributes:
user string provided during authentication./api or /healthz.get, list, create, update, patch, watch, delete, and deletecollection are used for resource requests. To determine the request verb for a resource API endpoint, see request verbs and authorization.get, post, put, and delete are used for non-resource requests.get, update, patch, and delete verbs, you must provide the resource name.Requests to endpoints other than /api/v1/... or /apis/<group>/<version>/...
are considered non-resource requests, and use the lower-cased HTTP method of the request as the verb.
For example, making a GET request using HTTP to endpoints such as /api or /healthz would use get as the verb.
To determine the request verb for a resource API endpoint, Kubernetes maps the HTTP verb used and considers whether or not the request acts on an individual resource or on a collection of resources:
| HTTP verb | request verb |
|---|---|
POST |
create |
GET, HEAD |
get (for individual resources), list (for collections, including full object content), watch (for watching an individual resource or collection of resources) |
PUT |
update |
PATCH |
patch |
DELETE |
delete (for individual resources), deletecollection (for collections) |
secrets
will reveal the data attributes of any returned resources.Kubernetes sometimes checks authorization for additional permissions using specialized verbs. For example:
users, groups, and serviceaccounts in the core API group, and the userextras in the authentication.k8s.io API group.roles and clusterroles resources in the rbac.authorization.k8s.io API group.Kubernetes expects attributes that are common to REST API requests. This means that Kubernetes authorization works with existing organization-wide or cloud-provider-wide access control systems which may handle other APIs besides the Kubernetes API.
The Kubernetes API server may authorize a request using one of several authorization modes:
AlwaysAllowAlwaysDenyABAC (attribute-based access control)RBAC (role-based access control)rbac.authorization.k8s.io API group to drive authorization decisions, allowing you to dynamically configure permission policies through the Kubernetes API.NodeWebhookConditionalAuthorization feature is enabled.Enabling the AlwaysAllow mode bypasses authorization; do not use this on a cluster where
you do not trust all potential API clients, including the workloads that you run.
Authorization mechanisms typically return either a deny or no opinion result; see
authorization verdicts for more on this.
Activating the AlwaysAllow means that if all other authorizers return “no opinion”,
the request is allowed. For example, --authorization-mode=AlwaysAllow,RBAC has the
same effect as --authorization-mode=AlwaysAllow because Kubernetes RBAC does not
provide negative (deny) access rules.
You should not use the AlwaysAllow mode on a Kubernetes cluster where the API server
is reachable from the public internet.
The system:masters group is a built-in Kubernetes group that grants unrestricted
access to the API server. Any user assigned to this group has full cluster administrator
privileges, bypassing any authorization restrictions imposed by the RBAC or Webhook mechanisms.
Avoid adding users
to this group. If you do need to grant a user cluster-admin rights, you can create a
ClusterRoleBinding
to the built-in cluster-admin ClusterRole.
You can configure the Kubernetes API server's authorizer chain using either a configuration file only or command line arguments.
You have to pick one of the two configuration approaches; setting both --authorization-config
path and configuring an authorization webhook using the --authorization-mode and
--authorization-webhook-* command line arguments is not allowed.
If you try this, the API server reports an error message during startup, then exits immediately.
Kubernetes v1.32 [stable](enabled by default)Kubernetes lets you configure authorization chains that can include multiple webhooks. The authorization items in that chain can have well-defined parameters that validate requests in a particular order, offering you fine-grained control, such as explicit Deny on failures.
The configuration file approach even allows you to specify CEL rules to pre-filter requests before they are dispatched to webhooks, helping you to prevent unnecessary invocations. The API server also automatically reloads the authorizer chain when the configuration file is modified.
You specify the path to the authorization configuration using the
--authorization-config command line argument.
If you want to use command line arguments instead of a configuration file, that's also a valid and supported approach. Some authorization capabilities (for example: multiple webhooks, webhook failure policy, and pre-filter rules) are only available if you use an authorization configuration file.
---
#
# DO NOT USE THE CONFIG AS IS. THIS IS AN EXAMPLE.
#
apiVersion: apiserver.config.k8s.io/v1
kind: AuthorizationConfiguration
authorizers:
- type: Webhook
# Name used to describe the authorizer
# This is explicitly used in monitoring machinery for metrics
# Note:
# - Validation for this field is similar to how K8s labels are validated today.
# Required, with no default
name: webhook
webhook:
# The duration to cache 'authorized' responses from the webhook
# authorizer.
# Same as setting `--authorization-webhook-cache-authorized-ttl` flag
# Default: 5m0s
authorizedTTL: 30s
# If set to false, 'authorized' responses from the webhook are not cached
# and the specified authorizedTTL is ignored/has no effect.
# Same as setting `--authorization-webhook-cache-authorized-ttl` flag to `0`.
# Note: Setting authorizedTTL to `0` results in its default value being used.
# Default: true
cacheAuthorizedRequests: true
# The duration to cache 'unauthorized' responses from the webhook
# authorizer.
# Same as setting `--authorization-webhook-cache-unauthorized-ttl` flag
# Default: 30s
unauthorizedTTL: 30s
# If set to false, 'unauthorized' responses from the webhook are not cached
# and the specified unauthorizedTTL is ignored/has no effect.
# Same as setting `--authorization-webhook-cache-unauthorized-ttl` flag to `0`.
# Note: Setting unauthorizedTTL to `0` results in its default value being used.
# Default: true
cacheUnauthorizedRequests: true
# Timeout for the webhook request
# Maximum allowed is 30s.
# Required, with no default.
timeout: 3s
# The API version of the authorization.k8s.io SubjectAccessReview to
# send to and expect from the webhook.
# Same as setting `--authorization-webhook-version` flag
# Required, with no default
# Valid values: v1beta1, v1
subjectAccessReviewVersion: v1
# MatchConditionSubjectAccessReviewVersion specifies the SubjectAccessReview
# version the CEL expressions are evaluated against
# Valid values: v1
# Required, no default value
matchConditionSubjectAccessReviewVersion: v1
# Controls the authorization decision when a webhook request fails to
# complete or returns a malformed response or errors evaluating
# matchConditions.
# Valid values:
# - NoOpinion: continue to subsequent authorizers to see if one of
# them allows the request
# - Deny: reject the request without consulting subsequent authorizers
# Required, with no default.
failurePolicy: Deny
# When ConditionalAuthorization is enabled, conditionsEndpointKubeConfigContext
# specifies the kubeconfig context to use for evaluating authorization conditions.
# The authorizer must support evaluating any condition type it returns.
# Optional; if unset, conditional authorization is not supported by this webhook.
conditionsEndpointKubeConfigContext: authorization-conditions
# The API version of the authorization.k8s.io AuthorizationConditionsReview to
# send to and expect from the webhook when evaluating conditions.
# Only relevant when conditionsEndpointKubeConfigContext is set.
# Valid values: v1alpha1
# This field has no default.
authorizationConditionsReviewVersion: v1alpha1
connectionInfo:
# Controls how the webhook should communicate with the server.
# Valid values:
# - KubeConfigFile: use the file specified in kubeConfigFile to locate the
# server.
# - InClusterConfig: use the in-cluster configuration to call the
# SubjectAccessReview API hosted by kube-apiserver. This mode is not
# allowed for kube-apiserver.
type: KubeConfigFile
# Path to KubeConfigFile for connection info
# Required, if connectionInfo.Type is KubeConfigFile
kubeConfigFile: /kube-system-authz-webhook.yaml
# matchConditions is a list of conditions that must be met for a request to be sent to this
# webhook. An empty list of matchConditions matches all requests.
# There are a maximum of 64 match conditions allowed.
#
# The exact matching logic is (in order):
# 1. If at least one matchCondition evaluates to FALSE, then the webhook is skipped.
# 2. If ALL matchConditions evaluate to TRUE, then the webhook is called.
# 3. If at least one matchCondition evaluates to an error (but none are FALSE):
# - If failurePolicy=Deny, then the webhook rejects the request
# - If failurePolicy=NoOpinion, then the error is ignored and the webhook is skipped
matchConditions:
# expression represents the expression which will be evaluated by CEL. Must evaluate to bool.
# CEL expressions have access to the contents of the SubjectAccessReview in v1 version.
# If version specified by subjectAccessReviewVersion in the request variable is v1beta1,
# the contents would be converted to the v1 version before evaluating the CEL expression.
#
# Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
#
# only send resource requests to the webhook
- expression: has(request.resourceAttributes)
# only intercept requests to kube-system
- expression: request.resourceAttributes.namespace == 'kube-system'
# don't intercept requests from kube-system service accounts
- expression: "!('system:serviceaccounts:kube-system' in request.groups)"
- type: Node
name: node
- type: RBAC
name: rbac
- type: Webhook
name: in-cluster-authorizer
webhook:
authorizedTTL: 5m
unauthorizedTTL: 30s
timeout: 3s
subjectAccessReviewVersion: v1
failurePolicy: NoOpinion
connectionInfo:
type: InClusterConfigWhen configuring the authorizer chain using a configuration file, make sure all the control plane nodes have the same file contents. Take a note of the API server configuration when upgrading / downgrading your clusters. For example, if upgrading from Kubernetes 1.34 to Kubernetes 1.35, you would need to make sure the config file is in a format that Kubernetes 1.35 can understand, before you upgrade the cluster. If you downgrade to 1.34, you would need to set the configuration appropriately.
Kubernetes reloads the authorization configuration file when the API server observes a change to the file, and also on a 60 second schedule if no change events were observed.
You must ensure that all non-webhook authorizer types remain unchanged in the file on reload.
A reload must not add or remove Node or RBAC authorizers (they can be reordered, but cannot be added or removed).
You can use the following modes:
--authorization-mode=ABAC (Attribute-based access control mode)--authorization-mode=RBAC (Role-based access control mode)--authorization-mode=Node (Node authorizer)--authorization-mode=Webhook (Webhook authorization mode)--authorization-mode=AlwaysAllow (always allows requests; carries security risks)--authorization-mode=AlwaysDeny (always denies requests)You can choose more than one authorization mode; for example:
--authorization-mode=Node,RBAC,Webhook
Kubernetes checks authorization modules based on the order that you specify them on the API server's command line, so an earlier module has higher priority to allow or deny a request.
You cannot combine the --authorization-mode command line argument with the
--authorization-config command line argument used for
configuring authorization using a local file.
For more information on command line arguments to the API server, read the
kube-apiserver reference.
Users who can create/edit pods in a namespace, either directly or through an object that enables indirect workload management, may be able to escalate their privileges in that namespace. The potential routes to privilege escalation include Kubernetes API extensions and their associated controllers.
There are different ways that an attacker or untrustworthy user could gain additional privilege within a namespace, if you allow them to run arbitrary Pods in that namespace:
Kubernetes v1.37 [alpha](disabled by default)When the ConditionalAuthorization feature is enabled, the authorization and
admission phases work together to enforce fine-grained policies:
Authorization phase: The authorizer performs partial evaluation of its policies based on available metadata (user, verb, resource, namespace, etc.). If the policy cannot be fully evaluated without seeing the request content, the authorizer returns conditions.
Request processing: If the authorization decision was a conditional allow (meaning the request could be allowed if conditions are met), the API server proceeds with normal request processing, including mutation by admission controllers.
Admission phase: The AuthorizationConditionsEnforcer admission controller
(always enabled when the feature is active) evaluates all returned conditions
against the fully-mutated request and stored objects. If the conditions evaluate
to allow, the request proceeds; otherwise, it's denied.
To understand how partial evaluation works, consider an authorizer with these two policies:
spec.storageClassName == 'dev'"Here's how these policies are partially evaluated for different requests during the authorization phase:
Request 1: Alice creates a ConfigMap
Result: The request is unconditionally allowed directly at the authorization stage, just like without conditional authorization. No admission-phase evaluation is needed.
Request 2: Alice creates a PersistentVolumeClaim
spec.storageClassName)
is not yet available. → Conditional Allow (condition: object.spec.storageClassName == 'dev')Result: The request proceeds to admission, where the condition is evaluated. If
spec.storageClassName is 'dev', the request is allowed; otherwise, it's denied.
Request 3: Bob creates a PersistentVolumeClaim
Result: The authorizer returns NoOpinion. If no other authorizer in the chain allows the request, it's rejected with HTTP 403 (Forbidden) at the authorization stage.
Consider an admin has configured a webhook authorizer with conditional authorization support:
apiVersion: apiserver.config.k8s.io/v1
kind: AuthorizationConfiguration
authorizers:
- type: Webhook
name: storage-policy-webhook
webhook:
timeout: 3s
subjectAccessReviewVersion: v1
failurePolicy: Deny
# Endpoint for evaluating authorization conditions
# This endpoint receives AuthorizationConditionsReview requests
conditionsEndpointKubeConfigContext: authorization-conditions
authorizationConditionsReviewVersion: v1alpha1
connectionInfo:
type: KubeConfigFile
kubeConfigFile: /etc/kubernetes/authz-webhook.yaml
The referenced kubeconfig file /etc/kubernetes/authz-webhook.yaml contains:
apiVersion: v1
kind: Config
clusters:
- name: storage-webhook
cluster:
server: https://storage-authz-webhook.example.com/authorize
certificate-authority: /etc/kubernetes/pki/webhook-ca.crt
- name: storage-webhook-conditions
cluster:
server: https://storage-authz-webhook.example.com/conditionsreview
certificate-authority: /etc/kubernetes/pki/webhook-ca.crt
contexts:
- name: default
context:
cluster: storage-webhook
user: kube-apiserver
- name: authorization-conditions
context:
cluster: storage-webhook-conditions
user: kube-apiserver
current-context: default
users:
- name: kube-apiserver
user:
client-certificate: /etc/kubernetes/pki/apiserver-webhook-client.crt
client-key: /etc/kubernetes/pki/apiserver-webhook-client.key
The webhook implements a policy: "allow user Alice to create PersistentVolumes,
but only when spec.storageClassName is 'dev'".
The following diagram shows a request where the authorization condition is satisfied (storageClassName is 'dev'):
The following diagram shows a request where the authorization condition is not satisfied (storageClassName is 'production' instead of 'dev'):
When Alice attempts to create a PersistentVolume with this manifest:
apiVersion: v1
kind: PersistentVolume
metadata:
name: my-pv
spec:
capacity:
storage: 10Gi
storageClassName: dev
accessModes:
- ReadWriteOnce
hostPath:
path: /data
Step 1: Authorization phase
The API server sends a SubjectAccessReview to the webhook authorizer:
apiVersion: authorization.k8s.io/v1
kind: SubjectAccessReview
spec:
resourceAttributes:
namespace: ""
verb: create
group: ""
version: v1
resource: persistentvolumes
user: alice
groups:
- system:authenticated
uid: "alice-uid-123"
The webhook examines the request and recognizes that:
spec.storageClassNameThe webhook returns a conditional response:
apiVersion: authorization.k8s.io/v1
kind: SubjectAccessReview
status:
allowed: false
denied: false
conditionalDecision:
type: ConditionsMap
conditionsMap:
conditions:
- id: storage-class-dev-only
effect: Allow
condition: "object.spec.storageClassName == 'dev'"
description: "User alice can only create PersistentVolumes with storageClassName 'dev'"
Because the response contains a conditional allow (effect: Allow), the API server
proceeds with the request, storing the conditions in the request context.
Step 2: Request processing
The request proceeds through the normal request chain:
storage-tier: standard to the PersistentVolume.Step 3: Admission phase - condition enforcement
The AuthorizationConditionsEnforcer admission controller runs first among
validating admission controllers. It extracts the conditions from the request
context and sends an AuthorizationConditionsReview to the webhook:
apiVersion: authorization.k8s.io/v1alpha1
kind: AuthorizationConditionsReview
request:
decision:
type: ConditionsMap
conditionsMap:
conditions:
- id: storage-class-dev-only
effect: Allow
condition: "object.spec.storageClassName == 'dev'"
description: "User alice can only create PersistentVolumes with storageClassName 'dev'"
admissionControlData:
requestKind:
group: ""
version: v1
kind: PersistentVolume
requestResource:
group: ""
version: v1
resource: persistentvolumes
name: my-pv
namespace: ""
operation: CREATE
userInfo:
username: alice
uid: "alice-uid-123"
groups:
- system:authenticated
object:
apiVersion: v1
kind: PersistentVolume
metadata:
name: my-pv
labels:
storage-tier: standard
spec:
capacity:
storage: 10Gi
storageClassName: dev
accessModes:
- ReadWriteOnce
hostPath:
path: /data
oldObject: null
options: null
Step 4: Condition evaluation
The webhook evaluates the condition against the actual request object:
object.spec.storageClassName == 'dev'object.spec.storageClassName is 'dev'trueBecause the condition with effect: Allow evaluates to true, the webhook
returns an allow decision:
apiVersion: authorization.k8s.io/v1alpha1
kind: AuthorizationConditionsReview
response:
decision:
type: Allow
reason: "Condition 'storage-class-dev-only' evaluated to true"
Step 5: Request completion
Since the conditions evaluated to allow, the AuthorizationConditionsEnforcer
allows the request to proceed. Other validating admission controllers run,
and if all succeed, the PersistentVolume is created in etcd.
If Alice had instead tried to create a PersistentVolume with
storageClassName: production:
Steps 1-3 would proceed identically
In step 4, the webhook would evaluate:
object.spec.storageClassName == 'dev'object.spec.storageClassName is 'production'falseBecause no effect: Allow conditions evaluated to true, the webhook returns:
apiVersion: authorization.k8s.io/v1alpha1
kind: AuthorizationConditionsReview
response:
decision:
type: NoOpinion
reason: "No Allow conditions matched"
The AuthorizationConditionsEnforcer denies the request with an error message:
Error from server (Forbidden): persistentvolumes "my-pv" is forbidden:
authorization conditions not satisfied: User alice can only create
PersistentVolumes with storageClassName 'dev'
In the examples above, the conditions use CEL (Common Expression Language) expressions like
object.spec.storageClassName == 'dev'. When a webhook returns conditions with
type: k8s.io/cel (or omits the type field for CEL expressions), the API server's
built-in CEL evaluator can evaluate them in-process without sending an
AuthorizationConditionsReview back to the webhook. This provides better performance
while maintaining the same security guarantees.
If the webhook had instead returned a custom condition type (for example,
type: example.com/custom-policy), then the AuthorizationConditionsReview
callback to the webhook would be required, as only the webhook knows how to
evaluate that condition type. However, it is worth noting that a webhook authorizer must support
evaluating any condition it authors, even if the condition type is k8s.io/cel, if the API server
does not enable in-process CEL evaluation or in version skew scenarios.
Conditions can have different effects:
true, the request is immediately
denied, short-circuiting evaluation of other authorizers.true, this authorizer has
no opinion, but other authorizers in the chain may still allow or deny the request.
The NoOpinion effect is useful for reducing the influence of Allow conditions by
factoring out common preconditions.true, it contributes to allowing
the request (unless overridden by deny conditions).Multiple authorizers can return conditions for the same request. They are evaluated in order, and the same short-circuiting logic applies as in the authorization phase.
The NoOpinion effect is useful for factoring out common preconditions from multiple Allow conditions. Instead of repeating the same precondition in every Allow condition, you can express it once as a NoOpinion condition.
For example, suppose you want to allow certain operations only when a namespace has a specific label. Without NoOpinion, you would need to repeat this check:
conditions:
- id: allow-create-pods
effect: Allow
condition: "namespace.metadata.labels['team'] == 'platform' && object.spec.containers.size() <= 5"
- id: allow-create-deployments
effect: Allow
condition: "namespace.metadata.labels['team'] == 'platform' && object.spec.replicas <= 10"
Using NoOpinion, you can factor out the common precondition:
conditions:
- id: team-precondition
effect: NoOpinion
condition: "namespace.metadata.labels['team'] != 'platform'"
- id: allow-create-pods
effect: Allow
condition: "object.spec.containers.size() <= 5"
- id: allow-create-deployments
effect: Allow
condition: "object.spec.replicas <= 10"
When namespace.metadata.labels['team'] != 'platform', the NoOpinion condition
evaluates to true, causing this authorizer to have no opinion (effectively overriding
all the Allow conditions). When the label equals 'platform', the NoOpinion condition
evaluates to false and is ignored, allowing the Allow conditions to be evaluated
normally.
This approach is clearer and more maintainable, especially when you have many Allow conditions that share the same precondition.
kubectl provides the auth can-i subcommand for quickly querying the API authorization layer.
The command uses the SelfSubjectAccessReview API to determine if the current user can perform
a given action, and works regardless of the authorization mode used.
kubectl auth can-i create deployments --namespace dev
The output is similar to this:
yes
kubectl auth can-i create deployments --namespace prod
The output is similar to this:
no
Administrators can combine this with user impersonation to determine what action other users can perform.
kubectl auth can-i list secrets --namespace dev --as dave
The output is similar to this:
no
Similarly, to check whether a ServiceAccount named dev-sa in Namespace dev
can list Pods in the Namespace target:
kubectl auth can-i list pods \
--namespace target \
--as system:serviceaccount:dev:dev-sa
The output is similar to this:
yes
SelfSubjectAccessReview is part of the authorization.k8s.io API group, which
exposes the API server authorization to external services. Other resources in
this group include:
Kubernetes v1.37 [alpha](disabled by default)These APIs can be queried by creating normal Kubernetes resources, where the response status
field of the returned object is the result of the query. For example:
kubectl create -f - -o yaml << EOF
apiVersion: authorization.k8s.io/v1
kind: SelfSubjectAccessReview
spec:
resourceAttributes:
group: apps
resource: deployments
verb: create
namespace: dev
EOF
The generated SelfSubjectAccessReview is similar to:
apiVersion: authorization.k8s.io/v1
kind: SelfSubjectAccessReview
metadata:
creationTimestamp: null
spec:
resourceAttributes:
group: apps
resource: deployments
namespace: dev
verb: create
status:
allowed: true
denied: falseWhen the ConditionalAuthorization feature is enabled and an authorizer returns
conditional responses, the status includes a conditionalDecision field instead of
simple allowed: true or denied: true. For example:
apiVersion: authorization.k8s.io/v1
kind: SelfSubjectAccessReview
metadata:
creationTimestamp: null
spec:
conditionalAuthorization:
enabled: true
resourceAttributes:
group: ""
resource: persistentvolumes
verb: create
status:
allowed: false
conditionalDecision:
type: ConditionsMap
conditionsMap:
conditions:
- id: storage-class-restriction
effect: "Allow"
condition: object.spec.storageClassName == "dev"
description: "User can only create PersistentVolumes with storageClassName 'dev'"The conditionalDecision represents an ordered list of condition sets from different
authorizers. Each condition set contains one or more conditions that must be
evaluated against the actual request object during the admission phase.