Security Contexts, Pod Security, and RBAC
What This Concept Is
Security in Kubernetes is two orthogonal questions:
- Workload isolation. What can the process inside the container do on the node? Controlled by the Pod/container
securityContextand by cluster policy (Pod Security Standards, admission webhooks). - API authorization. What can a user or ServiceAccount do through the api-server? Controlled by RBAC.
Pod-side levers (securityContext, per-Pod and per-container):
| Field | Effect |
|---|---|
runAsUser / runAsGroup | Override the UID/GID the process runs as |
runAsNonRoot: true | Refuse to start if effective UID is 0 |
readOnlyRootFilesystem: true | Mount / as read-only |
allowPrivilegeEscalation: false | Block setuid escalation inside |
capabilities.drop: ["ALL"] | Drop all Linux capabilities; add back only what's needed |
seccompProfile.type: RuntimeDefault | Apply the runtime's seccomp filter |
privileged: true | Disable almost all isolation -- almost never correct |
Pod Security Standards (PSS) are three preset levels -- privileged, baseline, restricted -- that admission controllers can enforce per namespace. The built-in Pod Security Admission controller reads a namespace label like pod-security.kubernetes.io/enforce: restricted and rejects non-compliant Pods.
RBAC has four types:
Role-- namespaced set of permissions (verbs × resources).ClusterRole-- cluster-scoped (or aggregated across namespaces).RoleBinding-- grants a Role to a subject within a namespace.ClusterRoleBinding-- grants a ClusterRole cluster-wide.
Subjects are users, groups, or ServiceAccounts. Every Pod runs as a ServiceAccount (default if unspecified), which determines what it can do through the api-server.
Why It Matters Here
A production cluster has to answer:
- "If this container is compromised, what can the attacker reach on the node? On the API?"
- "Can this CI pipeline's ServiceAccount delete arbitrary workloads?"
- "Does this namespace's workloads run as root?"
Without PSS, RBAC, and securityContexts, the default answers are "a lot," "yes," and "yes."
Concrete Example
A hardened Pod:
apiVersion: v1
kind: Pod
metadata:
name: hardened
spec:
serviceAccountName: web-sa
securityContext:
runAsNonRoot: true
runAsUser: 1000
seccompProfile: { type: RuntimeDefault }
containers:
- name: app
image: myorg/app:1.2
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities: { drop: ["ALL"] }
volumeMounts:
- { name: tmp, mountPath: /tmp }
volumes:
- { name: tmp, emptyDir: {} }
A minimal Role + RoleBinding for a CI ServiceAccount that is only allowed to apply Deployments and Services in prod:
apiVersion: v1
kind: ServiceAccount
metadata: { name: ci-deployer, namespace: prod }
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata: { name: ci-deployer, namespace: prod }
rules:
- apiGroups: ["apps"]
resources: ["deployments", "deployments/scale"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
- apiGroups: [""]
resources: ["services", "configmaps"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata: { name: ci-deployer, namespace: prod }
subjects:
- kind: ServiceAccount
name: ci-deployer
namespace: prod
roleRef:
kind: Role
name: ci-deployer
apiGroup: rbac.authorization.k8s.io
Namespace labeled for PSS:
apiVersion: v1
kind: Namespace
metadata:
name: prod
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/enforce-version: latest
Common Confusion / Misconception
"runAsNonRoot is enough."
Running as UID 1000 inside the container is necessary but not sufficient. Without dropping capabilities, the process may still have CAP_NET_ADMIN or others; without readOnlyRootFilesystem, it can still write to /etc; without a seccomp profile, it can still call risky syscalls; without allowPrivilegeEscalation: false, a setuid binary can escalate. Hardening is the combination of these controls.
A second confusion: "RBAC denies by default, so I'm safe." It does deny by default, but a common mistake is to grant the built-in cluster-admin ClusterRole to a broader group than intended, or to bind the default ServiceAccount so that every Pod in a namespace inherits dangerous permissions. Audit RoleBindings and ClusterRoleBindings, not just Roles.
A third confusion: "Pod Security Policy (PSP) is still a thing." PSP was removed in Kubernetes 1.25. Its replacement is Pod Security Admission with the PSS levels above, plus external policy engines (OPA/Gatekeeper, Kyverno) for anything PSS does not cover.
How To Use It
Baseline for any new workload:
- Namespace enforces
baselineorrestrictedPSS. - Pod runs as non-root, drops all capabilities, read-only root FS.
- Pod uses a dedicated ServiceAccount with a scoped Role -- never the
defaultServiceAccount for anything non-trivial. - Nothing is
privileged: trueunless there is a named exception with review.
Check Yourself
- What is the difference between what a
securityContextcontrols and what RBAC controls? - Why is
runAsNonRoot: truenot sufficient by itself? - What replaced Pod Security Policy, and how is it configured per namespace?
Mini Drill or Application
Label a namespace with pod-security.kubernetes.io/enforce: restricted. Try to apply a Pod with privileged: true. Record the admission error. Now modify the Pod to pass the restricted profile (non-root, drop capabilities, seccomp RuntimeDefault, read-only root FS). Re-apply. Write a paragraph matching each admission rule to a field you set.
Policy Beyond PSS: OPA/Gatekeeper and Kyverno
Pod Security Admission handles the three PSS levels. Real clusters always want more: "images must come from our registry," "every workload must have a team label," "no Service may be type: LoadBalancer in this namespace." Two projects fill that gap:
- OPA/Gatekeeper -- Rego-based policies enforced as admission webhooks. Mature, expressive, but Rego has a learning curve.
- Kyverno -- YAML-native policies that read like Kubernetes manifests. Often easier to start with; supports validation, mutation, and generation.
Both are also used for auditing -- dry-run a policy, list all existing violations, fix drift, then switch to enforce. This is the fitness-function pattern from S7 M5 applied to cluster policy: write the rule once, run it on every PR and every admission.
Read This Only If Stuck
- Linux Command Line: Reading, writing, and executing -- UID/GID, setuid, and file mode; every
securityContextfield expresses these. - Linux Command Line: Owners, group members, and everybody else -- the model
runAsUserandfsGroupoperate in. - Linux Command Line:
sudoandchgrp-- capability semantics you grant or drop inside containers. - Kubernetes: Pod Security Standards -- the three PSS levels and what each forbids.
- Kubernetes: Pod Security Admission -- how namespace labels drive
warn/audit/enforce. - Kubernetes: Configure a Security Context for a Pod or Container -- hands-on field reference.
- Kubernetes: RBAC Authorization -- Roles, Bindings, aggregation, and subject kinds.
- Kubernetes: Authorization overview -- how RBAC fits next to Node/ABAC/Webhook authorizers.
- Kubernetes: Controlling Access to the API -- end-to-end request path and authorization chain.
- OPA Gatekeeper documentation -- policy-as-code via Rego and ConstraintTemplates.
- Kyverno documentation -- YAML-native policies; validate, mutate, generate, and cleanup.