Kubernetes v1.36 introduces a new alpha feature that addresses a long-standing pain point for cluster administrators: ensuring critical admission policies are always in effect, even during cluster bootstrap or recovery. By reading policy configurations from files on disk before the API server starts serving requests, this manifest-based approach eliminates the security window where policies don't exist yet and prevents privileged users from deleting them. Below, we explore how it works and why it matters.
What problem does Kubernetes v1.36’s manifest-based admission control solve?
The core issue is a chicken-and-egg problem: admission policies are API objects that don't exist until someone creates them, and they can be deleted by anyone with sufficient permissions. This creates two specific vulnerabilities. First, during cluster bootstrap—or after an etcd failure and restore—there is a gap between when the API server starts serving requests and when policies are applied. Second, even in steady state, a privileged user can delete ValidatingWebhookConfiguration or ValidatingAdmissionPolicy objects because admission webhooks are intentionally not invoked on their own configuration resources to avoid circular dependencies. Manifest-based admission control solves both by letting administrators define policies as files on disk that the API server loads at startup, before any request is processed, and that cannot be removed through the API.
How does the bootstrap gap affect traditional admission policies?
When a Kubernetes cluster starts up, the API server begins accepting requests as soon as it is ready. Traditional policy enforcement relies on creating API objects like ValidatingAdmissionPolicy or webhook configurations after the server is live. This creates a window—sometimes significant if restoring from backup or recovering etcd—where no policies are active. During that time, any pod or resource can be created without validation. The longer the gap, the higher the risk of misconfigurations or security violations. Manifest-based policies close this gap by loading policies from a static directory before the API server begins serving, ensuring that from the very first request, all defined policies are enforced.
What is the self-protection problem with admission webhooks?
Admission webhooks and policies cannot intercept operations on their own configuration resources. For example, Kubernetes does not invoke webhooks on ValidatingWebhookConfiguration objects to avoid infinite loops or circular dependencies. This means a user with delete permission on these objects can remove a critical admission policy, and no admission plugin can stop them. This self-protection gap is particularly dangerous for security-focused policies (e.g., deny privileged containers). Manifest-based policies sidestep this entirely because they are not stored as API objects. They are read from disk at startup, so there is no corresponding API resource to delete. An administrator would need direct filesystem access to the API server’s host to modify or remove them.
How do you configure manifest-based admission policies in v1.36?
To enable this feature, you add a staticManifestsDir field to the AdmissionConfiguration file that you already pass to the API server via the --admission-control-config-file flag. This field points to a directory containing YAML policy files. For example:
apiVersion: apiserver.config.k8s.io/v1
kind: AdmissionConfiguration
plugins:
- name: ValidatingAdmissionPolicy
configuration:
apiVersion: apiserver.config.k8s.io/v1
kind: ValidatingAdmissionPolicyConfiguration
staticManifestsDir: "/etc/kubernetes/admission/validating-policies/"
The API server scans this directory before starting, loads all valid manifest files, and applies them immediately. The files themselves are standard Kubernetes resource definitions, but with one crucial naming requirement (see next question).
What naming conventions are required for static policy manifests?
All objects defined in static manifest files must have names ending in .static.k8s.io. This reserved suffix serves two purposes. First, it prevents name collisions with any API-based configurations that might have the same name (e.g., a ValidatingAdmissionPolicy created via kubectl). Second, it makes it easy to identify the origin of an admission decision when reviewing metrics or audit logs. For example, a policy named deny-privileged.static.k8s.io clearly indicates it was loaded from disk. This suffix is enforced by the API server; if you omit it, the static manifest will be rejected.
Can you show an example of a static admission policy denying privileged pods?
Here is a complete example that denies privileged containers outside the kube-system namespace:
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
name: "deny-privileged.static.k8s.io"
annotations:
kubernetes.io/description: "Deny launching privileged pods, anywhere this policy is applied"
spec:
failurePolicy: Fail
matchResources:
namespaceSelector:
matchExpressions:
- key: kubernetes.io/metadata.name
operator: NotIn
values: ["kube-system"]
validations:
- expression: "!has(object.spec.securityContext) || !has(object.spec.securityContext.privileged) || object.spec.securityContext.privileged != true"
message: "Privileged containers are not allowed outside kube-system"
Place this YAML file in the directory specified by staticManifestsDir. Because it ends with .static.k8s.io, it will be loaded at startup and cannot be deleted via the API—ensuring always-on enforcement of the no-privileged-containers rule.