我有一台服务器,只希望通过 SSH 在公共互联网上访问,但我希望它能够连接到 VPN,并将服务绑定到其 VPN 接口。
我正在使用 Linode 来托管 Ubuntu 16.04.2 LTS VM、使用 ZeroTier 作为我的 VPN 层,并使用 Swarm 模式(只有一个节点)的 Docker 来提供服务。
ifconfig -a
在 VM 上运行将返回此结果(为保护隐私,公共 IP 已被删除):
docker0 Link encap:Ethernet HWaddr 02:42:f7:e7:e6:1e
inet addr:172.17.0.1 Bcast:0.0.0.0 Mask:255.255.0.0
UP BROADCAST MULTICAST MTU:1500 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
docker_gwbridge Link encap:Ethernet HWaddr 02:42:ad:82:56:0f
inet addr:172.18.0.1 Bcast:0.0.0.0 Mask:255.255.0.0
inet6 addr: fe80::42:adff:fe82:560f/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:6083 errors:0 dropped:0 overruns:0 frame:0
TX packets:16927 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:6364682 (6.3 MB) TX bytes:49725061 (49.7 MB)
eth0 Link encap:Ethernet HWaddr f2:3c:91:a7:c3:ac
inet addr:<REDACTED> Bcast:<REDACTED> Mask:255.255.255.0
inet6 addr: <REDACTED>/64 Scope:Global
inet6 addr: <REDACTED>/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:94675 errors:0 dropped:0 overruns:0 frame:0
TX packets:47778 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:59390529 (59.3 MB) TX bytes:14765979 (14.7 MB)
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
inet6 addr: ::1/128 Scope:Host
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:54171 errors:0 dropped:0 overruns:0 frame:0
TX packets:54171 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1
RX bytes:6243573 (6.2 MB) TX bytes:6243573 (6.2 MB)
veth8323309 Link encap:Ethernet HWaddr 82:b3:ec:d8:8c:9b
inet6 addr: fe80::80b3:ecff:fed8:8c9b/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:15 errors:0 dropped:0 overruns:0 frame:0
TX packets:538 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:1248 (1.2 KB) TX bytes:22932 (22.9 KB)
veth602daf7 Link encap:Ethernet HWaddr 82:7e:cf:b3:cb:a4
inet6 addr: fe80::807e:cfff:feb3:cba4/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:3065 errors:0 dropped:0 overruns:0 frame:0
TX packets:11154 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:6187902 (6.1 MB) TX bytes:794193 (794.1 KB)
zt0 Link encap:Ethernet HWaddr 42:08:c2:e8:fc:93
inet addr:192.168.196.106 Bcast:192.168.196.255 Mask:255.255.255.0
inet6 addr: fe80::4008:c2ff:fee8:fc93/64 Scope:Link
inet6 addr: fc7b:7a95:194:6f84:bf9a::1/40 Scope:Global
UP BROADCAST RUNNING MULTICAST MTU:2800 Metric:1
RX packets:442 errors:0 dropped:0 overruns:0 frame:0
TX packets:279 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:52165 (52.1 KB) TX bytes:46603 (46.6 KB)
我的 Docker 撰写文件如下所示:
version: "3"
services:
jenkins:
image: jenkins/jenkins:lts
ports:
- "80:8080"
volumes:
- jenkins_home:/var/jenkins_home
deploy:
replicas: 1
volumes:
jenkins_home:
但是,我似乎无法正确设置 iptables 规则。我原本希望能够这样做:
# Allow established connections, including outbound connections this server initiated, to continue to receive replies
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
# Allow SSH on eth0
iptables -A INPUT -i eth0 -p tcp --dport 22 -j ACCEPT
# Allow UDP 9993 on eth0 (required for ZeroTier)
iptables -A INPUT -i eth0 -p udp --dport 9993 -j ACCEPT
iptables -A OUTPUT -p udp --dport 9993 -j ACCEPT
# Reject everything that hasn't already been accepted (rules are applied in order)
iptables -A INPUT -i eth0 -j REJECT
但是,当我部署并启动 Docker Swarm 服务时,我最终能够从不在 VPN 上的远程主机通过 eth0 上的地址访问该服务,从 VPN 上的远程主机通过 zt0 上的 VPN 地址访问该服务,但是不是来自虚拟机本身的本地主机。
我在这里做错了什么?如何获取 iptables 规则以阻止来自公共互联网的流量但仅允许来自 ZeroTier VPN 的流量?
答案1
经过进一步研究,我发现了我的问题。
其要点是 Docker 使用自己的链来转发数据包。Docker 守护进程启动时以及部署新容器时,它会修改DOCKER
和DOCKER-INGRESS
链,但不会修改DOCKER-USER
用于管理员定义的防火墙规则(我想要应用的那种类型)的链。
有了这些知识,我就能通过以下 iptables 规则得到我想要的东西:
#!/bin/sh
set -e
set -u
# REMINDER: DROP vs REJECT:
# a DROP operation blackholes the packet, likely causing client to wait dozens of seconds for a timeout
# a REJECT operation sends an ICMP response letting the client know the packet was rejected
# REMINDER: IPv4 vs IPv6
# `iptables` only applies IPv4 rules; `ip6tables` only lapplies IPv6 rules.
## System firewall rules
# Allow any established sessions from our container host - including replies to outbound queries - to receive traffic
iptables --append INPUT -m conntrack --ctstate ESTABLISHED,RELATED --jump ACCEPT
ip6tables --append INPUT -m conntrack --ctstate ESTABLISHED,RELATED --jump ACCEPT
# Allow SSH on eth0
iptables --append INPUT --in-interface eth0 --protocol tcp --dport 22 --jump ACCEPT
ip6tables --append INPUT --in-interface eth0 --protocol tcp --dport 22 --jump ACCEPT
# Allow UDP 9993 on eth0 (required for ZeroTier)
iptables --append INPUT --in-interface eth0 --protocol udp --dport 9993 --jump ACCEPT
ip6tables --append INPUT --in-interface eth0 --protocol udp --dport 9993 --jump ACCEPT
iptables --append OUTPUT --protocol udp --dport 9993 --jump ACCEPT
ip6tables --append OUTPUT --protocol udp --dport 9993 --jump ACCEPT
# Reject everything to eth0 that hasn't been accepted by a previous rule
iptables --append INPUT --in-interface eth0 --jump REJECT
ip6tables --append INPUT --in-interface eth0 --jump REJECT
## Docker firewall rules
# NOTE: Docker does not enable IPv6 networking by default, so there is no need for `ip6tables` here
# <https://docs.docker.com/engine/userguide/networking/default_network/ipv6/>
# NOTE: Docker promises not to modify the DOCKER-USER chain so that we can do this type of filtering
# Create the chain we need if Docker hasn't done so already
iptables --new DOCKER-USER 2>/dev/null || true
# Allow any established sessions from our containers - including replies to outbound queries - to receive traffic
iptables --append DOCKER-USER --match conntrack --ctstate ESTABLISHED,RELATED --jump ACCEPT
# Reject packets that would be forwarded to Docker when they come in over eth0 (the public interface)
iptables --append DOCKER-USER --in-interface eth0 ---jump REJECT
请注意,其中一些规则并非特定于 Docker,而是用于拒绝发往我的 Docker 服务器的数据包 - 我只想接受发往服务器本身的 VPN 和 SSH 数据包。