我在 AWS EC2 实例上运行 docker,我想阻止某些容器访问 EC2 实例元数据(IP 地址为 169.254.169.254)。我认为我可以通过以特定用户(例如 userx)身份运行这些容器来实现这一点,并遵循以下 ip 表规则:
$ iptables -A OUTPUT -m owner --uid-owner userx -d 169.254.169.254 -j DROP
当容器使用主机网络运行时,这将按预期阻止连接:
$ docker run -it --rm --network host -u $(id -u userx):$(id -g userx) appropriate/curl http://169.254.169.254/latest/meta-data/
...blocks..
但遗憾的是,当容器在其自己的网络中运行时,允许连接
$ docker run -it --rm -u $(id -u userx):$(id -g userx) appropriate/curl http://169.254.169.254/latest/meta-data/
...show metadata...
我该如何实现这一点?或者,是否有其他技术可以让特定容器获得完全网络访问权限,同时阻止实例元数据?
答案1
您的问题是OUTPUT
无法捕获从容器中出来的数据包FORWARD
。
这是为什么?
每个 Docker 容器都在自己的网络命名空间中运行。每个网络命名空间都有自己的路由表和 iptables 规则,其行为方式与单独的物理机器完全相同。
在 iptables 中:
INPUT
匹配发往本地进程的数据包FORWARD
匹配从一个网络接口进入并从另一个网络接口出去(通过路由)的数据包。OUTPUT
匹配来自本地进程的数据包
关键是“本地进程”的意思是“此网络命名空间中的进程”,而不是“此机器中的进程”。
我们来分析一下发生了什么:
- 数据包由 Docker 容器的网络命名空间中的进程生成。
- 它们通过
OUTPUT
容器的网络命名空间 iptables 中的 iptables 链。(它是空的!) - 它们被路由出了
veth
接口。 - 它们从接口到达主机的网络命名空间
veth
。 - 主机网络命名空间查阅路由表并决定它们需要离开例如
eth0
。 - 它们穿过
FORWARD
主机网络命名空间中的 iptables 链。 - 他们出去了
eth0
。
因此,解决方案是将规则放入FORWARD
链中。
问题是-m owner
在 中不起作用FORWARD
。根据man iptables-extensions
:
此匹配仅在 OUTPUT 和 POSTROUTING 链中有效。转发的数据包没有任何与之关联的套接字。
您可以对容器的 IP 地址进行硬编码,或者将要过滤的容器放在特殊网络中,并匹配整个范围。类似这样的操作应该有效:
# single container
iptables -A FORWARD -s 172.17.0.4 -d 169.254.169.254 -j DROP
# or entire network
iptables -A FORWARD -s 172.17.0.0/16 -d 169.254.169.254 -j DROP
此外,owner
无论如何使用可能都不是一个好主意,因为如果图像中有的话,docker 容器内的进程可以通过 setuid 二进制文件(如 sudo)更改其 uid。