Kubernetes v1.36 [alpha](disabled by default)This page provides an overview of manifest-based admission control configuration. Manifest-based admission control lets you load admission webhooks and CEL-based admission policies from static files on disk, rather than from the Kubernetes API. These policies are active from API server startup, operate independently of etcd, and can protect API-based admission resources from modification.
To use the feature, enable the ManifestBasedAdmissionControlConfig
feature gate and
configure the staticManifestsDir field in the
AdmissionConfiguration
file passed to the kube-apiserver via --admission-control-config-file.
Admission policies and webhooks registered through the Kubernetes API (such as ValidatingAdmissionPolicy, MutatingAdmissionPolicy, ValidatingWebhookConfiguration, and MutatingWebhookConfiguration) have several inherent limitations:
Manifest-based admission control addresses these limitations by loading configurations from files on disk. These configurations are:
You can include the following resource types in manifest files. Only the
admissionregistration.k8s.io/v1 API version is supported.
| Plugin name | Supported resource types |
|---|---|
ValidatingAdmissionWebhook | ValidatingWebhookConfiguration |
MutatingAdmissionWebhook | MutatingWebhookConfiguration |
ValidatingAdmissionPolicy | ValidatingAdmissionPolicy, ValidatingAdmissionPolicyBinding |
MutatingAdmissionPolicy | MutatingAdmissionPolicy, MutatingAdmissionPolicyBinding |
You can also use v1.List to wrap multiple resources of the same plugin type
in a single document.
Each admission plugin's staticManifestsDir must only contain resource types
allowed for that plugin. For example, a directory configured for the
ValidatingAdmissionPolicy plugin can only contain ValidatingAdmissionPolicy
and ValidatingAdmissionPolicyBinding resources.
To enable manifest-based admission control, you need:
ManifestBasedAdmissionControlConfig feature gate enabled on the
kube-apiserver.AdmissionConfiguration file with staticManifestsDir fields pointing
to directories containing your manifest files.Add staticManifestsDir to the plugin configuration for each admission plugin
that should load manifests from disk. Each plugin requires its own directory.
# This is an example AdmissionConfiguration that configures all four admission
# plugins to load manifest-based admission control from static files on disk.
apiVersion: apiserver.config.k8s.io/v1
kind: AdmissionConfiguration
plugins:
- name: ValidatingAdmissionWebhook
configuration:
apiVersion: apiserver.config.k8s.io/v1
kind: WebhookAdmissionConfiguration
kubeConfigFile: "<path-to-kubeconfig>"
staticManifestsDir: "/etc/kubernetes/admission/validating-webhooks/"
- name: MutatingAdmissionWebhook
configuration:
apiVersion: apiserver.config.k8s.io/v1
kind: WebhookAdmissionConfiguration
kubeConfigFile: "<path-to-kubeconfig>"
staticManifestsDir: "/etc/kubernetes/admission/mutating-webhooks/"
- name: ValidatingAdmissionPolicy
configuration:
apiVersion: apiserver.config.k8s.io/v1
kind: ValidatingAdmissionPolicyConfiguration
staticManifestsDir: "/etc/kubernetes/admission/validating-policies/"
- name: MutatingAdmissionPolicy
configuration:
apiVersion: apiserver.config.k8s.io/v1
kind: MutatingAdmissionPolicyConfiguration
staticManifestsDir: "/etc/kubernetes/admission/mutating-policies/"
The staticManifestsDir field accepts an absolute path to a directory. All
direct-children files with .yaml, .yml, or .json extensions in the
directory are loaded. Subdirectories and files with other extensions are ignored.
Glob patterns and relative paths are not supported.
Pass this file to the kube-apiserver with the --admission-control-config-file
flag.
Each admission plugin uses a specific configuration kind:
| Plugin | apiVersion | kind |
|---|---|---|
ValidatingAdmissionWebhook | apiserver.config.k8s.io/v1 | WebhookAdmissionConfiguration |
MutatingAdmissionWebhook | apiserver.config.k8s.io/v1 | WebhookAdmissionConfiguration |
ValidatingAdmissionPolicy | apiserver.config.k8s.io/v1 | ValidatingAdmissionPolicyConfiguration |
MutatingAdmissionPolicy | apiserver.config.k8s.io/v1 | MutatingAdmissionPolicyConfiguration |
Manifest files contain standard Kubernetes resource definitions. You can include
multiple resources in a single file using YAML document separators (---).
All objects in manifest files must have names ending with the .static.k8s.io
suffix. For example: deny-privileged.static.k8s.io.
When the ManifestBasedAdmissionControlConfig feature gate is enabled, creation
of API-based admission objects with names ending in .static.k8s.io is blocked.
When the feature gate is disabled, a warning is returned instead.
Manifest-based admission configurations exist in isolation and cannot reference API resources. The following restrictions apply:
clientConfig.url. The clientConfig.service field is
not allowed because the service network may not be available at API server
startup.spec.paramKind field is not allowed. Policies cannot
reference ConfigMaps or other cluster objects for parameters.spec.paramRef field is not allowed. The spec.policyName
must reference a policy defined in the same manifest file set and must end
with .static.k8s.io.Manifest files are decoded using the strict decoder, which rejects files containing duplicate fields or unknown fields. Each object undergoes the same defaulting and validation that the REST API applies.
A key capability of manifest-based admission control is the ability to intercept operations on admission configuration resources themselves (ValidatingAdmissionPolicy, MutatingAdmissionPolicy, ValidatingWebhookConfiguration, MutatingWebhookConfiguration, and their bindings). REST-based admission webhooks and policies are not invoked on these resource types to prevent circular dependencies, but manifest-based policies can enforce rules on them because they do not have that circular dependency.
The following example prevents deletion or modification of admission resources
that carry the platform.example.com/protected: "true" label:
# This is an example ValidatingAdmissionPolicy that prevents deletion or
# modification of API-based admission resources with the
# "platform.example.com/protected: true" label.
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
name: "example-protect-admission-resources.static.k8s.io"
annotations:
kubernetes.io/description: "Prevent modification or deletion of protected admission resources"
spec:
failurePolicy: Fail
matchConstraints:
resourceRules:
- apiGroups: ["admissionregistration.k8s.io"]
apiVersions: ["*"]
operations: ["DELETE", "UPDATE"]
resources:
- "validatingadmissionpolicies"
- "validatingadmissionpolicybindings"
- "mutatingadmissionpolicies"
- "mutatingadmissionpolicybindings"
- "validatingwebhookconfigurations"
- "mutatingwebhookconfigurations"
validations:
- expression: >-
!has(oldObject.metadata.labels) ||
!('platform.example.com/protected' in oldObject.metadata.labels) ||
oldObject.metadata.labels['platform.example.com/protected'] != 'true'
message: "Protected admission resources cannot be modified or deleted"
---
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicyBinding
metadata:
name: "example-protect-admission-resources-binding.static.k8s.io"
annotations:
kubernetes.io/description: "Bind protect-admission-resources policy to all admission resources"
spec:
policyName: "example-protect-admission-resources.static.k8s.io"
validationActions:
- Deny
The following example defines a policy that denies privileged containers in all
namespaces except kube-system:
# This is an example ValidatingAdmissionPolicy that denies privileged containers
# in all namespaces except kube-system.
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
name: "example-deny-privileged.static.k8s.io"
annotations:
kubernetes.io/description: "Deny privileged containers outside kube-system"
spec:
failurePolicy: Fail
matchConstraints:
resourceRules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["pods"]
variables:
- name: allContainers
expression: >-
object.spec.containers +
(has(object.spec.initContainers) ? object.spec.initContainers : []) +
(has(object.spec.ephemeralContainers) ? object.spec.ephemeralContainers : [])
validations:
- expression: >-
!variables.allContainers.exists(c,
has(c.securityContext) && has(c.securityContext.privileged) &&
c.securityContext.privileged == true)
message: "Privileged containers are not allowed"
---
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicyBinding
metadata:
name: "example-deny-privileged-binding.static.k8s.io"
annotations:
kubernetes.io/description: "Bind deny-privileged policy to all namespaces except kube-system"
spec:
policyName: "example-deny-privileged.static.k8s.io"
validationActions:
- Deny
matchResources:
namespaceSelector:
matchExpressions:
- key: "kubernetes.io/metadata.name"
operator: NotIn
values: ["kube-system"]
Place this file in the directory configured as staticManifestsDir for the
ValidatingAdmissionPolicy plugin. The policy and its binding are loaded
together atomically.
The following example configures a validating webhook that calls an external URL:
# This is an example ValidatingWebhookConfiguration that calls an external
# URL-based webhook endpoint to validate pod creation and updates.
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
name: "example-security-webhook.static.k8s.io"
annotations:
kubernetes.io/description: "Validate pod creation and updates via external webhook"
webhooks:
- name: "security.platform.example.com"
clientConfig:
url: "https://security-webhook.platform.example.com:443/validate"
caBundle: "<base64-encoded-CA-bundle>"
rules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["pods"]
admissionReviewVersions: ["v1"]
sideEffects: None
failurePolicy: Fail
You can use v1.List to group related resources together in a single document:
# This is an example of using the v1.List format to group a
# ValidatingAdmissionPolicy and its binding in a single document.
apiVersion: v1
kind: List
items:
- apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
name: "example-require-labels.static.k8s.io"
annotations:
kubernetes.io/description: "Require app.kubernetes.io/name label on all pods"
spec:
failurePolicy: Fail
matchConstraints:
resourceRules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE"]
resources: ["pods"]
validations:
- expression: >-
has(object.metadata.labels) &&
'app.kubernetes.io/name' in object.metadata.labels
message: "All pods must have the 'app.kubernetes.io/name' label"
- apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicyBinding
metadata:
name: "example-require-labels-binding.static.k8s.io"
annotations:
kubernetes.io/description: "Bind require-labels policy to all namespaces except kube-system"
spec:
policyName: "example-require-labels.static.k8s.io"
validationActions:
- Deny
matchResources:
namespaceSelector:
matchExpressions:
- key: "kubernetes.io/metadata.name"
operator: NotIn
values: ["kube-system"]
Manifest-based configurations are evaluated before API-based configurations. This ensures that platform-level policies enforced via static configuration take precedence over API-based policies.
For admission configuration resources themselves (ValidatingAdmissionPolicy, MutatingAdmissionPolicy, ValidatingAdmissionPolicyBinding, MutatingAdmissionPolicyBinding, ValidatingWebhookConfiguration, MutatingWebhookConfiguration), only manifest-based admission hooks are evaluated. API-based hooks are skipped for these resource types to prevent circular dependencies.
The kube-apiserver watches the configured directories for changes:
Initial load: At startup, all configured paths are read and validated. The API server does not become ready until all manifests are loaded successfully. Invalid manifests cause startup failure.
Runtime reloading: Changes to manifest files trigger a reload cycle:
Manifest-based admission control provides the following metrics for monitoring reload health:
| Type | Description | Metric |
|---|---|---|
| Counter | Total number of reload attempts, with status (success or failure), plugin, and apiserver_id_hash labels. | apiserver_manifest_admission_config_controller_automatic_reloads_total |
| Gauge | Timestamp of the last reload attempt, with status, plugin, and apiserver_id_hash labels. | apiserver_manifest_admission_config_controller_automatic_reload_last_timestamp_seconds |
| Gauge | Current configuration information (value is always 1), with plugin, apiserver_id_hash, and hash labels. Use the hash label to detect configuration drift across API servers. | apiserver_manifest_admission_config_controller_last_config_info |
The plugin label identifies which admission plugin the metric applies to:
ValidatingAdmissionWebhook, MutatingAdmissionWebhook,
ValidatingAdmissionPolicy, or MutatingAdmissionPolicy.
Since manifest-based objects have names ending in .static.k8s.io, existing
admission metrics (such as apiserver_admission_webhook_rejection_count) can
identify manifest-based decisions by filtering on the name label.
Existing audit annotations (such as
validation.policy.admission.k8s.io/validation_failure and
mutation.webhook.admission.k8s.io/round_0_index_0) include the object name.
You can identify manifest-based admission decisions by filtering for names
ending in .static.k8s.io.
Each kube-apiserver instance loads its own manifest files independently. In high availability setups with multiple API server instances:
apiserver_manifest_admission_config_controller_last_config_info metric
exposes a hash label that you can use to detect configuration drift across
API server instances.This behavior is similar to other file-based kube-apiserver configurations such as encryption at rest and authentication.
Upgrade: Enabling the feature and providing manifest configuration is opt-in. Existing clusters without manifest configuration see no behavioral change.
Downgrade: Before downgrading to a version without this feature:
staticManifestsDir entries from the AdmissionConfiguration file.staticManifestsDir configuration causes the
API server to fail to start due to unknown configuration fields.| Symptom | Possible cause | Resolution |
|---|---|---|
| API server fails to start | Invalid manifest file at startup | Check API server logs for validation errors. Fix the manifest file and restart. |
| API server fails to start | Duplicate object names across manifest files | Ensure all object names within a plugin's staticManifestsDir are unique. |
| Policies not enforced after file update | Reload validation failure | Check automatic_reloads_total{status="failure"} metric and API server logs. Fix the manifest and wait for the next reload cycle. |
| Webhook requests failing | Webhook URL not reachable | Verify that the URL specified in clientConfig.url is accessible from the kube-apiserver. |
Cannot create API objects with .static.k8s.io suffix | Name suffix reserved by feature gate | The .static.k8s.io suffix is reserved for manifest-based configurations when the feature gate is enabled. Use a different name for API-based objects. |