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

  1. Namespace Labeling: A namespace is assigned a specific PSA level (e.g., restricted).
  2. Kyverno Interception: When a user submits a Workload creation request, Kyverno intercepts the request during the admission phase.
  3. Compliance Plugin Mutation: Kyverno works together with the Compliance plugin to read the target namespace's PSA configuration.
  4. 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:

  1. Create a namespace with the restricted PSA label:
    kubectl create namespace test-psa
    kubectl label namespace test-psa pod-security.kubernetes.io/enforce=restricted
  2. Deploy a simple application without specifying a securityContext:
    kubectl create deployment nginx --image=nginxinc/nginx-unprivileged -n test-psa
  3. 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.