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/