Opening Framing
Containers have revolutionized application deployment with their portability, efficiency, and speed. Kubernetes has become the de facto standard for orchestrating containers at scale. But the speed and convenience that makes containers attractive also creates security challenges: images pulled from public registries may contain vulnerabilities, misconfigured containers can expose sensitive data, and Kubernetes itself presents a complex attack surface.
Container security spans the entire lifecycle: securing the build pipeline, hardening base images, scanning for vulnerabilities, configuring runtime protections, and monitoring deployed workloads. Kubernetes adds another layer of complexity with its API server, RBAC system, network policies, and the shared responsibility between managed services and customer configurations.
This week covers container image security, Docker hardening, Kubernetes architecture and security controls, managed Kubernetes services (EKS/AKS/GKE), and runtime security. You'll learn to secure containerized workloads from build to production.
Key insight: The ephemeral nature of containers is a security advantage—if you build security into the pipeline, every new container starts secure.
1) Container Security Fundamentals
Understanding container architecture is essential for securing containerized workloads:
Container Architecture:
CONTAINERS VS VIRTUAL MACHINES:
┌─────────────────────────────────────────────────────────────┐
│ │
│ VIRTUAL MACHINES CONTAINERS │
│ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │
│ │App 1│ │App 2│ │App 3│ │App 1│ │App 2│ │App 3│ │
│ ├─────┤ ├─────┤ ├─────┤ └──┬──┘ └──┬──┘ └──┬──┘ │
│ │Guest│ │Guest│ │Guest│ │ │ │ │
│ │ OS │ │ OS │ │ OS │ ┌──┴───────┴───────┴──┐ │
│ └──┬──┘ └──┬──┘ └──┬──┘ │ Container Runtime │ │
│ │ │ │ │ (Docker) │ │
│ ┌──┴───────┴───────┴──┐ └──────────┬──────────┘ │
│ │ Hypervisor │ │ │
│ └──────────┬──────────┘ ┌──────────┴──────────┐ │
│ │ │ Host OS │ │
│ ┌──────────┴──────────┐ └──────────┬──────────┘ │
│ │ Host OS │ │ │
│ └──────────┬──────────┘ ┌──────────┴──────────┐ │
│ │ Hardware │ │ Hardware │ │
│ └─────────────────────┘ └─────────────────────┘ │
│ │
│ VMs: Hardware virtualization Containers: OS-level │
│ Full OS per VM Shared kernel │
│ Stronger isolation Lighter weight │
└─────────────────────────────────────────────────────────────┘
CONTAINER ISOLATION MECHANISMS:
┌─────────────────────────────────────────────────────────────┐
│ Namespaces (What container can see): │
│ - PID: Process isolation │
│ - Network: Network stack isolation │
│ - Mount: Filesystem isolation │
│ - User: User ID mapping │
│ - UTS: Hostname isolation │
│ - IPC: Inter-process communication isolation │
│ │
│ Cgroups (What container can use): │
│ - CPU limits │
│ - Memory limits │
│ - I/O limits │
│ - Process limits │
│ │
│ Seccomp (What syscalls container can make): │
│ - Restricts available system calls │
│ - Default Docker profile blocks ~44 dangerous syscalls │
│ │
│ Capabilities (What privileges container has): │
│ - Fine-grained root privileges │
│ - Docker drops many by default │
│ - Further restrict with --cap-drop │
└─────────────────────────────────────────────────────────────┘
Container Security Risks:
Container Attack Surface:
IMAGE RISKS:
┌─────────────────────────────────────────────────────────────┐
│ - Vulnerable base images │
│ - Outdated packages │
│ - Malware in public images │
│ - Secrets embedded in images │
│ - Excessive packages (attack surface) │
│ │
│ Example: Official Python image had 400+ CVEs │
│ Alpine-based: ~10 CVEs (minimal attack surface) │
└─────────────────────────────────────────────────────────────┘
CONFIGURATION RISKS:
┌─────────────────────────────────────────────────────────────┐
│ - Running as root │
│ - Privileged containers │
│ - Excessive capabilities │
│ - Host filesystem mounts │
│ - Host network mode │
│ - Disabled security features │
│ │
│ Example: --privileged = full host access │
└─────────────────────────────────────────────────────────────┘
RUNTIME RISKS:
┌─────────────────────────────────────────────────────────────┐
│ - Container escape vulnerabilities │
│ - Kernel exploits (shared kernel) │
│ - Resource exhaustion attacks │
│ - Network-based attacks between containers │
│ - Cryptomining │
│ │
│ Example: CVE-2019-5736 runc escape vulnerability │
└─────────────────────────────────────────────────────────────┘
ORCHESTRATION RISKS:
┌─────────────────────────────────────────────────────────────┐
│ - Exposed Kubernetes API │
│ - Overly permissive RBAC │
│ - Missing network policies │
│ - Secrets in plain text │
│ - Insecure service accounts │
│ │
│ Example: Tesla cryptomining via exposed K8s dashboard │
└─────────────────────────────────────────────────────────────┘
Key insight: Containers share the host kernel. A kernel vulnerability affects all containers on the host.
2) Container Image Security
Secure containers start with secure images—vulnerabilities in images become vulnerabilities in every deployment:
Secure Image Building:
DOCKERFILE SECURITY BEST PRACTICES:
# BAD: Using latest tag, running as root
FROM python:latest
COPY . /app
RUN pip install -r requirements.txt
CMD ["python", "app.py"]
# GOOD: Pinned version, minimal base, non-root user
FROM python:3.11-alpine@sha256:abc123...
WORKDIR /app
# Install dependencies first (better caching)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Create non-root user
RUN adduser -D appuser
USER appuser
# Copy application code
COPY --chown=appuser:appuser . .
# Use exec form for signals
CMD ["python", "app.py"]
KEY PRINCIPLES:
┌─────────────────────────────────────────────────────────────┐
│ 1. Use minimal base images │
│ - Alpine, distroless, scratch │
│ - Fewer packages = fewer vulnerabilities │
│ │
│ 2. Pin image versions with digest │
│ - Never use :latest in production │
│ - SHA256 digest for reproducibility │
│ │
│ 3. Run as non-root user │
│ - Create dedicated user │
│ - Use USER directive │
│ │
│ 4. Multi-stage builds │
│ - Build in one stage, run in another │
│ - No build tools in production image │
│ │
│ 5. Don't store secrets in images │
│ - No passwords, API keys, tokens │
│ - Use secrets management at runtime │
│ │
│ 6. Minimize layers │
│ - Combine RUN commands │
│ - Clean up in same layer │
└─────────────────────────────────────────────────────────────┘
Multi-Stage Build Example:
Multi-Stage Build (Go application):
# Build stage
FROM golang:1.21-alpine AS builder
WORKDIR /build
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o app
# Production stage
FROM gcr.io/distroless/static:nonroot
COPY --from=builder /build/app /app
USER nonroot:nonroot
ENTRYPOINT ["/app"]
Benefits:
┌─────────────────────────────────────────────────────────────┐
│ - Build tools not in final image │
│ - Dramatically smaller image size │
│ - Reduced attack surface │
│ - No shell in distroless (harder to exploit) │
│ │
│ Size comparison: │
│ golang:1.21 ~800MB │
│ golang:1.21-alpine ~250MB │
│ distroless/static ~2MB │
└─────────────────────────────────────────────────────────────┘
Image Scanning:
Container Image Scanning:
SCANNING TOOLS:
┌─────────────────────────────────────────────────────────────┐
│ Trivy (Open Source): │
│ - Fast, comprehensive scanning │
│ - OS packages, language packages, IaC │
│ - CI/CD integration │
│ │
│ trivy image myapp:latest │
│ trivy image --severity HIGH,CRITICAL myapp:latest │
│ trivy image --exit-code 1 --severity CRITICAL myapp:latest │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Amazon ECR Scanning: │
│ - Basic scanning (Clair-based) │
│ - Enhanced scanning (Inspector integration) │
│ - Scan on push │
│ - Continuous monitoring │
│ │
│ aws ecr put-image-scanning-configuration \ │
│ --repository-name myrepo \ │
│ --image-scanning-configuration scanOnPush=true │
└─────────────────────────────────────────────────────────────┘
CI/CD INTEGRATION (GitHub Actions):
name: Build and Scan
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build image
run: docker build -t myapp:${{ github.sha }} .
- name: Run Trivy scan
uses: aquasecurity/trivy-action@master
with:
image-ref: myapp:${{ github.sha }}
format: 'table'
exit-code: '1'
severity: 'CRITICAL,HIGH'
- name: Push to ECR (if scan passes)
run: |
aws ecr get-login-password | docker login ...
docker push $ECR_REPO:${{ github.sha }}
SCANNING POLICY:
┌─────────────────────────────────────────────────────────────┐
│ Gate Criteria: │
│ - Critical vulnerabilities: 0 (block deployment) │
│ - High vulnerabilities: < 3 (warn, require approval) │
│ - Base image age: < 30 days │
│ - No secrets detected │
│ - No root user │
│ │
│ Continuous Monitoring: │
│ - Rescan when CVE database updates │
│ - Alert on newly discovered vulnerabilities │
│ - Track vulnerability trends over time │
└─────────────────────────────────────────────────────────────┘
Key insight: Scan images in CI/CD before deployment AND continuously in production—new CVEs are discovered daily.
3) Container Runtime Security
Secure runtime configuration limits what containers can do if compromised:
Docker Security Configuration:
RUNNING CONTAINERS SECURELY:
# Bad: Running as privileged
docker run --privileged myapp
# Good: Minimal privileges
docker run \
--user 1000:1000 \
--read-only \
--cap-drop ALL \
--cap-add NET_BIND_SERVICE \
--security-opt no-new-privileges:true \
--security-opt seccomp=default.json \
--memory 512m \
--cpus 1 \
--pids-limit 100 \
myapp
SECURITY OPTIONS EXPLAINED:
┌─────────────────────────────────────────────────────────────┐
│ --user 1000:1000 │
│ Run as non-root user │
│ │
│ --read-only │
│ Read-only root filesystem │
│ (use volumes for writable data) │
│ │
│ --cap-drop ALL │
│ Remove all Linux capabilities │
│ │
│ --cap-add NET_BIND_SERVICE │
│ Add back only what's needed │
│ │
│ --security-opt no-new-privileges:true │
│ Prevent privilege escalation │
│ │
│ --security-opt seccomp=default.json │
│ Apply seccomp profile (restrict syscalls) │
│ │
│ --memory 512m / --cpus 1 │
│ Resource limits (prevent DoS) │
│ │
│ --pids-limit 100 │
│ Limit processes (prevent fork bombs) │
└─────────────────────────────────────────────────────────────┘
Docker Daemon Security:
Docker Daemon Hardening:
DAEMON.JSON CONFIGURATION:
{
"icc": false,
"userns-remap": "default",
"no-new-privileges": true,
"seccomp-profile": "/etc/docker/seccomp.json",
"live-restore": true,
"userland-proxy": false,
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
},
"storage-driver": "overlay2"
}
OPTIONS EXPLAINED:
┌─────────────────────────────────────────────────────────────┐
│ "icc": false │
│ Disable inter-container communication by default │
│ Containers must be explicitly linked │
│ │
│ "userns-remap": "default" │
│ Enable user namespaces │
│ Container root maps to unprivileged host user │
│ │
│ "no-new-privileges": true │
│ Global setting to prevent privilege escalation │
│ │
│ "live-restore": true │
│ Keep containers running during daemon restart │
│ │
│ "userland-proxy": false │
│ Use iptables instead of docker-proxy │
│ Better performance and security │
└─────────────────────────────────────────────────────────────┘
DOCKER SOCKET SECURITY:
┌─────────────────────────────────────────────────────────────┐
│ /var/run/docker.sock = root on the host │
│ │
│ NEVER mount docker.sock into containers unless absolutely │
│ necessary (CI/CD builders, monitoring) │
│ │
│ If required: │
│ - Use read-only mount │
│ - Restrict to specific containers │
│ - Consider docker-socket-proxy │
│ - Audit all socket access │
│ │
│ Example attack: │
│ docker run -v /var/run/docker.sock:/var/run/docker.sock \ │
│ attacker-image docker run --privileged -v /:/host ... │
└─────────────────────────────────────────────────────────────┘
Container Registry Security:
Registry Security:
AMAZON ECR SECURITY:
┌─────────────────────────────────────────────────────────────┐
│ Access Control: │
│ - IAM policies for push/pull │
│ - Repository policies for cross-account │
│ - No anonymous access (by default) │
│ │
│ Image Security: │
│ - Image scanning (basic or enhanced) │
│ - Image signing (coming: ECR signing) │
│ - Immutable tags (prevent overwriting) │
│ │
│ Enable Immutable Tags: │
│ aws ecr put-image-tag-mutability \ │
│ --repository-name myrepo \ │
│ --image-tag-mutability IMMUTABLE │
└─────────────────────────────────────────────────────────────┘
REPOSITORY POLICY EXAMPLE:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowPushPull",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::111122223333:role/EKSNodeRole"
},
"Action": [
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage"
]
},
{
"Sid": "DenyUnscannedImages",
"Effect": "Deny",
"Principal": "*",
"Action": "ecr:BatchGetImage",
"Condition": {
"StringEquals": {
"ecr:ImageScanStatus": "FAILED"
}
}
}
]
}
IMAGE SIGNING WITH COSIGN:
┌─────────────────────────────────────────────────────────────┐
│ # Sign image │
│ cosign sign --key cosign.key myregistry/myapp:v1.0 │
│ │
│ # Verify signature │
│ cosign verify --key cosign.pub myregistry/myapp:v1.0 │
│ │
│ Benefits: │
│ - Proves image hasn't been tampered │
│ - Verify images came from trusted pipeline │
│ - Can enforce signed images in Kubernetes │
└─────────────────────────────────────────────────────────────┘
Key insight: The registry is a critical control point. Only allow deployment of scanned, signed images from trusted sources.
4) Kubernetes Security Architecture
Kubernetes adds powerful orchestration capabilities but also significant attack surface that must be secured:
Kubernetes Architecture:
COMPONENTS AND SECURITY:
┌─────────────────────────────────────────────────────────────┐
│ CONTROL PLANE │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ ┌────────────┐ ┌────────────┐ ┌──────────────┐ │ │
│ │ │ API Server │ │ Controller │ │ Scheduler │ │ │
│ │ │ │ │ Manager │ │ │ │ │
│ │ │ - AuthN │ │ │ │ │ │ │
│ │ │ - AuthZ │ │ │ │ │ │ │
│ │ │ - Admission│ │ │ │ │ │ │
│ │ └────────────┘ └────────────┘ └──────────────┘ │ │
│ │ │ │
│ │ ┌──────────────────────────────────────────────┐ │ │
│ │ │ etcd │ │ │
│ │ │ (encrypted secrets storage) │ │ │
│ │ └──────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ WORKER NODES │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │ kubelet │ │kube-proxy│ │Container │ │ │
│ │ │ │ │ │ │ Runtime │ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ │ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────┐ │ │
│ │ │ Pods / Containers │ │ │
│ │ └─────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
KEY SECURITY CONTROLS:
┌─────────────────────────────────────────────────────────────┐
│ Authentication (Who are you?): │
│ - Service accounts for pods │
│ - OIDC for users (AWS IAM, Azure AD, etc.) │
│ - No anonymous access │
│ │
│ Authorization (What can you do?): │
│ - RBAC (Role-Based Access Control) │
│ - Principle of least privilege │
│ │
│ Admission Control (What's allowed in?): │
│ - Validating/Mutating webhooks │
│ - Pod Security Standards │
│ - Policy engines (OPA/Gatekeeper, Kyverno) │
│ │
│ Network Security: │
│ - Network policies │
│ - Service mesh (mTLS) │
│ │
│ Secrets Management: │
│ - Encrypted secrets in etcd │
│ - External secrets (AWS Secrets Manager, Vault) │
└─────────────────────────────────────────────────────────────┘
Kubernetes RBAC:
RBAC Configuration:
RBAC CONCEPTS:
┌─────────────────────────────────────────────────────────────┐
│ Role / ClusterRole: │
│ Define WHAT actions are allowed │
│ Role = namespace-scoped │
│ ClusterRole = cluster-wide │
│ │
│ RoleBinding / ClusterRoleBinding: │
│ Bind roles to WHO (users, groups, service accounts) │
└─────────────────────────────────────────────────────────────┘
LEAST PRIVILEGE ROLE EXAMPLE:
# Role for application deployment
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: production
name: deployment-manager
rules:
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["pods/log"]
verbs: ["get"]
---
# Bind to service account
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: deployment-manager-binding
namespace: production
subjects:
- kind: ServiceAccount
name: cicd-deployer
namespace: production
roleRef:
kind: Role
name: deployment-manager
apiGroup: rbac.authorization.k8s.io
DANGEROUS PERMISSIONS TO AVOID:
┌─────────────────────────────────────────────────────────────┐
│ High Risk - Equivalent to cluster-admin: │
│ - * on * (all verbs on all resources) │
│ - create pods (can run privileged containers) │
│ - create/patch deployments, daemonsets, etc. │
│ │
│ Privilege Escalation Paths: │
│ - pods/exec (execute into pods) │
│ - secrets (read all secrets) │
│ - create serviceaccounts + rolebindings │
│ - create pods with service account │
│ │
│ Audit Question: "Can this role gain more access?" │
└─────────────────────────────────────────────────────────────┘
Pod Security:
Pod Security Standards:
POD SECURITY ADMISSION (PSA):
┌─────────────────────────────────────────────────────────────┐
│ Built-in Kubernetes admission controller │
│ Enforces Pod Security Standards │
│ Three profiles: Privileged, Baseline, Restricted │
│ │
│ Enable on namespace: │
│ kubectl label namespace production \ │
│ pod-security.kubernetes.io/enforce=restricted \ │
│ pod-security.kubernetes.io/audit=restricted \ │
│ pod-security.kubernetes.io/warn=restricted │
└─────────────────────────────────────────────────────────────┘
SECURITY PROFILES:
┌─────────────────────────────────────────────────────────────┐
│ PRIVILEGED: No restrictions (system workloads) │
│ │
│ BASELINE: Minimally restrictive │
│ - No privileged containers │
│ - No hostNetwork, hostPID, hostIPC │
│ - No hostPath volumes │
│ - Restricted capabilities │
│ │
│ RESTRICTED: Heavily restricted (best practice) │
│ - Must run as non-root │
│ - Must drop ALL capabilities │
│ - No privilege escalation │
│ - Seccomp profile required │
│ - Read-only root filesystem │
└─────────────────────────────────────────────────────────────┘
SECURE POD SPECIFICATION:
apiVersion: v1
kind: Pod
metadata:
name: secure-app
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: myapp:v1.0
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
resources:
limits:
memory: "128Mi"
cpu: "500m"
requests:
memory: "64Mi"
cpu: "250m"
volumeMounts:
- name: tmp
mountPath: /tmp
volumes:
- name: tmp
emptyDir: {}
automountServiceAccountToken: false
Key insight: Pod Security Standards replaced PodSecurityPolicy. Enforce "restricted" profile for all production workloads.
5) Managed Kubernetes and Network Policies
Managed Kubernetes services reduce operational burden but still require security configuration:
Amazon EKS Security:
EKS ARCHITECTURE:
┌─────────────────────────────────────────────────────────────┐
│ │
│ AWS MANAGED CUSTOMER MANAGED │
│ ┌─────────────────────┐ ┌─────────────────────┐ │
│ │ Control Plane │ │ Worker Nodes │ │
│ │ ┌─────────────┐ │ │ ┌─────────────┐ │ │
│ │ │ API Server │ │◄───────►│ │ kubelet │ │ │
│ │ │ etcd │ │ │ │ Pods │ │ │
│ │ │ Controllers │ │ │ │ Runtime │ │ │
│ │ └─────────────┘ │ │ └─────────────┘ │ │
│ └─────────────────────┘ └─────────────────────┘ │
│ │
│ AWS Responsibility: Your Responsibility: │
│ - Control plane availability - Node security │
│ - Control plane patching - Pod security │
│ - etcd encryption - Network policies │
│ - API server security - IAM roles for pods │
│ - Application security │
└─────────────────────────────────────────────────────────────┘
EKS SECURITY CONFIGURATION:
┌─────────────────────────────────────────────────────────────┐
│ Cluster Security: │
│ - Private endpoint access │
│ - Envelope encryption for secrets │
│ - Control plane logging │
│ │
│ aws eks create-cluster \ │
│ --name my-cluster \ │
│ --resources-vpc-config \ │
│ endpointPrivateAccess=true,endpointPublicAccess=false \ │
│ --encryption-config \ │
│ '[{"resources":["secrets"],"provider":{"keyArn":"..."}}]'│
└─────────────────────────────────────────────────────────────┘
IAM ROLES FOR SERVICE ACCOUNTS (IRSA):
┌─────────────────────────────────────────────────────────────┐
│ Pod-level IAM without node-level credentials │
│ │
│ 1. Create IAM role with trust policy: │
│ { │
│ "Principal": { │
│ "Federated": "arn:aws:iam::...:oidc-provider/..." │
│ }, │
│ "Condition": { │
│ "StringEquals": { │
│ "...:sub": "system:serviceaccount:ns:sa-name" │
│ } │
│ } │
│ } │
│ │
│ 2. Annotate service account: │
│ metadata: │
│ annotations: │
│ eks.amazonaws.com/role-arn: arn:aws:iam::...:role/...│
│ │
│ 3. Pod assumes role via OIDC, not node credentials │
└─────────────────────────────────────────────────────────────┘
Network Policies:
Kubernetes Network Policies:
DEFAULT: NO NETWORK POLICIES = ALL PODS CAN COMMUNICATE
WITH NETWORK POLICIES:
┌─────────────────────────────────────────────────────────────┐
│ Control traffic flow between pods and external endpoints │
│ Requires CNI that supports NetworkPolicy (Calico, Cilium) │
└─────────────────────────────────────────────────────────────┘
DEFAULT DENY ALL:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: production
spec:
podSelector: {} # Apply to all pods
policyTypes:
- Ingress
- Egress
ALLOW SPECIFIC TRAFFIC:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-frontend-to-backend
namespace: production
spec:
podSelector:
matchLabels:
app: backend
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- protocol: TCP
port: 8080
ALLOW EGRESS TO SPECIFIC SERVICES:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: backend-egress
namespace: production
spec:
podSelector:
matchLabels:
app: backend
policyTypes:
- Egress
egress:
# Allow DNS
- to:
- namespaceSelector: {}
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
# Allow database
- to:
- podSelector:
matchLabels:
app: database
ports:
- protocol: TCP
port: 5432
# Allow AWS services via VPC endpoints
- to:
- ipBlock:
cidr: 10.0.0.0/16
ports:
- protocol: TCP
port: 443
NETWORK POLICY STRATEGY:
┌─────────────────────────────────────────────────────────────┐
│ 1. Start with default deny in each namespace │
│ 2. Explicitly allow required communication │
│ 3. Use labels consistently for policy targeting │
│ 4. Allow DNS (or nothing works) │
│ 5. Test policies before enforcing │
│ 6. Monitor denied traffic │
└─────────────────────────────────────────────────────────────┘
Key insight: Network policies are namespaced. A default-deny policy must be created in each namespace you want to protect.
Real-World Context
Case Study: Tesla Kubernetes Cryptomining
In 2018, attackers compromised Tesla's Kubernetes cluster via an unsecured Kubernetes dashboard exposed to the internet without authentication. Once inside, they deployed cryptomining containers using Tesla's cloud resources. The attackers used sophisticated evasion techniques: mining software connected to an unlisted mining pool, CPU usage was throttled to avoid detection, and traffic was hidden behind CloudFlare. This incident highlighted multiple security failures: exposed dashboard, missing authentication, no network policies limiting egress, and insufficient monitoring for anomalous resource usage.
Case Study: Vulnerable Container Image Supply Chain
Researchers found that 51% of images in Docker Hub contained at least one critical vulnerability. Popular base images like python:latest contained hundreds of CVEs. Organizations pulling these images directly into production inherit all vulnerabilities. The solution: use minimal base images (Alpine, distroless), maintain your own vetted base images, scan all images before deployment, and continuously monitor for new CVEs affecting deployed images.
Container/Kubernetes Security Checklist:
Container Security Checklist:
IMAGE SECURITY:
□ Use minimal base images (Alpine, distroless)
□ Pin image versions with SHA256 digest
□ Scan images in CI/CD pipeline
□ Block deployment of images with critical CVEs
□ Sign images and verify signatures
□ Use private registry with access controls
□ Enable image scanning in registry
CONTAINER RUNTIME:
□ Run containers as non-root
□ Drop all capabilities, add only needed
□ Enable read-only root filesystem
□ Set resource limits (CPU, memory, PIDs)
□ Enable seccomp profile
□ Never run privileged containers
□ Never mount Docker socket
KUBERNETES SECURITY:
□ Enable RBAC with least privilege
□ Use namespaces for isolation
□ Enforce Pod Security Standards (restricted)
□ Enable network policies (default deny)
□ Encrypt secrets in etcd
□ Use IAM roles for service accounts
□ Disable automount of service account tokens
□ Keep Kubernetes version updated
CLUSTER ACCESS:
□ Private API endpoint (no public access)
□ OIDC authentication for users
□ No direct kubectl for developers (use CI/CD)
□ Audit logging enabled
□ Regular RBAC reviews
MONITORING:
□ Container runtime security (Falco)
□ Image vulnerability monitoring
□ Network policy logging
□ API audit logs
□ Resource usage anomaly detection
Container security requires attention throughout the lifecycle: build, ship, and run. Each phase has unique security requirements.
Guided Lab: Secure Kubernetes Deployment
In this lab, you'll deploy a secure application to Kubernetes with proper security controls.
Lab Environment:
- Kubernetes cluster (EKS, Minikube, or Kind)
- kubectl configured
- Docker for image building
- Trivy for scanning
Exercise Steps:
- Create secure Dockerfile with non-root user
- Build and scan image with Trivy
- Push to ECR with scanning enabled
- Create namespace with Pod Security Standards
- Create service account with IRSA
- Deploy pod with security context
- Apply network policies
- Test security controls
- Verify pod cannot escalate privileges
Reflection Questions:
- What happens when you try to deploy a privileged pod?
- How do network policies change application behavior?
- What's the difference between EKS node IAM and IRSA?
Week Outcome Check
By the end of this week, you should be able to:
- Explain container isolation mechanisms and their limitations
- Build secure container images with minimal attack surface
- Implement image scanning in CI/CD pipelines
- Configure container runtime security controls
- Design Kubernetes RBAC with least privilege
- Apply Pod Security Standards to namespaces
- Create network policies for pod-to-pod communication
- Configure managed Kubernetes security (EKS)
🎯 Hands-On Labs (Free & Essential)
Secure containers and Kubernetes from image build to production deployment.
🐋 TryHackMe: Docker Security
What you'll do: Exploit and secure Docker containers—escape attempts,
privileged containers, and image vulnerabilities.
Why it matters: Misconfigured containers can provide host-level access to
attackers.
Time estimate: 2-3 hours
☸️ Kubernetes Goat: K8s Security Practice
What you'll do: Attack and defend intentionally vulnerable Kubernetes
clusters—learn real attack vectors.
Why it matters: Kubernetes security is complex—hands-on exploitation
teaches defense.
Time estimate: 3-4 hours
🧪 HackTheBox: Container Escape Techniques
What you'll do: Practice container breakout techniques and learn to prevent
them (free tier challenges).
Why it matters: Container escapes turn application compromises into
infrastructure compromises.
Time estimate: 2-3 hours
💡 Lab Strategy: Kubernetes Goat is essential—it simulates real cluster vulnerabilities you'll encounter in production.
Resources
Lab
Complete the following lab exercises to practice container and Kubernetes security.