本文发布于Cylon的收藏册,转载请著名原文链接~

“IP 伪装” 通常应用于云环境中,例如 GKE, AWS, CCE 等云厂商都有使用 “IP伪装” 技术,本文将围绕 “IP伪装” 技术本身,以及这项技术在 Kubernetes 集群中的实现应用 ip-masq-agent 的源码分析,以及 ”IP伪装“ 能为 Kubernetes 带来什么作用,这三个方向阐述。

什么是IP伪装?

IP 伪装 (IP Masquerade) 是 Linux 中的一个网络功能,一对多 (1 to Many) 的网络地址转换 (NAT) 的功能 。

IP 伪装允许一组计算机通过 “伪装” 网关无形地访问互联网。对于互联网上的其他计算机,出站流量将看起来来自于 IP MASQ 服务器本身。互联网上任何希望发回数据包(作为答复)的主机必须将该数据包发送到网关 (IP MASQ 服务器本身)。记住,网关(IP MASQ 服务器本身)是互联网上唯一可见的主机。网关重写目标地址,用被伪装的机器的 IP 地址替换自己的地址,并将该数据包转发到本地网络进行传递。

除了增加的功能之外,IP Masquerade 为创建一个高度安全的网络环境提供了基础。通过良好构建的防火墙,突破经过良好配置的伪装系统和内部局域网的安全性应该会相当困难。

IP Masquerade 从 Linux 1.3.x 开始支持,目前基本所有 Linux 发行版都带有 IP 伪装的功能

什么情况下不需要IP伪装

  • 已经连接到互联网的独立主机
  • 为其他主机分配了多个公共地址

IP伪装在Kubernetes集群中的应用

IP 伪装通常应用在大规模 Kubernetes 集群中,主要用于解决 “地址冲突” 的问题,例如在 GCP 中,通常是一种 IP 可路由的网络模型,例如分配给 Pod service 的 ClusterIP 只能在 Kubernetes 集群内部可用,而分配 IP CIDR 又是一种不可控的情况,假设,我们为 k8s 分配的 IP CIDR 段如下表所示:

角色 IP CIDR
Kubernetes Nodes 10.0.0.0/16
Kubernetes Services 10.1.0.0/16
Kubernetes Pods 192.168.0.0/24
其他不可控业务网段 192.168.0.0/24

通过上表可以看出,通常管理员在管理 Kubernetes 集群会配置三个网段,此时的配置,如果 Pod 需要与其他节点网络进行通讯(如我需要连接数据库),那么可能会出现 ”IP 重叠“ 的现象,尤其是在公有云环境中,用户在配置 Kubernetes 集群网络时不知道数据中心所保留的 CIDR 是什么,在这种情况下就很容易产生 ”IP 重叠“ 的现象,为了解决这个问题,Kubernetes 提出了一种使用 “IP伪装” 技术来解决这个问题。

在不使用 IP Masquerade 的情况下, Kubernetes 集群管理员如果在规划集群 CIDR 时,必须要了解了解整个组织中已预留/未使用的 CIDR 规划。

IP Masquerade Agent

IP伪装在 kubernetes 中的应用是名为 ip-masq-agent 的项目, ip-masq-agent 是用于配置 iptables 规则,以便在将流量发送到集群节点的 IP 和集群 IP 范围之外的目标时处理伪装节点或 Pod 的 IP 地址。这本质上隐藏了集群节点 IP 地址后面的 Pod IP 地址。在某些环境中,去往"外部"地址的流量必须从已知的机器地址发出。 例如,在 GCP 中,任何到互联网的流量都必须来自 VM 的 IP。 使用容器时,如 GKE,从 Pod IP 发出的流量将被拒绝出站。 为了避免这种情况,我们必须将 Pod IP 隐藏在 VM 自己的 IP 地址后面 - 通常称为"伪装"。 默认情况下,代理配置为将 RFC 1918指定的三个私有 IP 范围视为非伪装 CIDR。 这些范围是 10.0.0.0/8172.16.0.0/12192.168.0.0/16。 默认情况下,代理还将链路本地地址(169.254.0.0/16)视为非伪装 CIDR。 代理程序配置为每隔 60 秒从 /etc/config/ip-masq-agent 重新加载其配置, 这也是可修改的。

masq/non-masq example

图:ip-masq-agent工作原理
Source:https://kubernetes.io/zh-cn/docs/tasks/administer-cluster/ip-masq-agent/

默认情况下,CIDR 10.0.0.0/8172.16.0.0/12, 192.168.0.0/16 范围内的流量不会被伪装。 任何其他 CIDR 流量将被伪装。 Pod 访问本地目的地的例子,可以是其节点 (Node) 的 IP 地址,另一节点 (Node) 的地址或集群的 IP 地址 (ClusterIP) 范围内的一个 IP 地址。 默认情况下,任何其他流量都将伪装。以下条目展示了 ip-masq-agent 的默认使用的规则:

1
2
3
4
5
6
7
$ iptables -t nat -L IP-MASQ-AGENT
target     prot opt source               destination
RETURN     all  --  anywhere             169.254.0.0/16       /* ip-masq-agent: cluster-local traffic should not be subject to MASQUERADE */ ADDRTYPE match dst-type !LOCAL
RETURN     all  --  anywhere             10.0.0.0/8           /* ip-masq-agent: cluster-local traffic should not be subject to MASQUERADE */ ADDRTYPE match dst-type !LOCAL
RETURN     all  --  anywhere             172.16.0.0/12        /* ip-masq-agent: cluster-local traffic should not be subject to MASQUERADE */ ADDRTYPE match dst-type !LOCAL
RETURN     all  --  anywhere             192.168.0.0/16       /* ip-masq-agent: cluster-local traffic should not be subject to MASQUERADE */ ADDRTYPE match dst-type !LOCAL
MASQUERADE  all  --  anywhere             anywhere             /* ip-masq-agent: outbound traffic should be subject to MASQUERADE (this match must come after cluster-local CIDR matches) */ ADDRTYPE match dst-type !LOCAL

部署 ip-masq-agent

ip-masq-agent 的部署可以直接使用官方提供的资源清单 [1]

1
kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/ip-masq-agent/master/ip-masq-agent.yaml

清除 ip-masq-agent

1
kubectl delete -f https://raw.githubusercontent.com/kubernetes-sigs/ip-masq-agent/master/ip-masq-agent.yaml

部署后需要同时将对应的节点标签应用于集群中希望代理运行的任何节点

1
kubectl label nodes my-node node.kubernetes.io/masq-agent-ds-ready=true

配置好之后,需要创建配置,以对不伪装的地址增加白名单

1
2
3
nonMasqueradeCIDRs:
  - 10.0.0.0/8
resyncInterval: 60s

ip-masq-agent 深入解析

ip-masq-agent 的代码很少,只有400多行,但是作用却很大,直接可以解决管理员集群网络规划与大拓扑网络的网络冲突问题,下面就分析他的原理,以及如何完成集群 IP 伪装功能

ip-masq-agent源码的分析

ip-masq-agent 只有这一个文件 cmd/ip-masq-agent/ip-masq-agent.go,包含了整个的业务逻辑

首先在 main() 启动时,定义了这个链的名称,之后调用 Run()

1
2
3
4
5
masqChain = utiliptables.Chain(*masqChainFlag)

..

m.Run()

Run() 中,只是做了周期性同步

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
func (m *MasqDaemon) Run() {
	// Periodically resync to reconfigure or heal from any rule decay
	for {
		func() {
			defer time.Sleep(time.Duration(m.config.ResyncInterval))
			// resync config
			if err := m.osSyncConfig(); err != nil {
				glog.Errorf("error syncing configuration: %v", err)
				return
			}
			// resync rules
			if err := m.syncMasqRules(); err != nil {
				glog.Errorf("error syncing masquerade rules: %v", err)
				return
			}
			// resync ipv6 rules
			if err := m.syncMasqRulesIPv6(); err != nil {
				glog.Errorf("error syncing masquerade rules for ipv6: %v", err)
				return
			}
		}()
	}
}

重点就在 m.osSyncConfig() , 这里做的是同步实际的规则

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
func (m *MasqDaemon) syncMasqRules() error {
    // 指定的链是否存在,如果不存在则创建,masqChain全局变量 是 main() 中初始化的名称,默认为IP-MASQ-AGENT
	m.iptables.EnsureChain(utiliptables.TableNAT, masqChain)

	// ensure that any non-local in POSTROUTING jumps to masqChain
	if err := m.ensurePostroutingJump(); err != nil {
		return err
	}

	// build up lines to pass to iptables-restore
	lines := bytes.NewBuffer(nil)
	writeLine(lines, "*nat")
	writeLine(lines, utiliptables.MakeChainLine(masqChain)) // effectively flushes masqChain atomically with rule restore

	// local-link cidr 不伪装("169.254.0.0/16") 固定值
	if !m.config.MasqLinkLocal {
		writeNonMasqRule(lines, linkLocalCIDR)
	}

	// 用户定义的不伪装的 CIDR 部分
	for _, cidr := range m.config.NonMasqueradeCIDRs {
		if !isIPv6CIDR(cidr) {
			writeNonMasqRule(lines, cidr)
		}
	}

	// masquerade all other traffic that is not bound for a --dst-type LOCAL destination
	writeMasqRule(lines)

	writeLine(lines, "COMMIT")

	if err := m.iptables.RestoreAll(lines.Bytes(), utiliptables.NoFlushTables, utiliptables.NoRestoreCounters); err != nil {
		return err
	}
	return nil
}

看完同步规则后,了解到上面就是两个操作,”伪装“ 和 “不伪装” 的操作如下所示

不伪装部分实际上就是关键词 RETURN

1
2
3
func writeNonMasqRule(lines *bytes.Buffer, cidr string) {
	writeRule(lines, utiliptables.Append, masqChain, nonMasqRuleComment, "-d", cidr, "-j", "RETURN")
}

伪装部分实际上就是关键词 MASQUERADE

1
2
3
func writeMasqRule(lines *bytes.Buffer) {
	writeRule(lines, utiliptables.Append, masqChain, masqRuleComment, "-j", "MASQUERADE", "--random-fully")
}

伪装网络包的分析

创建一个 ip-masq-agent 的配置文件

1
2
3
4
5
6
tee > config <<EOF
nonMasqueradeCIDRs:
  - 10.244.0.0/16
  - 192.0.0.0/8
resyncInterval: 60s
EOF

创建 configmap

1
kubectl create configmap ip-masq-agent --from-file=config --namespace=kube-system

验证规则是否生效

1
2
3
4
5
6
7
$ iptables -t nat -L IP-MASQ-AGENT
Chain IP-MASQ-AGENT (1 references)
target     prot opt source               destination         
RETURN     all  --  anywhere             link-local/16        /* ip-masq-agent: local traffic is not subject to MASQUERADE */
RETURN     all  --  anywhere             10.244.0.0/16        /* ip-masq-agent: local traffic is not subject to MASQUERADE */
RETURN     all  --  anywhere             192.0.0.0/8          /* ip-masq-agent: local traffic is not subject to MASQUERADE */
MASQUERADE  all  --  anywhere             anywhere             /* ip-masq-agent: outbound traffic is subject to MASQUERADE (must be last in chain) */

抓包查看包是否被伪装

1
2
3
4
5
$ cpid=`docker inspect --format '{{.State.Pid}}' 6b0a92ca4327`
$ nsenter -t $cpid -n ifconfig eth0|grep inet
        inet 10.244.196.132  netmask 255.255.255.255  broadcast 10.244.196.132
$ nsenter -t $cpid -n ping 10.0.0.2
$ tcpdump -i any icmp and host 10.0.0.2 -w icap.cap

通过导出的 wireshark 包,可以很清楚的看到,去往 10.0.0.2 的已经被伪装了

image-20231105222224315

图:Kubernetes集群节点IP伪装抓包

Reference

[1] ip-masq-agent.yaml

[2] IP Masquerade Agent 用户指南

[3] IP address management strategy — a crucial aspect of running GKE

本文发布于Cylon的收藏册,转载请著名原文链接~

链接:https://www.oomkill.com/2023/10/ch24-ip-masq/

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」 许可协议进行许可。