What is the best practice of docker + ufw under Ubuntu
Asked Answered
H

18

171

I just tried out Docker. It is awesome but seems not work nicely with ufw. By default, docker will manipulate the iptables a little bit. The outcome is not a bug but not what I expected. For more details you can read The dangers of UFW + Docker

My goal is to set up a system like

    Host (running ufw) -> docker container 1 - nginx (as a reverse proxy)
                       -> docker container 2 - node web 1
                       -> docker container 3 - node web 2
                       -> .......

I want to manage the incoming traffic (e.g. restrict access) through ufw therefore I don't want docker to touch my iptables. Here is my test

Environment:

  • a newly installed Ubuntu 14.04 (kernel: 3.13.0-53 )
  • Docker 1.6.2
  • ufw forwarding is enabled.( [Enable UFW forwarding] 2 )
  • --iptables=false was added to the Docker daemon.

First Attempt

docker run --name ghost -v /home/xxxx/ghost_content:/var/lib/ghost -d ghost
docker run --name nginx -p 80:80 -v /home/xxxx/nginx_site_enable:/etc/nginx/conf.d:ro --link ghost:ghost -d nginx

No luck. The first command is fine but the second command will throw an error

Error response from daemon: Cannot start container

Second Attempt

Then I found this: unable to link containers with --iptables=false #12701

After running the following command, everything looks OK.

sudo iptables -N DOCKER

However, I noticed that I can not establish any outbound connections inside containers. For example:

xxxxg@ubuntu:~$ sudo docker exec -t -i nginx /bin/bash
root@b0d33f22d3f4:/# ping 74.125.21.147
PING 74.125.21.147 (74.125.21.147): 56 data bytes
^C--- 74.125.21.147 ping statistics ---
35 packets transmitted, 0 packets received, 100% packet loss
root@b0d33f22d3f4:/# 

If I remove --iptables=false from the Docker daemon, then the internet connection of containers will be back to normal but the ufw will not work 'properly' (well...by my definition).

So, what is the best practice of docker + ufw? Can anyone provide some help?

Haleakala answered 21/5, 2015 at 20:35 Comment(2)
iptables -N DOCKERstarts a new chain with that name... maybe you can leave the iptables enable (I mean without removing --iptables=false and then you can run a "post command" for the chain start. I don't have the answer about what is the best practice o_OHaffner
Also has been reported in github/dockerSpilt
M
111

I've had such problem like months ago and lately decided to describe the issue along with the solution on my blog. Here's the shortcut.

Using --iptables=false won't help you much with the case you described. It's simply not enough here. By default, none of your containers can do any outgoing connection.

There's a small step you're omitting on your way to have containers behind UFW here. You can use --iptables=false or create /etc/docker/daemon.json file with content as follows

{
  "iptables": false
}

the result will be the same, but the latter option requires you to restart whole docker service with service docker restart or even do a reboot if docker had a chance to add iptables rules before you disabled this function.

When it's done, just do two more things:

$ sed -i -e 's/DEFAULT_FORWARD_POLICY="DROP"/DEFAULT_FORWARD_POLICY="ACCEPT"/g' /etc/default/ufw
$ ufw reload

so you set up default forward policy in UFW for accept, and use:

$ iptables -t nat -A POSTROUTING ! -o docker0 -s 172.17.0.0/16 -j MASQUERADE

That way what you're achieving is disabling docker messy behavior in your iptables rules and at the same time docker is provided with necessary routing so containers will do outgoing connections just fine. UFW rules will be still restricted from this point on, though.

Hope this resolves the issue for you and any that gets here in search of an answer.

I described the problem and solution more comprehensively at https://www.mkubaczyk.com/2017/09/05/force-docker-not-bypass-ufw-rules-ubuntu-16-04/

Mouflon answered 17/9, 2017 at 17:15 Comment(10)
I added some info on how to setup the iptables rule from the actual Docker network config. This might be of interest for you.Flyleaf
You are the real MVPCytaster
This works fine! However, the nat iptables rule seems to be flushed on reboot. How can I persist it?Cordillera
To persist the iptables rule install the linux package iptables-persistent according to your server distro, in my case (Debian) is sudo apt install iptables-persistent and the package installation will add the NAT rule to a persistent file which is executed on boot.Bewail
Thanks, @afboteros, should have known that... There's a reason I'm using uncomplicated firewall after all ;-)Cordillera
Just a problem: my containers seem to not resolve DNS correctly now :/Traprock
this seems to be the most simple solution; will try it. Was thinking about binding all my containers to 127.0.0.1:<port> and use nginx reverse proxy on host to expose the containers while being able to use UFW; do you think it is a legit solution also?Corley
You SHOULD NOT use it as it removes totally DNS and I couldn't resolve any server. Please provide new/improved solution.Florinda
This is BROKEN. It causes the system to not resolve any server and I cant even ping anything on my lan. This is NOT a solution!Ellingston
The answer by @feng is better. That should be the accepted answer.Subaxillary
A
241

