Skip to main content

PSP Migration

With the removal of PodSecurityPolicy in Kubernetes v1.25, you can leverage Kubewarden for admission control on your Kubernetes clusters. Contrasting with the PSPs, Kubewarden has separate policies to achieve the same goal. Therefore, each Kubewarden policy could be likened to a different configuration within the spec of a PSP. A mapping of the PSP configuration fields to their respective policies can be found below in the mapping table. Therefore, the operators have more granular control of the configuration they want to apply in their clusters. If they want to apply part of the PSP security checks it is not necessary to define the configurations related to the other fields.

Once you have the Kubewarden instance running, it's time to deploy some policies to replace the PodSecurityPolicy object. Start by listing the PSPs in use. For the sake of this example, the following enforcements have been considered:

  • a PSP disabling privileged escalation
  • privileged containers
  • blocking pods running as root
  • forcing a particular user group
  • blocking host namespaces
  • allowing pod to use the port 443 only

The yaml definition of the aforementioned PSP would look like the below:

apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
name: restricted
spec:
allowPrivilegeEscalation: false
runAsUser:
rule: MustRunAsNonRoot
supplementalGroups:
rule: MustRunAs
ranges:
- min: 1000
max: 65535
privileged: false
hostNetwork: false
hostIPC: false
hostPID: false
hostPorts:
- min: 443
max: 443

Let's create Kubewarden policies to achieve the same goal. One thing that you need to know about Kubewarden policies is that every rule will be enforced by a separate policy. In our example, individual policies will be required for the enforcement of user and group configuration, host namespaces, privileged escalation, and for the privileged container configuration.

Let's start with blocking container escalation. For that you can deploy a policy as shown below:

$ kubectl apply -f - <<EOF
apiVersion: policies.kubewarden.io/v1alpha2
kind: ClusterAdmissionPolicy
metadata:
name: psp-allowprivilegeescalation
spec:
module: registry://ghcr.io/kubewarden/policies/allow-privilege-escalation-psp:v0.1.11
rules:
- apiGroups:
- ""
apiVersions:
- v1
resources:
- pods
operations:
- CREATE
- UPDATE
mutating: false
settings:
default_allow_privilege_escalation: false
EOF

If you notice, we have specified default_allow_privilege_escalation to assume a value false. Once this policy starts running, it will restrict pods trying to run with more privileges than the parent container by default.

$ kubectl apply -f - <<EOF
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- name: nginx
image: nginx
securityContext:
allowPrivilegeEscalation: true
- name: sidecar
image: sidecar
EOF
Error from server: error when creating "STDIN": admission webhook "clusterwide-psp-allowprivilegeescalation.kubewarden.admission" denied the request: one of the containers has privilege escalation enabled

Now, to enforce the user and groups configuration, you can use the user-group-psp policy

$ kubectl apply -f - <<EOF
apiVersion: policies.kubewarden.io/v1alpha2
kind: ClusterAdmissionPolicy
metadata:
name: psp-usergroup
spec:
module: registry://ghcr.io/kubewarden/policies/user-group-psp:v0.2.0
rules:
- apiGroups:
- ""
apiVersions:
- v1
resources:
- pods
operations:
- CREATE
- UPDATE
mutating: true
settings:
run_as_user:
rule: MustRunAsNonRoot
supplemental_groups:
rule: MustRunAs
ranges:
- min: 1000
max: 65535
EOF

Notice the policy is configured as mutation: true. This is required because the policy will add supplementalGroups when the user does not define them.

So, now users cannot deploy pods running as root:

$ kubectl apply -f - <<EOF
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- name: nginx
image: nginx
securityContext:
runAsNonRoot: false
runAsUser: 0
EOF
Error from server: error when creating "STDIN": admission webhook "clusterwide-psp-usergroup-fb836.kubewarden.admission" denied the request: RunAsNonRoot should be set to true
kubectl apply -f - <<EOF
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- name: nginx
image: nginx
securityContext:
runAsNonRoot: true
runAsUser: 0
EOF
Error from server: error when creating "STDIN": admission webhook "clusterwide-psp-usergroup-fb836.kubewarden.admission" denied the request: Invalid user ID: cannot run container with root ID (0)

Also, the example below also demonstrates the addition of a Supplemental group, despite it not being defined by us.

kubectl apply -f - <<EOF
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- name: nginx
image: nginx
EOF
pod/nginx created
$ kubectl get pods -o json nginx | jq ".spec.securityContext"
{
"supplementalGroups": [
10000
]
}

To replace the PSP configuration that blocks privileged containers, it's necessary to deploy the pod-privileged policy. This policy does not require any settings. Once running, it will block privileged pods.

