Manifest-Based Admission Control

FEATURE STATE: 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.

Supported resource types for manifest-based admission control
Plugin nameSupported resource types
ValidatingAdmissionWebhookValidatingWebhookConfiguration
MutatingAdmissionWebhookMutatingWebhookConfiguration
ValidatingAdmissionPolicyValidatingAdmissionPolicy, ValidatingAdmissionPolicyBinding
MutatingAdmissionPolicyMutatingAdmissionPolicy, 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:

  1. The ManifestBasedAdmissionControlConfig feature gate enabled on the kube-apiserver.
  2. An AdmissionConfiguration file with staticManifestsDir fields pointing to directories containing your manifest files.
  3. 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:

Configuration types for each admission plugin
PluginapiVersionkind
ValidatingAdmissionWebhookapiserver.config.k8s.io/v1WebhookAdmissionConfiguration
MutatingAdmissionWebhookapiserver.config.k8s.io/v1WebhookAdmissionConfiguration
ValidatingAdmissionPolicyapiserver.config.k8s.io/v1ValidatingAdmissionPolicyConfiguration
MutatingAdmissionPolicyapiserver.config.k8s.io/v1MutatingAdmissionPolicyConfiguration

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. The clientConfig.service field is not allowed because the service network may not be available at API server startup.
  • Policies: The spec.paramKind field is not allowed. Policies cannot reference ConfigMaps or other cluster objects for parameters.
  • Bindings: The 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.

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:

  1. 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.

  2. 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.
  1. 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:

Metrics for manifest-based admission control
TypeDescriptionMetric
CounterTotal number of reload attempts, with status (success or failure), plugin, and apiserver_id_hash labels.apiserver_manifest_admission_config_controller_automatic_reloads_total
GaugeTimestamp of the last reload attempt, with status, plugin, and apiserver_id_hash labels.apiserver_manifest_admission_config_controller_automatic_reload_last_timestamp_seconds
GaugeCurrent 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_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 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:

  1. Remove staticManifestsDir entries from the AdmissionConfiguration file.
  2. If relying on manifest-based policies, recreate them as API objects where possible.
  3. Restart the kube-apiserver.

Warning:

Downgrading without removing the staticManifestsDir configuration causes the API server to fail to start due to unknown configuration fields.

Troubleshooting

Common issues and their resolution
SymptomPossible causeResolution
API server fails to startInvalid manifest file at startupCheck API server logs for validation errors. Fix the manifest file and restart.
API server fails to startDuplicate object names across manifest filesEnsure all object names within a plugin's staticManifestsDir are unique.
Policies not enforced after file updateReload validation failureCheck automatic_reloads_total{status="failure"} metric and API server logs. Fix the manifest and wait for the next reload cycle.
Webhook requests failingWebhook URL not reachableVerify that the URL specified in clientConfig.url is accessible from the kube-apiserver.
Cannot create API objects with .static.k8s.io suffixName suffix reserved by feature gateThe .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