Problem

This problem has been around for a long time.

Disable iptables in Docker will take other problems.

Rollback changes first

If you have modified your server according to the current solution that we find on the internet, please rollback these changes first, including:

  • Enable Docker's iptables feature. Remove all changes like --iptables=false , including configuration file /etc/docker/daemon.json.
  • UFW's default FORWARD rule changes back to the default DROP instead of ACCEPT.
  • Remove the rules related to the Docker network in the UFW configuration file /etc/ufw/after.rules.
  • If you have modified Docker configuration files, restart Docker first. We will modify the UFW configuration later, and we can restart it then.

Solving UFW and Docker issues

This solution needs to modify only one UFW configuration file, all Docker configurations and options remain the default. Doesn't need to disable the docker iptables function.

Modify the UFW configuration file /etc/ufw/after.rules and add the 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

Using the command sudo systemctl restart ufw to restart UFW after changing the file. Now the public network can't access any published docker ports, the container and the private network can visit each other regularly, and the containers can also access the external network from inside.

If you want to allow public networks to access the services provided by the Docker container, for example, the service port of a container is 80. Run the following command to allow the public networks to access this service:

ufw route allow proto tcp from any to any port 80

This command allows the public network to access all published ports whose container port is 80.

Note: If we publish a port by using option -p 8080:80, we should use the container port 80, not the host port 8080.

If there are multiple containers with a service port of 80, but we only want the external network to access a particular container. For example, if the private address of the container is 172.17.0.2, use the following command:

ufw route allow proto tcp from any to 172.17.0.2 port 80

If the network protocol of service is UDP, for example, a DNS service, you can use the following command to allow the external network to access all published DNS services:

ufw route allow proto udp from any to any port 53

Similarly, if only for a specific container, such as IP address 172.17.0.2:

ufw route allow proto udp from any to 172.17.0.2 port 53

How it works?

The following rules allow the private networks to be able to visit each other. Typically, private networks are more trusted than public networks.

-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

The following rules allow UFW to manage whether the public networks are allowed to visit the services provided by the Docker container. So that we can manage all firewall rules in one place.

-A DOCKER-USER -j ufw-user-forward

The following rules block connection requests initiated by all public networks, but allow internal networks to access external networks. For TCP protocol, it prevents from actively establishing a TCP connection from public networks. For UDP protocol, all accesses to ports which is less then 32767 are blocked. Why is this port? Since the UDP protocol is stateless, it is not possible to block the handshake signal that initiates the connection request as TCP does. For GNU/Linux we can find the local port range in the file /proc/sys/net/ipv4/ip_local_port_range. The default range is 32768 60999. When accessing a UDP protocol service from a running container, the local port will be randomly selected one from the port range, and the server will return the data to this random port. Therefore, we can assume that the listening port of the UDP protocol inside all containers are less then 32768. This is the reason that we don't want public networks to access the UDP ports that less then 32768.

-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

More

https://github.com/chaifeng/ufw-docker

sudo wget -O /usr/local/bin/ufw-docker https://github.com/chaifeng/ufw-docker/raw/master/ufw-docker
chmod +x /usr/local/bin/ufw-docker

