iptables(3)之配置网络防火墙

iptables(3)之配置网络防火墙

微信搜索 zze_coding 或扫描 👉 二维码关注我的微信公众号获取更多资源推送:

本篇文章参考自 http://www.zsythink.net/archives/1663

阅读这篇文章需要站在前文的基础之上,如果在阅读时遇到障碍,请回顾前文。
我们一起来回顾一下之前的知识,在第一篇介绍 iptables 的文章中,我们就描述过防火墙的概念,我们说过,防火墙从逻辑上讲,可以分为主机防火墙与网络防火墙。

  • 主机防火墙:针对于单个主机进行防护;
  • 网络防火墙:往往处于网络入口或边缘,针对于网络入口进行防护,服务于防火墙背后的本地局域网;

在前文的举例中,iptables 都是作为主机防火墙的角色出现的,那么,iptables 怎样作为网络防火墙呢?这就是我们今天要聊的话题。
回到刚才的概念,网络防火墙往往处于网络的入口或者边缘,那么,如果想要使用 iptables 充当网络防火墙,iptables 所在的主机则需要处于网络入口处,示意图如下。

image.png

上图中,带机箱的主机为 iptables 所在主机,此时 iptables 充当的角色即为网络防火墙,上图中的黄色圆形表示网络防火墙所防护的网络区域,圆形内的机器表示网络内的主机。

当外部网络中的主机与网络内部主机通讯时,不管是由外部主机发往内部主机的报文,还是由内部主机发往外部主机的报文,都需要经过 iptables 所在的主机,由 iptables 所在的主机进行“过滤并转发”,所以,防火墙主机的主要工作就是"过滤并转发",那么,说到这里,我们则不得不再次回顾之前的 iptables 报文流程图了,如下:

image.png

前文中,iptables 都是作为“主机防火墙”的角色出现的,所以我们举例时,只用到了上图中的 INPUT 链与 OUTPUT 链,因为拥有“过滤功能”的链只有 INPUT、OUTPUT、FORWARD,当报文发往本机时,如果想要过滤,只能在 INPUT 链与 OUTPUT 链中实现,而此时,iptables 的角色发生了转变,我们想要将 iptables 所在的主机打造成“网络防火墙”,而刚才已经说过,网络防火墙的职责就是“过滤并转发”,要想“过滤”,只能在 INPUT、OUTPUT、FORWARD 三条链中实现,要想“转发”,报文则只会经过 FORWARD 链(发往本机的报文才会经过 INPUT 链),所以,综上所述,iptables 的角色变为“网络防火墙”时,规则只能定义在 FORWARD 链中。

环境准备

为了方便测试,我们需要准备一下环境,如下图所示:

image.png

我们假设,上图中圆形所示的网络为内部网络。

注:此处所描述的内网、外网与我们平常所说的公网、私网不同。
此处描述的内外部网络你可以理解成两个网段,A 网络与 B 网络,为了方便描述,我们把圆形内的主机称为内部主机,把上图中圆形所表示的网络称为内部网络,把圆形外的网络称为外部网络。

假设,内部网络的网段为 172.16.1.0/24,此内部网络中存在主机 C,主机 C 的 IP 地址为 172.16.1.202

上图中的主机 B 充当了网络防火墙的角色,主机 B 也属于内部网络,同时主机 B 也能与外部网络进行通讯,如上图所示,主机 B 有两块网卡,eth0eth1,eth0 的 IP 地址为 10.0.1.200eth1 的 IP 地址为 172.16.1.200,所以,防火墙主机在内部网络中的 IP 地址为 172.16.1.200,防火墙主机与外部网络通讯的 IP 地址为 10.0.1.200

上图中的主机 A 充当了“外部网络主机”的角色,A 主机的 IP 地址为 10.0.1.201,我们使用主机 A 访问内部网络中的主机 C,但是需要主机 B 进行转发,主机 B 在转发报文时会进行过滤,以实现网络防火墙的功能。

这里我已经准备了这样的三台主机,由于 B 主机现在的角色是 172.16.1.0/24 中的“网络防火墙”,那么,我们直接将 C 主机的网关指向 B 主机的内部网络 IP,编辑 C 主机的网卡配置文件:

