F1、kubeadm 集群 etcd Sweet32 加固指南

作者: Brinnatt 分类: bugfix 发布时间: 2026-06-08 13:45

适用环境:kubeadm 安装的 Kubernetes 集群,CNI 为 Calico,etcd 以 hostNetwork 静态 Pod 运行在控制平面节点。

文档目的:说明 iptables 限制 2379 失效的原因,并给出可照着执行、不易出错的修复步骤。


1. 问题描述

安全扫描发现控制平面节点 etcd 客户端端口 2379 存在 Sweet32(CVE-2016-2183) 风险:TLS 仍协商 3DES 等 64-bit 块密码,长连接可能被生日攻击破解。

尝试在宿主机 INPUT 链用 iptables 限制 2379 仅白名单 IP 可访问:

iptables -I INPUT -p tcp -m multiport --dports 2379 -m set --match-set whitelist src -j ACCEPT
iptables -I INPUT 2 -p tcp -m multiport --dports 2379 -j DROP

现象:规则刚加上时有效,几分钟后失效,任意 IP 又能访问 2379。


2. 原因分析

失效有两层原因,缺一不可。

2.1 Calico 会周期性把规则插回第一位

这是刻意设计的:防止有人把规则插在 Calico 前面,从而绕过网络策略。Calico 官方文档和 GitHub issue #2672 都明确说明了这一点。

Calico 数据面进程 Felix(calico-node Pod)默认配置:

参数 默认值 含义
chainInsertMode Insert 始终把 -j cali-INPUT 插在 INPUT 链最前面
iptablesRefreshInterval 约 60–90 秒 定期全量刷新 iptables,修正被手动改乱的规则

Felix 刷新后,INPUT 链顺序变为:

1. -j cali-INPUT          ← Calico 自动插回
2. 你的 whitelist ACCEPT    ← 被挤到后面
3. 你的 DROP                ← 更靠后

参考:Calico FelixConfiguration - chainInsertMode

2.2 2379 在 cali-INPUT 内部已被 ACCEPT(关键)

常见误解是「cali-INPUT 处理完会 RETURN 回来,继续匹配我的 DROP」。

实际 iptables 行为:

子链动作 效果
RETURN 回到父链继续匹配
ACCEPT 立即终止,父链后续规则不再执行

Calico 为防止误配策略导致宿主机失联,内置 failsafe 规则。官方文档写明,2379/2380 默认对全网开放

端口 CIDR 说明
2379 0.0.0.0/0 etcd 客户端
2380 0.0.0.0/0 etcd peer

参考:Calico Protect hosts and VMs - Failsafe rules

因此实际流量路径是:

INPUT 第1条 → cali-INPUT
              └─ failsafe 匹配 dport 2379 → ACCEPT(在此结束)
你的 whitelist / DROP 规则 → 永远执行不到

结论:在 Calico 管理的节点上,手写 INPUT 规则无法长期可靠地限制 2379;应通过 etcd 自身配置(方案 A)或 Calico 官方机制(方案 B)处理。


3. 操作前准备(两个方案共用)

每一台控制平面节点上执行。必须先完成 3.2 版本确认,再决定方案 A 能否执行。

3.1 确认 etcd 运行方式

# 应为 Running,且 NETWORK 模式为 host
crictl ps | grep etcd

# 确认监听地址
ss -lntp | grep -E '2379|2380'

kubeadm 默认 etcd 是静态 Pod,manifest 路径:

/etc/kubernetes/manifests/etcd.yaml

3.2 确认实际 etcd 版本(必做)

容器镜像里可能同时存在多个 etcd-3.x.y 二进制(如 etcd-3.2.24etcd-3.3.17etcd-3.4.13),这不代表实际在跑哪个版本。必须以 manifest 和运行进程为准,否则方案 A 可能直接导致 etcd 启动失败。

# 1) 看 manifest 里指定的镜像和启动命令(以这里为准)
grep -E 'image:|command:' -A 30 /etc/kubernetes/manifests/etcd.yaml | head -40

# 2) 看运行中 etcd 自身报告的版本(最可靠)
ETCDCTL_API=3 etcdctl version
# 或进入容器执行
docker exec -it $(docker ps --filter name=etcd -q | head -1) etcd --version

# 3) 交叉确认 Kubernetes 版本(决定 etcd 可升级上限)
kubectl version --short

