Requirements
Docker Server Version: 20.10.12
On a server with Docker, I need to restrict external access using iptables as a firewall.
Iptables && Docker
iptables is divided into three levels: tables, chains and rules. We generally only use the filter table, which contains.
- INPUT, input chain. Packets sent to this machine pass through this chain.
- OUTPUT, the output chain. Packets sent from this machine pass through this chain.
- FORWARD, the forwarding chain. Packets forwarded by this machine pass through this chain.
Since Docker connects the default virtual bridge (docker0) to the container’s default gateway (ens33) via NAT (Network Address Translation) by default, setting INPUT is useless and the FORWARD chain should be set. And since the FORWARD chain defaults to DROP
, all forwarding is blocked by DOCKER-USER
.
1
2
3
4
5
6
7
8
|
Chain FORWARD (policy DROP)
target prot opt source destination
DOCKER-USER all -- 0.0.0.0/0 0.0.0.0/0
DOCKER-ISOLATION-STAGE-1 all -- 0.0.0.0/0 0.0.0.0/0
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHED
DOCKER all -- 0.0.0.0/0 0.0.0.0/0
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0
|
Therefore, we have to use the DOCKER-USER
chain to set the container access rules, which is implemented as follows.
Note: The firewall manager should be closed.
1
2
3
4
5
|
# Close the firewall manager
for target in firewalld python-firewall firewalld-filesystem iptables; do
systemctl stop $target &>/dev/null || true
systemctl disable $target &>/dev/null || true
done
|
Option 1
You need to install iptables-services: yum install -y iptables-services
.
System’s rules
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
# 先允许所有,这里很重要,不加会被拒绝
iptables -P INPUT ACCEPT
iptables -F # 清空所有默认规则
iptables -X # 清空所有自定义规则
iptables -Z # 所有计数器归0
# 对外开放的端口
iptables -A INPUT -p tcp -m multiport --dport 22 -j ACCEPT
# 允许ping
iptables -A INPUT -p icmp -j ACCEPT
# 允许主动连接其他主机
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
# 其他入站一律丢弃
iptables -P INPUT DROP
# 所有出站一律绿灯
iptables -P OUTPUT ACCEPT
# 保存规则
service iptables save
|
docker’s rules
1
2
|
# Restart docker, generate docker's rules
systemctl restart docker
|
Rules for the DOCKER-USER
chain
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
# 其他入站一律丢弃
iptables -I DOCKER-USER -i ens33 ! -s 127.0.0.1 -j DROP
# 允许主动连接其他主机
iptables -I DOCKER-USER -m state --state established,related -j ACCEPT
iptables -A DOCKER-USER -m conntrack --ctstate established,related -j ACCEPT
# 对外开放的端口
iptables -I DOCKER-USER -p tcp -m conntrack --ctorigdstport 80 -j ACCEPT
iptables -I DOCKER-USER -p tcp -m conntrack --ctorigdstport 443 -j ACCEPT
# 保存规则
service iptables save
|
ens33 is my node’s external network interface.
The iptables rule at this point
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
# iptables -L -n
Chain INPUT (policy DROP)
target prot opt source destination
ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 multiport dports 22
ACCEPT icmp -- 0.0.0.0/0 0.0.0.0/0 icmptype 0
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHED
Chain FORWARD (policy DROP)
target prot opt source destination
DOCKER-USER all -- 0.0.0.0/0 0.0.0.0/0
DOCKER-ISOLATION-STAGE-1 all -- 0.0.0.0/0 0.0.0.0/0
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHED
DOCKER all -- 0.0.0.0/0 0.0.0.0/0
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
Chain DOCKER (1 references)
target prot opt source destination
ACCEPT tcp -- 0.0.0.0/0 172.17.0.2 tcp dpt:8080
ACCEPT tcp -- 0.0.0.0/0 172.17.0.2 tcp dpt:80
ACCEPT tcp -- 0.0.0.0/0 172.17.0.3 tcp dpt:80
Chain DOCKER-ISOLATION-STAGE-1 (1 references)
target prot opt source destination
DOCKER-ISOLATION-STAGE-2 all -- 0.0.0.0/0 0.0.0.0/0
RETURN all -- 0.0.0.0/0 0.0.0.0/0
Chain DOCKER-ISOLATION-STAGE-2 (1 references)
target prot opt source destination
DROP all -- 0.0.0.0/0 0.0.0.0/0
RETURN all -- 0.0.0.0/0 0.0.0.0/0
Chain DOCKER-USER (1 references)
target prot opt source destination
ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 ctorigdstport 80
ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 ctorigdstport 443
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHED
DROP all -- !127.0.0.1 0.0.0.0/0
RETURN all -- 0.0.0.0/0 0.0.0.0/0
|
This implementation has a drawback, that is, every time the docker container changes, you need to save the current iptables configuration, otherwise when restarting the iptables service, it will load the old rules, which will lead to confusing iptables rules.
Option 2
To solve the above problem, we can
- Create a new chain called FILTERS into which network traffic from INPUT and DOCKER-USER is placed, and store this configuration in a file.
- Create an iptables systemd service to reload the iptables rules.
Here we don’t need the system installation of iptables-services, use the command to uninstall it. yum remove -y iptables-services
Create the iptables.conf rule file
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
cat <<EOF > /etc/iptables.conf
*filter
# Reset counters
:INPUT ACCEPT [0:0]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [0:0]
:DOCKER-USER - [0:0]
:FILTERS - [0:0]
# Flush
-F INPUT
-F DOCKER-USER
-F FILTERS
# Select
-A INPUT -i lo -j ACCEPT
-A INPUT -i ens33 -j FILTERS
-A DOCKER-USER -i ens33 -j FILTERS
-A DOCKER-USER -j RETURN
# Filters
## icmp
-A FILTERS -p icmp --icmp-type any -j ACCEPT
## Activate established connexions
-A FILTERS -m state --state ESTABLISHED,RELATED -j ACCEPT
-A FILTERS -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
## Allow ssh
-A FILTERS -p tcp -m multiport --dport 22 -j ACCEPT
## Allow all on http/https
-A FILTERS -p tcp -m tcp -m conntrack --ctorigdstport 80 -j ACCEPT
-A FILTERS -p tcp -m tcp -m conntrack --ctorigdstport 443 -j ACCEPT
# Block all external
-A FILTERS -i ens33 -j DROP
-A FILTERS -j RETURN
## Commit
COMMIT
EOF
|
ens33 is my node’s external network interface
Create system services
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
cat << EOF > /usr/lib/systemd/system/iptables.service
[Unit]
Description=Restore iptables firewall rules
Before=network-pre.target
[Service]
Type=oneshot
ExecStart=/sbin/iptables-restore -n /etc/iptables.conf
[Install]
WantedBy=multi-user.target
EOF
systemctl enable iptables
systemctl start iptables
|
iptables-restore -n turns off implicit global refresh and only performs our manual explicit refresh.
The iptables rule at this point
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
Chain INPUT (policy ACCEPT)
target prot opt source destination
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0
FILTERS all -- 0.0.0.0/0 0.0.0.0/0
Chain FORWARD (policy DROP)
target prot opt source destination
DOCKER-USER all -- 0.0.0.0/0 0.0.0.0/0
DOCKER-ISOLATION-STAGE-1 all -- 0.0.0.0/0 0.0.0.0/0
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHED
DOCKER all -- 0.0.0.0/0 0.0.0.0/0
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
Chain DOCKER (1 references)
target prot opt source destination
Chain DOCKER-ISOLATION-STAGE-1 (1 references)
target prot opt source destination
DOCKER-ISOLATION-STAGE-2 all -- 0.0.0.0/0 0.0.0.0/0
RETURN all -- 0.0.0.0/0 0.0.0.0/0
Chain DOCKER-ISOLATION-STAGE-2 (1 references)
target prot opt source destination
DROP all -- 0.0.0.0/0 0.0.0.0/0
RETURN all -- 0.0.0.0/0 0.0.0.0/0
Chain DOCKER-USER (1 references)
target prot opt source destination
FILTERS all -- 0.0.0.0/0 0.0.0.0/0
RETURN all -- 0.0.0.0/0 0.0.0.0/0
Chain FILTERS (2 references)
target prot opt source destination
ACCEPT icmp -- 0.0.0.0/0 0.0.0.0/0 icmptype 255
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHED
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHED
ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 multiport dports 22
ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp ctorigdstport 80
ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp ctorigdstport 443
DROP all -- 0.0.0.0/0 0.0.0.0/0
RETURN all -- 0.0.0.0/0 0.0.0.0/0
|
If we need to update the rules, just edit the /etc/iptables.conf
file and execute systemctl restart iptables
to reload the rules.