Usage

ufw-docker help
ufw-docker install
ufw-docker status
ufw-docker allow webapp
ufw-docker allow webapp 80
ufw-docker allow webapp 53/udp
ufw-docker list webapp
ufw-docker delete allow webapp 80/tcp
ufw-docker delete allow webapp

Update: 2018-09-10

The reason for choosing ufw-user-forward, not ufw-user-input

using ufw-user-input

Pro:

Easy to use and understand, supports older versions of Ubuntu.

For example, to allow the public to visit a published port whose container port is 8080, use the command:

ufw allow 8080

Con:

It not only exposes ports of containers but also exposes ports of the host.

For example, if a service is running on the host, and the port is 8080. The command ufw allow 8080 allows the public network to visit the service and all published ports whose containers' port is 8080. But we just want to expose the service running on the host, or just the service running inside containers, not the both.

To avoid this problem, we may need to use a command similar to the following for all containers:

ufw allow proto tcp from any to 172.16.0.3 port 8080

using ufw-user-forward

Pro:

Cannot expose services running on hosts and containers at the same time by the same command.

For example, if we want to publish the port 8080 of containers, use the following command:

ufw route allow 8080

The public network can access all published ports whose container ports are 8080.

But the port 8080 of the host is still not be accessed by the public network. If we want to do so, execute the following command to allow the public access the port on the host separately:

ufw allow 8080

Con:

Doesn't support older versions of Ubuntu, and the command is a bit more complicated. But you can use my script https://github.com/chaifeng/ufw-docker.

Conclusion

If we are using an older version of Ubuntu, we can use ufw-user-input chain. But be careful to avoid exposing services that should not be exposed.

If we are using a newer version of Ubuntu which is support ufw route sub-command, we'd better use ufw-user-forward chain, and use ufw route command to manage firewall rules for containers.


Update: Oct 6, 2018

The script ufw-docker supports Docker Swarm now. Please see the latest code for more, https://github.com/chaifeng/ufw-docker

Install for Docker Swarm mode

We can only use this script on manager nodes to manage firewall rules when using in Swarm mode.

  • Modifying all after.rules files on all nodes, including managers and workers
  • Deploying this script on manager nodes

Running in Docker Swarm mode, this script will add a global service ufw-docker-agent. The image chaifeng/ufw-docker-agent is also automatically built from this project.

