Container & Cloud Security
Docker Hardening, Image Scanning, Kubernetes Security, AWS IAM
Introduction
Containers changed how applications are deployed, and introduced new security challenges. A poorly configured container can give an attacker root-equivalent access to the host. This lesson covers Docker security hardening, image vulnerability scanning, Kubernetes security primitives (RBAC, NetworkPolicy, PodSecurityContext), and AWS IAM least-privilege principles with Python.
Docker Security Baseline
Never run containers as root. Add USER directive to Dockerfile.
Mount root filesystem as read-only. Use tmpfs for writable paths.
Drop ALL Linux capabilities; add back only what's needed.
Use distroless or alpine. Smaller attack surface = fewer CVEs.
# Dockerfile, Security-hardened Python application (multi-stage build)
# ── Stage 1: Build ────────────────────────────────────────────
FROM python:3.12-slim AS builder
WORKDIR /build
# Install dependencies in build stage
COPY requirements.txt .
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt
# ── Stage 2: Runtime (minimal attack surface) ─────────────────
FROM python:3.12-slim AS runtime
# Create non-root user
RUN groupadd --gid 1001 appgroup && \
useradd --uid 1001 --gid 1001 --no-create-home appuser
WORKDIR /app
# Copy only installed packages from build stage
COPY --from=builder /install /usr/local
COPY --chown=appuser:appgroup src/ ./src/
# Run as non-root
USER appuser
# Health check
HEALTHCHECK --interval=30s --timeout=3s CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"
EXPOSE 8000
CMD ["python", "-m", "uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8000"]
# ── docker run security flags ──────────────────────────────────
# docker run \
# --read-only \ # Read-only root filesystem
# --cap-drop ALL \ # Drop all Linux capabilities
# --cap-add NET_BIND_SERVICE \ # Add back only what's needed (port < 1024)
# --security-opt no-new-privileges \ # Prevent privilege escalation
# --memory="256m" \ # Memory limit
# --cpus="0.5" \ # CPU limit
# --tmpfs /tmp:rw,noexec \ # Writable temp directory
# my-app:latest
Image Scanning with trivy
trivy (pronounced "try-vee") is an open-source vulnerability scanner by Aqua Security. It scans container images, filesystems, Git repositories, and cloud configurations for known CVEs (Common Vulnerabilities and Exposures), misconfigurations, and exposed secrets. trivy works by pulling vulnerability data from public databases (NVD, GitHub Advisory, OS vendor advisories) and matching it against the packages installed in the image, including OS packages (apt/apk/rpm) and language-level packages (pip, npm, maven).
Unlike Dockerfile linters that only check your build instructions, trivy inspects the final image layers, catching CVEs introduced by the base image itself (e.g. python:3.12-slim pulling in a vulnerable version of libssl). The --exit-code 1 flag makes trivy fail the CI/CD pipeline if findings above a chosen severity threshold are found.
What trivy scans
- OS packages: apt, apk, rpm packages in all image layers
- Language packages: pip, npm, gem, cargo, maven
- Secrets, API keys: private keys accidentally baked into the image
- Misconfigurations: Dockerfile best-practice violations
Severity levels (CVSS-based)
- CRITICAL - CVSS 9.0–10.0, block deployment
- HIGH - CVSS 7.0–8.9, fix before release
- MEDIUM - CVSS 4.0–6.9, track and schedule fix
- LOW / UNKNOWN - informational, review periodically
# trivy, Container image vulnerability scanner
# Install (Linux)
# curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh
# Scan a local image
trivy image python:3.12-slim
# Scan with severity filter (only CRITICAL and HIGH)
trivy image --severity CRITICAL,HIGH python:3.12-slim
# Scan and output JSON for CI/CD processing
trivy image --format json --output results.json python:3.12-slim
# Fail CI/CD if any CRITICAL CVEs found
trivy image --exit-code 1 --severity CRITICAL python:3.12-slim
# ── Python: Parse trivy results ───────────────────────────────
import json
def parse_trivy_report(report_file: str) -> dict:
"""Parse trivy JSON output and summarize vulnerabilities."""
with open(report_file) as f:
report = json.load(f)
summary = {"CRITICAL": 0, "HIGH": 0, "MEDIUM": 0, "LOW": 0}
critical_cves = []
for result in report.get("Results", []):
for vuln in result.get("Vulnerabilities", []):
severity = vuln.get("Severity", "UNKNOWN")
if severity in summary:
summary[severity] += 1
if severity == "CRITICAL":
critical_cves.append({
"id": vuln["VulnerabilityID"],
"package": vuln["PkgName"],
"fixed_in": vuln.get("FixedVersion", "No fix available"),
})
return {"summary": summary, "critical_cves": critical_cves}
# Example output structure
example = {"summary": {"CRITICAL": 2, "HIGH": 8, "MEDIUM": 15, "LOW": 31}, "critical_cves": [
{"id": "CVE-2024-1234", "package": "libssl1.1", "fixed_in": "1.1.1w-0+deb11u1"},
{"id": "CVE-2024-5678", "package": "libc6", "fixed_in": "2.31-13+deb11u9"},
]}
for sev, count in example["summary"].items():
print(f" {sev}: {count}")
print("Critical CVEs:")
for cve in example["critical_cves"]:
print(f" {cve['id']} in {cve['package']} → fix: {cve['fixed_in']}")
Kubernetes Security Primitives
# kubernetes_security.yaml, Security-hardened Pod spec
apiVersion: v1
kind: Pod
metadata:
name: secure-app
spec:
# ── Pod-level security context ────────────────────────────
securityContext:
runAsNonRoot: true # Refuse to run as root
runAsUser: 1001 # Specific non-root UID
runAsGroup: 1001
fsGroup: 1001 # File system group
seccompProfile:
type: RuntimeDefault # Restrict syscalls to common set
containers:
- name: app
image: my-app:1.2.3 # Pin specific version, not 'latest'
# ── Container-level security context ──────────────────
securityContext:
allowPrivilegeEscalation: false # Prevent sudo/setuid
readOnlyRootFilesystem: true # Read-only filesystem
capabilities:
drop: ["ALL"] # Drop all Linux capabilities
add: [] # Add back nothing
# ── Resource limits ───────────────────────────────────
resources:
requests:
memory: "64Mi"
cpu: "100m"
limits:
memory: "128Mi"
cpu: "250m"
# ── Writable volume for temp files ────────────────────
volumeMounts:
- name: tmp
mountPath: /tmp
volumes:
- name: tmp
emptyDir: {} # tmpfs, destroyed when pod exits
---
# NetworkPolicy, restrict pod-to-pod communication
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-only-frontend
spec:
podSelector:
matchLabels:
app: backend
policyTypes: [Ingress]
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- protocol: TCP
port: 8080
# Default deny: backend only accepts traffic from frontend pods
AWS IAM Least Privilege with Python
The Principle of Least Privilege (PoLP) states that every identity, whether a human user, an application, or a service, should be granted only the minimum permissions required to perform its intended function, and nothing more. In AWS, this is enforced through IAM (Identity and Access Management) policies attached to users, groups, and roles.
Over-permissioned identities are one of the most exploited misconfigurations in cloud environments. If an attacker compromises a Lambda function or EC2 instance with an "Action": "*" policy, they inherit full AWS access, able to exfiltrate data, spin up resources for crypto mining, or destroy the entire account. Least privilege limits this blast radius: a compromised identity can only affect the specific resources it was permitted to touch.
Identity
Who is making the request? User, role, or service account.
Action
What are they allowed to do? Specific API calls, not wildcards.
Resource
Which resources exactly? A specific bucket ARN, not *.
Install: pip install boto3
# iam_check.py, Analyze IAM policies for over-permission
import boto3
import json
OVERLY_PERMISSIVE = ["*", "iam:*", "s3:*", "ec2:*", "rds:*"]
def analyze_iam_policy(policy_document: dict) -> list[str]:
"""Check an IAM policy document for overly permissive rules."""
warnings = []
for statement in policy_document.get("Statement", []):
if statement.get("Effect") != "Allow":
continue
actions = statement.get("Action", [])
if isinstance(actions, str):
actions = [actions]
resources = statement.get("Resource", [])
if isinstance(resources, str):
resources = [resources]
for action in actions:
if action in OVERLY_PERMISSIVE or action == "*":
warnings.append(f"⚠️ Overly permissive action: {action!r}")
if "*" in resources:
warnings.append("⚠️ Action applies to ALL resources (*)")
return warnings
# Example: Overly permissive policy (what NOT to do)
bad_policy = {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "*", # ← ALL AWS actions
"Resource": "*", # ← On ALL resources
}
]
}
# Least-privilege policy (what TO do)
good_policy = {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject", # Only read objects
"s3:PutObject", # Only write objects
],
"Resource": "arn:aws:s3:::my-app-bucket/*", # Only this bucket
}
]
}
print("BAD policy warnings:")
for w in analyze_iam_policy(bad_policy):
print(f" {w}")
print("\nGOOD policy warnings:")
warnings = analyze_iam_policy(good_policy)
if not warnings:
print(" ✅ No over-permission issues detected")
Expected Output:
BAD policy warnings: ⚠️ Overly permissive action: '*' ⚠️ Action applies to ALL resources (*) GOOD policy warnings: ✅ No over-permission issues detected
Key Takeaways
- Never run containers as root, a container escape as root gives attacker root on the host
- Read-only filesystem + tmpfs, prevents attackers from dropping payloads or modifying application code
- Drop all capabilities,
--cap-drop ALLremoves 37 dangerous Linux capabilities by default - Scan images before deployment, trivy in CI/CD catches CVEs before they reach production
- Kubernetes NetworkPolicy is default-deny, pods can communicate with everything by default; add NetworkPolicy to restrict
- IAM least privilege, specify exact actions and resources; never use
"Action": "*"or"Resource": "*"together
What's Next?
Lesson 18 covers Network Security Operations, the day-to-day work of monitoring, detecting, and responding to security incidents.
- Firewall concepts, iptables rules and filtering
- IDS/IPS, intrusion detection and prevention
- Security monitoring, Python log parser for anomaly detection
- Incident response, NIST framework and automated response scripts