From a Compromised Pod to Enterprise‑Grade Kubernetes Security: A Deep‑Defense Playbook
This article walks through a real‑world pod compromise, breaks down the six‑layer Kubernetes attack surface, and presents a step‑by‑step, enterprise‑grade defense framework—including supply‑chain hardening, identity isolation, network segmentation, secret management, runtime detection, and automated response—to build a verifiable, scalable, and continuously enforceable security posture.
1. Real‑world attack chain
At 3 am a monitoring alert shows a production order‑service pod establishing a persistent TLS connection to an unknown public IP, the node becomes NotReady, and the security team discovers that the attacker has read several Secrets and is enumerating other workloads. The chain consists of:
Exploiting an unpatched Java dependency inside the container.
Running as root with extra toolchains that enable payload download and persistence.
Auto‑mounted high‑privilege ServiceAccount token used to call the API server.
Overly permissive RBAC allowing read of ConfigMaps, Secrets, and pod information.
Cluster‑wide default network connectivity enabling lateral scans of Redis, MySQL, Kafka, and internal admin interfaces.
Static Secrets stored in etcd without rotation, amplifying data loss.
The key insight is that the security boundary is not the pod or node but a dynamic boundary formed by many weak constraints; any single failure can let the attack spread.
2. Correct security model – six layers
Supply‑chain layer : source code, dependencies, images, artifacts – protect with SBOM, vulnerability scanning, image signing, and admission verification.
Cluster control layer : API server, etcd, scheduler, controller – protect with API audit, RBAC, etcd encryption, and control‑plane isolation.
Workload layer : pod, container, ServiceAccount – protect with PSA, securityContext, and least‑privilege ServiceAccounts.
Network communication layer : east‑west, north‑south, DNS, service mesh – protect with NetworkPolicy, mTLS, and L7 authorization.
Data & key layer : Secrets, config, DB credentials – protect with external secret managers, dynamic credentials, and rotation.
Runtime & response layer : processes, syscalls, anomalous connections – protect with eBPF monitoring, anomaly detection, and automatic isolation.
These can be abstracted into a three‑line “defense in depth” model:
First line – Prevention : block insecure images, disallow non‑compliant pods, enforce least‑privilege identities.
Second line – Limitation : assume something will slip through and restrict what it can do (default‑deny network, read‑only root filesystem, minimal permissions).
Third line – Detection & Response : quickly detect anomalies (Falco, eBPF) and cut off the workload (automatic isolation, credential revocation).
3. Detailed defensive layers
3.1 Workload hardening – Pod Security Admission (PSA)
Since Kubernetes 1.25 the legacy PodSecurityPolicy is removed. PSA defines three levels: privileged, baseline, and restricted. The recommended rollout is:
Core system namespaces – whitelist specific pods.
Regular business namespaces – enforce restricted.
Legacy workloads – start with audit + warn, then switch to enforce.
Example namespace configuration:
apiVersion: v1
kind: Namespace
metadata:
name: prod-order
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/enforce-version: latest
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/warn: restricted3.2 Production‑grade SecurityContext
A realistic deployment template that enforces non‑root execution, read‑only root filesystem, seccomp profile, and disables token auto‑mount:
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-service
namespace: prod-order
spec:
replicas: 6
selector:
matchLabels:
app: order-service
template:
metadata:
labels:
app: order-service
spec:
serviceAccountName: order-service-sa
automountServiceAccountToken: false
securityContext:
runAsNonRoot: true
runAsUser: 10001
runAsGroup: 10001
fsGroup: 10001
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: registry.example.com/order-service:2.3.7
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8080
env:
- name: JAVA_TOOL_OPTIONS
value: "-XX:MaxRAMPercentage=75 -Djava.security.egd=file:/dev/urandom"
volumeMounts:
- name: tmp
mountPath: /tmp
- name: logs
mountPath: /app/logs
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
resources:
requests:
cpu: "500m"
memory: "1024Mi"
limits:
cpu: "2"
memory: "2048Mi"
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 15
periodSeconds: 5
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
volumes:
- name: tmp
emptyDir: {}
- name: logs
emptyDir: {}Key take‑aways: readOnlyRootFilesystem: true may break some Java apps; mount a writable /tmp if needed.
Log frameworks should write to stdout or a dedicated writable volume.
Enforcing seccompProfile: RuntimeDefault limits system calls.
Disabling token auto‑mount prevents silent credential exposure.
3.3 Image hardening & supply‑chain security
Recommendations:
Base images should be distroless or alpine with no shells, package managers, or debugging tools.
Fix UID/GID, avoid creating users at runtime.
Use multi‑stage builds to shrink layers.
Explicitly set ENTRYPOINT to avoid long execution chains.
Example Dockerfile for a Java service:
FROM maven:3.9.9-eclipse-temurin-21 AS builder
WORKDIR /workspace
COPY pom.xml .
COPY src ./src
RUN mvn -B -DskipTests clean package
FROM gcr.io/distroless/java21-debian12:nonroot
WORKDIR /app
COPY --from=builder /workspace/target/order-service.jar /app/order-service.jar
EXPOSE 8080
ENTRYPOINT ["java","-jar","/app/order-service.jar"]3.4 Identity isolation – ServiceAccount & RBAC
Principles:
Each workload gets its own ServiceAccount.
Do not mount the token unless the pod needs to call the API.
Scope permissions to specific resources, namespaces, verbs, and optionally resource names.
Example ServiceAccount and Role:
apiVersion: v1
kind: ServiceAccount
metadata:
name: order-service-sa
namespace: prod-order
automountServiceAccountToken: false
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: order-service-role
namespace: prod-order
rules:
- apiGroups: [""]
resources: ["configmaps"]
resourceNames: ["order-service-config"]
verbs: ["get"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: order-service-rb
namespace: prod-order
subjects:
- kind: ServiceAccount
name: order-service-sa
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: order-service-roleFor API access, use short‑lived projected ServiceAccount tokens:
apiVersion: v1
kind: Pod
metadata:
name: policy-agent
namespace: platform-security
spec:
serviceAccountName: policy-agent-sa
automountServiceAccountToken: false
containers:
- name: agent
image: registry.example.com/policy-agent:1.0.0
volumeMounts:
- name: kube-api-token
mountPath: /var/run/secrets/tokens
readOnly: true
volumes:
- name: kube-api-token
projected:
sources:
- serviceAccountToken:
path: token
expirationSeconds: 3600
audience: kubernetes.default.svc3.5 Network segmentation – default‑deny NetworkPolicy
Start with a namespace‑wide default deny policy:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny
namespace: prod-order
spec:
podSelector: {}
policyTypes:
- Ingress
- EgressThen add business‑specific allow rules. Example for an e‑commerce order service:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: order-service-policy
namespace: prod-order
spec:
podSelector:
matchLabels:
app: order-service
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: prod-gateway
podSelector:
matchLabels:
app: api-gateway
ports:
- protocol: TCP
port: 8080
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: prod-order
podSelector:
matchLabels:
app: order-db
ports:
- protocol: TCP
port: 5432
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: prod-inventory
podSelector:
matchLabels:
app: inventory-service
ports:
- protocol: TCP
port: 8080
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: middleware
podSelector:
matchLabels:
app: kafka
ports:
- protocol: TCP
port: 9092
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53For large clusters, generate policies automatically from a service‑dependency graph, store them in GitOps, and validate with tools like Cilium/Hubble.
3.6 Secret management – external secret stores
Kubernetes Secrets are only a delivery mechanism. For production, move credentials to external secret managers (Vault, AWS Secrets Manager, GCP Secret Manager, Azure Key Vault) and use short‑lived, workload‑bound tokens.
Typical Vault workflow:
Pod runs with its own ServiceAccount.
Vault Kubernetes auth validates the ServiceAccount.
Vault issues a dynamic PostgreSQL credential with a TTL (e.g., 30 min).
Application reads the credential via a sidecar injector.
Vault injector example:
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-service
namespace: prod-order
spec:
template:
metadata:
annotations:
vault.hashicorp.com/agent-inject: "true"
vault.hashicorp.com/role: "order-service-role"
vault.hashicorp.com/agent-inject-secret-db-creds: "database/creds/order-service"
vault.hashicorp.com/agent-inject-template-db-creds: |
{{- with secret "database/creds/order-service" -}}
spring.datasource.username={{ .Data.username }}
spring.datasource.password={{ .Data.password }}
{{- end }}
spec:
serviceAccountName: order-service-sa
containers:
- name: app
image: registry.example.com/order-service:2.3.7
env:
- name: SPRING_CONFIG_IMPORT
value: "optional:file:/vault/secrets/db-creds"Advantages: credentials are short‑lived, bound to the workload identity, and fully auditable.
3.7 Control‑plane hardening
Enable audit logging and forward logs to a central system.
Disable anonymous access.
Restrict the system:masters group.
Enable API Priority and Fairness (APF) to protect the control plane from request storms.
Encrypt secrets at rest with a static key and rewrite existing objects.
Enforce mTLS between API server and etcd.
Keep etcd off the public internet and isolate it on a dedicated network.
Sample etcd encryption configuration (only the relevant snippet):
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
- configmaps
providers:
- aescbc:
keys:
- name: key1
secret: BASE64_ENCODED_32_BYTE_KEY
- identity: {}3.8 Runtime detection & automated response
Falco remains valuable because many malicious actions happen at the syscall level. Example rule detecting an interactive shell spawned from a Java process:
- rule: Unexpected shell in java container
desc: Detect an interactive shell spawned from a Java process inside a container
condition: >
spawned_process and container and
proc.name in (bash, sh, zsh) and
proc.pname in (java, java11, java17, java21)
output: Shell spawned from Java process (user=%user.name container=%container.name pod=%k8s.pod.name namespace=%k8s.ns.name shell=%proc.name parent=%proc.pname cmd=%proc.cmdline)
priority: CRITICALAutomated response chain:
Falco emits an alert.
Alert is sent to SIEM / Alertmanager.
Automation tags the offending pod with security-state=quarantine.
A NetworkPolicy that selects the quarantine label drops all ingress and egress.
Optionally revoke Vault credentials or cloud IAM tokens.
Notify on‑call engineers and create a ticket.
Quarantine NetworkPolicy:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: quarantine
namespace: prod-order
spec:
podSelector:
matchLabels:
security-state: quarantine
policyTypes:
- Ingress
- Egress4. End‑to‑end case study – securing the prod-order namespace
The article walks through a four‑week transformation:
Week 1 – Identity & baseline : create dedicated ServiceAccounts, disable token auto‑mount, switch namespace to PSA restricted, remove privileged, hostPath, and root containers.
Week 2 – Network isolation : apply default‑deny NetworkPolicy, generate allow rules based on observed traffic, restrict egress to DNS, Kafka, DB, and required third‑party APIs.
Week 3 – Secrets & supply‑chain : migrate DB credentials to Vault dynamic accounts, add SBOM generation, vulnerability scanning, and image signing in CI; enforce admission checks for signatures and disallow latest tags.
Week 4 – Detection & response : deploy Falco with critical rules, collect API audit logs, implement automatic quarantine workflow.
Post‑remediation benefits include limited lateral movement, short‑lived credential windows, blocked untrusted images, and minute‑level isolation of anomalous pods.
5. Roadmap for large‑scale environments
Baseline : PSA baseline → restricted, disable default ServiceAccount tokens, universal image scanning, default‑deny NetworkPolicy, enable API audit.
Critical gaps : enforce non‑root containers, least‑privilege RBAC, external secret managers, image signing, mTLS for critical services.
Close the loop : integrate runtime detection with a central event bus, automate isolation and credential revocation, automate RBAC pruning, generate network dependency graphs for policy back‑fill.
Zero‑trust : unified workload identity (SPIFFE/SPIRE), full L7 authorization, multi‑cluster policy governance, regular security chaos engineering.
Key governance practices: policy‑as‑code with GitOps, staged rollout (audit → enforce), environment‑specific parameterization, periodic baseline scans, and continuous security drills.
6. Pre‑deployment checklist
All pods define a securityContext and avoid privileged, hostNetwork, and unnecessary hostPath.
Run containers as non‑root and disable automatic ServiceAccount token mounting.
Each service has a dedicated ServiceAccount with minimal RBAC; no wildcard * verbs on high‑privilege resources.
Production namespaces enforce default‑deny NetworkPolicy and only allow required dependencies (DNS, monitoring, logging, DB, Kafka, etc.).
High‑value secrets are stored in external secret managers with dynamic credentials and rotation.
etcd encryption is enabled and existing data has been rewritten.
CI pipelines generate SBOMs, run vulnerability scans, sign images, and reject latest tags.
API audit logging is active; runtime anomaly detection (Falco/eBPF) is deployed.
Automated isolation and credential revocation mechanisms are in place; regular security drills are scheduled.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Cloud Architecture
Focuses on cloud‑native and distributed architecture engineering, sharing practical solutions and lessons learned. Covers microservice governance, Kubernetes, observability, and stability engineering to help your systems run stable, fast, and cost‑effectively.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