Ardy answered 8/8, 2018 at 8:7 Comment(29)
Don't have time to implement it yet. But it seems a more sophisticated solution. Thanks!!!Haleakala
Perfect! Thought I had to replace 172.16.0.0 with 172.17.0.0Westernize
@Ardy Your solution looks interesting. There's another slightly simpler proposal here. I had no time yet to analyze the differences but maybe you can comment on that? One more thing: I think you could rename "internal port" to "host port". I was confused what "internal port" means. In the docker manual it's always either host or container port, which I find clearer.Imogen
@MichaelHärtl Thank you for your correction! My English is not good. The "internal port" means "container port" and the "external port" means "host port". I'll fix it soon. I took a quick look at the solution you mentioned. It's similar to mine. The main difference is that I use IP addresses, not ethernet cards.Ardy
@Ardy Thanks. I've also seen that you use the ufw-user-forward chain while the other solution uses ufw-user-input. I guess that's why you need ufw route .... I found that ufw route ... is not supported in older versions.Imogen
@MichaelHärtl Yes, you are right. I remembered ufw route added since Ubuntu 15.10. I don’t think using ufw-user-input is a good idea. But we can use this chain on older versions of Ubuntu. For latest version we should use ufw-user-forward.Ardy
Why do you think ufw-user-input is a bad idea? Actually I even think that it's easier to understand. When I map ports from the container to the host, I would expect that I can use the usual host rules like ufw allow http. I find the forward rule a bit harder to remember and also not as intuitive. If you could add some pro's and con's for each solution, that would be great.Imogen
@MichaelHärtl I added the reason at the end of my answer. Please let me know if there is any unclear description. Thanks!Ardy
what about docker + firewalld? do you have any directions on that? thanksWestward
@BirkhoffLee Sorry, I haven't used firewalld yet.Ardy
@Ardy works as expected. This solution is better than accepted one, since you don't have to play around with iptables. If you started using ufw, why should it be a way back to iptables solution? Thank you, it works like a charm and nothing more than adding some lines and restarting service. Good job.Touber
I reinstalled docker fresh and just added the rules at the end of after.rules, but still network to my libvirt guest isn't workingOverword
@Overword What's the version of docker/ubuntu? Did you restart the ufw service or reboot your server? It doesn't work means what? Could you show me more details? Thanks!Ardy
Ubuntu 18.04 and I tried lastest docker: both the stock docker.io package and the one provided by docker's official repo. I rebooted many times and it just behaves like default: docker breaking the bridge networkOverword
I submitted an issue on your github repo, will be easier to solve it there: github.com/chaifeng/ufw-docker/issues/11Overword
I don't understand why this is not flagged as the best answer... I've lost an entire day with the first solution until I found this.Pontificate
I've tried with Ubuntu 20.04 and latest Docker and this doesn't work. Running "docker run -it -p 8080:80 nginx" exposes the 8080 port.Venu
So the trick here is that the docker networks need to be allocated "by hand" ie not allow docker to do so. (Or else you need to figure what was allocated an expose that) Critically however, if binding to more that one network, you HAVE to specify the networks that acts as the default route "out" of the container" And this can not be pre-determined. There used to be a setting that allows for this, but it was removed from docker.Tertia
@Pontificate the first solution seems to be working fine for me. It is much simpler and requires less work. that's why it is the selected solutionCorley
Hey @Feng, Your utility has been a lifesaver in my case, but I have a drawback ... how do you handle the containers when they are destroyed and created again with a new ip? In my case I have to manually rerun the ufw-docker allow command, but I wanted to ask you if you see any way to automate this. I tried a cron task but it failed to work for me.Belabor
@Belabor Because we can create a different service with the previous same container name. We cannot guarantee they are the same service just by their names. So we’d better manually rerun the ufw-docker command.Ardy
Thak you very much!Panthia
Here comes when we all realize that SO is unable to differentiate a great answer from several other lower-ranked answers. I really expect this answer to be on top of the list.Weightlessness
"The following rules allow the private networks to be able to visit each other." -- It's not clear from the text whether these rules are optional or essential for the solution to work. Normally I don't want to expose my docker ports to the wi-fi network in a coffeshop. Will it still work if I just remove all three?Abysm
@Abysm Yes, you can change the three private networks as you want to meet your needs.Ardy
Sadly this solution does not allow containers to access host services.Maurey
It does allow services on the host to be accessed. But containers can't see anything on local lan or internet. No ping. No Tcp. Any ideas how to rectify this?Tertia
depending on your docker config something like sudo ufw allow from 172.16.0.0/12 comment 'Allow any access from Docker containers to host' (See the issue page on github)Tertia
Using ufw-user-forward as opposed to ufw-user-input is a strange choice. What you're describing as a "con" of ufw-user-input is precisely the kind of behavior one would expect, IMO.Cleruchy
M
111

I've had such problem like months ago and lately decided to describe the issue along with the solution on my blog. Here's the shortcut.

Using --iptables=false won't help you much with the case you described. It's simply not enough here. By default, none of your containers can do any outgoing connection.

There's a small step you're omitting on your way to have containers behind UFW here. You can use --iptables=false or create /etc/docker/daemon.json file with content as follows

{
  "iptables": false
}

the result will be the same, but the latter option requires you to restart whole docker service with service docker restart or even do a reboot if docker had a chance to add iptables rules before you disabled this function.

When it's done, just do two more things:

$ sed -i -e 's/DEFAULT_FORWARD_POLICY="DROP"/DEFAULT_FORWARD_POLICY="ACCEPT"/g' /etc/default/ufw
$ ufw reload

so you set up default forward policy in UFW for accept, and use:

