CKA — Security Contexts

5 minute read

CKA Exam Domain

Cluster Architecture, Installation & Configuration — 25%

Security Contexts control privilege and access at the Pod and container level. While RBAC controls who can talk to the API server, Security Contexts control what containers can do on the node — which user they run as, what Linux capabilities they have, and whether they can escalate privileges.

What Are Security Contexts?

A Security Context defines privilege and access control settings for a Pod or container:

  • Which user/group the process runs as
  • Whether the container can escalate privileges
  • Whether the filesystem is read-only
  • Which Linux capabilities are added or dropped

Security Contexts apply the principle of least privilege — containers should only have the minimum permissions they need to function.

Pod-Level vs Container-Level

Security Contexts can be set at two levels. Container-level settings override Pod-level settings when both are specified.

Field Pod-Level Container-Level
runAsUser ✅ ✅ (overrides Pod)
runAsGroup ✅ ✅ (overrides Pod)
runAsNonRoot ✅ ✅ (overrides Pod)
fsGroup ✅ ❌
supplementalGroups ✅ ❌
readOnlyRootFilesystem ❌ ✅
allowPrivilegeEscalation ❌ ✅
capabilities ❌ ✅
privileged ❌ ✅
seccompProfile ✅ ✅ (overrides Pod)

Pod-level applies to all containers in the Pod. Container-level is specific to one container.

Common Security Context Fields

runAsUser and runAsGroup

Force the container process to run as a specific UID/GID:

securityContext:
  runAsUser: 1000
  runAsGroup: 3000
  • runAsUser: 1000 — Process runs as UID 1000 (not root)
  • runAsGroup: 3000 — Primary group is GID 3000

fsGroup

Sets the group ownership for all files in mounted volumes:

securityContext:
  fsGroup: 2000

All files created in volumes will be owned by GID 2000. This is a Pod-level only setting.

runAsNonRoot

Prevents the container from running as root (UID 0):

securityContext:
  runAsNonRoot: true

If the container image tries to run as root, Kubernetes will reject the Pod. This is a safety net — even if the image is configured to run as root, Kubernetes will block it.

readOnlyRootFilesystem

Makes the container’s root filesystem read-only:

securityContext:
  readOnlyRootFilesystem: true

The container can still write to mounted volumes (emptyDir, PVC, etc.), but cannot modify anything on the root filesystem. This prevents attackers from modifying binaries or writing malicious files.

allowPrivilegeEscalation

Controls whether a process can gain more privileges than its parent:

securityContext:
  allowPrivilegeEscalation: false

Set to false for security hardening. When false, the no_new_privs flag is set on the container process.

capabilities

Add or remove Linux capabilities:

securityContext:
  capabilities:
    add: ["NET_BIND_SERVICE"]
    drop: ["ALL"]

Common capabilities:

  • NET_BIND_SERVICE — Bind to ports below 1024
  • SYS_TIME — Modify system clock
  • NET_ADMIN — Network configuration
  • ALL — Drop all capabilities (recommended starting point)

Best practice: Drop ALL capabilities, then add back only what you need.

Full YAML Example

Here’s a Pod demonstrating both Pod-level and container-level Security Contexts:

apiVersion: v1
kind: Pod
metadata:
  name: secure-pod
  namespace: dev
spec:
  securityContext:
    runAsUser: 1000
    runAsGroup: 3000
    fsGroup: 2000
    runAsNonRoot: true
  containers:
  - name: app
    image: nginx
    securityContext:
      readOnlyRootFilesystem: true
      allowPrivilegeEscalation: false
      capabilities:
        drop: ["ALL"]
        add: ["NET_BIND_SERVICE"]
    volumeMounts:
    - name: tmp
      mountPath: /tmp
    - name: cache
      mountPath: /var/cache/nginx
    - name: run
      mountPath: /var/run
  volumes:
  - name: tmp
    emptyDir: {}
  - name: cache
    emptyDir: {}
  - name: run
    emptyDir: {}

What’s happening here:

  • Pod-level: Runs as UID 1000, GID 3000, non-root enforced
  • Container-level: Read-only root filesystem, no privilege escalation, only NET_BIND_SERVICE capability
  • Volumes: writable directories mounted via emptyDir (since root FS is read-only)

Pod Security Standards

Kubernetes defines three Pod Security Standards that describe different levels of restriction:

Standard Description Use Case
Privileged Unrestricted — allows everything System-level workloads (CNI, storage drivers)
Baseline Minimally restrictive — prevents known privilege escalation Standard workloads
Restricted Heavily restricted — follows hardening best practices Security-sensitive workloads

What Each Standard Restricts

