Manifest-Based Admission Control
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.
Why use manifest-based admission control?
Admission policies and webhooks registered through the Kubernetes API (such as ValidatingAdmissionPolicy, MutatingAdmissionPolicy, ValidatingWebhookConfiguration, and MutatingWebhookConfiguration) have several inherent limitations:
- Bootstrap gap: REST-based policy enforcement requires the API objects to be created and loaded by the dynamic admission controller. Until that happens, policies are not enforced.
- Self-protection gap: Admission configuration resources (such as ValidatingWebhookConfiguration) are not themselves subject to webhook admission, to prevent circular dependencies. A user with sufficient privileges can delete or modify critical admission policies.
- etcd dependency: REST-based admission configurations depend on etcd availability. If etcd is unavailable or corrupted, admission policies may not load correctly.
Manifest-based admission control addresses these limitations by loading configurations from files on disk. These configurations are:
- Active as soon as the API server is ready to serve requests
- Not visible or changeable through the Kubernetes API
- Independent of etcd availability
- Able to intercept operations on API-based admission resources themselves
Supported resource types
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.
Configuring manifest-based admission control
To enable manifest-based admission control, you need:
- The
ManifestBasedAdmissionControlConfigfeature gate enabled on the kube-apiserver. - An
AdmissionConfigurationfile withstaticManifestsDirfields pointing to directories containing your manifest files. - The manifest files themselves on disk, accessible to the kube-apiserver process.
AdmissionConfiguration
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.
Configuration types
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 |
Writing manifest files
Manifest files contain standard Kubernetes resource definitions. You can include
multiple resources in a single file using YAML document separators (---).
Naming convention
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.
Note:
If two manifest files define objects of the same type with the same name, the API server fails to start, displaying a descriptive error.Restrictions
Manifest-based admission configurations exist in isolation and cannot reference API resources. The following restrictions apply:
- Webhooks: Must use
clientConfig.url. TheclientConfig.servicefield is not allowed because the service network may not be available at API server startup. - Policies: The
spec.paramKindfield is not allowed. Policies cannot reference ConfigMaps or other cluster objects for parameters. - Bindings: The
spec.paramReffield is not allowed. Thespec.policyNamemust 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.
Examples
Protecting API-based admission resources
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
Enforcing a ValidatingAdmissionPolicy from disk
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.
Configuring a ValidatingWebhookConfiguration from disk
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
Note:
Webhook URLs must be reachable from the kube-apiserver at startup. Only URL-based endpoints are supported; service references are not allowed in manifest-based webhook configurations.Using the List format
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"]
Evaluation order
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.
File watching and dynamic reloading
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:
- File modifications are detected using fsnotify with a polling fallback (default 1 minute interval), similar to other config file reloading in kube-apiserver.
- A content hash of all manifest files is computed on each check. If the hash is unchanged, no reload occurs.
- New configurations are validated before being applied.
- If validation fails, the error is logged, metrics are updated, and the previous valid configuration is retained.
- Successful reloads atomically replace the previous configuration.
- Atomic file updates: To avoid partial reads during file writes, make changes atomically (for example, write to a temporary file and rename it). This is especially important when updating mounted ConfigMaps or Secrets in containerized environments.
Caution:
If an invalid manifest file is present at startup, the API server does not start. At runtime, if a reload fails due to validation errors, the previous valid configuration is retained and the error is logged.Observability
Metrics
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.
Audit annotations
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.
High availability considerations
Each kube-apiserver instance loads its own manifest files independently. In high availability setups with multiple API server instances:
- Each API server must be configured individually. There is no cross-apiserver synchronization of manifest-based configurations.
- Use external configuration management tools (such as Ansible, Puppet, or shared storage mounts) to keep manifest files consistent across instances.
- The
apiserver_manifest_admission_config_controller_last_config_infometric exposes ahashlabel 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 and downgrade
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:
- Remove
staticManifestsDirentries from theAdmissionConfigurationfile. - If relying on manifest-based policies, recreate them as API objects where possible.
- Restart the kube-apiserver.
Warning:
Downgrading without removing thestaticManifestsDir configuration causes the
API server to fail to start due to unknown configuration fields.Troubleshooting
| 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. |
What's next
- Learn about ValidatingAdmissionPolicy for CEL-based validation policies.
- Learn about MutatingAdmissionPolicy for CEL-based mutation policies.
- Learn about Dynamic Admission Control for webhook-based admission control.
- Read the KEP-5793 design document.