$ iptables -t nat -A POSTROUTING ! -o docker0 -s 172.17.0.0/16 -j MASQUERADE

That way what you're achieving is disabling docker messy behavior in your iptables rules and at the same time docker is provided with necessary routing so containers will do outgoing connections just fine. UFW rules will be still restricted from this point on, though.

Hope this resolves the issue for you and any that gets here in search of an answer.

I described the problem and solution more comprehensively at https://www.mkubaczyk.com/2017/09/05/force-docker-not-bypass-ufw-rules-ubuntu-16-04/

Mouflon answered 17/9, 2017 at 17:15 Comment(10)
I added some info on how to setup the iptables rule from the actual Docker network config. This might be of interest for you.Flyleaf
You are the real MVPCytaster
This works fine! However, the nat iptables rule seems to be flushed on reboot. How can I persist it?Cordillera
To persist the iptables rule install the linux package iptables-persistent according to your server distro, in my case (Debian) is sudo apt install iptables-persistent and the package installation will add the NAT rule to a persistent file which is executed on boot.Bewail
Thanks, @afboteros, should have known that... There's a reason I'm using uncomplicated firewall after all ;-)Cordillera
Just a problem: my containers seem to not resolve DNS correctly now :/Traprock
this seems to be the most simple solution; will try it. Was thinking about binding all my containers to 127.0.0.1:<port> and use nginx reverse proxy on host to expose the containers while being able to use UFW; do you think it is a legit solution also?Corley
You SHOULD NOT use it as it removes totally DNS and I couldn't resolve any server. Please provide new/improved solution.Florinda
This is BROKEN. It causes the system to not resolve any server and I cant even ping anything on my lan. This is NOT a solution!Ellingston
The answer by @feng is better. That should be the accepted answer.Subaxillary
H
36

Not saying solutions here are wrong, but they look a bit "terrifying" and error-prune for someone looking for a quick one-step instruction. I came with this problem recently as well, have read all the similar answers online, and have not found anything quick & clear at the time of writing. Surprisingly, my alternative solution is easy to comprehend and manage, and it works: just implement your firewall outside your host machine.

Treating Firewall as first-class citizen seems to have a lot of benefits.

Huston answered 2/5, 2020 at 23:31 Comment(6)
Although I agree with you, sometimes an extra layer is also good. And sometimes, we just have to deal with a bunch of bare-metal servers.Precedent
Thank you that is a very thoughtful and helpful answer (I just went the DO firewall route, exactly as you suggested), and indeed the "terror factor" plays a role in a situation like that, where we already need to juggle with the complexity of 25 other aspects of the tech stack we're trying to bring up and running.Semiannual
Holy cow, you're right. Linode offers this as well.Volcanism
Here's a link to Linode's Firewall docs: linode.com/docs/guides/getting-started-with-cloud-firewallUpholster
That is sensible if possible.. It's a shame that docker's firewall changes are so riskyWorld
I went with this in the end. Docker/iptables/ufw is a headache, especially when you have a simple setup of 22/80/443 open and everything else shut.Panacea
H
23

I spent two hours trying out the proposals above and from other posts. The only solution that worked was from Tsuna's post in this Github thread:

Append the following at the end of /etc/ufw/after.rules (replace eth0 with your external facing interface):

# Put Docker behind UFW
*filter
:DOCKER-USER - [0:0]
:ufw-user-input - [0:0]

-A DOCKER-USER -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A DOCKER-USER -m conntrack --ctstate INVALID -j DROP
-A DOCKER-USER -i eth0 -j ufw-user-input
-A DOCKER-USER -i eth0 -j DROP
COMMIT

And undo any and all of:

  • Remove "iptables": "false" from /etc/docker/daemon.json
  • Revert to DEFAULT_FORWARD_POLICY="DROP" in /etc/default/ufw
  • Remove any docker related changes to /etc/ufw/before.rules
  • Be sure to test that everything comes up fine after a reboot. I still believe Docker's out of the box behavior is dangerous and many more people will continue to unintentionally expose internal services to the outside world due to Docker punching holes in otherwise safe iptables configs.