$ vim /etc/sysconfig/network-scripts/ifcfg-eth0
TYPE=Ethernet
BOOTPROTO=none
DEFROUTE=yes
NAME=eth0
DEVICE=eth0
ONBOOT=yes
IPADDR=172.16.1.202
GATEWAY=172.16.1.200
PREFIX=24

同时,为了尽量简化路由设置,我们直接将 A 主机访问 172.16.1.0/24 网络时的网关指向 B 主机的 eth0 上的 IP,在 A 主机中执行添加路由命令如下:

$ route add -net 172.16.1.0/24 gw 10.0.1.200

注:该路由配置是临时生效。

现在 A 主机通往 172.16.1.0/24 网络的网关已经指向了 B 主机,那么,现在 A 主机能够达到 10.1.0.0/16 网络吗?我们来试试。
在 A 主机上执行 ping C 主机的命令:

$ $ ping 172.16.1.202
$ ping 172.16.1.202
PING 172.16.1.202 (172.16.1.202) 56(84) bytes of data.
^C                 
--- 172.16.1.202 ping statistics ---
148 packets transmitted, 0 received, 100% packet loss, time 147177ms

会发现是一直阻塞,ping 不通。

那么,我们再来试试 B 主机上的内部网 IP,如下图所示,直接在 A 主机上向 B 主机的内部网 IP 发起 ping 请求,发现是可以 ping 通的,这是为什么呢?

$ ping 172.16.1.200
PING 172.16.1.200 (172.16.1.200) 56(84) bytes of data.
64 bytes from 172.16.1.200: icmp_seq=1 ttl=64 time=0.328 ms
64 bytes from 172.16.1.200: icmp_seq=2 ttl=64 time=0.488 ms
64 bytes from 172.16.1.200: icmp_seq=3 ttl=64 time=0.630 ms
64 bytes from 172.16.1.200: icmp_seq=4 ttl=64 time=0.495 ms
^C
--- 172.16.1.200 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3931ms
rtt min/avg/max/mdev = 0.328/0.485/0.630/0.108 ms

按照道理来说,172.16.1.200172.16.1.202 都属于 172.16.1.0/24 网段,为什么 B 主机上的 IP 就能通,C 主机上的 IP 却不通呢?

咱们先来聊聊为什么 172.16.1.202 没有回应。
A 主机通过路由表得知,发往 172.16.1.0/24 网段的报文的网关为 B 主机,当报文达到 B 主机时,B 主机发现 A 的目标为 172.16.1.202,而自己的 IP 是 172.16.1.200,这时,B 主机则需要将这个报文转发给 172.16.1.202(也就是 C 主机)。
但是,Linux 主机在默认情况下,并不会转发报文,如果想要让 Linux 主机能够转发报文,需要额外的设置,这就是为什么 172.16.1.202 没有回应的原因,因为 B 主机压根就没有将 A 主机的 ping 请求转发给 C 主机,C 主机压根就没有收到 A 的 ping 请求,所以 A 自然得不到回应。

现在再来聊聊为什么 172.16.1.200 会回应。
这是因为 172.16.1.200 这个 IP 与 10.0.1.200 这个 IP 都属于 B 主机,当 A 主机通过路由表将 ping 报文发送到 B 主机上时,B 主机发现自己既是 10.0.1.200 又是 172.16.1.200,所以,B 主机就直接回应了 A 主机,并没有将报文转发给谁,所以 A 主机得到了 172.16.1.200 的回应。

我想我应该说明白了,那么,我们应该怎样设置,才能让 Linux 主机转发报文呢?我们一起来设置一遍就好了。
首先,我们可以查看 /proc/sys/net/ipv4/ip_forward 文件中的内容,如果文件内容为 0,则表示当前主机不支持转发,在 B 主机中查看:

$ cat /proc/sys/net/ipv4/ip_forward
0

如果我们想要让当前主机支持核心转发功能,只需要将此文件中的值设置为 1 即可,在 B 主机中执行如下操作:

$ echo 1 > /proc/sys/net/ipv4/ip_forward

好了,现在我们就开启了 B 主机的核心转发功能。

除了上述方法,还能使用 sysctl 命令去设置是否开启核心转发,示例如下:

$ sysctl -w net.ipv4.ip_forward=1

上述两种方法都能控制是否开启核心转发,但是通过上述两种方法设置后,只能临时生效,当重启网络服务以后,核心转发功能将会失效。
如果想要永久生效,则需要设置 /etc/sysctl.conf 文件(CentOS 7中配置 /usr/lib/sysctl.d/00-system.conf 文件),添加或修改配置项 net.ipv4.ip_forward = 1 即可。

现在,B 主机已经具备了核心转发功能,已经可以转发报文了,现在,我们再次回到 A 主机中,向 C 主机发起 ping 请求,已经可以 ping 通,如下:

$ ping 172.16.1.202
PING 172.16.1.202 (172.16.1.202) 56(84) bytes of data.
64 bytes from 172.16.1.202: icmp_seq=1 ttl=63 time=0.798 ms
64 bytes from 172.16.1.202: icmp_seq=2 ttl=63 time=0.796 ms
64 bytes from 172.16.1.202: icmp_seq=3 ttl=63 time=1.49 ms
^C
--- 172.16.1.202 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2319ms
rtt min/avg/max/mdev = 0.796/1.028/1.490/0.326 ms

好了,我们的测试环境已经准备完毕,现在可以开始测试了。
但是在开始之前,请确定三个主机上没有对应的 iptables 规则,因为此处我们主要是用来测试“网络防火墙”的,为了减少主机防火墙带来的影响,我们直接将几个主机上的规则清空。

测试网络防火墙

之前说过,iptables 作为网络防火墙时,主要负责“过滤与转发”,既然要过滤,则需配置 filter 表,既然要转发,则需在 FORWARD 链中定义规则,所以,我们应该在 filter 表中的 FORWARD 链中配置规则。
使用之前要确保 B 主机中的 filter 表上不存在规则哦,如下:

$ iptables -nL
Chain INPUT (policy ACCEPT)
target     prot opt source               destination         

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination         

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination 

下面设定 B 主机的 filter 表上的 FORWARD 链默认规则为丢弃,如下:

$ iptables -t filter -P FORWARD DROP

好了,配置完上述规则后,主机 A 与主机 C 已经无法通讯了,因为它们之间如果想要通讯,则需要靠主机 B 进行转发,而上述规则设置完成后,所有报文都无法通过 FORWARD 链了,所以任何经过转发的报文在经过 FORWARD 链时都会被丢弃,外部主机的报文无法转发到内部主机中,内部网主机的报文也无法转发到外部主机中,因为主机 B 已经不转发所有报文。

现在,我们同时将 A 主机与 C 主机中的 httpd 服务启动,以便进行测试。
修改 A 主机 httpd 主页内容如下:

$ cat /var/www/html/index.html    
in host A

修改 C 主机 httpd 主页内容如下:

$ cat /var/www/html/index.html    
in host C

由于刚才已经在主机 B 中设置了默认拒绝的规则,所以此刻,C 主机无法访问 A 主机的 Web 服务,A 主机同样无法访问 C 主机的 Web 服务。
那么,如果我们想要使内部的主机能够访问外部主机的 Web 服务,我们应该怎样做呢?没错,我们需要在 FORWARD 链中放行内部主机对外部主机的 Web 请求,只需在 B 主机添加如下配置即可。

$ iptables -A FORWARD -d 10.0.1.201 -p tcp --dport=80 -j ACCEPT

如上所示,防火墙放行了目标为外部主机的 Web 请求,因为我们将来自目标网络为外网、目标端口为 80 的报文都放行了,那么此时,我们在 C 主机上访问 A 主机的 Web 服务试试。

$ curl 10.0.1.201
...

可以看到,主机 C 并无法访问到主机 A 上的 Web 服务,这是为什么呢?
聪明如你肯定已经想到了,我们只在主机 B 上放行了内部主机访问外部主机 80 端口的请求,但是并没有放行外部主机的回应报文,虽然内部主机的请求能够通过防火墙主机 B 转发出去,但是回应给内部主机的报文则无法进入防火墙,所以,我们仍然需要在主机 B 上进行如下设置。

$ iptables -A FORWARD -s 10.0.1.201 -p tcp --sport=80 -j ACCEPT

如上,当外部主机中的 Web 服务响应内部主机时,目标地址肯定为内部主机,所以,我们需要放行目标 IP 属于内部主机网段的报文,源端口为 80,因为外部主机肯定会使用 80 端口进行回应。

完成上述配置后,再次回到 C 主机上,访问 A 主机的 Web 服务,可以看到,已经能够正常访问了。

$ curl 172.16.1.202
in host A

从上述示例可以看出,当 iptables 作为“网络防火墙”时,在配置规则时,往往需要考虑“双向性”,也就是说,我们为了达成一个目的,往往需要两条规则才能完成。
那么此时,A 主机能够访问 C 主机中的 Web 服务吗?我想你已经知道答案了,没错,A 主机此时无法访问 C 主机中的 Web 服务,因为 B 主机中并没有放行访问内部主机 Web 服务请求的相关报文。

结合之前的知识,我们可以将上述规则配置进行优化,比如,不管是由内而外,还是由外而内,只要是"响应报文",我们统统放行,配置如下。

$ iptables -nvL
Chain INPUT (policy ACCEPT 48 packets, 4233 bytes)
 pkts bytes target     prot opt in     out     source               destination         

Chain FORWARD (policy DROP 3 packets, 252 bytes)
 pkts bytes target     prot opt in     out     source               destination         
    5   342 ACCEPT     tcp  --  *      *       0.0.0.0/0            10.0.1.201           tcp dpt:80
    5   544 ACCEPT     tcp  --  *      *       10.0.1.201           0.0.0.0/0            tcp spt:80

Chain OUTPUT (policy ACCEPT 28 packets, 3170 bytes)
 pkts bytes target     prot opt in     out     source               destination  

$ iptables -D FORWARD 2
$ iptables -I FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT

$ iptables -nvL
Chain INPUT (policy ACCEPT 44 packets, 4208 bytes)
 pkts bytes target     prot opt in     out     source               destination         

Chain FORWARD (policy DROP 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 ACCEPT     all  --  *      *       0.0.0.0/0            0.0.0.0/0            state RELATED,ESTABLISHED
    5   342 ACCEPT     tcp  --  *      *       0.0.0.0/0            10.0.1.201           tcp dpt:80

Chain OUTPUT (policy ACCEPT 24 packets, 3824 bytes)
 pkts bytes target     prot opt in     out     source               destination

如上图所示,先将“外部主机 Web 响应报文放行规则”删除,同时增加了 state 扩展中的规则,只需要在网络防火墙主机的 FORWARD 链中添加如上一条规则,就可以将绝大多数响应报文放行了,不管是外部响应内部,还是内部响应外部,一条规则就能搞定。当 iptables 作为网络防火墙时,每次配置规则时都要考虑“双向”的问题,但是配置完上述规则后,我们只要考虑请求报文的方向就行了,而回应报文,上述一条规则就能搞定,这样配置,即使以后有更多服务的响应报文需要放行,我们也不用再去针对响应报文设置规则了,应该会让我们省去不少规则吧。

比如,我们除了想要让内部主机能够访问外部的 Web 服务,还想让内部主机能够访问外部的 sshd 服务,那么,我们则可以进行如下设置。

$ iptables -A FORWARD -d 10.0.1.201 -p tcp --dport 22 -j ACCEPT

如上图所示,我们只要考虑内部主机的请求方向的报文规则即可,因为响应报文的规则已经被之前配置的规则“承包了”。
此刻,使用 C 主机即可访问 A 主机的 22 端口:

$ ssh 10.0.1.201
root@10.0.1.201's password:

目前,我们只允许内部主机访问外部主机的 Web服务与 sshd 服务,但是外部主机还无法访问内部主机的服务,也很简单,现在我们只要放行外部主机对内部主机 80 和 22 端口的请求报文就行了,如下:

$ iptables -A FORWARD -d 172.16.1.202 -p tcp -m multiport --dports 22,80 -j ACCEPT

测试 A 主机访问 C 主机的 Web 服务和 ssh 服务:

$ curl 172.16.1.202
in host C
$ ssh 172.16.1.202
The authenticity of host '172.16.1.202 (172.16.1.202)' can't be established.
RSA key fingerprint is 8b:3e:6b:96:07:60:8e:35:b0:d6:c8:23:b2:61:64:3a.
Are you sure you want to continue connecting (yes/no)? 

可以看到成功访问了~~~

Copyright: 采用 知识共享署名4.0 国际许可协议进行许可

Links: https://www.zze.xyz/archives/iptables3.html

Buy me a cup of coffee ☕.