1.5 iptables
iptables在Docker和Kubernetes网络中应用甚广。例如,Docker容器和宿主机的端口映射、Kubernetes Service的默认模式、CNI的portmap插件、Kubernetes网络策略等都是通过iptables实现的,因此本节补充一些介绍iptables的基本知识,这对后面理解这些概念大有裨益。由于iptables涉及的知识点太广,面面俱到的话足以单独写成一本书(有不少专门讲解iptables的英文书籍),例如,Linux Iptables Pocket Reference,感兴趣的读者可以自行翻阅。本节只介绍在后面章节会用到的iptables的基本工作机制和相关用法。
1.5.1 祖师爷netfilter
iptables的底层实现是netfilter。netfilter是Linux内核2.4版引入的一个子系统,最初由Linux内核防火墙和网络的维护者Rusty Russell提出。它作为一个通用的、抽象的框架,提供一整套hook函数的管理机制,使得数据包过滤、包处理(设置标志位、修改TTL等)、地址伪装、网络地址转换、透明代理、访问控制、基于协议类型的连接跟踪,甚至带宽限速等功能成为可能。netfilter的架构就是在整个网络流程的若干位置放置一些钩子,并在每个钩子上挂载一些处理函数进行处理。
IP层的5个钩子点的位置,对应iptables就是5条内置链,分别是PREROUTING、POSTROUTING、INPUT、OUTPUT和FORWARD。netfilter原理图如图1-13所示。
图1-13 netfilter原理图
当网卡上收到一个包送达协议栈时,最先经过的netfilter钩子是PREROUTING,如果确实有用户埋了这个钩子函数,那么内核将在这里对数据包进行目的地址转换(DNAT)。不管在PREROUTING有没有做过DNAT,内核都会通过查本地路由表决定这个数据包是发送给本地进程还是发送给其他机器。如果是发送给其他机器(或其他network namespace),就相当于把本地当作路由器,就会经过netfilter的FORWARD钩子,用户可以在此处设置包过滤钩子函数,例如iptables的reject函数。所有马上要发到协议栈外的包都会经过POSTROUTING钩子,用户可以在这里埋下源地址转换(SNAT)或源地址伪装(Masquerade,简称Masq)的钩子函数。如果经过上面的路由决策,内核决定把包发给本地进程,就会经过INPUT钩子。本地进程收到数据包后,回程报文会先经过OUTPUT钩子,然后经过一次路由决策(例如,决定从机器的哪块网卡出去,下一跳地址是多少等),最后出协议栈的网络包同样会经过POSTROUTING钩子。
netfilter是Linux内核网络模块的一个经典框架,毫不夸张地说,整个Linux系统的网络安全大厦都构建在netfilter之上,如图1-14所示。
图1-14 netfilter在Linux网络中的地位
我们可以发现,构建在netfilter钩子之上的网络安全策略和连接跟踪的用户态程序就有ebtables、arptables、(IPv6版本的)ip6tables、iptables、iptables-nftables(iptables的改进版本)、conntrack(连接跟踪)等。Kubernetes网络之间用到的工具就有ebtables、iptables/ip6tables和conntrack,其中iptables是核心。
1.5.2 iptables的三板斧:table、chain和rule
iptables是用户空间的一个程序,通过netlink和内核的netfilter框架打交道,负责往钩子上配置回调函数。一般情况下用于构建Linux内核防火墙,特殊情况下也做服务负载均衡(这是Kubernetes的特色操作,我们将在后面章节专门分析)。iptables的工作原理如图1-15所示。
图1-15 iptables的工作原理
我们常说的iptables 5X5,即5张表(table)和5条链(chain)。5条链即iptables的5条内置链,对应上文介绍的netfilter的5个钩子。这5条链分别是:
·INPUT链:一般用于处理输入本地进程的数据包;
·OUTPUT链:一般用于处理本地进程的输出数据包;
·FORWARD链:一般用于处理转发到其他机器/network namespace的数据包;
·PREROUTING链:可以在此处进行DNAT;
·POSTROUTING链:可以在此处进行SNAT。
除了系统预定义的5条iptables链,用户还可以在表中定义自己的链,我们将通过例子详细说明。
5张表如下所示。
·filter表:用于控制到达某条链上的数据包是继续放行、直接丢弃(drop)或拒绝(reject);
·nat表:用于修改数据包的源和目的地址;
·mangle表:用于修改数据包的IP头信息;
·raw表:iptables是有状态的,即iptables对数据包有连接追踪(connection tracking)机制,而raw是用来去除这种追踪机制的;
·security表:最不常用的表(通常,我们说iptables只有4张表,security表是新加入的特性),用于在数据包上应用SELinux。
这5张表的优先级从高到低是:raw、mangle、nat、filter、security。需要注意的是,iptables不支持用户自定义表。
不是每个链上都能挂表,iptables表与链的对应关系如表1-1所示。
表1-1 iptables表与链的对应关系
所以,如果我们扩充图1-13,给它加上iptables的5张表,那么一个网络包经过iptables的处理路径如图1-16所示。
图1-16 一个网络包经过iptables的处理路径
可能有读者会问,iptables的链(chain)的概念还比较好理解,就是对应netfilter的5处钩子,而iptables的表(table)的具体作用是什么呢?而且上文说的“不是每个链上都能挂表”这句话的表述似乎也很奇怪。
任何概念都不是凭空想象出来的,它都是有实际用途的。其实,iptables的表是来分类管理iptables规则(rule)的,系统所有的iptables规则都被划分到不同的表集合中。上文也提到了,filter表中会有过滤数据包的规则,nat表中会有做地址转换的规则。因此,iptables的规则就是挂在netfilter钩子上的函数,用来修改数据包的内容或过滤数据包,iptables的表就是所有规则的5个逻辑集合!下面就让我们见识iptables的规则吧。
iptables的规则是用户真正要书写的规则。一般情况下,一条iptables规则包含两部分信息:匹配条件和动作。匹配条件很好理解,即匹配数据包被这条iptables规则“捕获”的条件,例如协议类型、源IP、目的IP、源端口、目的端口、连接状态等。每条iptables规则允许多个匹配条件任意组合,从而实现多条件的匹配,多条件之间是逻辑与(&&)关系。
那么,数据包匹配后该怎么办,常见的动作有下面几个:
·DROP:直接将数据包丢弃,不再进行后续的处理。应用场景是不让某个数据源意识到你的系统的存在,可以用来模拟宕机;
·REJECT:给客户端返回一个connection refused或destination unreachable报文。应用场景是不让某个数据源访问你的系统,善意地告诉他:我这里没有你要的服务内容;
·QUEUE:将数据包放入用户空间的队列,供用户空间的程序处理;
·RETURN:跳出当前链,该链里后续的规则不再执行;
·ACCEPT:同意数据包通过,继续执行后续的规则;
·JUMP:跳转到其他用户自定义的链继续执行。
值得一提的是,用户自定义链中的规则和系统预定义的5条链里的规则没有区别。由于自定义的链没有与netfilter里的钩子进行绑定,所以它不会自动触发,只能从其他链的规则中跳转过来,这也是JUMP动作存在的意义。
在初步认识了iptables的表、链和规则三个最重要的概念后,我们介绍iptables命令的常见用法。
1.5.3 iptables的常规武器
在见识了iptables的“三板斧”之后,相信读者已经对iptables的工作机制了然于胸,接下来将以iptables在Kubernetes网络中的使用为背景,简单介绍iptables的常见用法。本节的目的是为后面理解Kubernetes网络,尤其是Service部分做铺垫。
1. 查看所有iptables规则
Kubernetes一个节点上的iptables规则输出如下。
严格地说,输出是iptables的filter表的所有规则。使用iptables命令,必须指定针对哪个表进行操作,默认是filter表。因此,如果想输出系统中nat表的所有iptables规则,可以使用如下命令:
使用-n选项将以数字形式列出信息,即将域名解析成IP地址。想输出更详细的信息,例如,经过某条rule处理的数据包字节、进出网口等信息,可以使用-v选项。
从上面的输出结果可以看出,filter表上挂了5条链,分别是INPUT、FORWARD和OUTPUT这三条系统内置链,以及KUBE-FIREWALL和KUBE-FORWARD这两条用户自定义链。iptables的内置链都有默认规则,例如在我们的例子中,INPUT、FORWARD和OUTPUT的默认规则是ACCEPT,即全部放行。用户自己定义的链后面都有一个引用计数,在我们的例子中KUBE-FIREWALL和KUBE-FORWARD都有一个引用,它们分别在INPUT和FORWARD中被引用。iptables的每条链下面的规则处理顺序是从上到下逐条遍历的,除非中途碰到DROP,REJECT,RETURN这些内置动作。如果iptables规则前面是自定义链,则意味着这条规则的动作是JUMP,即跳到这条自定义链遍历其下的所有规则,然后跳回来遍历原来那条链后面的规则。以上过程如图1-17所示。
图1-17 iptables遍历规则
在我们的例子中,iptables规则的遍历顺序应该是rule 1,1→rule 1,2→rule 2,1→rule 2,2→rule 2,3→rule 1,3。
2. 配置内置链的默认策略
当然,我们可以自己配置内置链的默认策略,决定是放行还是丢弃,如下所示:
3. 配置防火墙规则策略
防火墙策略一般分为通和不通两种。如果默认策略是“全通”,例如上文的policy ACCEPT,就要定义一些策略来封堵;反之,如果默认策略是“全不通”,例如上文的policy DROP,就要定义一些策略来解封。如果是做访问控制列表(ACL),即俗称的白名单,则用解封策略更常用。我们将用几个实际的例子来说明。
1)配置允许SSH连接
简单分析这条命令,-A的意思是以追加(Append)的方式增加这条规则。-A INPUT表示这条规则挂在INPUT链上。-s 10.20.30.40/24表示允许源(source)地址是10.20.30.40/24这个网段的连接。-p tcp表示允许TCP(protocol)包通过。--dport 22的意思是允许访问的目的端口(destination port)为22,即SSH端口。-j ACCEPT表示接受这样的连接。综上所述,这条iptables规则的意思是允许源地址是10.20.30.40/24这个网段的包发到本地TCP 22端口。除了按追加的方式添加规则,还可以使用iptables-I[chain][number]将规则插入(Insert)链的指定位置。如果不指定number,则插到链的第一条处。
2)阻止来自某个IP/网段的所有连接
如果要阻止10.10.10.10上所有的包,则可以使用以下命令:
也可以使用-j REJECT,这样就会发一个连接拒绝的回程报文,客户端收到后立刻结束。不像-j DROP那样不返回任何响应,客户端只能一直等待直到请求超时。如果要屏蔽一个网段,例如10.10.10.0/24,则可以使用-s 10.10.10.0/24或10.10.10.0/255.255.255.0。如果要“闭关锁国”,即屏蔽所有的外来包,则可以使用-s 0.0.0.0/0。
3)封锁端口
要阻止从本地1234端口建立对外连接,可以使用以下命令:
注意,我们要在OUTPUT链上挂规则是因为我们的需求是屏蔽本地进程对外的连接。如果我们要阻止外部连接访问本地1234端口,就需要在INPUT链上挂规则,命令如下:
感到困惑的读者可以复习关于netfilter的内容。
4)端口转发
有时,我们要把服务器的某个端口流量转发给另一个端口。例如,我们对外声称Web服务在80端口上运行,但由于种种原因80端口被人占了,实际的Web服务监听在8080端口上。为了让外部客户端能无感知地依旧访问80端口,可以使用以下命令实现端口转发:
以上命令中-p tcp--dport 80-j REDIRECT--to-port 8080的意思是发到TCP 80端口的流量转发(REDIRECT)给8080端口,iptables并不会修改任何IP地址或者协议。需要注意的是,我们修改了目的端口,因此该规则应该发生在nat表上的PREROUTING链上。至于-i eth0则表示匹配eth0网卡上接收到的包。
5)禁用PING
大部分的公有云默认都是屏蔽ICMP的,即禁止ping报文。具体是如何实现的呢?请看下面这条命令:
以上命令的-p icmp-j DROP即表示匹配ICMP报文,然后丢弃。
6)删除规则
最暴力地清除当前所有的规则命令(请慎用):
要清空特定的表可以使用-t参数进行指定,例如清除nat表所有的规则如下:
删除规则的最直接的需求是解封某条防火墙策略。因此,笔者建议使用iptables的-D参数:
-D表示从链中删除一条或多条指定的规则,后面跟的就是要删除的规则。
当某条链上的规则被全部清除变成空链后,可以使用-X参数删除:
以上命令删除FOO这条用户自定义的空链,但需要注意的是系统内置链无法删除。
7)自定义链
说了这么多自定义链,具体该怎么创建它呢?请看以下命令:
如上所示,我们在filter表(因为未指定表,所以可以使用-t参数指定)创建了一条用户自定义的链BAR。
4. DNAT
DNAT根据指定条件修改数据包的目标IP地址和目标端口。DNAT的原理和我们上文讨论的端口转发原理差不多,差别是端口转发不修改IP地址。使用iptables做目的地址转换的一个典型例子如下:
-j DNAT表示目的地址转换,-d 1.2.3.4-p tcp--dport 80表示匹配的包条件是访问目的地址和端口为1.2.3.4:80的TCP包,--to-destination表示将该包的目的地址和端口修改成10.20.30.40:8080。同样,DNAT不修改协议。如果要匹配网卡,可以用-i eth0指定收到包的网卡(i是input的缩写)。需要注意的是,DNAT只发生在nat表的PREROUTING链,这也是我们要指定收到包的网卡而不是发出包的网卡的原因。
需要注意的是,当涉及转发的目的IP地址是外机时,需要确保启用ip forward功能,即把Linux当交换机用,命令如下:
5. SNAT/网络地址欺骗
神秘的网络地址欺骗其实是SNAT的一种。SNAT根据指定条件修改数据包的源IP地址,即DNAT的逆操作。与DNAT的限制类似,SNAT策略只能发生在nat表的POSTROUTING链。具体命令如下:
-j SNAT表示源地址转换,-s 192.168.1.12表示匹配的包源地址是192.168.1.12,--to-source表示将该包的源地址修改成10.172.16.1。与DNAT类似,我们也可以匹配发包的网卡,例如-o eth0(o是output的缩写)。
至于网络地址伪装,其实就是一种特殊的源地址转换,报文从哪个网卡出就用该网卡上的IP地址替换该报文的源地址,具体用哪个IP地址由内核决定。下面这条规则的意思是:源地址是10.8.0.0/16的报文都做一次Masq。
与SNAT类似,如果要控制被替换的源地址,则可以指定匹配从哪块网卡发出报文。例如:-o eth0选项指定报文从eth0出去并使用eth0的IP地址做源地址伪装。
6. 保存与恢复
上述方法对iptables规则做出的改变是临时的,重启机器后就会丢失。如果想永久保存这些更改,则需要运行以下命令:
iptables-save在保存系统所有iptables规则的同时,也会在标准输出打印这些规则,如有需要可以重定向到一个文件,例如:
后续,我们可以使用iptables-restore命令还原iptables-save命令备份的iptables配置,原理就是逐条执行文件里的iptables规则,如下所示: