How to Set Up Permanent Admission Policies in Kubernetes v1.36 with Manifest-Based Control
Introduction
If you’ve ever tried to enforce security policies across a fleet of Kubernetes clusters, you know the frustration: your admission policies are API objects. They don’t exist until someone creates them, and they can be deleted by anyone with the right permissions. This creates a chicken-and-egg problem—during cluster bootstrap, there's always a window where policies aren’t active, and no mechanism prevents a privileged user from removing them. Kubernetes v1.36 introduces an alpha feature called manifest-based admission control that solves this. It lets you define admission webhooks and CEL-based policies as files on disk, loaded by the API server at startup—before it serves any requests. This guide walks you through setting up these permanent, undeletable admission policies.
What You Need
- Kubernetes cluster running v1.36 (or later with the alpha feature enabled). The manifest-based admission control feature is in alpha in v1.36; you need to enable the
AdmissionPolicyManifestsfeature gate. - API server binary compiled with the feature gate support (standard kube-apiserver from v1.36).
- Access to the API server configuration file (the
AdmissionConfigurationfile passed via--admission-control-config-file). - YAML files for your admission policies (e.g., ValidatingAdmissionPolicy, MutatingAdmissionPolicy, or webhook configurations). These must follow the naming convention: resource names ending in
.static.k8s.io. - A directory on the API server host to store the static manifest files (e.g.,
/etc/kubernetes/admission/validating-policies/).
Step-by-Step Guide
Step 1: Enable the Alpha Feature Gate
Edit the API server startup command or manifest to include the feature gate. Add --feature-gates=AdmissionPolicyManifests=true to your kube-apiserver command line. If you use kubeadm, you can add it to the kube-apiserver.yaml under spec.containers.command. For a static pod, update the manifest file on the control plane node.
Step 2: Prepare the AdmissionConfiguration File
If you don't already have an AdmissionConfiguration file, create one. This file is passed to the API server via --admission-control-config-file. It configures the admission plugins. For manifest-based control, add a staticManifestsDir field under the plugin you want to use. Currently, the feature works with ValidatingAdmissionPolicy and MutatingAdmissionPolicy plugins. Below is an example for validating policies:
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/"
Save this file (e.g., /etc/kubernetes/admission-config.yaml) and ensure the API server uses it.
Step 3: Create Your Policy YAML Files
Write standard Kubernetes resource definitions for your admission policies. The only requirement is that each resource name ends with .static.k8s.io. This reserved suffix prevents name collisions with API-based configurations and helps identify policy origins in metrics and audit logs. Here’s 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
matchConstraints:
resourceRules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["pods"]
validations:
- expression: "!object.spec.containers.all(c, c.securityContext != null && c.securityContext.privileged == true) || namespace == 'kube-system'"
message: "Privileged containers are not allowed outside kube-system namespace."
Place all such YAML files in the directory you specified in Step 2. You can include multiple policy files; the API server will load them all.
Step 4: Start (or Restart) the API Server
With the feature gate enabled and the configuration file in place, start or restart the kube-apiserver process. The API server will read the AdmissionConfiguration file, locate the staticManifestsDir, and load all YAML files in that directory. It parses and validates them immediately. If any file contains invalid syntax or violates naming rules, the API server fails to start, preventing unsafe configurations. Once loaded, these policies become active before the API server begins serving any requests—closing the bootstrap gap.
Step 5: Verify the Policies Are Active
After the API server is running, check that your static policies appear in the cluster. They are not visible via the normal API (they are not stored in etcd), but you can verify their effect. In the Tips section, we list ways to confirm the policies are enforcing correctly. For example, try creating a privileged pod outside kube-system—it should be denied. Check the API server logs for messages indicating that admission decisions came from a static policy.
Tips and Conclusion
- Naming convention is crucial: All manifest files must have resource names ending in
.static.k8s.io. This suffix is reserved exclusively for static manifests. If you place a regular policy file without this suffix, the API server will ignore it. - Audit logs and metrics: When a static policy makes an admission decision, the audit log includes the suffix to differentiate it from dynamic policies. Use this to monitor and debug your enforcement.
- You cannot delete static policies via the API: Because they are not stored in etcd, there is no API object to delete. The only way to remove them is to delete the YAML file from the directory and restart the API server. This makes them tamper-proof against accidental or malicious deletion.
- Test your policies thoroughly: Since a syntax error prevents the API server from starting, always test your YAML files on a non-production cluster first. Use
kubectl create --dry-run=serverto validate against the API server schema (though the static manifest loading does its own validation). - Combination with dynamic policies: Static policies work alongside normal API-based policies. Both are evaluated; if you have both a static and a dynamic policy for the same admission point, the static one is evaluated first. Use static policies for “base layer” security that must always be present.
- Future-proofing: This feature is alpha in v1.36. Check the Kubernetes changelog for updates in later versions. The concept of manifest-based admission may expand to more plugins.
With manifest-based admission control, you finally have a way to say “these policies are always on, full stop.” No more bootstrap gaps, no more sabotage from privileged insiders. Follow the steps above to lock down your clusters permanently.