Docker on Debian / Ubuntu not respecting ufw firewall settings exposing ports
Docker bypasses ufw firewall settings and exposes ports to the outside world, a very serious security leak.
Again another unexpected Docker issue. In a previous post I described why and how you must force Docker to use a subnet, to prevent sudden unexpected changes in the network with consequences like mail no longer working.
This post is about Docker not respecting firewall settings, at least when running Debian / Ubuntu and ufw (Uncomplicated Firewall). Docker does not tell you this, and exposes ports, so this behavior is totally unexpected.
My ISPConfig server is running Debian Stretch. I use ufw as a firewall, below are the main settings.
ufw status verbose
Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), deny (routed)
New profiles: skip
To Action From
-- ------ ----
22/tcp ALLOW IN Anywhere
80/tcp ALLOW IN Anywhere
443/tcp ALLOW IN Anywhere
8080/tcp ALLOW IN Anywhere
25 ALLOW IN Anywhere
As you can see, all incoming connections are blocked except a few allowed.
My Docker Python web applications use the Gunicorn WSGI HTTP server to serve the Python Flask app. Nginx is running on the host and is configured as a reverse proxy for these applications. Works like a charm. In the docker-compose.yml file the ports are loaded from the .env file:
services:
web:
...
env_file:
- ./.env
ports:
- "${SERVER_PORT_HOST}:${SERVER_PORT_CONTAINER}"
For one application .env file contains these lines:
SERVER_PORT_HOST=8001
SERVER_PORT_CONTAINER=8001
So in effect the docker-compose line that is used is:
ports:
- "8001:8001"
The problem: Docker port 8001 is exposed to the outside network even if ufw is configured to block incoming connections.
This means that the Docker application/service also is visible at <SERVER-IP>:8001. This not only is very bad, it is also totally unexpected as ufw was configured to block all incoming connections.
Solutions
There are several solutions to this, here I choose to bind the port to the host machine. The docker-compose.yml lines then become:
ports:
- "127.0.0.1:8001:8001"
This works for me.
Other ways to solve this are to prevent Docker messing with the firewall, add rules to the ufw file /etc/ufw/after.rules, etc. See links below.
Summary
Some people consider this Docker behavior as a bug, and I can only agree to this. Ufw is a firewall, a very important program to control security. Ufw is very widely used and if another program bypasses its behavior the user must be informed. Until this is fixed, at minimum Docker should issue a big warning message when starting an application.
Links / credits
Be careful with Docker ports!
https://dev.to/kovah/be-careful-with-docker-ports-3pih
Docker service exposed publicly though made to expose ports to localhost only
https://stackoverflow.com/questions/50621936/docker-service-exposed-publicly-though-made-to-expose-ports-to-localhost-only
Docker services only available to the local host
https://stackoverflow.com/questions/54261105/docker-services-only-available-to-the-local-host
How to fix the Docker and UFW security flaw
https://www.techrepublic.com/article/how-to-fix-the-docker-and-ufw-security-flaw/
The dangers of UFW + Docker
https://blog.viktorpetersson.com/2014/11/03/the-dangers-of-ufw-docker.html
What is the best practice of docker + ufw under Ubuntu
https://stackoverflow.com/questions/30383845/what-is-the-best-practice-of-docker-ufw-under-ubuntu
Read more
Docker
Leave a comment
Comment anonymously or log in to comment.
Comments (1)
Leave a reply
Reply anonymously or log in to reply.
Ein wirklich schweres und immer noch aktuelles Sicherheitsproblem. Danke für die schnelle Lösung für Docker Compose.
Recent
- Hiding database UUID primary keys of your web application
- Don't Repeat Yourself (DRY) with Jinja2
- SQLAlchemy, PostgreSQL, maximum number of rows per user
- Show the values in SQLAlchemy dynamic filters
- Secure data transfer with Public Key encryption and pyNaCl
- rqlite: a high-availability and distributed SQLite alternative
Most viewed
- Using Python's pyOpenSSL to verify SSL certificates downloaded from a host
- Using UUIDs instead of Integer Autoincrement Primary Keys with SQLAlchemy and MariaDb
- Connect to a service on a Docker host from a Docker container
- Using PyInstaller and Cython to create a Python executable
- SQLAlchemy: Using Cascade Deletes to delete related objects
- Flask RESTful API request parameter validation with Marshmallow schemas