HIGH
Container and Kubernetes Security: Securing Cloud-Native Infrastructure
A deep dive into container security threats, Kubernetes hardening, and best practices for securing containerized workloads.
The Container Security Challenge
Containers have revolutionized application deployment, but they’ve also introduced new attack surfaces. 76% of organizations have experienced a container security incident, with misconfigurations being the leading cause.
Container Attack Surface
| Layer | Attack Vectors | Key Risks |
|---|---|---|
| Container Image | Vulnerable base images, embedded secrets, malicious packages | Supply chain compromise, credential exposure |
| Container Runtime | Container escape vulnerabilities, privileged containers, insecure mounts | Host system compromise, data access |
| Orchestrator (K8s) | RBAC misconfigurations, exposed API server, insecure network policies | Cluster takeover, lateral movement |
| Host/Infrastructure | Kernel vulnerabilities, node compromise, cloud misconfigurations | Full infrastructure compromise |
Image Security
Scanning for Vulnerabilities
# Trivy - comprehensive scanner
trivy image nginx:latest
# Grype
grype nginx:latest
# Docker Scout
docker scout cves nginx:latest
# Snyk
snyk container test nginx:latest
Sample Trivy Output:
nginx:latest (debian 12.4)
===========================
Total: 142 (UNKNOWN: 0, LOW: 89, MEDIUM: 41, HIGH: 10, CRITICAL: 2)
┌──────────────┬────────────────┬──────────┬────────────────────┐
│ Library │ Vulnerability │ Severity │ Fixed Version │
├──────────────┼────────────────┼──────────┼────────────────────┤
│ openssl │ CVE-2024-XXXX │ CRITICAL │ 3.0.13-1 │
│ curl │ CVE-2024-YYYY │ HIGH │ 7.88.1-10+deb12u5 │
└──────────────┴────────────────┴──────────┴────────────────────┘
Secure Base Images
# Bad: Using latest tag, full OS
FROM ubuntu:latest
# Better: Specific version, minimal image
FROM ubuntu:22.04
# Best: Distroless or minimal images
FROM gcr.io/distroless/static-debian12:nonroot
# Or Alpine for smaller attack surface
FROM alpine:3.19
Multi-stage Builds
# Build stage
FROM golang:1.22 AS builder
WORKDIR /app
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o /app/server
# Runtime stage - minimal image
FROM gcr.io/distroless/static-debian12:nonroot
COPY --from=builder /app/server /server
USER nonroot:nonroot
ENTRYPOINT ["/server"]
Scanning in CI/CD
# GitHub Actions example
name: Container Security Scan
on: [push, pull_request]
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build image
run: docker build -t myapp:${{ github.sha }} .
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: 'myapp:${{ github.sha }}'
format: 'sarif'
output: 'trivy-results.sarif'
severity: 'CRITICAL,HIGH'
exit-code: '1' # Fail on critical/high
- name: Upload scan results
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: 'trivy-results.sarif'
Container Runtime Security
Docker Security Best Practices
# Never run as root
docker run --user 1000:1000 myapp
# Drop all capabilities, add only what's needed
docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE myapp
# Read-only filesystem
docker run --read-only myapp
# No new privileges
docker run --security-opt=no-new-privileges myapp
# Resource limits
docker run --memory=512m --cpus=0.5 myapp
# Combined secure run
docker run \
--user 1000:1000 \
--cap-drop=ALL \
--read-only \
--security-opt=no-new-privileges:true \
--memory=512m \
--cpus=0.5 \
myapp
Dangerous Docker Configurations
# NEVER do these in production:
# Privileged mode (full host access)
docker run --privileged myapp # ❌
# Host network namespace
docker run --network=host myapp # ❌
# Host PID namespace
docker run --pid=host myapp # ❌
# Docker socket mount (container escape)
docker run -v /var/run/docker.sock:/var/run/docker.sock myapp # ❌
# Host filesystem mount
docker run -v /:/host myapp # ❌
Kubernetes Security
RBAC Configuration
# Principle of least privilege
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: pod-reader
namespace: production
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list"] # Only read access
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: read-pods
namespace: production
subjects:
- kind: ServiceAccount
name: my-service-account
namespace: production
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.io
Dangerous RBAC to Avoid:
# NEVER: Cluster-admin to everyone
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: bad-binding
subjects:
- kind: Group
name: system:authenticated # All authenticated users
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: cluster-admin # Full cluster access
apiGroup: rbac.authorization.k8s.io
Pod Security Standards
# Restricted Pod Security Standard (most secure)
apiVersion: v1
kind: Namespace
metadata:
name: production
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/warn: restricted
Secure Pod Spec:
apiVersion: v1
kind: Pod
metadata:
name: secure-pod
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: myapp:v1.0.0
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
resources:
limits:
memory: "512Mi"
cpu: "500m"
requests:
memory: "256Mi"
cpu: "250m"
volumeMounts:
- name: tmp
mountPath: /tmp
volumes:
- name: tmp
emptyDir: {}
automountServiceAccountToken: false # If not needed
Network Policies
# Default deny all ingress
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
namespace: production
spec:
podSelector: {}
policyTypes:
- Ingress
---
# 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
Secrets Management
# Bad: Secrets in plain text
apiVersion: v1
kind: Secret
metadata:
name: my-secret
type: Opaque
data:
password: cGFzc3dvcmQxMjM= # Just base64, not encrypted!
Better: External Secrets Operator
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: my-secret
spec:
refreshInterval: 1h
secretStoreRef:
name: aws-secrets-manager
kind: SecretStore
target:
name: my-secret
data:
- secretKey: password
remoteRef:
key: production/myapp
property: password
Kubernetes Audit Logging
# Audit policy
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
# Log all requests to secrets
- level: Metadata
resources:
- group: ""
resources: ["secrets"]
# Log all changes to RBAC
- level: RequestResponse
resources:
- group: "rbac.authorization.k8s.io"
resources: ["clusterroles", "clusterrolebindings", "roles", "rolebindings"]
# Log pod exec/attach (potential intrusion)
- level: RequestResponse
resources:
- group: ""
resources: ["pods/exec", "pods/attach"]
# Log authentication failures
- level: Metadata
users: ["system:anonymous"]
verbs: ["*"]
Runtime Security
Falco Rules
# Detect container escape attempts
- rule: Container Escape via Mounts
desc: Detect attempts to escape container via mount manipulation
condition: >
spawned_process and container and
(proc.name = "mount" or proc.name = "umount") and
not user_known_mount_program
output: >
Mount command in container (user=%user.name command=%proc.cmdline
container=%container.name image=%container.image.repository)
priority: WARNING
# Detect crypto mining
- rule: Detect Crypto Mining
desc: Detect crypto mining processes
condition: >
spawned_process and container and
(proc.name in (cryptomining_processes) or
proc.cmdline contains "stratum+tcp" or
proc.cmdline contains "xmrig")
output: >
Crypto mining detected (user=%user.name command=%proc.cmdline
container=%container.name)
priority: CRITICAL
Security Scanning Tools
Kubernetes Security Assessment
# kube-bench - CIS benchmark
kube-bench run --targets=master,node,etcd
# kubeaudit
kubeaudit all -f deployment.yaml
# Polaris
polaris audit --audit-path ./manifests/
# kube-hunter
kube-hunter --remote <cluster-ip>
# kubesec
kubesec scan deployment.yaml
Sample kube-bench Output:
[INFO] 1 Control Plane Security Configuration
[PASS] 1.1.1 Ensure API server pod specification file permissions
[FAIL] 1.1.2 Ensure API server pod specification file ownership
[WARN] 1.1.3 Ensure controller manager pod specification file permissions
== Summary ==
45 checks PASS
10 checks FAIL
5 checks WARN
Security Checklist
## Container Security Checklist
### Image Security
- [ ] Use minimal base images (distroless, Alpine)
- [ ] Scan images for vulnerabilities in CI/CD
- [ ] Sign and verify images
- [ ] Don't embed secrets in images
- [ ] Use multi-stage builds
- [ ] Pin image versions (no :latest)
### Runtime Security
- [ ] Run as non-root user
- [ ] Drop all capabilities
- [ ] Use read-only filesystem
- [ ] Set resource limits
- [ ] Enable seccomp profiles
- [ ] No privileged containers
### Kubernetes Security
- [ ] Enable RBAC with least privilege
- [ ] Use Pod Security Standards
- [ ] Implement Network Policies
- [ ] Enable audit logging
- [ ] Use secrets management (not K8s secrets)
- [ ] Disable automount service account tokens
### Infrastructure
- [ ] Keep nodes patched
- [ ] Use private registries
- [ ] Enable control plane audit logs
- [ ] Implement admission controllers
- [ ] Regular security assessments
References
- CIS Kubernetes Benchmark
- NIST Container Security Guide
- Kubernetes Security Documentation
- Docker Security Best Practices
- Falco Runtime Security
Containers make deployment easier, but security harder. Shift security left, and never assume the container boundary is a security boundary.