Learning about Linux namespaces in #docker and #kubernetes !

9 June 2022
Security
Networking
linux
docker
kubernetes

A practical walkthrough in exploring namespaces in relation to docker and kubernetes.

Introduction

As you probably know, containers running on the same host, share the Linux kernel. That’s why a container image does not contain a kernel, only software and tools that make up a distro like for example a package manager.

So, if containers share the same kernel, how are they ’isolated’? Technically, containers are processes that can use limited resources (controlled by cgroups) and isolated by Linux namespaces.

Namespaces are a feature of the kernel that partitions kernel resources such that one set of processes sees one set of resources while another set of processes sees a different set of resources.

Examples of such resources are process IDs, hostnames, user IDs, file names, and some names associated with network access, and interprocesses communication. (cfr. https://en.wikipedia.org/wiki/Linux_namespaces)

So, let’s give it a try.

Exploring namespaces on a docker host

Let's get started!

Install docker

apt update; apt install -y docker.io
docker version

Create a container and find the pid

Let's spin-up a simple nginx container.

docker run -d --name demo-nginx nginx

We can find the process ID using different ways

PID=$(docker inspect -f '{{.State.Pid}}' demo-nginx); echo $PID

or in a more verbose way

sudo ps aux | grep -i nginx

Finding the linux namespaces of the container

There are several ways to find the namespace id’s, but the most effective way I found is:

sudo ps -ax -n -o pid,netns,utsns,ipcns,mntns,pidns,cmd | grep -i $PID

Now we found the namespace id, let's focus on the netns id and store the id into an env variable for further reference.

NETNS=$(sudo ps -ax -n -o pid,netns,utsns,ipcns,mntns,pidns,cmd | grep -i -m 1 $PID | awk '{print $2}'); \
echo $NETNS

Now, let's find all processes that share the netns.

sudo ps -ax -n -o pid,netns,utsns,ipcns,mntns,pidns,cmd | \
grep $NETNS

We now found all the processes that share the namespace. These are all the processes currently running inside the context of the container. You can easily add another process to an existing namespace using the docker cli. For example:

docker run -d --net=container:demo-nginx busybox /bin/sh -c "wget http://127.0.0.1; sleep 120"

This will execute the curl command and add the sleep process while sharing the existing netns namespace.

sudo ps -ax -n -o pid,netns,utsns,ipcns,mntns,pidns,cmd | \
grep $NETNS

We are sharing the netns namespace in these examples. This means that all containers share the networking stack. This is the reason why the containers can communicate over 127.0.0.1. To verify the curl command was successful.

docker logs demo-nginx

Let's cleanup

docker stop demo-nginx; docker rm demo-nginx

Exploring namespaces on a kubernetes node

Create a k8s pod and find the pid

 kubectl run --image nginx www-demo

This pod is running on a single node cluster. (Note: in a real-life scenario, you must connect to the node the pod is actually running on). You can identify the node easily.

kubectl get po -o wide

On the node the pod is running, we can find the process id.

ps -ax -n -o pid,netns,cmd  | grep -i -m 1 "master process nginx" 

Finding the linux namespaces of the pod

Let's store the netns in an env variable.

NETNS=$(ps -ax -n -o pid,netns,cmd | grep -i -m 1  "master process nginx" | awk '{print $2}'); echo $NETNS

And find all the processes that share the netns namespace

sudo ps -ax -n -o pid,netns,utsns,ipcns,mntns,pidns,cmd | \
grep $NETNS

We now found all the processes in the context of the pod, but what we see is even more interesting. We found the /pause container. It’s a container which holds the network netns, utsnsand ipcns namespace for the pod. Kubernetes creates the ‘pause’ containers to acquire for example the respective pod’s IP address and share it with all other containers that join that pod.

Hacking the pod via namespaces

Could we modify the content of the content of www-demo ? Let's verify the pod is still there.

kubectl get po 

Let's quickly store the PID.

PID=$(ps -ax -n -o pid,netns,cmd | grep -i -m 1  "master process nginx" | awk '{print $1}'); echo $PID

nsenter will help us navigate into the namespaces of the pod

PID=$(ps -ax -n -o pid,netns,cmd | grep -i -m 1  "master process nginx" | awk '{print $1}'); echo $PID
sudo nsenter --target $PID --mount --uts --ipc --net --pid

We now have full access to the pod, including the filesystem. Let's try to add a file to the nginx container.

echo  "Hacking" >/usr/share/nginx/html/hacking.html; exit

Let exec into the pod and verify !

kubectl exec -it www-demo -- curl 127.0.0.1/hacking.html

Conclusion

I hope this small tutorial helps in better understanding how containers and pods relate to Linux namespaces and sheds a light on how isolation works in containerised environments.

About the Author

Philippe Bogaerts

Philippe Bogaerts

Co-founder of https://brucon.org

Senior Solutions Architect Security, DevSecOps and Kubernetes. Co-founder of https://brucon.org.