Container Security: Beyond the Basics

December 4, 2017

Containers provide process isolation, not security isolation. A container escape—breaking out of container boundaries to the host—is a known attack class. Container security requires defense in depth: multiple layers of protection so that any single failure doesn’t compromise the system.

Beyond basic container usage, here’s how to secure containerized workloads properly.

Image Security

Security starts with what goes into containers.

Minimal Base Images

Every package is attack surface. Minimal images reduce it.

Alpine Linux provides a functional base in ~5MB. Most applications don’t need full Ubuntu or Debian.

Distroless images contain only your application and runtime dependencies. No shell, no package manager, no utilities for attackers to use.

# Multi-stage build with distroless
FROM golang:1.9 AS builder
WORKDIR /app
COPY . .
RUN CGO_ENABLED=0 go build -o service .

FROM gcr.io/distroless/static
COPY --from=builder /app/service /
CMD ["/service"]

Pin Base Image Versions

FROM ubuntu:latest means different images over time. Pin versions for reproducibility and security predictability:

FROM ubuntu:18.04@sha256:abc123...

The digest ensures you get exactly the same image regardless of tag changes.

Scan for Vulnerabilities

Container images contain packages with vulnerabilities. Scan regularly:

During build:

# Trivy scanning
trivy image my-app:latest

In registries: Enable automatic scanning in your container registry.

Continuously: Vulnerabilities are discovered after images are built. Re-scan existing images.

Block deployment of images with critical vulnerabilities. Create exception processes for images that can’t be immediately remediated.

Sign and Verify Images

Image signing ensures you’re running images you built, not images an attacker injected.

Docker Content Trust:

export DOCKER_CONTENT_TRUST=1
docker push my-registry/my-app:latest  # Signs image
docker pull my-registry/my-app:latest  # Verifies signature

Don’t Run as Root

Containers running as root mean container escapes get root on the host. Run as non-root:

# Create non-root user
RUN adduser -D appuser
USER appuser

Kubernetes can enforce this cluster-wide:

apiVersion: policy/v1beta1
kind: PodSecurityPolicy
spec:
  runAsUser:
    rule: MustRunAsNonRoot

Runtime Security

What protections exist while containers run?

Read-Only Filesystems

If your application doesn’t need to write to the filesystem, make it read-only:

# Kubernetes
securityContext:
  readOnlyRootFilesystem: true

Mount writable volumes only where necessary:

volumeMounts:
  - name: tmp
    mountPath: /tmp
volumes:
  - name: tmp
    emptyDir: {}

Read-only filesystems prevent attackers from writing malware or modifying configuration.

Drop Capabilities

Linux capabilities grant specific root powers. Containers don’t need most of them.

Drop all capabilities, add only what’s required:

securityContext:
  capabilities:
    drop: ["ALL"]
    add: ["NET_BIND_SERVICE"]  # Only if needed

Seccomp Profiles

Seccomp restricts which system calls containers can make. Docker’s default profile blocks dangerous syscalls.

For higher security, create custom profiles allowing only needed syscalls:

{
  "defaultAction": "SCMP_ACT_ERRNO",
  "syscalls": [
    {"names": ["read", "write", "exit", "exit_group"], "action": "SCMP_ACT_ALLOW"}
  ]
}

AppArmor and SELinux

Mandatory access control (MAC) systems restrict what processes can do regardless of user permissions.

AppArmor: Create profiles defining allowed file access, network operations, and capabilities.

SELinux: Label-based security limiting process interactions.

Enable MAC enforcement for containerized workloads. Default profiles provide baseline protection.

Runtime Monitoring

Detect suspicious container behavior:

Tools like Falco, Sysdig, and Aqua provide runtime monitoring for containers.

# Falco rule example
- rule: Terminal shell in container
  desc: Detect shell spawned in container
  condition: >
    spawned_process and container and
    shell_procs
  output: "Shell spawned in container (user=%user.name container=%container.name)"
  priority: WARNING

Network Security

Network Policies

By default, all pods can communicate with all other pods. Network policies restrict this:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: api-network-policy
spec:
  podSelector:
    matchLabels:
      app: api
  policyTypes:
    - Ingress
    - Egress
  ingress:
    - from:
        - podSelector:
            matchLabels:
              app: frontend
      ports:
        - port: 8080
  egress:
    - to:
        - podSelector:
            matchLabels:
              app: database
      ports:
        - port: 5432

Apply least-privilege networking: pods can only communicate with what they need.

Service Mesh Security

Service meshes (Istio, Linkerd) provide:

# Istio authorization policy
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: api-policy
spec:
  selector:
    matchLabels:
      app: api
  rules:
    - from:
        - source:
            principals: ["cluster.local/ns/default/sa/frontend"]

Secrets Management

Don’t bake secrets into images. Don’t store them in environment variables visible to anyone with pod access.

Kubernetes Secrets provide basic secret storage. They’re base64 encoded, not encrypted by default. Enable encryption at rest.

External secret managers (Vault, AWS Secrets Manager) provide better security:

# Using external secrets operator
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: database-credentials
spec:
  secretStoreRef:
    name: vault-backend
    kind: SecretStore
  target:
    name: database-credentials
  data:
    - secretKey: password
      remoteRef:
        key: secret/data/database
        property: password

Orchestrator Security

RBAC

Restrict what users and service accounts can do:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: deployment-reader
rules:
  - apiGroups: ["apps"]
    resources: ["deployments"]
    verbs: ["get", "list"]

Apply least privilege. Not everyone needs cluster-admin.

Pod Security Standards

Kubernetes Pod Security Standards define security levels:

Enforce appropriate levels per namespace:

apiVersion: v1
kind: Namespace
metadata:
  name: production
  labels:
    pod-security.kubernetes.io/enforce: restricted

Audit Logging

Enable Kubernetes audit logging to track API server operations:

Audit logs are essential for security investigation and compliance.

Key Takeaways