用iptables关闭docker映射到host上的端口

docker可以让我们很方便地安装本地服务。但同时,默认的docker的设置使这些端口可以很轻松地从remote访问。
理想的做法是利用nginx把docker 默认打开的端口反向代理到别的端口,然后对新的端口进行用户验证保护。

屏蔽外部端口访问我们很自然而然就想到了使用iptables。

目的

我们在运行某个container的时候,使用了端口映射,例如

$ docker docker run --name myservice -p 5000:5000 some_image_name

我们想要仅允许在服务器上通过localhost:5000访问docker container的服务,而屏蔽掉外部通过myhostname:5000来访问服务的request。

错误的尝试

$ sudo iptables -A INPUT -p tcp -m tcp --dport 5000 -j ACCEPT

尝试在浏览器输入myhostname:5000,发现依然可以访问到5000端口。

这个是为什么呢?

选择正确的chain

第一次通过简单搜索谷歌,复制粘贴的方法失败了。看来还得静下来看一下iptables这个东西。

这篇文章https://securitynik.blogspot.jp/2016/12/docker-networking-internals-how-docker_16.html

很好解释了docker中iptables的用法。

然而iptables的概念太多。我们把范围缩小到filter这个table中。

简要来说,我们通常需要处理的是三条chain(INPUT, FORWARD, OUTPUT)。所有的package 通过相应的chain,chain中的规则进行match。如果有match的规则,则执行规则规定的动作,否则继续向后访问规则,最后最后如果没有匹配的规则,则使用chain的默认的policy来处理。 默认的Policy可以是 ACCEPT 或者 DROP。分别表示接受和丢弃该Package。 被Drop的表现通常是,在浏览器上,显示load中但是总是无法出来结果。

当我们启动docker deamon,挂上docker container的时候,docker会在FORWARD chain中追加叫DOCKER和DOCKER-ISOLATION的自定义CHAIN。

由此可见。我们要追加的规则应该追加在FORWARD chain而不是在INPUT chain。

正确的做法是

$ sudo iptables -I FORWARD -p tcp -m tcp --dport 5000 -j ACCEPT

但是注意,docker每次重启之后都会把自定义的DOCKER chain 插到FORWARD chain的第一个。所以,我们不如把这个规则写到DOCKER chain中。

$ sudo iptables -I DOCKER -p tcp -m tcp --dport 5000 -j ACCEPT

其他疑惑

  • 为什么iptables知道特定的外部reqeust需要走的是FORWARD而不是INPUT的chain?

这个是因为在filter table之前由NAT table替换了走向docker的request的destination。并不是所有到本机的请求都是走INPUT chain的,想象一下如果本机是NAT的gateway,那么很显然,大部分package需要转发到下面去。

  • 为什么经常有两条一摸一样的规则?

光用iptables -L的话会看到几乎完全一样的两行,我们查看具体内容需要iptables -v,这样可以看到in out两个参数,这两个参数分别为in的网卡端口,和out的网卡端口。

标签:IptablesDocker 发布于:2019-11-14 18:08:05