KCSA — Security Contexts

6 minute read

What Are Security Contexts?

A Security Context defines privilege and access control settings for a pod or container. It’s the primary mechanism for configuring how a workload runs from a security perspective — user IDs, capabilities, filesystem access, and system call restrictions.

Security contexts are set directly in the pod spec. They are the building blocks that Pod Security Standards (PSS) evaluate.

Key Point: PSS/PSA defines the policy. Security contexts are the implementation — the actual fields you set on pods and containers.


Pod-Level vs Container-Level Security Context

Security context fields can be set at two levels. When both are set, the container-level setting takes precedence.

Aspect Pod Level (spec.securityContext) Container Level (spec.containers[].securityContext)
Scope Applies to all containers in the pod Applies to a specific container
Precedence Lower — acts as default Higher — overrides pod-level
Fields available runAsUser, runAsGroup, runAsNonRoot, fsGroup, seccompProfile, supplementalGroups, sysctls All pod-level fields + allowPrivilegeEscalation, privileged, readOnlyRootFilesystem, capabilities, procMount
When to use Set defaults for the entire pod Override for specific containers

YAML Structure

apiVersion: v1
kind: Pod
metadata:
  name: security-context-demo
spec:
  securityContext:          # Pod-level security context
    runAsNonRoot: true
    runAsUser: 1000
    runAsGroup: 3000
    fsGroup: 2000
    seccompProfile:
      type: RuntimeDefault
  containers:
  - name: app
    image: myapp:1.0
    securityContext:        # Container-level security context
      allowPrivilegeEscalation: false
      readOnlyRootFilesystem: true
      capabilities:
        drop: ["ALL"]

Key Security Context Fields

This is the most important reference table for the KCSA exam:

Field Level Description Recommended Value
runAsNonRoot Both Prevents running as root (UID 0) true
runAsUser Both Specifies the UID to run as > 1000
runAsGroup Both Specifies the primary GID > 1000
readOnlyRootFilesystem Container Makes the container filesystem read-only true
allowPrivilegeEscalation Container Prevents gaining more privileges than parent false
privileged Container Gives full host access (all capabilities + devices) false (never!)
capabilities Container Fine-grained Linux capabilities Drop ALL, add only needed
seccompProfile Both System call filtering profile RuntimeDefault
fsGroup Pod GID for all volumes mounted in the pod Set to app’s GID
procMount Container Type of proc mount (Default or Unmasked) Default

Field Deep Dives

runAsNonRoot

Ensures the container does not run as the root user (UID 0).

securityContext:
  runAsNonRoot: true
  • If the container image tries to run as root, the pod will not start
  • The container image must have a non-root USER defined, or you must set runAsUser
  • This is required by the Restricted PSS level

Why it matters: Running as root inside a container means root on the host if there’s a container escape vulnerability.

runAsUser

Specifies the exact UID the container process should run as:

securityContext:
  runAsUser: 1000
  • Overrides the USER directive in the Dockerfile
  • Use a UID > 1000 to avoid conflicts with system users
  • If set at the pod level, applies to all containers (unless overridden)

readOnlyRootFilesystem

Makes the container’s root filesystem read-only:

securityContext:
  readOnlyRootFilesystem: true
  • Prevents attackers from writing malicious scripts or modifying binaries inside the container
  • If the app needs to write files, use emptyDir volumes for temporary storage
  • This is a best practice for the Restricted level
# Complete example with writable temp directory
spec:
  containers:
  - name: app
    image: myapp:1.0
    securityContext:
      readOnlyRootFilesystem: true
    volumeMounts:
    - name: tmp
      mountPath: /tmp
  volumes:
  - name: tmp
    emptyDir: {}

allowPrivilegeEscalation

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

securityContext:
  allowPrivilegeEscalation: false
  • When true, setuid and setgid binaries can escalate privileges
  • Must be false under the Restricted PSS level
  • Set this to false unless the container specifically needs setuid/setgid

privileged

Grants the container full host access — equivalent to running on the host:

securityContext:
  privileged: true   # ⚠️ DANGEROUS — avoid unless absolutely necessary
  • The container gets ALL Linux capabilities
  • The container can access ALL host devices
  • The container can modify the host kernel via /proc and /sys
  • Only allowed under the Privileged PSS level
  • Use cases: CNI plugins, device plugins, node monitoring agents

Never use privileged: true unless the workload absolutely requires host-level access (e.g., system daemons). A compromised privileged container = a compromised host.


Linux Capabilities

Linux capabilities break down root’s monolithic power into smaller units. Instead of giving a container full root access, you can grant specific capabilities.

Common Capabilities

Capability What It Allows Risk Level
NET_BIND_SERVICE Bind to ports below 1024 Low
SYS_TIME Modify the system clock Medium
NET_RAW Use raw sockets (ping, packet crafting) Medium
SYS_ADMIN Broad administrative access (mount, namespace ops) Critical
SYS_PTRACE Debug/trace other processes High
NET_ADMIN Network configuration (iptables, routing) High
DAC_OVERRIDE Bypass file permission checks High