记录结果,后续对照 3.3 节表格。三台 master 版本应一致,不一致先排查再操作。

3.3 版本兼容性对照表

--cipher-suites 参数并非所有 etcd 版本都支持。官方说明:该参数在 v3.2.22+、v3.3.7+、v3.4+ 引入(etcd v3.3 security 文档)。

实际 etcd 版本 --cipher-suites 方案 A 方案 B 说明
3.0.x 不支持 不可做 可做 参数不存在,加了会导致启动失败
3.1.x 不支持 不可做 可做 同上
3.2.0 – 3.2.21 不支持 不可做 可做 需先升级到 3.2.22+
3.2.22 – 3.2.x 支持 可做 可做 使用保守 cipher 列表(见 4.1)
3.3.0 – 3.3.6 不支持 不可做 可做 需先升级到 3.3.7+
3.3.7 – 3.3.x 支持 可做 可做 使用保守 cipher 列表(见 4.1)
3.4.x 支持 可做 可做 使用完整 cipher 列表(见 4.1)
3.5.x+ 支持 可做 可做 注意 cipher 顺序和 HTTP/2 要求(见 4.1 注意项)

kubeadm 集群常见对应关系(etcd 由 kubeadm 管理时,官方捆绑版本):

Kubernetes 版本 kubeadm 默认 etcd 版本 方案 A
1.16 3.3.17-0 可做(≥ 3.3.7)
1.17 – 1.18 3.4.3-0 可做
1.19 – 1.21 3.4.13-0 可做
1.22+ 3.5.x 可做

若你的镜像里同时有 etcd-3.4.13etcdctl version 显示 3.4.13,方案 A 可用。

若实际版本是 3.0.x / 3.1.x,不要执行方案 A,只能做方案 B,或通过 kubeadm upgrade 升级集群(不要单独改镜像 tag 超出版本兼容范围)。

3.4 收集必须放行的 IP

封 2379/2380 前,先列出所有合法访问方,漏掉会导致 apiserver 或 etcd 集群异常。

# 1) 本机回环(apiserver 健康检查、本机 etcd 客户端)
echo "127.0.0.1"

# 2) 所有控制平面节点 IP(HA 时 etcd peer 走 2380,跨节点也可能访问 2379)
kubectl get nodes -l node-role.kubernetes.io/control-plane -o wide

# 3) 确认 apiserver 连 etcd 的地址
grep -E 'etcd-servers|etcd-servers-overrides' /etc/kubernetes/manifests/kube-apiserver.yaml

kubeadm 典型放行清单(按实际 IP 替换):