Humism answered 25/9, 2019 at 12:54 Comment(4)
sudo ufw reload outputs ERROR: Could not load logging rulesUser
Worked for me on a wireguard interface.Electrostriction
Your post is awesome! This is easy and WORKING solution! Thanks a million!Copulative
This is a great answer if you want to supersede docker and manage yourself access to ports and protocols from external ip addresses using ufwBrogan
H
9

I also had this problem. In my case, I decided to open the ufw firewall for the whole docker0 interface.

sudo ufw allow in on docker0

Alternatively, you can also allow the subnet docker uses. E.g.:

sudo ufw allow from 172.0.0.0/8

Both rules worked for me.

Horowitz answered 24/5, 2023 at 17:43 Comment(4)
Compare with sudo ufw default allow routed.Professoriate
Any risk to allowing from such a huge subnet?Indeterminate
Yes, of course. The larger the subnet, the higher the risk. This is just an example about how to make it work. If you are concerned about the security, you can/should of course restrict it to the IPs you actually want to use. BUT more important than that is that the applications you deploy are safe and secure, because even if you open the whole network the network would be secure, if 0 applications listen on that network. So, in my humble opinion, the number and the quality of applications you deploy are more important.Abercrombie
You can also lookup the subnet docker uses with "ifconfig": For me, it looks similiar this: docker0: inet 172.17.0.1 netmask 255.255.0.0Abercrombie
N
6

Disclaimer: This response applies to ufw (i.e. Ubuntu) As default/standard Docker bridge network works on 172.17.0.0/16 (see docker inspect bridge Subnet), the most straightforward IMHO is to do:

ufw allow from 172.17.0.0/16
Neurovascular answered 16/11, 2021 at 16:23 Comment(3)
Together agree. Don't forget DEFAULT_FORWARD_POLICY="ACCEPT"Weinman
Compare with sudo ufw default allow routed.Professoriate
For some reason I have Docker networks like 172.19.0.0/16 so this doesn't work. 172.0.0.0/8 will cover them all, but not sure if there's a potential risk there.Indeterminate
B
5

Summarizing the post from @mkubaczyk:

tell docker to stay away from my firewall

cat << EOF >> /etc/docker/daemon.json
{
     "iptables": false
}
EOF

echo "DOCKER_OPTS=\"--iptables=false\"" >>  /etc/default/docker
service docker restart

change ufw forward policy

sed -i -e 's/DEFAULT_FORWARD_POLICY="DROP"/DEFAULT_FORWARD_POLICY="ACCEPT"/g' /etc/default/ufw 

add nat rules targeting containers

cat << EOF >> /etc/ufw/before.rules
# NAT table rules
*nat
:POSTROUTING ACCEPT [0:0]

# Forward traffic through eth0 - Change to match your out-interface
-A POSTROUTING -s 10.66.66.0/24 -o ens0 -j MASQUERADE

# don't delete the 'COMMIT' line or these nat table rules won't
# be processed
COMMIT

EOF
ufw reload
Barcroft answered 21/5, 2021 at 17:38 Comment(0)
F
3

For what it's worth here's an addendum to @mkubaczyk's answer for the case where there are more bridge networks involved in the whole setup. These may be provided by Docker-Compose projects and here's how the proper rules can be generated, given that these projects are controlled by systemd.

/etc/systemd/system/[email protected]

[Unit]
Description=Docker-Compose project: %I
After=docker.service
BindsTo=docker.service
AssertPathIsDirectory=/<projects_path>/%I
AssertFileNotEmpty=/<projects_path>/%I/docker-compose.yml

[Service]
Type=simple
Restart=always
WorkingDirectory=/<projects_path>/%I
ExecStartPre=/usr/bin/docker-compose up --no-start --remove-orphans
ExecStartPre=+/usr/local/bin/update-iptables-for-docker-bridges
ExecStart=/usr/bin/docker-compose up
ExecStop=/usr/bin/docker-compose stop --timeout 30
TimeoutStopSec=30
User=<…>
StandardOutput=null

