Container Security
Security measures for containerized applications (Docker, Kubernetes). Containers share the host kernel—a compromised container can put other containers or the host at risk. Container security includes image hardening, runtime protection, network segmentation, and supply chain security.
Container Security protects containerized applications throughout their entire lifecycle: from image creation through the registry to runtime. Unlike VMs, containers share the host operating system’s kernel—which offers performance benefits but also creates specific security risks.
The Container Threat Model
| Layer | Countermeasure |
|---|---|
| Application (code vulnerabilities) | SAST/DAST |
| Container Image (base image) | Image scanning |
| Container Runtime (Docker) | Runtime Security |
| Kubernetes / Orchestration | K8s Hardening |
| Host OS (Linux Kernel) | gVisor / Kata |
| Infrastructure (Cloud/On-Prem) | IaC Security |
Phase 1: Building Secure Images
Dockerfile Best Practices
# BAD: fat image, root user
FROM ubuntu:latest
RUN apt-get update && apt-get install -y python3 pip curl wget
COPY . /app
CMD ["python3", "/app/app.py"]
# GOOD: minimal image, non-root, pinned versions
FROM python:3.12.2-slim@sha256:abc123... # Pin digest instead of tag!
# Dependencies as a separate layer (cache)
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# App code
COPY --chown=appuser:appuser . .
# Non-root user
RUN useradd --uid 10001 --no-create-home appuser
USER appuser
# Expose only the health port
EXPOSE 8080
# No shell entry point (prevents shell injection)
ENTRYPOINT ["python3", "-m", "uvicorn", "main:app"]
Image Scanning
# Trivy - free, CNCF project
trivy image --severity HIGH,CRITICAL myapp:latest
# Output:
# myapp:latest (debian 12.4)
# Total: 3 (HIGH: 2, CRITICAL: 1)
# ┌──────────────┬────────────────┬──────────┬──────────────────────────┐
# │ Library │ Vulnerability │ Severity │ Installed Version │
# ├──────────────┼────────────────┼──────────┼──────────────────────────┤
# │ libssl3 │ CVE-2024-5535 │ CRITICAL │ 3.0.11-1~deb12u2 │
# └──────────────┴────────────────┴──────────┴──────────────────────────┘
# In CI/CD (GitHub Actions)
- name: Scan image
uses: aquasecurity/trivy-action@master
with:
image-ref: 'myapp:${{ github.sha }}'
exit-code: '1' # Pipeline aborts on CRITICAL
severity: 'CRITICAL'
Supply Chain Security (SLSA)
# Cosign - Image signing
cosign sign --key cosign.key myregistry.io/myapp:1.0.0
# Verification before deployment
cosign verify --key cosign.pub myregistry.io/myapp:1.0.0
# Generate SBOM (Software Bill of Materials)
syft myapp:latest -o spdx-json > sbom.json
# Scan SBOM for vulnerabilities
grype sbom:sbom.json
Phase 2: Registry Security
Registry Security:
- Use a private registry: Harbor, ECR, ACR (no public registry!)
- Image scanning before push (Admission Policy)
- Deploy only signed images (Policy Engine: Kyverno, OPA Gatekeeper)
- Immutable tags:
:1.2.3instead of:latest - Registry credentials as secrets (not in Dockerfile!)
Harbor Sample Policy:
- Allow: Images with CRITICAL: 0
- Allow: Images with Cosign signature
- Deny: Images older than 90 days without a re-scan
Phase 3: Kubernetes Cluster Hardening
Pod Security Standards
# Restrictive PodSecurityContext
apiVersion: v1
kind: Pod
spec:
securityContext:
runAsNonRoot: true # No root!
runAsUser: 10001
fsGroup: 10001
seccompProfile:
type: RuntimeDefault # seccomp profile enabled
containers:
- name: app
securityContext:
allowPrivilegeEscalation: false # No sudo in container!
readOnlyRootFilesystem: true # Read-only root
capabilities:
drop:
- ALL # Drop all Linux capabilities
add:
- NET_BIND_SERVICE # Add only necessary ones
resources:
limits:
memory: "256Mi"
cpu: "500m"
requests:
memory: "128Mi"
cpu: "100m"
NetworkPolicy (Kubernetes Firewall)
# Micro-segmentation: Frontend may only communicate with backend, not directly with DB
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: backend-isolation
spec:
podSelector:
matchLabels:
app: backend
policyTypes:
- Ingress
- Egress
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- port: 8080
egress:
- to:
- podSelector:
matchLabels:
app: database
ports:
- port: 5432
Secrets Management
# WRONG: Secret hardcoded in YAML
env:
- name: DB_PASSWORD
value: "super-secret-password" # → in git history, etcd, logs!
# BETTER: Kubernetes Secret (base64-encoded, not encrypted by default!)
kubectl create secret generic db-secret \
--from-literal=password="super-secret-password"
# BEST PRACTICE: External Secrets Operator + HashiCorp Vault
# secrets.yaml
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
spec:
secretStoreRef:
name: vault-backend
target:
name: db-secret
data:
- secretKey: password
remoteRef:
key: prod/database
property: password
Phase 4: Runtime Security
Falco - Runtime Threat Detection
# Falco Rule: Shell in Container (common sign of compromise)
- rule: Terminal Shell in Container
desc: A shell was used as the entrypoint/exec point into a container
condition: >
spawned_process and container
and shell_procs and proc.tty != 0
and container_entrypoint
output: >
A shell was spawned in a container with an attached terminal
(user=%user.name container=%container.name image=%container.image.repository)
priority: NOTICE
Additional Falco Rules:
- Privileged container started
/etc/passwdor/etc/shadowread- Network connection to unknown IP
- Crypto-miner activity detected
- Kubectl executed in container
OWASP Top 10 Container Security Risks
| Risk | Countermeasure |
|---|---|
| C1: Insecure Supply Chain | Image Signing, SBOM, SCA |
| C2: Authorization Flaws | RBAC, NetworkPolicy, OPA |
| C3: Overly Permissive Container | Read-only FS, Drop ALL Caps |
| C4: Improper Network Access | NetworkPolicy, Service Mesh (Istio) |
| C5: Inadequate Logging | Falco, CloudTrail, Audit Logs |
| C6: Insecure Secrets | Vault, External Secrets Operator |
| C7: Vulnerable Components | Trivy, Grype, SBOM |
| C8: Runtime Threats | Falco, Tetragon, Sysdig |
| C9: Misconfigured Orchestration | kube-bench (CIS Benchmark) |
| C10: Exposed Dashboard | Never make the Kubernetes Dashboard public! |
kube-bench: CIS Benchmark Check
# Automatic check against the CIS Kubernetes Benchmark
kubectl apply -f https://raw.githubusercontent.com/aquasecurity/kube-bench/main/job.yaml
kubectl logs job/kube-bench
# Sample output:
# [FAIL] 1.2.1 Ensure that the --anonymous-auth argument is set to false
# [PASS] 1.2.6 Ensure that the --kubelet-https argument is set to true
# [FAIL] 4.2.1 Ensure that the --anonymous-auth argument is set to false
#
# == Summary ==
# 42 checks PASS
# 7 checks FAIL
# 11 checks WARN
Service Mesh (Istio/Linkerd)
Service Mesh enhances container security:
- mTLS between all services (automatic)
- Fine-grained authorization policies
- Traffic encryption without changing app code
- Observability: traces, metrics per service
Istio AuthorizationPolicy:
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
spec:
action: ALLOW
rules:
- from:
- source:
principals: ["cluster.local/ns/prod/sa/frontend"]
to:
- operation:
methods: ["GET", "POST"]
paths: ["/api/*"]