来源 用途 是否必须
127.0.0.1/32 本机 apiserver → 本机 etcd 必须
各 control-plane 节点 IP/32 etcd peer(2380)、多 master 互通 HA 必须
管控网段(如 10.0.1.0/24 运维、监控、跳板机 按需

工作节点 Pod 不需要直接访问 2379,不要把整个 Pod CIDR 放进白名单。

3.5 备份

cp /etc/kubernetes/manifests/etcd.yaml /root/etcd.yaml.bak.$(date +%F)
kubectl get felixconfiguration default -o yaml > /root/felix-default.bak.$(date +%F) 2>/dev/null || true

4. 方案 A:禁用弱 TLS 密码套件(根治 Sweet32)

作用:从协议层移除 3DES,消除 Sweet32 根因。

特点:不限制谁能连 2379,只限制能用什么密码协商。

前提:3.2 节确认的版本在 3.3 节表格中标记为「方案 A:可做」。不满足则跳过本章,直接做第 5 章。

4.1 修改 etcd 静态 Pod

每台控制平面节点编辑:

vi /etc/kubernetes/manifests/etcd.yaml

containers[0].command 参数列表末尾追加(保持 YAML 列表格式)。

按实际版本选择 cipher 列表,不要混用:

etcd 3.4.x / 3.5.x(含常见的 3.4.13-0)

    - --cipher-suites=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256

etcd 3.2.22+ / 3.3.7+(较老版本,用保守列表)

    - --cipher-suites=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384

参考:etcd Transport security model(按你的 etcd 大版本选对应文档)

注意项(按版本):

版本 注意
所有支持版本 TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 必须保留
3.5.x+ cipher 顺序有要求;缺少上述 GCM 套件可能导致 etcd 异常退出(etcd #10652
3.4.13 及以下 通常不受 #10652 影响,但保守列表更稳妥
3.2.x / 3.3.x 不要加 CHACHA20 等该版本 Go 运行时不确定的套件,先用两个 GCM

不支持 --cipher-suites 的版本(3.0 / 3.1 / 过低 patch):加了该参数 etcd 会报 unknown flag 无法启动。此时只能:

  1. 执行方案 B 限制访问来源(缓解,不根治 Sweet32);
  2. 规划 kubeadm upgrade 升级 Kubernetes 及捆绑的 etcd 版本(不要单独修改镜像 tag 到 kubeadm 未支持的版本)。

4.2 等待 kubelet 自动重启 etcd

# 无需手动重启,保存 manifest 后 kubelet 会自动重建 Pod
watch -n 2 'crictl ps | grep etcd'

4.3 验证

# 1) 集群健康
kubectl get nodes
kubectl get pods -A | grep -v Running | grep -v Completed

# 2) etcd 成员健康(在任一 master 上)
ETCDCTL_API=3 etcdctl \
  --endpoints=https://127.0.0.1:2379 \
  --cacert=/etc/kubernetes/pki/etcd/ca.crt \
  --cert=/etc/kubernetes/pki/etcd/server.crt \
  --key=/etc/kubernetes/pki/etcd/server.key \
  endpoint health

# 3) 密码套件(Sweet32 应消失)
nmap --script ssl-enum-ciphers -p 2379 127.0.0.1

期望结果:不再出现 TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA 及 Sweet32 警告。

4.4 回滚

cp /root/etcd.yaml.bak.<日期> /etc/kubernetes/manifests/etcd.yaml

5. 方案 B:限制 2379/2380 访问来源(配合方案 A)

cursor 科谱,实践方案 A 成功,方案 B 没有实践,请以实践为准

作用:缩小暴露面,只允许指定 IP 访问 etcd 端口。

5.1 kubeadm 环境下的正确做法

etcd 使用 hostNetwork,流量走宿主机网络栈,不经过 Pod 网络策略。

Calico 默认不会自动保护宿主机端口;但 Felix 的 failsafe 规则始终在 cali-INPUT 里对 2379/2380 全网放行——这正是 iptables 失效的原因。

因此 kubeadm 场景下,方案 B 的正确做法是:修改 Felix 的 failsafe 配置,把 2379/2380 从「全网放行」改为「仅控制平面网段/节点 IP」。

不建议在 kubeadm 集群直接套用「创建 HostEndpoint + GlobalNetworkPolicy」完整主机防护方案——需要额外规划接口标签、策略顺序,操作复杂、容易锁死节点。下文为适合 kubeadm 的简化路径。

5.2 规划 failsafe 放行网段

根据第 3.2 节收集的 IP,确定一个覆盖所有合法来源的 CIDR。

单 master 示例(节点 IP 192.168.1.10):

127.0.0.1/32
192.168.1.10/32

三 master HA 示例(节点 IP 分别为 .10.11.12):

127.0.0.1/32
192.168.1.10/32
192.168.1.11/32
192.168.1.12/32

若控制平面 IP 连续,可合并为一个网段(如 192.168.1.0/28),但不要用过大网段(如 10.0.0.0/8),否则失去限制意义。

5.3 修改 FelixConfiguration

以下配置通过 Kubernetes API 下发,只需执行一次(Felix 会同步到所有 calico-node),建议在运维窗口操作。

kubectl get felixconfiguration default -o yaml > /root/felix-etcd-restrict.yaml
vi /root/felix-etcd-restrict.yaml

修改为(net 换成 5.2 节规划的网段;以下为三 master 示例):

apiVersion: projectcalico.org/v3
kind: FelixConfiguration
metadata:
  name: default
spec:
  failsafeInboundHostPorts:
    # 保留集群必需端口(去掉默认全网开放的 2379/2380)
    - protocol: tcp
      port: 22
    - protocol: udp
      port: 68
    - protocol: tcp
      port: 179
    - protocol: tcp
      port: 6443
    - protocol: tcp
      port: 5473
    - protocol: tcp
      port: 6666
    - protocol: tcp
      port: 6667
    # etcd:仅允许控制平面来源
    - protocol: tcp
      port: 2379
      net: "127.0.0.1/32"
    - protocol: tcp
      port: 2379
      net: "192.168.1.10/32"
    - protocol: tcp
      port: 2379
      net: "192.168.1.11/32"
    - protocol: tcp
      port: 2379
      net: "192.168.1.12/32"
    - protocol: tcp
      port: 2380
      net: "127.0.0.1/32"
    - protocol: tcp
      port: 2380
      net: "192.168.1.10/32"
    - protocol: tcp
      port: 2380
      net: "192.168.1.11/32"
    - protocol: tcp
      port: 2380
      net: "192.168.1.12/32"
  failsafeOutboundHostPorts:
    - protocol: udp
      port: 53
    - protocol: udp
      port: 67
    - protocol: tcp
      port: 179
    - protocol: tcp
      port: 6443
    - protocol: tcp
      port: 2379
      net: "192.168.1.10/32"
    - protocol: tcp
      port: 2379
      net: "192.168.1.11/32"
    - protocol: tcp
      port: 2379
      net: "192.168.1.12/32"
    - protocol: tcp
      port: 2380
      net: "192.168.1.10/32"
    - protocol: tcp
      port: 2380
      net: "192.168.1.11/32"
    - protocol: tcp
      port: 2380
      net: "192.168.1.12/32"

应用:

kubectl apply -f /root/felix-etcd-restrict.yaml

5.4 等待生效并验证

Felix 会在 iptablesRefreshInterval 周期内自动刷新规则(通常 1–2 分钟)。

# 1) 确认 Felix 已加载新配置
kubectl get felixconfiguration default -o yaml | grep -A2 2379

# 2) 从白名单 IP 访问应成功(在 master 上)
ETCDCTL_API=3 etcdctl \
  --endpoints=https://127.0.0.1:2379 \
  --cacert=/etc/kubernetes/pki/etcd/ca.crt \
  --cert=/etc/kubernetes/pki/etcd/server.crt \
  --key=/etc/kubernetes/pki/etcd/server.key \
  endpoint health

# 3) 从非白名单机器访问应失败(在工作节点或外部机器上)
nmap -p 2379 <control-plane-ip>
# 期望:filtered 或无法建立 TLS

# 4) 集群功能正常
kubectl get nodes

5.5 回滚

kubectl apply -f /root/felix-default.bak.<日期>

6. 推荐实施顺序

第零步:3.2 确认实际 etcd 版本 → 对照 3.3 表格决定能否做方案 A
         ↓
第一步:方案 A(仅版本支持时;每台 master 改 etcd.yaml)
         ↓ 验证 etcd 健康 + Sweet32 消失
         (版本不支持则跳过,直接第二步)
第二步:方案 B(改 FelixConfiguration,收紧 2379/2380 源 IP)
         ↓ 验证白名单内外访问符合预期 + 集群正常
第三步:删除之前手写的 iptables 规则(已无用,且会被 Calico 覆盖)
         iptables -D INPUT <规则编号>
方案 解决什么 版本要求 kubeadm 操作范围 优先级
A --cipher-suites Sweet32 根因 ≥ 3.2.22 / 3.3.7 / 3.4+ 每台 master 的 etcd.yaml 版本支持时必做
B failsafe 收紧 限制访问来源 无 etcd 版本要求 一次 kubectl apply(全局) 建议做
kubeadm upgrade 升级 etcd 获得方案 A 能力 按 K8s 版本规划 集群升级流程 版本过低时考虑
手写 iptables 不推荐 放弃

7. 常见错误

错误操作 后果
未确认版本,对 3.0/3.1 加 --cipher-suites etcd 报 unknown flag,静态 Pod 反复 CrashLoop,控制平面宕机
看容器里有 etcd-3.4.13 就认为在跑 3.4.13 实际可能跑 3.0/3.1 二进制,方案 A 配置错误
对 3.2/3.3 使用 3.5 的完整 cipher 列表 含不支持的套件时 TLS 握手失败或 etcd 启动异常
单独把 etcd 镜像 tag 改到 kubeadm 不支持的版本 集群组件不兼容,etcd 数据损坏风险
只封 2379 不封 2380 HA 集群 etcd peer 同步失败
白名单漏掉 127.0.0.1 本机 apiserver 无法连 etcd,控制平面宕机
白名单漏掉某个 master IP 多 master etcd 选主/同步异常
chainInsertMode: Append 绕过 Calico 可能同时绕过全部 Calico 网络策略,安全风险大
未备份直接改 failsafe 配置错误可能导致 SSH/etcd 不可达,需 console 恢复

8. 参考链接

标签云