Controlled container communication
Return to Sender
Docker enables the configuration of virtual networks and, for its part, makes extensive use of iptables on Linux to configure network communication between the containers, the host system, and remote computers. However, if you want to secure running containers, it is not enough to just look at the host's INPUT chain and filter incoming traffic.
As a firewall administrator, you might know the feeling when you have added a filter rule to your ruleset and realize afterward that it does not serve the intended purpose. Iptables as a packet filter is still the tool of choice on Linux systems. However, automatically added rules (e.g., those created by the Docker daemon) always lead to side effects in manually or semi-automatically created rulesets. The bigger security problem, however, arises when a rule is supposed to filter incoming packets, but it is not taken into account when packets for Docker containers are received.
Tables and Chains
The basic organization of filter rules in iptables is easy to explain. The three best-known tables are filter , mangle , and NAT . The filter table is used mainly to create those rules that make up a packet filter. The mangle table lets you specifically manipulate the fields of the IP header and mark the packets in the kernel to identify them in other rules when passing through the iptables chains.
Within the NAT table, you define rules to perform address translation for the packets during packet forwarding. On your home router, for example, you can use the NAT table to send packets from your private network area to the Internet and to reassign received packets to the corresponding computers on your private network.
The raw and security tables are used far less frequently and provide functionality to prevent connection tracking or to mark packets in SELinux contexts.
Each of the five tables have different rule chains that are run through in sequence from top to bottom until a rule is applied to the checked package. In addition to fixed chains, users can define additional chains that are mainly used to structure and order rules and to facilitate the automated creation and modification of rules.
Rules for Docker
The Docker daemon, which is a mandatory prerequisite for Docker container virtualization, already creates its own chains and rules at startup. Without a running container, however, they are only the substructure for ordering the rules that are later created automatically. As you can see in Figure 1, the following four chains are created in the filter table: DOCKER, DOCKER-USER, DOCKER-ISOLATION-STAGE1, and DOCKER-ISOLATION-STAGE2. With the exception of the DOCKER-USER chain, you should not change these chains, if possible.
Docker uses a virtualized network with its own interface, normally named docker0 . Rules are used in the FORWARD chain for forwarding packets on this interface to the running containers. Docker uses private IP addresses from the range 172.16.0.0/20 for the interface and the containers.
To enable network access of the host system from the containers, corresponding rules with source and destination NAT are created in the NAT table for each container. With these rules, the container communication works in all directions, as well as between containers. If you create a separate network for your containers, Docker creates a separate bridge interface for each of these networks and then automatically extends the filter rules with corresponding rules for the bridge interfaces.
Pitfalls
Changes to the rules and chains in the iptables filter tables can sometimes cause your containers to become unreachable. Therefore, iptables is always one of the first places to go in case of container network problems. Depending on how you manage your own rules, you might use the -F
option to flush the tables or individual chains in them before creating them. After a flush, your Docker containers are isolated from the outside world and from each other, as expected. Before you try to read the automatically created rules in their tools and reinstate them after a flush, restart the Docker daemon to help you recover sensibly.
Generally, to prevent access to services on your computer, you usually want to create rules in the INPUT chain of the filter table. At best, you set the policy of this chain to DROP and release individual ports or protocols as required. You will quickly notice that your container services are still accessible from the outside despite this measure. Attempts to set iptables rules inside the containers usually fail because of missing permissions. You could start your Docker container in privileged mode for additional capabilities, but for various reasons, this step is not desirable, at least not for every container.
If you want to restrict access to a container's services from the outside, you need to create additional rules in the FORWARD chain. However, although Docker leaves the INPUT and OUTPUT chains untouched, it uses the FORWARD chain very often and quite recklessly. Because Docker always places itself at the top of the chains, these rules are applied first; the manual rules then sometimes slip further and further down the chain – and are no longer considered at all in some cases as a consequence.
Docker offers the DOCKER-USER chain for your own rules. Take another look at the rules in Figure 1. There, you can see that this chain is checked before the other rules listed in the FORWARD chain. Your big chance to enter your filter rules is here.
In the following discussion, I assume that you are running a container with its own virtual network (i.e., with a randomly named bridge). If you now want to prevent access to port 443 of a container on this network, for example, you first need to find out which bridge is assigned to the virtual network:
docker network list
If you look for the name of the network, the network ID will come first in the entry. By default, Docker names bridges br-<networkID> . You can output an overview of all bridges with the
brctl show
command. To discover the Docker-assigned address range of the bridge, use ifconfig
and pass in the name of the bridge as an argument:
ifconfig br-2881be13f7f9
Now you can further restrict access to this network from outside. The following command prevents access to the MySQL database inside the network:
iptables -I DOCKER-USER -o br-2881be13f7f9 -p tcp --dport 3306 -j DROP
Of course, you can also allow or prevent access to individual services for the virtual networks on the basis of the individual IP addresses of your containers, as long as you have assigned them static IP addresses.
If you need other rules for forwarding packets for your host system, independently of Docker, your best approach is to use the DOCKER-USER chain here, too. Although Docker is unlikely to filter out packets that are not addressed to containers, your own rules in the DOCKER-USER chain are always executed before any others and apply not just to targets in Docker containers.
Buy this article as PDF
(incl. VAT)