Dynamically Inject PSA Security Contexts for Workloads
When Pod Security Admission (PSA) is enabled on a namespace, any Workload (such as a Deployment or StatefulSet) created within it must comply with the specified security profile. If the securityContext of the Workload does not meet the requirements, the Pods generated by the Workload will be rejected during Pod admission by the Kubernetes API server, even if the Workload resource itself is accepted.
To simplify the user experience and ensure continuous compliance, you can use Kyverno along with the Alauda Container Platform Compliance with Kyverno plugin to dynamically inject the required securityContext fields based on the namespace's PSA policy.
Prerequisites
Before proceeding, ensure that:
- The Alauda Container Platform Compliance with Kyverno plugin is installed and enabled in your cluster. For installation details, please refer to Install Compliance Plugin.
- Your target namespace has a PSA label applied, such as
pod-security.kubernetes.io/enforce: restricted or baseline.
How It Works
- Namespace Labeling: A namespace is assigned a specific PSA level (e.g.,
restricted).
- Kyverno Interception: When a user submits a Workload creation request, Kyverno intercepts the request during the admission phase.
- Compliance Plugin Mutation: Kyverno works together with the Compliance plugin to read the target namespace's PSA configuration.
- Dynamic Patching: Based on the PSA level, Kyverno dynamically patches the Workload's Pod template with the appropriate
securityContext (e.g., adding runAsNonRoot: true, dropping specific capabilities, or setting the seccompProfile).
PSA Enforcement Configurations
Once the Compliance plugin is installed, you can apply the following ClusterPolicy configurations. These policies use context variables to query the namespace's pod-security.kubernetes.io/enforce label and apply the exact securityContext mutations required by the respective PSA profile across all container types (including initContainers and ephemeralContainers).
1. Restricted Policy Mutation
When a namespace enforces the restricted profile, Workloads must drop all privileges, run as non-root, and use the default seccomp profile.
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: mutate-psa-restricted
annotations:
policies.kyverno.io/title: Mutate Workload for PSA Restricted
policies.kyverno.io/description: >-
Dynamically injects the Restricted PSA securityContext into Workloads based on the target Namespace label.
spec:
rules:
- name: inject-restricted-security-context-controllers
match:
any:
- resources:
kinds:
- Deployment
- StatefulSet
- DaemonSet
- Job
- CronJob
context:
- name: namespacePSA
apiCall:
urlPath: "/api/v1/namespaces/{{request.namespace}}"
jmesPath: "metadata.labels.\"pod-security.kubernetes.io/enforce\" || 'none'"
preconditions:
all:
- key: "{{namespacePSA}}"
operator: Equals
value: "restricted"
mutate:
patchStrategicMerge:
spec:
template:
spec:
securityContext:
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
containers:
- (name): "?*"
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
initContainers:
- (name): "?*"
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
ephemeralContainers:
- (name): "?*"
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
- name: inject-restricted-security-context-pods
match:
any:
- resources:
kinds:
- Pod
context:
- name: namespacePSA
apiCall:
urlPath: "/api/v1/namespaces/{{request.namespace}}"
jmesPath: "metadata.labels.\"pod-security.kubernetes.io/enforce\" || 'none'"
preconditions:
all:
- key: "{{namespacePSA}}"
operator: Equals
value: "restricted"
mutate:
patchStrategicMerge:
spec:
securityContext:
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
containers:
- (name): "?*"
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
initContainers:
- (name): "?*"
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
ephemeralContainers:
- (name): "?*"
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
2. Baseline Policy Mutation
When a namespace enforces the baseline profile, Workloads are prohibited from using host namespaces and privileged containers.
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: mutate-psa-baseline
annotations:
policies.kyverno.io/title: Mutate Workload for PSA Baseline
policies.kyverno.io/description: >-
Dynamically injects the Baseline PSA securityContext into Workloads based on the target Namespace label.
spec:
rules:
- name: inject-baseline-security-context-controllers
match:
any:
- resources:
kinds:
- Deployment
- StatefulSet
- DaemonSet
- Job
- CronJob
context:
- name: namespacePSA
apiCall:
urlPath: "/api/v1/namespaces/{{request.namespace}}"
jmesPath: "metadata.labels.\"pod-security.kubernetes.io/enforce\" || 'none'"
preconditions:
all:
- key: "{{namespacePSA}}"
operator: Equals
value: "baseline"
mutate:
patchStrategicMerge:
spec:
template:
spec:
hostNetwork: false
hostIPC: false
hostPID: false
containers:
- (name): "?*"
securityContext:
privileged: false
initContainers:
- (name): "?*"
securityContext:
privileged: false
ephemeralContainers:
- (name): "?*"
securityContext:
privileged: false
- name: inject-baseline-security-context-pods
match:
any:
- resources:
kinds:
- Pod
context:
- name: namespacePSA
apiCall:
urlPath: "/api/v1/namespaces/{{request.namespace}}"
jmesPath: "metadata.labels.\"pod-security.kubernetes.io/enforce\" || 'none'"
preconditions:
all:
- key: "{{namespacePSA}}"
operator: Equals
value: "baseline"
mutate:
patchStrategicMerge:
spec:
hostNetwork: false
hostIPC: false
hostPID: false
containers:
- (name): "?*"
securityContext:
privileged: false
initContainers:
- (name): "?*"
securityContext:
privileged: false
ephemeralContainers:
- (name): "?*"
securityContext:
privileged: false
Note: For the privileged PSA profile, no mutation is required as it allows unrestricted access.
Verification
To verify that the dynamic configuration works:
- Create a namespace with the
restricted PSA label:
kubectl create namespace test-psa
kubectl label namespace test-psa pod-security.kubernetes.io/enforce=restricted
- Deploy a simple application without specifying a
securityContext:
kubectl create deployment nginx --image=nginxinc/nginx-unprivileged -n test-psa
- Check the created Pod's YAML definition:
kubectl get pod -n test-psa -l app=nginx -o yaml
You should see the securityContext fields automatically injected to meet the restricted profile requirements.