Skip to main content

Kubernetes Security: Pod Security Admission (PSA) - Overview, Enforce Pod Security Standard at a Namespace; Example Nginx Pod SecurityContext for Restricted PSS

725 words·
Kubernetes Kubectl
Kubernetes-Components - This article is part of a series.
Part 6: This Article

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=server Checks 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: true Enforces that the container must run as a non-root user

  • allowPrivilegeEscalation: false Prevents 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: RuntimeDefault Specifies 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/
Kubernetes-Components - This article is part of a series.
Part 6: This Article