One of the most important things when creating secure Web applications is setting up good firewall. UFW, or Uncomplicated Firewall, is a command line tool that allows you to configure firewall settings easily compared to using iptables. In this tutorial we will prepare Docker container contains basic Nginx code with using UFW to showcase use case in real life.
I also provided more resources in the end of this article.
Before starting our journey, let's check if you meet the following prerequisites:
Would be good if you have basic understanding of how Docker and Docker Compose works. Familiarity with basic commands like docker compose up
, docker ps
, docker build
and docker run
.
General understanding of how reverse proxies, Nginx works.
Already having an Remote server. We will use it in every operation in this article.
Let's first install the UFW. To check whether UFW is installed or not we can run:
sudo ufw status
If its not exists we can simply install it:
sudo apt install ufw
To check if installation was succesful or not, we can run sudo ufw status
again.
Let's start begin with docker-compose.yml file. In this example we will just host static html with Nginx and docker. I'm keeping examples as pure as possible since we just want to learn how UFW works.
docker-compose.yml:
services:
nginx:
image: nginx:1-alpine
ports:
- 8082:80
volumes:
- ./index.html/:/usr/share/nginx/html
Let's break down this Docker compose file:
nginx
.nginx
service, in our case its nginx:1-alpine
.8082
is public port which can be accessible by outside world. 80
is container's port we are listening for.index.html
will be replaced to /usr/share/nginx/html
inside container.Let's replace our index.html
file next to docker-compose.yml
file:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<h1>Hello world</h1>
</body>
</html>
To start our application we can run:
docker compose up
You should be able to reach application from http://yourip:8082.
Now, let's test UFW with our example so we can understand it better. Firstly let's disable UFW.
sudo ufw disable
Depending on your needs, let's say you want to enable or disable IPv6. In latest versions of Ubuntu, IPv6 is enabled by default. But if we would want to change it, we need edit configuration file located at /etc/default/ufw
. This is for high level configurations, such as default policies, IPv6 support and kernel modules to use.
We can simply change IPV6
to yes
to allow IPV6.
There are lots of configuration files allows us to fine tune ufw or adding new iptables commands. Its not possible to change them via UFW commands we need to manually edit them.
/etc/default/ufw: high level configuration, such as default policies, IPv6 support and kernel modules to use
/etc/ufw/before[6].rules: rules in these files are evaluated before any rules added via the ufw command
/etc/ufw/after[6].rules: rules in these files are evaluated after any rules added via the ufw command
/etc/ufw/sysctl.conf: kernel network tunables
/var/lib/ufw/user[6].rules or /lib/ufw/user[6].rules (0.28 and later): rules added via the ufw command (should not normally be edited by hand)
/etc/ufw/ufw.conf: sets whether or not ufw is enabled on boot, and in 9.04 (ufw 0.27) and later, sets the LOGLEVEL
/etc/ufw/after.init: initialization customization script run after ufw is initialized (ufw 0.34 and later)
/etc/ufw/before.init: initialization customization script run before ufw is initialized (ufw 0.34 and later)
After changing configuration files, rules, we can apply and run ufw enabling ufw. But before doing that be aware, UFW by default blocks SSH connection so first we need to allow SSH before enabling it otherwise we will not able to connect our server.
sudo ufw allow ssh
Let's also allow http, https connections:
sudo ufw allow http
sudo ufw allow https
And let's start ufw by running:
sudo ufw enable
Now UFW will run automatically whenever we start our server.
Now you still be able to access your static website. Let's start by blocking all incoming connections. Disabling all incoming connections allows us to manually decide which connections are should be allowed later on.
sudo ufw default deny incoming
Now you shouldn't be able to access the website publicly, right? because we deny all incoming requests. Well... the issue is, Docker by default changes iptables and that causes UFW to can't deny incoming requests even if we denied them. We need to somehow disable this behaviour from docker.
Thanks to Feng's stack overflow answer we can do that by modifying UFW configuration file /etc/ufw/after.rules
and add following rules at the end of the file:
# BEGIN UFW AND DOCKER
*filter
:ufw-user-forward - [0:0]
:DOCKER-USER - [0:0]
-A DOCKER-USER -j RETURN -s 10.0.0.0/8
-A DOCKER-USER -j RETURN -s 172.16.0.0/12
-A DOCKER-USER -j RETURN -s 192.168.0.0/16
-A DOCKER-USER -j ufw-user-forward
-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 192.168.0.0/16
-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 10.0.0.0/8
-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 172.16.0.0/12
-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 192.168.0.0/16
-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 10.0.0.0/8
-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 172.16.0.0/12
-A DOCKER-USER -j RETURN
COMMIT
# END UFW AND DOCKER
Run following commands to restart UFW and docker:
sudo systemctl restart ufw &&
sudo systemctl restart docker
Now when we try to access our website at port 8082, it should be blocked. To allow access to specific ports, we can use:
sudo ufw allow 8082
We've learned how to: