The best way to understand this topic is with an example. Let's start an application in a Docker container.
The application runs inside the container on port 8080 and should be accessible on the Docker host at 9090.
We deviate in the
example from the default port 8080 of the host, so that one can better understand the ports of host and
container in our
example. The start is done via the following command:
docker run -p 9090:8080
Now we can list the running containers with the following command:
docker ps
eb77b9f37140 … 0.0.0.0:9090->8080/tcp
The listing usually contains Container ID, Image, Command, Created, Status, Ports and Names. For us are
interesting in the
especially the ports. In the example we get the route 0.0.0.0:9090, but we didn't specify it like
this. specified.
Doesn't matter - it's standard, isn't it? Well, it depends on where we are, because 0.0.0.0 means, that the
port is open for all
addresses. To better understand what the problem is, we need to look at the network layer.
Network interfaces
The following figure illustrates the structure of a non-Docker-enabled host system. That is, Docker is
running in a VM on the
host. First, we have our container - considered on its own. So the whole thing is quite simple for now:
The network interface eth0 as well as lo (loopback) are open for port 8080. eth0 offers this interface to the
outside.
The next figure shows the communication of the docker-engine. The docker-engine manages the containers
and provides the
interface for communication - docker0. The container is connected to docker0 via another virtual
interface vethXY. Thus,
requests travel to docker0, via vethXY finally to the eth0 interface of our container.
In order for our request to be routed to docker0 docker-proxy are used. These listen on the desired
ports and forward
accordingly:
… /usr/bin/docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 9090 -container-ip 172.17.0.2
-container-port 8080
Here we see now also where the magic 0.0.0.0 comes from. To clarify the problem again, the following figure
shows the structure
of the host machine:
The IP 0.0.0.0 is attached to the interface of the VM and is not released to the outside. Thus, we can access
the application
under the port from our browser, but a third party from the outside cannot. The port 9090 is not shared
directly on our host
machine but in the VM.
Now we move our test application to a Production Server from the cloud, a Linux-based, docker-enabled
host:
The VM is gone since our host can run docker directly. Now the docker proxies listen directly on our host
interface and expose
ports that we might not want to be visible to the outside. We can check this using nmap
nmap my.server -p 9090
Starting Nmap 7.80 ( https://nmap.org ) at 2020-05-28 14:40 CEST
Nmap scan report for my.server
Host is up (0.056s latency).
PORT STATE …
9090/tcp open …
Port 9090 is therefore open to the whole world - consciously or unconsciously. This should be prevented,
otherwise there is a
potential security gap. The already mentioned lo (loopback) interface back is suitable for this.
Secure the ports
The whole thing is not very difficult. We simply tell the container to listen to the lo interface instead of
0.0.0.0.
docker run -p 127.0.0.1:9090:8080
With this restriction, the container is still accessible inside our system on port 9090, but it is blocked to
the outside. Let's
check this again:
ps -ef | grep docker-proxy
… /usr/bin/docker-proxy -proto tcp -host-ip 127.0.0.1 -host-port 9090 -container-ip 172.17.0.2 -container-port 8080
nmap my.server -p 9090
Starting Nmap 7.80 ( https://nmap.org ) at 2020-05-28 15:04 CEST
Nmap scan report for my.server
Host is up (0.061s latency).
PORT STATE …
9090/tcp closed …
Port 9090 is therefore closed to the outside and only accessible internally. This already looks much
better.
Conclusion
Getting started in the docker world is easy and makes it possible to provide services in a quick and simple
way. To make this is
also so simple, a lot happens under the hood, which can, however, lead to surprises. If you leave a secure
test environment, you
have to deal with the issue of security anyway. Opening the ports to the outside is certainly not always
intentional and
therefore a security risk if you use docker without verifying the way common tutorials do it.