$ kubectl apply -f - <<EOF
apiVersion: policies.kubewarden.io/v1alpha2
kind: ClusterAdmissionPolicy
metadata:
name: psp-privileged
spec:
module: registry://ghcr.io/kubewarden/policies/pod-privileged:v0.1.10
rules:
- apiGroups:
- ""
apiVersions:
- v1
resources:
- pods
operations:
- CREATE
- UPDATE
mutating: false
settings: null
EOF

To test the policy, we can try running a pod with privileged configuration enabled:

$ kubectl apply -f - <<EOF
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
securityContext:
privileged: true
- name: sleeping-sidecar
image: alpine
command: ["sleep", "1h"]
EOF
Error from server: error when creating "STDIN": admission webhook "clusterwide-psp-privileged.kubewarden.admission" denied the request: User 'system:admin' cannot schedule privileged containers

To complete the PSP migration exercise, it's necessary to disable host namespace sharing. To do that, we shall be using the host-namespace-psp policy. It allows the cluster administrator to block IPC, PID, and network namespaces individually and set the ports that the pods can be exposed on the host IP.

$ kubectl apply -f - <<EOF
apiVersion: policies.kubewarden.io/v1alpha2
kind: ClusterAdmissionPolicy
metadata:
name: psp-hostnamespaces
spec:
module: registry://ghcr.io/kubewarden/policies/host-namespaces-psp:v0.1.2
rules:
- apiGroups:
- ""
apiVersions:
- v1
resources:
- pods
operations:
- CREATE
- UPDATE
mutating: false
settings:
allow_host_ipc: false
allow_host_pid: false
allow_host_ports:
- min: 443
max: 443
allow_host_network: false
EOF

Again, let's validate the policy. The pod should not be able to share host namespaces:

$ kubectl apply -f - <<EOF
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
hostIPC: true
hostNetwork: false
hostPID: false
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
- name: sleeping-sidecar
image: alpine
command: ["sleep", "1h"]
EOF

Error from server: error when creating "STDIN": admission webhook "clusterwide-psp-hostnamespaces.kubewarden.admission" denied the request: Pod has IPC enabled, but this is not allowed
$ kubectl apply -f - <<EOF
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
hostIPC: false
hostNetwork: true
hostPID: false
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
- name: sleeping-sidecar
image: alpine
command: ["sleep", "1h"]
EOF
Error from server: error when creating "STDIN": admission webhook "clusterwide-psp-hostnamespaces.kubewarden.admission" denied the request: Pod has host network enabled, but this is not allowed
$ kubectl apply -f - <<EOF
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
hostIPC: false
hostNetwork: false
hostPID: true
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
- name: sleeping-sidecar
image: alpine
command: ["sleep", "1h"]
EOF
Error from server: error when creating "STDIN": admission webhook "clusterwide-psp-hostnamespaces.kubewarden.admission" denied the request: Pod has host PID enabled, but this is not allowed

The pod should be only able to expose the port 443 and should throw an error when other port numbers are configured against the hostPort section.

$ kubectl apply -f - <<EOF
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
hostPort: 80
- name: sleeping-sidecar
image: alpine
command: ["sleep", "1h"]
EOF
Error from server: error when creating "STDIN": admission webhook "clusterwide-psp-hostnamespaces.kubewarden.admission" denied the request: Pod is using unallowed host ports in containers

Mapping Kuberwarden policies to PSP fields

The following table show which Kubewarden policy can be used to replace each PSP configuration

PSP fieldKubewarden equivalent policy
privilegedpod-privileged-policy
hostPIDhost-namespaces-psp-policy
hostIPChost-namespaces-psp-policy
hostNetworkhost-namespaces-psp-polic
hostPortshost-namespaces-psp-policy
volumesvolumes-psp-policy
allowedHostPathshostpaths-psp-policy
readOnlyRootFilesystemreadonly-root-filesystem-psp-policy
fsgroupallowed-fsgroups-psp-policy
allowedFlexVolumesflexvolume-drivers-psp-policy
runAsUseruser-group-psp-policy
runAsGroupuser-group-psp-policy
supplementalGroupsuser-group-psp-policy
allowPrivilegeEscalationallow-privilege-escalation-psp-policy
defaultAllowPrivilegeEscalationallow-privilege-escalation-psp-policy
allowedCapabilitiescapabilities-psp-policy
defaultAddCapabilitiescapabilities-psp-policy
requiredDropCapabilitiescapabilities-psp-policy
seLinuxselinux-psp-policy
allowedProcMountTypesallowed-proc-mount-types-psp-policy
apparmorapparmor-psp-policy
seccompseccomp-psp-policy
forbiddenSysctlssysctl-psp-policy
allowedUnsafeSysctlssysctl-psp-policy