[Install]
WantedBy=multi-user.target

/usr/local/bin/update-iptables-for-docker-bridges

#!/bin/sh

for network in $(docker network ls --filter 'driver=bridge' --quiet); do
  iface=$(docker network inspect --format '{{index .Options "com.docker.network.bridge.name"}}' ${network})
  [ -z $iface ] && iface="br-${network}"
  subnet=$(docker network inspect --format '{{range .IPAM.Config}}{{.Subnet}}{{end}}' ${network})
  rule="! --out-interface ${iface} --source ${subnet} --jump MASQUERADE"
  iptables --table nat --check POSTROUTING ${rule} || iptables --table nat --append POSTROUTING ${rule}
done

Obviously, this won't scale that well.

It's also noteworthy that the whole basic concept will disguise the source of any connection for the applications running in a container.

Flyleaf answered 5/3, 2018 at 12:43 Comment(0)
M
3

I don't like the operational overhead required by the iptables: false flag in docker daemon. In fact, from what I see, and please correct me if I am wrong, all the solutions are way too complicated hacks.

Just insert this in /etc/ufw/after.rules, before the *filter section:

*mangle
# Allow a whitelisted ip to access postgres port
-I PREROUTING 1 -s <whitelisted_ip> -p tcp --dport 5432 -j ACCEPT
# Allow everyone to access port 8080
-I PREROUTING 2 -p tcp --dport 8080 -j ACCEPT
# Drop everything else
-I PREROUTING 3 -p tcp -j DROP
COMMIT

There is no need to mess with docker networking or with unnecessary hacks.

Mcneil answered 12/2, 2020 at 19:30 Comment(1)
This dose not work ... some exploration would have been great .. just punting ..some directives is not enough for someone searching .. could be for a pro ..User
O
2

Sorry for digging up this old thread. I had the same problem and it helped me just to constrain ufw to specific ip and interface. Because by default ufw is applied on all network interface, also the internal one from docker. Thats why all this nice docker port forwarding story (like -p80:8080) don't work. To overcome this problem just specify an specific interface and ip on what ufw should be applied. In my case it was the one that is exposed to the world on a server.

ufw allow in on eth0 to ip_of_eth0 port 22 proto tcp
ufw allow in on eth0 to ip_of_eth0 port 80 proto tcp
ufw allow in on eth0 to ip_of_eth0 port 443 proto tcp

change eth0 to your desired interface.

With this solution it is possible now without messing with iptables or the iptables:false in /etc/docker/daemon.json flag to expose only that ports that are really needed.

Output of nmap from outside computer:

