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

LayerAttack VectorsKey Risks
Container ImageVulnerable base images, embedded secrets, malicious packagesSupply chain compromise, credential exposure
Container RuntimeContainer escape vulnerabilities, privileged containers, insecure mountsHost system compromise, data access
Orchestrator (K8s)RBAC misconfigurations, exposed API server, insecure network policiesCluster takeover, lateral movement
Host/InfrastructureKernel vulnerabilities, node compromise, cloud misconfigurationsFull 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


Containers make deployment easier, but security harder. Shift security left, and never assume the container boundary is a security boundary.