Console Login

Container Security is a Lie: Hardening Kubernetes in a Hostile Environment

Container Security is a Lie: Hardening Kubernetes in a Hostile Environment

Let’s get one thing straight: a container is just a process with a curfew. If you believe that a standard Docker container provides a solid security boundary, you are operating on hope, not engineering. In 2025, with supply chain attacks targeting the software registry layer and kernel exploits like Dirty Pipe variants still echoing in our logs, default configurations are negligence.

I recently audited a cluster for a fintech client in Oslo. They had impeccable CI/CD pipelines, but their pods were running as root, mounting the host filesystem, and communicating freely with the entire VPC. They weren't hacked because they were smart; they weren't hacked because they were lucky. Luck runs out.

We are going to lock this down. No fluff. Just the raw config and architectural decisions required to run compliant, hardened workloads in Northern Europe.

1. The Base Image is Your First Vulnerability

Most developers treat FROM node:22 as a benign starting point. It isn't. It is a massive surface area containing shells, package managers, and libraries you do not need. If /bin/sh exists in your production container, an attacker has a keyboard. If apt or apk exists, they have an arsenal.

We use Distroless or chaotic-stripped Alpine images. But even then, we never trust tags. Tags are mutable. A tag pointing to a clean image today can point to a compromised one tomorrow.

The Immutable Build Pattern

Always pin by SHA256 digest. This ensures that the binary definition of your environment never shifts without a git commit.

# BAD: Mutable tag
# FROM alpine:3.21

# GOOD: Immutable digest
FROM alpine@sha256:a8560b36e8b8210634f77d9f7f942750877a1104e578b97d2685713e254e26ef AS builder
WORKDIR /build
COPY . .
RUN go build -ldflags="-w -s" -o app main.go

# FINAL STAGE: Scratch (No shell, no users)
FROM scratch
COPY --from=builder /build/app /app
# Import certificates for external API calls (essential for Datatilsynet compliance checks)
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
USER 10001
ENTRYPOINT ["/app"]

By using FROM scratch, we remove the OS entirely. There is no shell to spawn. If an attacker manages a Remote Code Execution (RCE), they have nowhere to go.

2. Runtime Defense: The Kernel is the Battlefield

Containers share the host kernel. This is the fundamental trade-off of containerization versus virtualization. If a process in a container crashes the kernel, the whole node goes down. If a process exploits a syscall, they own the host.

This is where infrastructure choice becomes critical. At CoolVDS, we enforce strict KVM (Kernel-based Virtual Machine) isolation for our instances. Unlike legacy OpenVZ or LXC providers where you share a kernel with noisy neighbors, a CoolVDS NVMe instance gives you your own kernel. You can enable SELinux, load eBPF probes, and tune sysctl flags without asking for permission.

Enforcing Security Contexts

Kubernetes allows us to define the blast radius. You must forbid root execution and ensure the filesystem is read-only. If your app needs to write logs, mount a emptyDir volume.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: secure-payment-gateway
  namespace: finance
spec:
  selector:
    matchLabels:
      app: gateway
  template:
    metadata:
      labels:
        app: gateway
    spec:
      securityContext:
        runAsNonRoot: true
        runAsUser: 10001
        runAsGroup: 10001
        fsGroup: 10001
      containers:
      - name: gateway
        image: private-registry.coolvds.com/gateway@sha256:...
        securityContext:
          allowPrivilegeEscalation: false
          readOnlyRootFilesystem: true
          capabilities:
            drop:
            - ALL
            add:
            - NET_BIND_SERVICE
        volumeMounts:
        - name: tmp
          mountPath: /tmp
      volumes:
      - name: tmp
        emptyDir: {}

Dropping ALL capabilities and only adding back NET_BIND_SERVICE (if you bind low ports) neutralizes a vast majority of privilege escalation exploits.

3. Network Policies and Data Sovereignty

In Norway, data sovereignty isn't just a buzzword; it's legal dogma. Under GDPR and the continued fallout of Schrems II, you must guarantee that traffic doesn't accidentally route through non-compliant regions. By default, Kubernetes allows all pods to talk to all pods. This is insane.

We use NetworkPolicies to create a "Default Deny" posture. Nothing moves unless explicitly whitelisted.

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: finance
spec:
  podSelector: {}
  policyTypes:
  - Ingress
  - Egress
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-dns-egress
  namespace: finance
spec:
  podSelector: {}
  policyTypes:
  - Egress
  egress:
  - to:
    - namespaceSelector:
        matchLabels:
          kubernetes.io/metadata.name: kube-system
    - podSelector:
        matchLabels:
          k8s-app: kube-dns
    ports:
    - protocol: UDP
      port: 53

This configuration ensures that a compromised frontend pod cannot scan your database pod unless you explicitly write a policy allowing that specific TCP flow.

Pro Tip: When hosting in Oslo, latency to the NIX (Norwegian Internet Exchange) matters. Misconfigured ingress controllers often route traffic through centralized load balancers in Frankfurt or Amsterdam before returning to Norway. Using CoolVDS local instances ensures your internal traffic stays within the national grid, reducing latency and simplifying compliance audits.

4. Supply Chain Verification

In 2025, we don't trust; we verify. Implementing SBOM (Software Bill of Materials) scanning is mandatory. We integrate tools like Trivy or Grype into the CI pipeline. The build fails if a 'Critical' CVE is detected.

Here is a snippet for your pipeline configuration:

# Scan the image before pushing
trivy image --exit-code 1 --severity CRITICAL --no-progress private-registry.coolvds.com/gateway:v1.2.5

# Verify the signature (using Cosign)
cosign verify --key k8s://finance/cosign-key private-registry.coolvds.com/gateway:v1.2.5

5. The Underlying Host: Sysctl Hardening

You cannot have a secure container on an insecure host. Because CoolVDS provides full KVM virtualization, you have access to the /etc/sysctl.conf of your node. You should be tuning this to prevent network stack exploits.

Apply these settings to your host nodes:

# Prevent IP Spoofing
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1

# Disable ICMP Redirects (Prevent Man-in-the-Middle)
net.ipv4.conf.all.accept_redirects = 0
net.ipv6.conf.all.accept_redirects = 0

# Log Martians (Packets with impossible addresses)
net.ipv4.conf.all.log_martians = 1

# Protect against SYN flood attacks
net.ipv4.tcp_syncookies = 1

Conclusion

Security is not a product; it is a process of reducing risk to an acceptable level. By stripping your base images, enforcing read-only runtimes, locking down the network, and hardening the host kernel, you make your infrastructure hostile to attackers.

However, all this configuration is useless if the underlying hardware is oversold or the hypervisor is insecure. You need raw, predictable I/O and true isolation.

Stop fighting noisy neighbors. Deploy your hardened Kubernetes nodes on CoolVDS NVMe instances today and keep your data strictly within Norwegian jurisdiction.