Starting Nmap 7.91 ( https://nmap.org ) at <time>
Nmap scan report for <domain> (ip)
Host is up (0.042s latency).
Not shown: 997 filtered ports
PORT    STATE SERVICE
22/tcp  open  ssh
80/tcp  open  http
443/tcp open  https

Nmap done: 1 IP address (1 host up) scanned in 11.44 seconds
Orlop answered 20/1, 2021 at 8:43 Comment(0)
M
1

UFW is quite simple and I don't want to dive into iptables if I am not forced to. Also Docker behavior regarding iptables / ufw seems ok to me, although not enough documented. My opinion is that when starting containers, one should understand exactly whats going on with exposed ports. Then the docker ps command gives a good feedback about what's going on.

Let's run a MariaDb container :

$ docker run --detach --env MARIADB_ROOT_PASSWORD="superSecret" mariadb:10.4

$ docker ps --format "table {{.Names}}\t{{.Ports}}"
NAMES           PORTS
happy_jackson   3306/tcp

Here the PORTS columns shows 3306/tcp : port 3306 is potentially available but actually not published, meaning that the 3306 port is not accessible neither from the host nor from the host network.

Let's run another MariaDb container :

$ docker run --detach --env MARIADB_ROOT_PASSWORD="superSecret" -p 3306:3306 mariadb:10.4

$ docker ps --format "table {{.Names}}\t{{.Ports}}"
NAMES              PORTS
trusting_goodall   0.0.0.0:3306->3306/tcp

Now the PORTS columns shows 0.0.0.0:3306->3306/tcp : port is published, meaning that it port is available from the host and from the host network.

Let's run a last MariaDb container :

$ docker run --detach --env MARIADB_ROOT_PASSWORD="superSecret" -p 127.0.0.1:3306:3306 mariadb:10.4

$ docker ps --format "table {{.Names}}\t{{.Ports}}"
NAMES             PORTS
quizzical_gauss   127.0.0.1:3306->3306/tcp

Now the PORTS columns shows 127.0.0.1:3306->3306/tcp : port 3306 is locally published, meaning that it is available only from the host, not from the host network.

So yes Docker has to tweak UFW, but that's only to achive what's has been asked : expose a port locally or to the network. So as long as you know what you're doing with ports publishing, you should be safe.

Also, while I am not a network security specialist, having done some full ports scans on my servers has reassured me : the results that I got are in the line with what was expected.

Mcmurry answered 29/11, 2021 at 9:31 Comment(0)
L
1

I had a similar case.

The way I solved it was to create a custom network and define it as external.

docker network create my_app_net

# put this in all the project related containers' docker compose files.
networks:
      - my_app_net

networks:
  my_app_net:
    external: true

Then I was able to connect to the container via hostname that I had defined (in docker compose).

hostname: "my_app_db" container_name: "my_app_db"

Then I was able to connect from one of the containers to the db server. I also have made sure that the server binds to all IPs e.g. 0.0.0.0 (I am using a custom my.cnf file for this) mysql -uUSER -pPASS -hDOCKER_HOST --port 3306 --protocol=tcp DB_NAME

Another important detail is when creating the mysql user is to allow it to connect from any host by specifying % as db host and not user@localhost.

Lananna answered 14/4, 2022 at 8:24 Comment(0)
P
0

From what I can gather you would like better control over who can access your apps running inside Docker? I have answered a similar question here to control traffic via a front end proxy rather than with IP tables: Block external access to docker containers

Edit

With the above approach you can then use UFW to only allow incoming connections to port 80 (ie the proxy). This keeps any port exposure to a minimum with the added bonus that you can control traffic through a proxy configuration & DNS.

Pompom answered 22/2, 2017 at 10:46 Comment(0)
D
0

You can also choose to attach containers to the host network if network isolation is not much of importance to your application.

Reference:

Desirae answered 9/12, 2021 at 15:46 Comment(1)
Yeah, but you have to have custom images and expose different ports on each containerLananna
F
0

In Ubuntu 22.04 names of the chains are changed. I added in the file /etc/ufw/after.rules

-A ufw-after-forward -j ACCEPT -s 172.16.0.0/12

before COMMIT

Fernyak answered 9/1, 2023 at 1:37 Comment(0)
C
0

This worked for me: (also see https://docs.docker.com/network/iptables/)

sudo iptables -I DOCKER-USER -i eth0 ! -s 10.56.122.0/24 -j DROP

Specifically it add a rule that drops all traffic except traffic from 10.56.122.0, which in my case was the private network between machines.

Result: Public traffic could not get through and traffic from local network could get through.

Crowberry answered 26/2, 2023 at 14:51 Comment(0)
D
0

This Answer worked fine for me: https://mcmap.net/q/142970/-what-is-the-best-practice-of-docker-ufw-under-ubuntu (sadly i cannot add a comment) but it had one problem. If I disable and enable UFW it will not work properly.

Just use this Guide: https://unix.stackexchange.com/questions/617240/ufw-iptables-jump-rule-errors-with-could-not-load-logging-rules

And replace iptables -F FOO-CHAIN || true with iptables -F DOCKER-USER || true

Danita answered 10/10, 2023 at 22:11 Comment(0)
P
0

All I had to do is run sudo ufw default allow routed. This allowed outgoing docker connections.

My ufw defaults now are:

dev@localhost ~/p/irc-bots (master)> sudo ufw status verbose | grep ^Default
Default: deny (incoming), allow (outgoing), allow (routed)
Professoriate answered 29/3 at 19:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.