Best Practice: Drop ALL, Add Only What’s Needed

securityContext:
  capabilities:
    drop:
    - ALL
    add:
    - NET_BIND_SERVICE

Why drop ALL?

  • Docker containers start with a default set of capabilities (about 14)
  • Many of these are unnecessary for most applications
  • Dropping ALL and adding back only what’s needed follows the principle of least privilege
  • This is required by the Restricted PSS level

Default Container Capabilities (Docker/containerd)

These capabilities are granted by default to containers:

AUDIT_WRITE, CHOWN, DAC_OVERRIDE, FOWNER, FSETID, KILL,
MKNOD, NET_BIND_SERVICE, NET_RAW, SETFCAP, SETGID,
SETPCAP, SETUID, SYS_CHROOT

Under the Restricted PSS level, you must drop ALL of these and may only add back NET_BIND_SERVICE.


Seccomp Profiles

Seccomp (Secure Computing Mode) filters the system calls a container can make to the host kernel. It’s a critical defense-in-depth layer.

Profile Types

Profile Description Security Level
Unconfined No syscall filtering ❌ Least secure
RuntimeDefault Default profile from the container runtime ✅ Recommended
Localhost Custom profile from a file on the node ✅ Most customizable

YAML Configuration

securityContext:
  seccompProfile:
    type: RuntimeDefault

For a custom profile:

securityContext:
  seccompProfile:
    type: Localhost
    localhostProfile: profiles/my-seccomp-profile.json

What RuntimeDefault Blocks

The RuntimeDefault profile blocks approximately 40-60 dangerous syscalls including:

  • reboot — rebooting the host
  • mount / umount — mounting filesystems
  • swapon / swapoff — managing swap
  • kexec_load — loading a new kernel
  • bpf — Berkeley Packet Filter operations

Exam Tip: The Restricted PSS level requires either RuntimeDefault or Localhost seccomp profile. Unconfined is not allowed.


AppArmor and SELinux

While the KCSA exam focuses primarily on security contexts, capabilities, and seccomp, you should be aware of these additional Linux security modules:

AppArmor

  • Mandatory Access Control (MAC) system for Linux
  • Confines programs to a limited set of resources
  • Kubernetes supports AppArmor profiles via annotations (GA since Kubernetes 1.30)
  • Common on Ubuntu/Debian-based nodes
securityContext:
  appArmorProfile:
    type: RuntimeDefault

SELinux

  • Mandatory Access Control (MAC) system, common on RHEL/CentOS/Fedora
  • Uses labels to define access policies
  • Configured via seLinuxOptions in the security context
securityContext:
  seLinuxOptions:
    level: "s0:c123,c456"

For the KCSA exam: Know that AppArmor and SELinux exist as additional layers of defense. Focus your study on security context fields, capabilities, and seccomp.


Complete Hardened Pod Example

Here’s a pod spec that follows all security best practices:

apiVersion: v1
kind: Pod
metadata:
  name: hardened-pod
spec:
  securityContext:
    runAsNonRoot: true
    runAsUser: 10000
    runAsGroup: 10000
    fsGroup: 10000
    seccompProfile:
      type: RuntimeDefault
  automountServiceAccountToken: false
  containers:
  - name: app
    image: myapp:1.0
    securityContext:
      allowPrivilegeEscalation: false
      readOnlyRootFilesystem: true
      privileged: false
      capabilities:
        drop:
        - ALL
    resources:
      requests:
        cpu: "100m"
        memory: "64Mi"
      limits:
        cpu: "200m"
        memory: "128Mi"
    volumeMounts:
    - name: tmp
      mountPath: /tmp
  volumes:
  - name: tmp
    emptyDir:
      sizeLimit: "64Mi"

This pod:

  • ✅ Runs as non-root (UID 10000)
  • ✅ Has a read-only root filesystem
  • ✅ Cannot escalate privileges
  • ✅ Drops ALL capabilities
  • ✅ Uses RuntimeDefault seccomp profile
  • ✅ Does not mount the service account token
  • ✅ Has resource limits set
  • ✅ Uses emptyDir for temporary writes

Key Takeaways for the Exam

  1. Security contexts are set at two levels: pod-level (default for all containers) and container-level (overrides pod-level).

  2. Critical fields to remember: runAsNonRoot, readOnlyRootFilesystem, allowPrivilegeEscalation, capabilities, seccompProfile.

  3. Never use privileged: true unless the workload requires host-level access (kube-system components).

  4. Best practice for capabilities: Drop ALL, then add only what’s needed. Under Restricted PSS, only NET_BIND_SERVICE can be added.

  5. Seccomp profiles: RuntimeDefault is recommended. Unconfined means no syscall filtering. Localhost uses a custom profile.

  6. SYS_ADMIN is the most dangerous capability — it grants near-root access.

  7. Container-level security context takes precedence over pod-level when both are set.


Next: Network Policies — controlling pod-to-pod network traffic with firewall rules.

Updated: