Cloud & Container Security · intermediate · ~11 min
**What you will learn** - Explain why the Docker socket (`/var/run/docker.sock`) is equivalent to root on the host, and how mounting or exposing it leads to full host takeover. - Identify the runtime flags that break container isolation — `--privileged`, host mounts (`-v /:/host`), shared namespaces (host PID/network), and dangerous Linux capabilities such as `CAP_SYS_ADMIN` — and choose safer, narrowly-scoped alternatives. - Describe how Kubernetes RBAC, service-account tokens, Pod Security Standards, network policies, and secret encryption combine into a least-privilege posture. - Read a Kubernetes RBAC `Role`/`RoleBinding` and a `securityContext`, and decide whether they are over-permissive. - Apply a defensive checklist to a container/cluster configuration and **verify each mitigation** in an authorized lab, then wire up detection/logging so abuse is visible.
Security objective. The asset you are protecting is the host kernel and node, the other containers/pods sharing it, and the cluster's secrets and API server. The threat is an attacker who gets code execution inside one container (say, through an application RCE) and tries to cross a boundary — escaping the container to own the node, then pivoting to own the whole cluster. What you will learn to detect and prevent is the class of runtime and orchestration misconfigurations that make that crossing easy.
When people think about container security, they usually picture a vulnerable image — an outdated library or a known CVE in a base layer. That matters, and you covered it in the prerequisite Docker images, containers, and Dockerfile security. But in real incidents, most container compromises and escapes come from the runtime configuration: how the container is launched and how the orchestrator is wired up, not what is inside the image.
The reason is structural. A container is not a virtual machine. It is a normal Linux process whose view of the system is narrowed by kernel features — namespaces (what it can see), cgroups (what it can use), capabilities (which privileged operations it can perform), and seccomp/AppArmor (which syscalls it can make). Every one of those narrowing features can be turned off at launch time by a flag. Turn enough of them off and the "container" is just a root process on the host wearing a thin disguise.
This lesson covers two layers. First, the single-host runtime: the Docker daemon, the socket that controls it, and the launch flags that weaken isolation — building directly on the Docker knowledge from the prereq. Second, Kubernetes, the orchestrator that schedules containers (grouped into pods) across many machines. Kubernetes adds its own attack surface: an API server that controls everything, RBAC that decides who may call which API, service-account tokens that pods carry automatically, and secrets that are only base64-encoded by default. The same idea runs through both layers — isolation and least privilege are the defaults you must protect, and a single misconfiguration can collapse them. By the end you should be able to look at a docker run command or a pod spec and spot the line that hands an attacker the host.
The core promise of containers is blast-radius containment: if one service is compromised, the damage stays inside that one container. Runtime misconfigurations break that promise. A web app with a remote-code-execution bug should give an attacker a shell in one container with one application's data. With an exposed Docker socket or a --privileged flag, that same bug gives the attacker root on the host — and therefore every other container on it.
In Kubernetes the stakes are higher because the unit of compromise is the cluster. An over-permissive RBAC rule or an over-scoped service-account token can let a single pod read every secret, create new privileged pods, or schedule workloads onto any node. That is the container-world equivalent of cloud IAM privilege escalation: one foothold becomes total control.
These are not theoretical. Exposed Docker sockets (often mounted so a CI runner or monitoring agent can "see" other containers), --privileged flags copied from a forum answer, and default-permissive RBAC are among the most common high-severity findings in container security reviews. They are common because they are convenient, and serious because they bypass every other control. Understanding them is what separates "I ran a scanner" from "I understand my attack surface." (And note: passing an automated scanner does not prove a cluster is secure — scanners miss logic-level RBAC escalation and context-specific mounts.)
Authorization and ethics. Everything here is defensive and for systems you own or are explicitly authorized to test. Container-escape and RBAC-abuse techniques are described only so you can find and fix them. Practice only on localhost, a disposable VM, a local kind/minikube cluster, or a deliberately vulnerable lab. Never test these against shared, production, or third-party infrastructure.
Authorization checklist (before any lab):
kind/minikube/VM you created).THREAT MODEL — container runtime + Kubernetes
ASSETS host kernel & filesystem, other containers/pods,
node credentials, K8s Secrets, the API server, etcd
ENTRY POINTS app vulnerability (RCE) in a pod
exposed docker.sock (mounted, or over TCP)
exposed API server / kubelet / etcd
an over-scoped service-account token
TRUST BOUNDARIES
[internet] =|= [ingress] =|= [pod/app] =|= [runtime] =|= [host kernel]
^ ^
RBAC / API boundary namespace + capability boundary
ATTACKER GOAL cross a boundary: escape container -> own node -> own cluster
Definition. /var/run/docker.sock is a Unix socket the Docker daemon (dockerd) listens on. The docker CLI is just a client that sends API requests to this socket. The daemon runs as root.
Plain explanation / how it works. Anything that can talk to the socket can tell the daemon to do anything the daemon can do — and the daemon is root. The classic move: ask the daemon to start a new container that bind-mounts the host's root filesystem (/) and runs a shell. Now the attacker reads and writes the host's files as root, from inside what was supposed to be an isolated container.
container (web app) HOST
+--------------------+ +---------------------------+
| compromised proc | writes API | dockerd (runs as ROOT) |
| /var/run/docker.sock -------------> create container w/ |
| (mounted in!) | request | -v /:/host -> host FS |
+--------------------+ +---------------------------+
| |
| "start a container mounting host /" v
+---------------------------------> attacker now root on host
When you might see it mounted (and the safer answer). Tools like CI runners, build agents, or monitoring sidecars sometimes mount the socket so they can manage other containers. Treat that as a red flag. Safer options: a rootless daemon, a socket-proxy that allows only specific read-only API calls, or building images with a daemonless tool like Buildah/Kaniko.
When-not. There is essentially no production workload that legitimately needs raw socket access mounted into an app container.
Pitfall. Membership in the host's docker group is the same risk on a plain server: it is effectively root, even without sudo.
Knowledge check. A teammate mounts /var/run/docker.sock into a container "just to list other containers." In one sentence, explain why that is equivalent to giving the container root on the host. (Which insecure assumption caused this? Which log would show the resulting privileged-container creation?)
Definition. Container isolation is built from kernel features that can be individually disabled at launch.
| Flag / option | What it does | Why it is dangerous |
|---|---|---|
--privileged |
Turns off almost all isolation (all capabilities, device access, relaxed seccomp/AppArmor) | Escape to host becomes near-trivial |
-v /:/host (or -v /etc:/etc) |
Bind-mounts host paths into the container | Direct read/write of host files |
--pid=host, --net=host |
Shares the host PID or network namespace | See/affect host processes; bypass network policy |
--cap-add=SYS_ADMIN |
Adds a powerful Linux capability | SYS_ADMIN is "the new root"; enables many escape paths |
--device=/dev/... |
Exposes host devices | Raw access to disks, kernel interfaces |
Capabilities, briefly. Root's power is split into ~40 capabilities (e.g., CAP_NET_BIND_SERVICE, CAP_SYS_ADMIN). Containers start with a reduced set. Adding CAP_SYS_ADMIN re-grants enough power (mount, namespace manipulation) that escape is usually feasible.
When to use these. Almost never in production. Legitimate uses exist (e.g., a short-lived debugging container, a storage driver that needs a specific device) but should be narrowly scoped, time-boxed, and reviewed — never a blanket --privileged.
Pitfall. People reach for --privileged to fix a permission error. It "works" because it disables the control that was protecting you. The fix is to add the one specific capability or device needed, not all of them.
Knowledge check (predict the outcome). A container is started with docker run --privileged -v /:/host .... An attacker gets code execution inside it. What can they now do to the host, and which single flag did the most damage? (Where is the trust boundary that was removed?)
Definition. A container escape is when a process inside a container gains access to the host (or another container) outside its intended boundary.
How it happens. Three broad routes: (a) misconfiguration — the socket/flags above hand it over directly; (b) excess privilege — capabilities or host mounts that let the process reach kernel or host resources; (c) kernel vulnerability — because all containers share the host kernel, a kernel bug can be exploited from inside a container. Routes (a) and (b) are configuration problems you can fix; route (c) is why patching the host kernel matters.
Pitfall. Assuming a container is a strong security boundary like a VM. It is a process boundary. Treat it as defense-in-depth, not a wall. If you need VM-strength isolation, use a sandboxed runtime (gVisor, Kata Containers) — but that is an addition, not a replacement for the hardening below.
Definition. Kubernetes runs pods across a cluster; everything goes through the API server. RBAC (Role-Based Access Control) decides which subjects (users, groups, service accounts) may perform which verbs (get, list, create, delete) on which resources (pods, secrets, …). A Role/ClusterRole lists permissions; a RoleBinding/ClusterRoleBinding grants them to a subject.
Service-account (SA) tokens. Every pod is associated with a service account, and by default a token for it is mounted into the pod at /var/run/secrets/kubernetes.io/serviceaccount/. Code in the pod can use that token to call the API. If the SA has broad RBAC, a compromised pod inherits it.
Pod --(SA token at /var/run/secrets/...)--> API server
| checks RBAC:
| RoleBinding -> Role -> verbs/resources
v
allowed? yes -> action no -> 403 Forbidden
The escalation. A pod whose SA can get secrets cluster-wide, or create pods, can often pivot to full cluster control (read all secrets; or create a privileged pod that mounts a node). This is the Kubernetes analogue of cloud IAM escalation.
When to scope down. Most pods need no API access at all. Set automountServiceAccountToken: false unless the workload genuinely calls the API, and grant the minimum verbs/resources, namespaced (Role) rather than cluster-wide (ClusterRole) whenever possible.
Pitfall. Using a wildcard rule (verbs: ["*"], resources: ["*"]) or binding to the default SA. Wildcards age badly and grant far more than intended.
Knowledge check (explain in your own words). Why does disabling automountServiceAccountToken reduce risk even when RBAC is configured correctly? (Which log — Kubernetes audit log — would reveal a compromised pod using its token against the API?)
Pod Security. Kubernetes Pod Security Standards define three levels — privileged, baseline, restricted. The restricted profile blocks privileged: true, host namespaces, hostPath volumes, and forces non-root, dropped capabilities, and read-only root filesystems. A pod's securityContext is where these are set; a namespace label enforces the standard at admission time.
Secrets. Kubernetes Secret objects are stored base64-encoded, which is encoding, not encryption — anyone who can read the object (or etcd) reads the value. Enable encryption at rest for secrets in etcd and restrict who can get/list them via RBAC. (Reminder that mirrors JWT confusion elsewhere: base64-decoding a value is not protecting it, just as decoding a JWT is not verifying it.)
Network policy. By default the cluster network is flat: any pod can reach any other pod. A NetworkPolicy restricts ingress/egress by label/namespace, so a compromised pod cannot freely scan and pivot.
No NetworkPolicy: With NetworkPolicy (default-deny):
podA <-> podB <-> podC podA -> [api] only; podB isolated;
(everyone talks to everyone) lateral movement blocked
Pitfall. Believing base64 secrets are "protected," or shipping a cluster with no network policies and assuming the firewall at the edge is enough — it does nothing for pod-to-pod traffic.
Knowledge check. A restricted-labelled namespace rejects a pod spec with privileged: true. Which asset did that admission control just protect, and why is rejecting at admission better than catching it in code review? (Why must all of this be exercised only in an authorized lab?)
These are configuration shapes, not a programming language. The key things to read are the flag, the securityContext, and the RBAC rule.
# Pod securityContext: the hardening lives here
securityContext:
runAsNonRoot: true # refuse to run as UID 0
allowPrivilegeEscalation: false # block setuid-based escalation
privileged: false # never disable isolation
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"] # start from zero, add back only what is needed
# add: ["NET_BIND_SERVICE"]
# RBAC Role: a permission is (apiGroups, resources, verbs). Narrow all three.
rules:
- apiGroups: [""] # "" = the core API group
resources: ["pods"] # NOT ["*"]
verbs: ["get", "list"] # NOT ["*"]
# The single most useful review command: ask the API what a subject can do.
kubectl auth can-i --list \
--as=system:serviceaccount:web:app-sa -n web
Reading rule of thumb: a "*" in verbs or resources, privileged: true, hostPath, host{PID,Network,IPC}: true, or a mounted docker.sock are the lines to flag first.
Beyond the image itself, the runtime configuration is where most container compromise and escape happens.
/var/run/docker.sock) is the channel to the Docker daemon. If it is mounted into a container or exposed over TCP, an attacker can control the daemon. The daemon runs as root, so socket access equals root on the host (for example, by starting a container that mounts /). This is the container version of docker-group privilege escalation.--privileged containers. This flag disables most isolation. Escaping to the host becomes near-trivial.-v /:/host, host network or PID namespaces, and added capabilities (such as CAP_SYS_ADMIN) all weaken the boundary. A capability is a fine-grained slice of root privilege.Kubernetes runs containers, grouped into pods, across a cluster of machines. Its security centers on:
/var/run/secrets/.... This is the Kubernetes equivalent of cloud IAM escalation.hostPath volumes can lead to compromise of the underlying node.--privileged.Below is a side-by-side of an insecure pod spec and a hardened one, plus the RBAC that should accompany the pod, and the checks that prove the hardening works. Use these only in a local, authorized lab (e.g., kind or minikube).
# WARNING: intentionally vulnerable — use only in a local, isolated, authorized
# lab. Do not deploy.
apiVersion: v1
kind: Pod
metadata:
name: insecure-app
spec:
containers:
- name: app
image: example/app:1.0
securityContext:
privileged: true # disables isolation
runAsUser: 0 # runs as root
volumeMounts:
- name: dockersock
mountPath: /var/run/docker.sock # socket = root on the node
- name: hostroot
mountPath: /host # full host filesystem inside the pod
hostPID: true # sees all host processes
volumes:
- name: dockersock
hostPath: { path: /var/run/docker.sock }
- name: hostroot
hostPath: { path: / }
# Hardened version: same app, isolation preserved.
apiVersion: v1
kind: Pod
metadata:
name: secure-app
spec:
serviceAccountName: app-sa
automountServiceAccountToken: false # app does not call the API
securityContext:
runAsNonRoot: true
seccompProfile: { type: RuntimeDefault }
containers:
- name: app
image: example/app:1.0
securityContext:
allowPrivilegeEscalation: false
privileged: false
readOnlyRootFilesystem: true
runAsUser: 10001
capabilities:
drop: ["ALL"]
# no docker.sock, no hostPath, no hostPID
# Least-privilege RBAC for a workload that genuinely must read one configmap.
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata: { namespace: web, name: read-app-config }
rules:
- apiGroups: [""]
resources: ["configmaps"]
resourceNames: ["app-config"] # only this one object
verbs: ["get"] # read only
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata: { namespace: web, name: bind-read-app-config }
subjects:
- kind: ServiceAccount
name: app-sa
namespace: web
roleRef:
kind: Role
name: read-app-config
apiGroup: rbac.authorization.k8s.io
# VERIFY the fix: bad input REJECTED, good input ACCEPTED.
# 1) Label the namespace to enforce the 'restricted' standard.
kubectl label ns web \
pod-security.kubernetes.io/enforce=restricted --overwrite
# 2) Bad input -> REJECTED at admission (this is the control working):
kubectl apply -n web -f insecure-app.yaml
# Error: violates PodSecurity "restricted": privileged, hostPath volumes,
# hostPID, runAsNonRoot != true ... (pod is NOT created)
# 3) Good input -> ACCEPTED:
kubectl apply -n web -f secure-app.yaml # pod/secure-app created
# 4) Prove least privilege actually holds:
kubectl auth can-i get secrets \
--as=system:serviceaccount:web:app-sa -n web # -> no
kubectl auth can-i get configmap/app-config \
--as=system:serviceaccount:web:app-sa -n web # -> yes
What it does. The insecure pod hands an attacker the node three different ways (privileged, docker.sock, host /). The hardened pod runs the same image as a non-root user with no extra power, no host access, and no API token. The RBAC grants exactly one verb on exactly one object. The verify block proves the insecure spec is rejected and the hardened spec is accepted, and that the SA can read only what it should.
Expected behavior. Applying the hardened pod under a restricted Pod Security namespace label succeeds; applying the insecure pod there is rejected by the admission controller with a message naming each violation. That rejection is the control working, not a failure.
Edge cases. readOnlyRootFilesystem: true breaks apps that write to disk — give them an emptyDir volume for their temp/cache paths instead of removing the control. runAsNonRoot: true fails if the image's default user is root; rebuild the image with a non-root USER (this ties back to the Dockerfile hardening in the prereq).
Walking the insecure-vs-hardened diff, line by line, in the order an attacker or reviewer would care about.
| Line in insecure spec | What it grants | Hardened replacement | Effect |
|---|---|---|---|
privileged: true |
All capabilities, device access, relaxed seccomp | privileged: false + capabilities.drop: [ALL] |
Isolation restored; escape paths closed |
runAsUser: 0 |
Process is root inside container | runAsNonRoot: true, runAsUser: 10001 |
A bug yields a non-root shell, not root |
mountPath: /var/run/docker.sock |
Control of the node's Docker/containerd daemon | (removed) | No daemon control from the pod |
hostPath: { path: / } -> /host |
Read/write the entire node filesystem | (removed) + readOnlyRootFilesystem: true |
Pod cannot touch host files; even its own FS is read-only |
hostPID: true |
View/signal every process on the node | (removed) | Pod sees only its own processes |
| (default token mount) | SA token usable against API server | automountServiceAccountToken: false |
A compromised pod has no API credential |
Trace of an attack on the insecure pod. (1) Attacker gets RCE in the app. (2) They read /host/etc/shadow directly — host files are mounted. (3) Or they write to /var/run/docker.sock to start a new privileged container, becoming root on the node. (4) From the node they reach other pods and, if a powerful SA token is present, the API server. Each step depends on a specific line above; remove the line and the step fails.
Trace on the hardened pod. (1) Attacker gets RCE in the app — as UID 10001, not root. (2) /host does not exist; the root FS is read-only; no docker.sock to talk to. (3) No SA token to call the API. (4) A default-deny NetworkPolicy (next section) limits which pods they can even reach. The foothold stays a foothold — exactly the blast-radius containment containers are supposed to give you.
Mistake 1 — Reaching for --privileged to fix a permission error.
Wrong:
docker run --privileged myapp # "now it works"
Why it is wrong: it works because it disables the very protection that was blocking you, opening every escape path. Recognize it by grepping configs for privileged. Corrected — add only the specific capability the app needs:
docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE myapp
Mistake 2 — Mounting the Docker socket into a helper container.
Wrong: -v /var/run/docker.sock:/var/run/docker.sock so a CI/monitoring container can "see" others. Why it is wrong: that container now has root on the host. Corrected: use a read-only socket proxy that allowlists only the needed API calls, or a daemonless builder (Kaniko/Buildah). Prevent it by policy: deny hostPath mounts of the socket in admission control.
Mistake 3 — Treating base64 secrets as encrypted.
Wrong: "Our secrets are in Kubernetes Secret objects, so they're safe." Why it is wrong: base64 is reversible in one command (base64 -d), and etcd stores them readable by default. Corrected: enable encryption at rest for secrets in etcd and lock down get/list secrets in RBAC. Recognize the gap by checking the EncryptionConfiguration on the API server.
Mistake 4 — Wildcard RBAC / binding to the default SA.
Wrong:
rules: [{ apiGroups: ["*"], resources: ["*"], verbs: ["*"] }]
Why it is wrong: any pod using that SA can do anything, including read every secret and create privileged pods. Corrected: narrow apiGroups, resources (and resourceNames), and verbs; create a dedicated SA per workload; set automountServiceAccountToken: false when no API access is needed. Prevent it by reviewing RBAC with kubectl auth can-i --list --as=system:serviceaccount:<ns>:<sa>.
Mistake 5 — No network policy. Assuming the edge firewall protects pod-to-pod traffic. It does not. Add a default-deny NetworkPolicy per namespace and allow only required flows.
Mistake 6 — "The scanner passed, so we're secure." Why it is wrong: image/CVE scanners do not evaluate RBAC escalation paths, mounted sockets in live pods, or over-scoped tokens. Corrected: treat scanners as one input; add policy-as-code, RBAC review, and mitigation-verification tests. Never claim a cluster is "completely secure" — claim specific controls are in place and verified.
"It only works with --privileged — how do I find the real requirement?" Drop the flag, run, and read the error. A failed mount, setns, or device-open points at a specific capability or device. Add back the one capability (e.g., --cap-add=...) or device and re-test. If nothing specific fails, you probably never needed it.
"My hardened pod won't start." Common causes and checks:
container has runAsNonRoot and image will run as root -> the image's USER is root; rebuild with a non-root user or set runAsUser.Read-only file system at runtime -> the app writes somewhere; mount an emptyDir at that path instead of dropping readOnlyRootFilesystem."Is this RBAC over-permissive?" Don't read YAML by eye alone — ask the API:
kubectl auth can-i --list \
--as=system:serviceaccount:web:app-sa -n web
If the output includes secrets [get list] cluster-wide, or pods [create], treat it as an escalation path.
"Did my mitigation actually take effect?" Verify, don't assume:
kubectl exec <pod> -- ls /var/run/secrets/kubernetes.io/serviceaccount should fail (no such directory).wget/nc with a short timeout) and confirm it times out.Questions to ask when it doesn't work: What capability/syscall actually failed (read the kernel/error message)? Is the failing path a write the app genuinely needs? Which RBAC subject is the request running as? Is the pod in a namespace with Pod Security enforcement enabled? Are the changes even applied to the running pod, or only to the YAML on disk?
Security & safety. This is a security topic, so the focus is the threat model, detection, and logging rather than memory bugs.
DETECTION VIEW — where boundary crossings become visible
Kubernetes audit log: who (subject) did which verb on which resource, and the
decision (allow/deny). Turn it on; it is your primary
evidence for RBAC abuse and secret access.
Runtime/host: process, syscall, and mount events inside pods (e.g. via
Falco or the kernel audit subsystem) reveal escapes.
Defensive practices, in priority order.
privileged: false, allowPrivilegeEscalation: false, drop: [ALL] capabilities, readOnlyRootFilesystem, RuntimeDefault seccomp. Add power back only with evidence.restricted) by namespace so bad specs are rejected at admission, not in review.automountServiceAccountToken: false by default.get/list secrets; never bake secrets into images or logs.NetworkPolicy, then allowlist required flows.What to LOG (per security-relevant event): timestamp, source (subject / pod / node / IP), resource acted on (object name/namespace), verb/action, the security decision (allow/deny, admission accept/reject), and a correlation/request id to tie events together.
What to NEVER log: service-account or bearer token values, Secret contents, passwords, private keys, session cookies, or unneeded PII. Log a reference (object name) instead of the value.
Events that signal abuse: pod creation with privileged: true, hostPath, or host namespaces; kubectl exec into pods; get/list secrets from an unexpected subject; new or widened RBAC bindings; a service account suddenly calling the API from a workload that never did before.
False positives. Legitimate operators do exec into pods and read secrets; CI may create pods; a new deploy may add an expected RBAC binding. Tune alerts with an allowlist of known automation subjects and change windows, and correlate with change tickets — otherwise alert fatigue hides the real event.
Where this shows up. Practically every cloud-native platform runs containers under an orchestrator: managed Kubernetes (EKS/GKE/AKS), CI/CD runners that build and test in containers, monitoring/agent sidecars, and edge/IoT fleets. The exact misconfigurations in this lesson — mounted Docker sockets in CI runners, --privileged debugging containers left in production, default-permissive RBAC, base64-only secrets, and flat pod networks — are the recurring high-severity findings in real container security assessments and the subject of major CNCF/Kubernetes hardening guidance.
Concrete authorized example. In an authorized review of a client's staging cluster, a reviewer finds a monitoring agent given the Docker socket so it can list containers. They demonstrate the risk only in that isolated staging environment: an RCE in an unrelated web pod on the same node could reach that socket and start a privileged container mounting the node's filesystem — full node compromise. The remediation: the agent uses the read-only kubelet/metrics API, not the socket, and Pod Security forbids privileged pods cluster-wide. The reviewer then retests to confirm the socket is gone and the privileged pod is rejected.
Professional best-practice habits.
Beginner rules:
--privileged as a fix.Secret objects as encoded, not encrypted.Advanced practices:
Do these only in a local, isolated, authorized lab (a disposable VM, kind, or minikube). Do not target shared or third-party systems.
Authorization check first: confirm the cluster is one you created locally, it has no production data, and you can delete it afterward.
Beginner 1 — Spot the dangerous lines. Given the insecure pod spec in this lesson, list every line that weakens isolation and write one sentence per line explaining the risk. Objective: recognize runtime red flags. Output: a bullet list mapping line -> risk. Concepts: privileged, hostPath, hostPID, docker.sock. Hint: there are at least four.
Beginner 2 — Harden a docker run. Start from docker run --privileged -v /:/host myimage. Rewrite it to run as a non-root user, drop all capabilities, and remove host access, adding back only NET_BIND_SERVICE (assume the app must bind port 80). Output: the corrected command. Constraint: no --privileged, no host mounts. Hint: --user, --cap-drop=ALL, --cap-add=, --read-only.
Intermediate 1 — Least-privilege RBAC + proof. Write a Role + RoleBinding (namespace web, service account reporter-sa) that allows only get and list on pods in web — nothing else. Then give the exact kubectl auth can-i commands that prove (a) the SA can list pods and (b) the SA cannot read secrets. Concepts: RBAC verbs/resources, mitigation verification.
Intermediate 2 — Default-deny network policy. For namespace web, write a NetworkPolicy that denies all ingress by default, then a second policy that allows ingress to pods labeled app: api only from pods labeled app: frontend. Output: two YAML manifests. Hint: an empty podSelector with policyTypes: [Ingress] and no ingress rules is the default-deny.
Challenge — Mitigation verification report (defensive conclusion). Take the hardened pod + RBAC + network policy from your earlier tasks and write a short test plan that verifies each control and states the expected result meaning "mitigation works": (a) confirm the SA token is absent inside the pod, (b) confirm the pod's SA cannot create a privileged pod, (c) confirm a blocked pod-to-pod connection times out, (d) confirm a Secret value is encrypted at rest (describe how you'd inspect etcd). For each control, also name the detection/logging you would add (which audit event, what fields to log, what to never log). Constraint: lab-only, no exploitation of third-party systems. Concepts: everything in this lesson, plus verification-first thinking.
Lab cleanup / reset. When done: kubectl delete pod insecure-app secure-app -n web --ignore-not-found, delete the Role/RoleBinding/NetworkPolicies you created, and tear down the whole cluster (kind delete cluster or minikube delete) so no hardened/insecure remnants linger. Recreate fresh for the next exercise.
/var/run/docker.sock into a container or expose the daemon over TCP; treat docker-group membership the same way.--privileged, host mounts (-v /:/host), shared namespaces (--pid=host/--net=host), and capabilities like CAP_SYS_ADMIN each break isolation. Default to non-root, drop: [ALL], privileged: false, readOnlyRootFilesystem; add power back only with evidence.automountServiceAccountToken: false.get/list secrets.NetworkPolicy and allowlist required flows.restricted) reject insecure specs at admission. Verify every mitigation (kubectl auth can-i, token-absence checks, blocked-connection timeouts), and audit-log privileged-pod creation, secret access, and RBAC changes — logging subject/verb/resource/decision, never the secrets themselves.kubectl auth can-i --list --as=system:serviceaccount:<ns>:<sa>, namespace label pod-security.kubernetes.io/enforce=restricted.