Overview #
Kubernetes Cluster #
In this tutorial I’m using the following Kubernetes cluster:
NAME      STATUS   ROLES                  AGE   VERSION        INTERNAL-IP     EXTERNAL-IP   OS-IMAGE             KERNEL-VERSION     CONTAINER-RUNTIME
ubuntu1   Ready    control-plane,master   20d   v1.30.5+k3s1   192.168.30.10   <none>        Ubuntu 24.04.1 LTS   6.8.0-45-generic   containerd://1.7.21-k3s2
ubuntu2   Ready    worker                 20d   v1.30.5+k3s1   192.168.30.11   <none>        Ubuntu 24.04.1 LTS   6.8.0-45-generic   containerd://1.7.21-k3s2
ubuntu3   Ready    worker                 20d   v1.30.5+k3s1   192.168.30.12   <none>        Ubuntu 24.04.1 LTS   6.8.0-45-generic   containerd://1.7.21-k3s2
ubuntu4   Ready    worker                 20d   v1.30.5+k3s1   192.168.30.13   <none>        Ubuntu 24.04.1 LTS   6.8.0-45-generic   containerd://1.7.21-k3s2
Pod Security Admission Overview #
- PSA ensures that newly created pod comply with specific security rules.
The following security levels are available:
- 
Privileged: No restrictions, allows almost anything, including risky configurations. 
- 
Baseline: Prevents known risks while maintaining compatibility for most workloads. Provides a moderate level of security, allowing common use cases but restricting risky behaviors. 
- 
Restricted: Enforces strict security policies, disallowing almost all risky actions. 
Applying PSA at the Namespace Level #
Create an Example Namespace #
# Create a new namespace with the name "example-namespace"
kubectl create ns example-namespace
Dry-Run PSA #
# Enforce Pod Security Standard "Privileged"
kubectl label --dry-run=server --overwrite ns example-namespace pod-security.kubernetes.io/enforce=privileged
# Enforce Pod Security Standard "Baseline"
kubectl label --dry-run=server --overwrite ns example-namespace pod-security.kubernetes.io/enforce=baseline
# Enforce Pod Security Standard "Restricted"
kubectl label --dry-run=server --overwrite ns example-namespace pod-security.kubernetes.io/enforce=restricted
# Shell output:
namespace/example-namespace labeled (server dry run)
- --dry-run=serverChecks the command on the server-side. It sends the request to the API server to validate it against the actual state of the cluster, without making any changes.
Apply PSA #
# Enforce Pod Security Standard "Privileged"
kubectl label --overwrite ns example-namespace pod-security.kubernetes.io/enforce=privileged
# Enforce Pod Security Standard "Baseline"
kubectl label --overwrite ns example-namespace pod-security.kubernetes.io/enforce=baseline
# Enforce Pod Security Standard "Restricted"
kubectl label --overwrite ns example-namespace pod-security.kubernetes.io/enforce=restricted
# Shell output:
namespace/example-namespace labeled
Verify PSA Label of a Namespace #
# List labels of "example-namespace" namespace
kubectl get ns example-namespace --show-labels
# Shell output:
NAME                STATUS   AGE     LABELS
example-namespace   Active   5m47s   kubernetes.io/metadata.name=example-namespace,pod-security.kubernetes.io/enforce=restricted
Example Pod: Without Adopted Permissions #
# Create a pod manifest
vi example-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: nginx
  namespace: example-namespace
  labels:
    app: some-pod
spec:
  containers:
    - image: nginx:latest
      name: nginx
      ports:
        - containerPort: 80
# Deploy the pod
kubectl apply -f example-pod.yaml
# Shell output:
Error from server (Forbidden): error when creating "example-pod.yaml": pods "nginx" is forbidden: violates PodSecurity "restricted:latest": allowPrivilegeEscalation != false (container "nginx" must set securityContext.allowPrivilegeEscalation=false), unrestricted capabilities (container "nginx" must set securityContext.capabilities.drop=["ALL"]), runAsNonRoot != true (pod or container "nginx" must set securityContext.runAsNonRoot=true), seccompProfile (pod or container "nginx" must set securityContext.seccompProfile.type to "RuntimeDefault" or "Localhost")
Example Pod: Without Adopted Permissions #
# Create a pod manifest
vi example-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: nginx
  namespace: example-namespace
  labels:
    app: some-pod
spec:
  containers:
    - image: nginx:latest
      name: nginx
      ports:
        - containerPort: 80
      securityContext:
        runAsUser: 1000  # Define non-root user
        runAsGroup: 1000  # Define group
        runAsNonRoot: true  # Explicitly ensure non-root user
        allowPrivilegeEscalation: false  # Disable privilege escalation
        readOnlyRootFilesystem: true  # Read-only filesystem for security
        capabilities:
          drop:
            - ALL  # Drop all Linux capabilities
        seccompProfile:
          type: RuntimeDefault  # Use default seccomp profile
      volumeMounts:
        - name: nginx-cache
          mountPath: /var/cache/nginx  # Mount emptyDir at /var/cache/nginx
        - name: nginx-run
          mountPath: /var/run  # Mount emptyDir at /var/run
  volumes:
    - name: nginx-cache
      emptyDir: {}  # Temporary storage for NGINX cache
    - name: nginx-run
      emptyDir: {}  # Temporary storage for NGINX PID file
- 
runAsNonRoot: trueEnforces that the container must run as a non-root user
- 
allowPrivilegeEscalation: falsePrevents privilege escalation within the container / prevents processes in the container from gaining additional privilege. (No sudo to escalate privileges within the container)
- 
readOnlyRootFilesystem: true:Makes the root filesystem read-only.
- 
capabilities.drop: ["ALL"]Ensures no unnecessary Linux capabilities are granted to the container by default.
- 
seccompProfile.type: RuntimeDefaultSpecifies the default seccomp profile, which is recommended for hardened security. Restricts the system calls a process can make (such as reading a file or creating a network connection).
# Deploy the pod
kubectl apply -f example-pod.yaml
# Shell output:
pod/nginx created
Verify the Pod #
# List pods in "example-namespace"
kubectl get pods -n example-namespace
# Shell output:
NAME    READY   STATUS    RESTARTS   AGE
nginx   1/1     Running   0          4s
Test the Nginx output:
# Create a port forwarding for the Nginx pod
kubectl port-forward pod/nginx 8080:80 -n example-namespace
# Curl the Nginx pod (in a new shell session)
curl http://localhost:8080
Delete the Pod #
# Delete the Nginx pod
kubectl delete pod nginx -n example-namespace
Links #
# Official Documentation
https://kubernetes.io/docs/tutorials/security/cluster-level-pss/