Baseline restricts:

  • Privileged containers
  • Host namespaces (hostPID, hostIPC, hostNetwork)
  • Host ports
  • Certain volume types
  • Certain capabilities (beyond a safe set)

Restricted adds:

  • Must run as non-root
  • Must drop ALL capabilities
  • Seccomp profile must be RuntimeDefault or Localhost
  • No privilege escalation

Pod Security Admission

Pod Security Admission (PSA) enforces the Pod Security Standards at the namespace level using labels.

Modes

Mode Behavior
enforce Reject Pods that violate the standard
audit Allow but log violations
warn Allow but show a warning to the user

Namespace Labels

Apply PSA via namespace labels:

apiVersion: v1
kind: Namespace
metadata:
  name: secure-ns
  labels:
    pod-security.kubernetes.io/enforce: restricted
    pod-security.kubernetes.io/audit: restricted
    pod-security.kubernetes.io/warn: restricted

You can also set a version:

labels:
  pod-security.kubernetes.io/enforce: restricted
  pod-security.kubernetes.io/enforce-version: v1.28

Example: Apply baseline enforcement to a namespace:

kubectl label namespace dev pod-security.kubernetes.io/enforce=baseline

Now any Pod in the dev namespace that violates the baseline standard will be rejected.

Testing PSA

# Try creating a privileged Pod in a restricted namespace
kubectl run test --image=nginx --privileged -n secure-ns
# Error: pods "test" is forbidden: violates PodSecurity "restricted:latest"

kubectl Commands

# Check the security context of a running Pod
kubectl get pod secure-pod -o jsonpath='{.spec.securityContext}'

# Check container-level security context
kubectl get pod secure-pod -o jsonpath='{.spec.containers[0].securityContext}'

# Verify which user the container is running as
kubectl exec secure-pod -- id
# uid=1000 gid=3000 groups=2000

# Check if root filesystem is read-only
kubectl exec secure-pod -- touch /test-file
# touch: cannot touch '/test-file': Read-only file system

# Check Pod Security Admission labels on a namespace
kubectl get namespace secure-ns -o yaml | grep pod-security

# Apply Pod Security Standard to a namespace
kubectl label namespace dev pod-security.kubernetes.io/enforce=baseline

# Dry-run to check if a Pod would be admitted
kubectl apply -f pod.yaml --dry-run=server -n secure-ns

Practice Questions

Q1. Create a Pod named restricted-pod that runs as user ID 1000 and group ID 3000 with a read-only root filesystem.

Show Answer
apiVersion: v1
kind: Pod
metadata:
  name: restricted-pod
spec:
  securityContext:
    runAsUser: 1000
    runAsGroup: 3000
  containers:
  - name: app
    image: nginx
    securityContext:
      readOnlyRootFilesystem: true
    volumeMounts:
    - name: tmp
      mountPath: /tmp
  volumes:
  - name: tmp
    emptyDir: {}

Note: nginx needs a writable /tmp, so we mount an emptyDir there.

Q2. What is the difference between Pod-level and container-level security contexts?

Show Answer

Pod-level security context (under spec.securityContext) applies to all containers in the Pod. It supports fields like runAsUser, runAsGroup, fsGroup, and runAsNonRoot.

Container-level security context (under spec.containers[].securityContext) applies to a single container and overrides Pod-level settings where both are supported. It has additional fields like readOnlyRootFilesystem, capabilities, allowPrivilegeEscalation, and privileged.

If both set runAsUser, the container-level value wins.

Q3. How do you enforce the “restricted” Pod Security Standard in namespace prod?

Show Answer
kubectl label namespace prod pod-security.kubernetes.io/enforce=restricted

Or declaratively:

apiVersion: v1
kind: Namespace
metadata:
  name: prod
  labels:
    pod-security.kubernetes.io/enforce: restricted
    pod-security.kubernetes.io/warn: restricted

Q4. Create a Pod that drops ALL Linux capabilities and only adds NET_BIND_SERVICE.

Show Answer
apiVersion: v1
kind: Pod
metadata:
  name: minimal-caps
spec:
  containers:
  - name: app
    image: nginx
    securityContext:
      capabilities:
        drop: ["ALL"]
        add: ["NET_BIND_SERVICE"]

Q5. How do you verify that a running Pod’s process is not running as root?

Show Answer
kubectl exec <pod-name> -- id

Expected output for a non-root Pod:

uid=1000 gid=3000 groups=2000

If it shows uid=0(root), the container is running as root.

You can also check the security context:

kubectl get pod <pod-name> -o jsonpath='{.spec.securityContext.runAsUser}'

Updated: