nftables 与 OpenVPN 的结合实践

2021-07-16 10:54:06 浏览数 (1)

本文对比了 linux 环境各类防火墙工具,还展示了 iptables 规则如何保存到文件并翻译成 nftables 规则,并给出了 nftables 与 openvpn 配合对混合云内网用户访问权限的精准控制方案。

先看下目前的架构:

1. 选型与对比

鉴于之前写的 VPN 权限管理项目的缺点,以及对比 iptables(ipset)、nftables、ebpf-iptables 后,确定过滤网络数据包的底层工具还是选用 nftables 而不是 iptables ipset。

理由如下:

  • ebpf 很优秀,但需要 C 编程,开发效率低下且目及处无人会 C;其 golang 库小众年久失修;
  • 红帽自己的 iptables 和 ipset 替代方案 nftables 纳入考虑;而且有 golang 的比较成熟的库;
  • iptables ipset,优点是只需要熟悉一下 ipset,有 golang 的两个库;缺点是既然 1 1=2,为什么要用两个东西,封装学习两个 golang 库?

再次总结下 nftables 的优点:

  1. 相较于 ebpf,是完整的防火墙实现,有命令行;ebpf 是底层,有特别小众的防火墙应用 【方便能上手】
  2. 内置查找表而不是线性处理 【处理速度更快】
  3. 以原子方式应用规则,而不是获取、更新和存储完整的规则集 【这一点对开发管理极其有利】

2. 实践应用考虑

旧的管理只有 iptables,生产上没用过 ipset,现在决定直接用 nftables 替代 iptables;

旧的 iptables 规则有三部分,按照从用户到目标服务器的顺序为:

  1. openvpn 的基础 iptables 规则,把来自 openvpn 虚拟 IP 网段的用户的请求 全部通过 openvpn 服务器的 eth0 网卡转发出去 也就是我们平时所说的 IPtables 的 NAT 规则,并进行原地址伪装例如iptables -t nat -A POSTROUTING -s 10.10.0.0/16 -o eth0 -j MASQUERADE 10.10.0.0/16 为 openvpn 用户虚拟 IP 网段【在 VPN 中枢 需要改变为 nft】
  2. 各个 VPC 中 wireguard 中继器的配置中中继器负责路由整个 VPC 流量的路由【中继器不作改动 还用 iptables】
  3. 真正作用于用户访问内网地址的业务规则【在 VPN 中枢服务器 需要改变为 nft】

3. openvpn 权限控制原理

VPN 权限管理的核心是 masquerade,即源地址伪装:VPN 用户访问内网的流量全都在 VPN 服务器这里进行路由和转发。

当一个数据包走到 VPN 服务器时,netfilter 将数据包的源 IP 伪装成本机(VPN 服务器)的地址,然后根据规则将数据包送往不同的地址。

例如nft insert rule ip nat postrouting oifname eth0 ip saddr 10.121.0.3 ip daddr 192.168.3.0/24 tcp dport 80 counter jump masquerade,从 VPN 虚拟 IP10.121.0.3 到 192.168.3.0/24 的 80 端口的流量,前者源地址被 masquerade 成本机地址,然后就成了本机发往 192.168.3.0/24 的数据包,再参考路由表,发现路由表有这样一条路由,于是数据包就从本机的 etho 接口转给了 wg0 接口。wg0 就是 wireguard 服务的接口,于是数据包就发给了 wireguard 内网的对应 peer 上,再由该 peer 通过 eth0 网卡转发到本地网络即可。

代码语言:javascript复制
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
default         gateway         0.0.0.0         UG    100    0        0 eth0
192.168.0.0     0.0.0.0         255.255.0.0     U     0      0        0 wg0

因此,我要这里要保证:

  • 各个云的 ACL 和安全组配置好、wg 中继器 VPC 的路由配置好
  • wireguard 混合云的各个 VPC 与这台 VPN 服务器(中枢)全通
  • 所有的控制点都放在 VPN 中枢,用是否有用户到目的地的规则来控制访问权限,粒度可以精细到【哪个用户>>目的 ip-协议-端口】

因此业务场景下我们要先准备好基础规则:

4. nftables 替换 iptables 规则步骤及测试

维护 iptables 基本的规则,未来将此 iptables 规则全部转换为 nftables 规则

代码语言:javascript复制
# 先清空规则,然后INPUT OUTPUT FORWARD全接受,如果drop会让22端口也被干掉!
iptables -F
iptables -X
iptables -Z
iptables -P INPUT ACCEPT
iptables -P OUTPUT ACCEPT
iptables -P FORWARD ACCEPT
# 查询nat表所有规则
iptables -t nat -L --line-numbers
# 删除nat表POSTROUTING链规则的第一条
iptables -t nat -D POSTROUTING  1

然后到 ubantu 用翻译工具翻译成 nftables 规则:

必要:ubantu linux 内核高于 4.8 才能支持 iptables 翻译工具!

代码语言:javascript复制
# 保存iptables规则
iptables-save > default.rules
# 将文件default.rules发送到一台有iptables翻译nftables规则的ubantu上并输入nft命令
iptables-restore-translate -f default.rules

得到 nft 命令,这个命令在关掉 iptables 以及 iptables 的 nat 后进入 nftables 的交互模式执行:

代码语言:javascript复制
# Translated by iptables-restore-translate v1.6.1 on Tue Jul  6 16:30:39 2021
add table ip filter
add chain ip filter INPUT { type filter hook input priority 0; policy accept; }
add chain ip filter FORWARD { type filter hook forward priority 0; policy accept; }
add chain ip filter OUTPUT { type filter hook output priority 0; policy accept; }
add table ip nat
add chain ip nat PREROUTING { type nat hook prerouting priority 0; policy accept; }
add chain ip nat INPUT { type nat hook input priority 0; policy accept; }
add chain ip nat OUTPUT { type nat hook output priority 0; policy accept; }
add chain ip nat POSTROUTING { type nat hook postrouting priority 0; policy accept; }
add rule ip nat POSTROUTING oifname eth0 ip saddr 10.121.6.6 ip daddr 192.168.5.77 counter masquerade
add rule ip nat POSTROUTING oifname eth0 ip saddr 10.121.6.6 ip daddr 10.10.210.11 counter masquerade
# Completed on Tue Jul  6 16:30:39 2021

关闭 iptables 服务&卸载 iptables 的相关 nat 模块,只有先关闭 iptables 的 nat 模块,流量才能走 nft 的 nat

代码语言:javascript复制
# 停止iptables服务
systemctl stop iptables.service
# 查看并确定服务是停止状态
systemctl status iptables.service
# 卸载nat模块
modprobe -v -r ip6table_nat
modprobe -v -r iptable_nat
modprobe -v -r ip_nat_ftp

跑 nft 命令生成对应的 nft 规则

代码语言:javascript复制
# -i参数进入交互模式
nft -i
# 执行从iptables基础规则转换来的nftables规则
add table ip filter
add chain ip filter INPUT { type filter hook input priority 0; policy accept; }
add chain ip filter FORWARD { type filter hook forward priority 0; policy accept; }
add chain ip filter OUTPUT { type filter hook output priority 0; policy accept; }
add table ip nat
add chain ip nat PREROUTING { type nat hook prerouting priority 0; policy accept; }
add chain ip nat INPUT { type nat hook input priority 0; policy accept; }
add chain ip nat OUTPUT { type nat hook output priority 0; policy accept; }
add chain ip nat POSTROUTING { type nat hook postrouting priority 0; policy accept; }
add rule ip nat POSTROUTING oifname eth0 ip saddr 10.121.6.6 ip daddr 192.168.5.71 counter masquerade
add rule ip nat POSTROUTING oifname eth0 ip saddr 10.121.6.6 ip daddr 10.10.210.18 counter masquerade
# 增加测量流量规则
add rule filter INPUT counter
add rule nat POSTROUTING counter
# 查看目前的nft规则
nft list ruleset -a

table ip filter {
 chain INPUT {
  type filter hook input priority 0; policy accept;
  counter packets 2530 bytes 583977 # handle 4
 }

 chain FORWARD {
  type filter hook forward priority 0; policy accept;
 }

 chain OUTPUT {
  type filter hook output priority 0; policy accept;
 }
}
table ip nat {
 chain PREROUTING {
  type nat hook prerouting priority 0; policy accept;
 }

 chain INPUT {
  type nat hook input priority 0; policy accept;
 }

 chain OUTPUT {
  type nat hook output priority 0; policy accept;
 }

 chain POSTROUTING {
  type nat hook postrouting priority 0; policy accept;
  counter packets 335 bytes 22534 # handle 11
  oifname "eth0" ip saddr 10.121.6.6 ip daddr 192.168.5.77 counter packets 0 bytes 0 masquerade # handle 12
  oifname "eth0" ip saddr 10.121.6.6 ip daddr 10.10.210.11 counter packets 0 bytes 0 masquerade # handle 13
 }
}

测试

这边 openvpn 的服务端配置的用户虚拟 IP 网段是 10.121.0.0/16,subnet 拓扑模式

客户端(win10)连通 openvpn 后,ping 192.168.5.77 和 ping 10.10.210.11

代码语言:javascript复制
通过查看流量可以看出流量通过了nft的POSTROUTING链的两条规则:
table ip filter {
 chain INPUT {
  type filter hook input priority 0; policy accept;
  counter packets 12481 bytes 4032765 # handle 4
 }

 chain FORWARD {
  type filter hook forward priority 0; policy accept;
 }

 chain OUTPUT {
  type filter hook output priority 0; policy accept;
 }
}
table ip nat {
 chain PREROUTING {
  type nat hook prerouting priority 0; policy accept;
 }

 chain INPUT {
  type nat hook input priority 0; policy accept;
 }

 chain OUTPUT {
  type nat hook output priority 0; policy accept;
 }

 chain POSTROUTING {
  type nat hook postrouting priority 0; policy accept;
  counter packets 2318 bytes 156490 # handle 11
  oifname "eth0" ip saddr 10.121.6.6 ip daddr 192.168.5.77 counter packets 3 bytes 180 masquerade # handle 12
  oifname "eth0" ip saddr 10.121.6.6 ip daddr 10.10.210.11 counter packets 3 bytes 180 masquerade # handle 13
 }
}

这里可以看到

代码语言:javascript复制
oifname "eth0" ip saddr 10.121.6.6 ip daddr 192.168.5.77 counter packets 3 bytes 180 masquerade # handle 12
oifname "eth0" ip saddr 10.121.6.6 ip daddr 10.10.210.11 counter packets 3 bytes 180 masquerade # handle 13

两条规则有流量经过,我们在 vpn 客户端也得到了 ICMP 的正确返回。

因为实际生产上需要先给大家统一分配,等稳定后再优化为细粒度的权限控制,最终的规则写在 nftables 日志实践一文中了,另外还有 openvpn 服务端的配置也没放出来,未来写上层权限控制平台的时候,一并给出。

5. 单个 iptables 规则翻译成 nftables 规则

VPN 用户的 权限主要依赖于防火墙的 masquerade,因此用 iptables 时候,用程序生成最多的就是地址伪装规则,例如:

代码语言:javascript复制
iptables -t nat -I POSTROUTING -s 10.121.0.3 -d 192.168.3.0/24 -o eth0 -p tcp --dport 80 -j MASQUERADE;

含义是虚拟 IP 为 10.121.0.3 的用户到 192.168.3.0/24 网段的流量,伪装成本机发送的流量。

因为已经考虑完全用 nftables 替代 iptables 了所以要考虑用 nftables 来组织规则,该怎么写呢?我尝试执行了自己猜测的语句:

代码语言:javascript复制
nft add rule nat postrouting ip saddr 10.121.0.3 tcp sport 80 oifname eth0 counter masquerade

但是还是没有对目标地址进行翻译-d 192.168.3.0/24

于是改写成规则:nft add rule nat postrouting ip saddr 10.121.0.3 tcp sport 80 ip daddr 192.168.3.0/24 tcp dport 80 oifname eth0 counter masquerade会有语法错误:

同事买了一台 ubantu 安装了翻译工具apt install iptables-nftables-compat

翻译出的句子如下:

代码语言:javascript复制
iptables-translate -t nat -I postrouting -s 10.121.0.3 -d 192.168.3.0/24 -o eth0 -p tcp --dport 80 -j masquerade;

nft insert rule ip nat postrouting oifname eth0 ip saddr 10.121.0.3 ip daddr 192.168.3.0/24 tcp dport 80 counter jump masquerade

我的问题在于,在 centos7 内核 5.12 的环境下 yum search 翻译工具没找到,没想到去安装一台 ubantu;其次语法上确实在官方文档没找到这么复杂的案例,因此没遵循语法写错了

6. 参考资料

  • Moving from iptables to nftables[1]

脚注

[1]

Moving from iptables to nftables: https://www.oschina.net/action/GoToLink?url=https://wiki.nftables.org/wiki-nftables/index.php/Moving_from_iptables_to_nftables

0 人点赞