【K8S 运维实战】27-安全加固CISBenchmark
2026/8/1 1:27:55 网站建设 项目流程

安全加固:CIS Benchmark 基线检查

一句话定位:别等被打了才想起来 kube-apiserver 还开着匿名访问——用 CIS Benchmark 把集群默认的不安全配置一次性捋干净。

写在前面

我接手过一个存量集群,扫了一眼 kube-apiserver 的启动参数,--anonymous-auth=true默认开着,--audit-log-path压根没配,kubelet 的--protect-kernel-defaults也是 false。当时我心就凉了半截——这种集群一旦边界被打穿,Pod 里curl https://kubernetes/api都能拿到集群信息。

K8s 的设计哲学是"默认开放,方便上手",这对你学习是好事,对生产环境是灾难。CIS(Center for Internet Security) Benchmark 就是社区维护的一份"安全基线清单",告诉你哪些默认配置要改、改成什么样。这篇我们就以 CIS Kubernetes Benchmark 为纲,把控制面、节点、Pod 三层的安全加固走一遍,工具就用官方推荐的 kube-bench。

核心问题

集群有哪些默认不安全配置?怎么用 CIS Benchmark 做一次系统性的基线检查,而不是凭感觉改几个参数?


一、原理剖析

1.1 CIS Benchmark 是什么

CIS Benchmark 是一份针对特定软件版本的安全配置基线文档,K8s 有专门的一份,当前主流是 CIS Kubernetes Benchmark v1.9.0(覆盖 K8s 1.27+,1.30 也适用)。它把检查项分成几大类:

CIS Kubernetes Benchmark 结构 ├── 1. Control Plane Components (apiserver/scheduler/controller-manager/etcd) ├── 2. etcd (独立章节,强调数据面安全) ├── 3. Control Plane Configuration (RBAC/审计/PodSecurity) ├── 4. Worker Nodes (kubelet/kube-proxy/组件权限) ├── 5. Kubernetes Policies (PSS/NetworkPolicy/ResourceQuota) └── 6. Managed Services (云厂商托管集群补充项)

每个检查项有唯一编号,比如1.2.1表示第1大类第2小节第1项,每个检查项标注三个状态:

  • Scored:计分项,必须满足才算合规
  • Not Scored:建议项,不计分但强烈推荐
  • Manual:需要人工判断,自动化工具只能提示

理解这个结构很重要,因为 kube-bench 的输出就是按这个编号来的,你看到1.2.1 FAIL,就知道去 Benchmark 文档里查这一条具体要求。

1.2 K8s 默认配置的安全风险

为什么需要 CIS Benchmark?因为 K8s 默认配置在安全上做了很多"让步"。我用一张图把默认开放的风险点串起来:

┌─────────────────────────────────────────────────────────────┐ │ K8s 默认配置风险全景 │ ├──────────────┬──────────────────────┬───────────────────────┤ │ 组件 │ 默认配置(风险) │ CIS 要求 │ ├──────────────┼──────────────────────┼───────────────────────┤ │ apiserver │ anonymous-auth=true │ =false(CIS 1.2.1) │ │ │ profiling=true │ =false(CIS 1.2.21) │ │ │ audit-log 未配置 │ 必须开启(CIS 3.x) │ ├──────────────┼──────────────────────┼───────────────────────┤ │ kubelet │ anonymous-auth=true │ =false(CIS 4.1.1) │ │ │ protect-kernel- │ │ │ │ defaults=false │ =true(CIS 4.1.2) │ │ │ event-qps=0(无限) │ 设置上限(CIS 4.1.4) │ ├──────────────┼──────────────────────┼───────────────────────┤ │ etcd │ client-cert-auth │ =true(CIS 2.1.4) │ │ │ 可能未开启 │ │ ├──────────────┼──────────────────────┼───────────────────────┤ │ Pod │ 默认以 root 运行 │ runAsNonRoot=true │ │ │ 全部 capabilities │ drop ALL │ │ │ 无 seccomp │ seccompProfile │ └──────────────┴──────────────────────┴───────────────────────┘

这套默认配置的逻辑是:集群刚搭起来,你还没配 RBAC、还没配证书,如果默认全锁死,连kubectl get pod都跑不了。所以默认开放是为了"能跑起来",但生产环境必须收敛。

1.3 PSP 已死,PSS 当立

K8s 1.25 起,Pod Security Policy(PSP)被彻底移除。1.30 你只有一条路:Pod Security Standards(PSS)。PSS 的逻辑和 PSP 完全不同:

PSP 已移除

PSS 三级模型

privileged
完全无限制

baseline
只禁高危能力

restricted
严格安全策略

namespace 级别
label 开关

enforce 拒绝创建

audit 记录告警

warn 提示用户

PSS 不是通过 Admission Webhook 实现的,而是内置在 kube-apiserver 的PodSecurity准入插件里,通过给 namespace 打 label 来生效。三种模式:

  • enforce:违反就直接拒绝创建 Pod
  • audit:允许创建,但在审计日志里记录
  • warn:允许创建,但 kubectl 返回警告

你可以三种模式叠加,比如对某个 namespace 同时开enforce=restrictedaudit=restricted,这样既能硬性拦截,又有审计记录。

1.4 加固的分层模型

安全加固不是改几个参数就完事,要分层思考:

┌───────────────────┐ │ Pod 层(PSS/SC) │ ← 最内层,每个 Pod 自身安全 ├───────────────────┤ │ 节点层(kubelet) │ ← 每台机器的 kubelet 配置 ├───────────────────┤ │ 数据层(etcd加密) │ ← 集群状态数据 ├───────────────────┤ │ 控制面(apiserver) │ ← 集群入口 ├───────────────────┤ │ 网络层(边界) │ ← 最外层,网络可达性 └───────────────────┘

CIS Benchmark 覆盖了中间四层,网络层主要靠 NetworkPolicy 和防火墙(下一篇专门讲)。这篇我们聚焦上面四层。


二、实战操作

2.1 部署 kube-bench 做基线扫描

kube-bench 是 Aqua Security 维护的 CIS Benchmark 自动化扫描工具,它的原理是把 Benchmark 的检查项写成 YAML 配置文件,然后去读组件的启动参数、文件权限、配置文件内容,对比 Benchmark 要求给出 PASS/FAIL。

方式一:Job 方式跑(推荐,适合定期巡检)

# kube-bench-job.yamlapiVersion:batch/v1kind:Jobmetadata:name:kube-benchnamespace:kube-systemspec:template:spec:hostPID:truecontainers:-name:kube-benchimage:docker.io/aquasec/kube-bench:v0.6.20command:["kube-bench","run","--benchmark=cis-1.9"]volumeMounts:-name:var-lib-etcdmountPath:/var/lib/etcd-name:var-lib-kubeletmountPath:/var/lib/kubelet-name:etc-kubernetesmountPath:/etc/kubernetes-name:etc-systemdmountPath:/etc/systemdrestartPolicy:Nevervolumes:-name:var-lib-etcdhostPath:path:/var/lib/etcd-name:var-lib-kubelethostPath:path:/var/lib/kubelet-name:etc-kuberneteshostPath:path:/etc/kubernetes-name:etc-systemdhostPath:path:/etc/systemdbackoffLimit:1

注意hostPID: true和一堆 hostPath 挂载是必须的——kube-bench 要读节点的配置文件,得能看到宿主机文件系统。

# 部署并查看结果kubectl apply-fkube-bench-job.yaml kubectl logs job/kube-bench-nkube-system# 想看完整结果导出文件kubectl logs job/kube-bench-nkube-system>kube-bench-report.txt

方式二:二进制方式跑(适合离线环境或节点级排查)

# 下载二进制curl-Lhttps://github.com/aquasecurity/kube-bench/releases/download/v0.6.20/kube-bench_0.6.20_linux_amd64.tar.gz-okube-bench.tar.gztar-xzfkube-bench.tar.gz&&chmod+x kube-bench# 扫描 master 节点./kube-bench run--benchmark=cis-1.9--target=master# 只扫描 apiserver./kube-bench run--benchmark=cis-1.9--target=apiserver# 扫描 worker 节点./kube-bench run--benchmark=cis-1.9--target=node# 输出 JSON 便于解析./kube-bench run--benchmark=cis-1.9--json--output-file=/tmp/report.json

2.2 扫描结果解读

跑完你会看到类似这样的输出:

== Summary total == 0 Checks PASS 0 Checks FAIL 0 Checks WARN 0 Checks INFO [FAIL] 1.2.1 Ensure that the --anonymous-auth argument is set to false (Automated) [FAIL] 1.2.2 Ensure that the --basic-auth-file argument is not set (Automated) [PASS] 1.2.3 Ensure that the --token-auth-file parameter is not set (Automated) [FAIL] 1.2.4 Ensure that the --kubelet-https argument is set to true (Automated) [WARN] 1.2.5 Ensure that the --kubelet-client-certificate and --kubelet-client-key arguments are set as appropriate (Manual)

解读逻辑:

  • PASS:该检查项通过
  • FAIL:该检查项未通过,Scored 项必须修复
  • WARN:该检查项需要人工判断(比如证书路径是否符合规范)
  • INFO:建议性信息,不计分

重点看 FAIL 且标注Scored的项,这些是硬性不合规项。比如1.2.1就是开头说的 apiserver 匿名访问问题。

2.3 控制面加固:apiserver 参数清单

apiserver 是整个集群的入口,加固最关键。以下参数必须配置(基于 CIS 1.9):

# /etc/kubernetes/manifests/kube-apiserver.yaml 关键参数(去掉无关项)apiVersion: v1 kind: Pod metadata: name: kube-apiserver namespace: kube-system spec: containers: - name: kube-apiserver image: registry.k8s.io/kube-apiserver:v1.30.0 command: - kube-apiserver - --anonymous-auth=false# CIS 1.2.1 关闭匿名访问- --token-auth-file=# CIS 1.2.3 禁用 token 文件认证- --authorization-mode=Node,RBAC# CIS 1.2.7 必须含 Node+RBAC- --enable-admission-plugins=NodeRestriction,ServiceAccount,PodSecurity,NamespaceLifecycle# CIS 1.2.16- --audit-log-path=/var/log/kubernetes/audit/audit.log# CIS 3.1 开启审计日志- --audit-log-maxage=30# 日志保留30天- --audit-log-maxbackup=10# 保留10个备份- --audit-log-maxsize=100# 单文件100MB轮转---profiling=false# CIS 1.2.21 关闭 profiling- --tls-cert-file=/etc/kubernetes/pki/apiserver.crt - --tls-private-key-file=/etc/kubernetes/pki/apiserver.key - --client-ca-file=/etc/kubernetes/pki/ca.crt - --encryption-provider-config=/etc/kubernetes/encryption-config.yaml# etcd 加密volumeMounts: - mountPath: /var/log/kubernetes/audit name: audit-log - mountPath: /etc/kubernetes/encryption-config.yaml name: encryption-config readOnly:truevolumes: - name: audit-log hostPath: path: /var/log/kubernetes/audit type: DirectoryOrCreate - name: encryption-config hostPath: path: /etc/kubernetes/encryption-config.yaml type: File

几个关键参数解释:

  • --anonymous-auth=false:默认 true,开了之后任何能连到 6443 端口的请求都能拿到集群基础信息(虽然只能看/healthz之类,但信息泄露就是风险)。
  • --enable-admission-plugins=...,PodSecurity:必须包含PodSecurity,否则 PSS 不生效。NodeRestriction限制 kubelet 只能操作自己节点的 Pod。
  • --audit-log-path:没开审计日志,出事了根本查不到谁干的。等保三级强制要求审计留存。

2.4 etcd 加密配置

默认情况下,Secret 存在 etcd 里是 base64 编码(不是加密),任何能读 etcd 的人都能解出明文。必须开启静态加密:

# /etc/kubernetes/encryption-config.yamlapiVersion:apiserver.config.k8s.io/v1kind:EncryptionConfigurationresources:-resources:-secrets-configmapsproviders:-aescbc:keys:-name:key1secret:<base64编码的32字节随机密钥>-identity:{}# 兜底,允许读取未加密的旧数据

生成密钥:

# 生成32字节随机密钥并base64编码head-c32/dev/urandom|base64

etcd 本身的启动参数也要加固(如果是自建 etcd):

# /etc/kubernetes/manifests/etcd.yaml 关键参数-command:-etcd---client-cert-auth=true# CIS 2.1.4 强制客户端证书认证---trusted-ca-file=/etc/kubernetes/pki/etcd/ca.crt---cert-file=/etc/kubernetes/pki/etcd/server.crt---key-file=/etc/kubernetes/pki/etcd/server.key---auto-tls=false# CIS 2.1.5 禁用自动TLS,必须显式证书---peer-client-cert-auth=true# CIS 2.1.6 peer 间也要证书认证---encryption-provider-config=/etc/kubernetes/encryption-config.yaml

开启加密后,已有的 Secret 不会自动加密,要手动 re-encrypt:

# 触发所有 Secret 重新加密kubectl get secrets --all-namespaces-ojson|kubectl replace-f-

2.5 节点加固:kubelet 参数

kubelet 跑在每个节点上,默认配置也很松。K8s 1.30 用KubeletConfiguration文件管理,不再推荐命令行参数:

# /var/lib/kubelet/config.yamlapiVersion:kubelet.config.k8s.io/v1beta1kind:KubeletConfigurationauthentication:anonymous:enabled:false# CIS 4.1.1 关闭匿名访问webhook:enabled:true# 通过 apiserver 做 RBACx509:clientCAFile:/etc/kubernetes/pki/ca.crtauthorization:mode:Webhook# CIS 4.1.3 授权走 WebhookprotectKernelDefaults:true# CIS 4.1.2 保护内核默认参数eventRecordQPS:5# CIS 4.1.4 限制事件QPS,防止事件风暴readOnlyPort:0# CIS 4.1.5 关闭只读端口(10255)streamingConnectionIdleTimeout:4h# CIS 4.1.6 空闲连接超时makeIPTablesUtilChains:true# CIS 4.1.7 用 iptables 规则

修改后重启 kubelet:

systemctl restart kubelet systemctl status kubelet

2.6 PSS 约束配置

下面是 PSS 的实际配置。对生产 namespace 强制 restricted 级别:

# 给 namespace 打 label 开启 PSS# enforce=restricted 表示违反 restricted 策略的 Pod 直接拒绝创建kubectl label namespace production\pod-security.kubernetes.io/enforce=restricted\pod-security.kubernetes.io/enforce-version=latest\pod-security.kubernetes.io/audit=restricted\pod-security.kubernetes.io/audit-version=latest\pod-security.kubernetes.io/warn=restricted\pod-security.kubernetes.io/warn-version=latest

验证效果——尝试创建一个 root 用户的 Pod,会被拒绝:

# 写一个不合规的 Podcat<<EOF|kubectl apply-nproduction-f-apiVersion: v1 kind: Pod metadata: name: bad-pod spec: containers: - name: nginx image: nginx:1.25 EOF# 输出:# Error from server (Forbidden): error when creating "STDIN":# pods "bad-pod" is forbidden: violates PodSecurity "restricted:latest":# runAsNonRoot != true (container "nginx" must set securityContext.runAsNonRoot=true)

2.7 SecurityContext 最佳实践

restricted 级别要求每个 Pod 都配 SecurityContext。这是一份"安全 Pod"模板:

# secure-pod.yaml - 符合 PSS restricted 级别的 Pod 模板apiVersion:v1kind:Podmetadata:name:secure-appnamespace:productionspec:automountServiceAccountToken:false# 不自动挂载 SA tokensecurityContext:runAsNonRoot:true# 必须非root(CIS 5.1.1)runAsUser:10001# 指定非0 UIDrunAsGroup:10001# 指定非0 GIDfsGroup:10001# 卷文件属组seccompProfile:# CIS 5.1.3 seccomptype:RuntimeDefaultcontainers:-name:appimage:myapp:1.0securityContext:allowPrivilegeEscalation:false# CIS 5.1.2 禁止提权privileged:false# CIS 5.1.4 禁止特权模式readOnlyRootFilesystem:true# 只读根文件系统runAsNonRoot:truerunAsUser:10001capabilities:drop:-ALL# CIS 5.1.5 drop所有capabilitiesadd:-NET_BIND_SERVICE# 只加必要的resources:limits:cpu:500mmemory:512Miephemeral-storage:1Girequests:cpu:100mmemory:128MivolumeMounts:-name:tmpmountPath:/tmp# 只读fs下需要写的目录挂emptyDir-name:cachemountPath:/var/cachevolumes:-name:tmpemptyDir:{}-name:cacheemptyDir:sizeLimit:100Mi

这份模板涵盖了 PSS restricted 的全部要求:runAsNonRootdrop ALL capabilitiesseccompProfilereadOnlyRootFilesystemallowPrivilegeEscalation: false。生产环境所有业务 Pod 都应该照这个标准来。


三、踩坑与排查

踩坑 1:改完 apiserver 参数导致集群起不来

现象:照着 CIS 把--anonymous-auth=false加上,结果 kube-apiserver 重启失败,整个集群不可用。

原因:我顺手把--authorization-mode=RBAC改成了Node,RBAC,但忘了 kubelet 的客户端证书没配好,导致 kubelet 连不上 apiserver,节点 NotReady。

定位:看 apiserver 容器日志:

crictl logs$(crictlps--namekube-apiserver-q)|tail-50# 看到:failed to authenticate request from kubelet

解决:改参数要分步。先改--anonymous-auth=false,确认 apiserver 起得来;再改--authorization-mode,同时确保 kubelet 的客户端证书配好了:

# 检查 kubelet 客户端证书ls-l/etc/kubernetes/pki/kubelet-client-*# kubelet-client-current.pem 应该是个软链,指向 kubelet-client-YYYY.pem

经验:apiserver 参数修改一次只改一个,改完kubectl get nodes确认正常,再改下一个。生产环境务必先在测试集群演练。

踩坑 2:kube-bench 报 hostPath 文件不存在

现象:kube-bench Job 跑起来一堆 WARN,提示/etc/kubernetes/manifests/etcd.yamlnot found。

原因:不同集群部署方式,文件路径不一样。kubeadm 部署的路径是/etc/kubernetes/manifests/,但托管集群(如 EKS/GKE)节点上根本没有这些 static pod manifest,etcd 是云厂商托管的。

解决:区分场景。自建集群用完整扫描;托管集群只能扫 worker 节点的 kubelet 配置,控制面不用管(云厂商负责):

# 托管集群只扫 worker 节点./kube-bench run--benchmark=cis-1.9--target=node# 自建集群全扫./kube-bench run--benchmark=cis-1.9

踩坑 3:开启 etcd 加密后集群变慢

现象:配置encryption-provider-config后,kubectl get secrets 明显变慢,apiserver CPU 升高。

原因:aescbc每次读写 Secret 都要加解密,且没有缓存。如果业务大量用 Secret(比如挂载很多 ImagePullSecret),开销会累积。另外identityprovider 必须放在最后做兜底,如果放在前面,新数据会用 identity(明文)存储,等于没加密。

解决:

  1. 确认 provider 顺序,aescbc在前,identity在后(上面配置正确)
  2. 触发 re-encrypt 把历史明文 Secret 都加密一遍(命令见 2.4)
  3. 如果性能确实扛不住,考虑用secretboxchacha20poly1305算法,比 aescbc 快一些:
providers:-secretbox:keys:-name:key1secret:<base64密钥>-identity:{}

踩坑 4:PSS enforce 导致现网业务挂了

现象:给生产 namespace 打了pod-security.kubernetes.io/enforce=restrictedlabel,然后业务滚动更新时新 Pod 创建失败,服务中断。

原因:旧业务镜像以 root 跑,没配runAsNonRoot,enforce 直接拒绝创建,新 Pod 起不来,旧 Pod 还在滚动删除,导致服务不可用。

解决:

  1. 先用warnaudit模式过渡,只告警不拦截:
kubectl label namespace production\pod-security.kubernetes.io/warn=restricted\pod-security.kubernetes.io/audit=restricted
  1. 观察一段时间(比如2周),把所有违规 Pod 都改合规
  2. 最后再切到enforce=restricted
  3. 紧急回退:
# 出问题立刻把 enforce 拿掉kubectl label namespace production pod-security.kubernetes.io/enforce-

经验:PSS 切 enforce 必须走"audit→warn→enforce"三步走,不能一步到位。


四、最佳实践

基线扫描

  1. 生产集群每月跑一次 kube-bench,FAIL 项纳入 SRE 周报跟踪
  2. 新集群上线前必须过一次完整 CIS 扫描,Scored 项全部 PASS 才能交付
  3. kube-bench 结果接入 CI,集群配置变更后自动触发扫描

apiserver 加固

  1. --anonymous-auth=false必关
  2. --authorization-mode=Node,RBAC必配,不要用AlwaysAllow
  3. --enable-admission-plugins必含NodeRestrictionPodSecurityServiceAccount
  4. 审计日志必开,保留期 ≥ 90 天(等保要求)
  5. --profiling=false,关闭 pprof 端点

kubelet 加固

  1. anonymous.enabled=false,authorization.mode=Webhook
  2. protectKernelDefaults=true,防止 kubelet 改内核参数
  3. readOnlyPort=0,关闭 10255 端口
  4. eventRecordQPS设置合理上限(5-10),防事件风暴

etcd 加固

  1. client-cert-auth=truepeer-client-cert-auth=true都开
  2. auto-tls=false,必须显式证书
  3. 开启静态加密,密钥定期轮转(半年一次)
  4. etcd 数据目录权限0700,属主etcd:etcd

Pod 安全

  1. 所有生产 namespace 打enforce=restricted
  2. 切 enforce 前先走 audit+warn 过渡期
  3. CI 里强制校验 Pod YAML 的 SecurityContext
  4. 禁用automountServiceAccountToken,需要时显式true
  5. 默认 drop ALL capabilities,按需 add

五、小结

CIS Benchmark 不是一份"参考文档",它是生产集群的安全底线。这篇我们走完了三层加固:

  1. 控制面层:apiserver 关匿名访问、配准入插件、开审计日志
  2. 数据层:etcd 强制证书认证、开启静态加密
  3. 节点层:kubelet 关匿名、保护内核参数、限制只读端口
  4. Pod 层:用 PSS restricted 替代 PSP,SecurityContext 全量配置

关键认知是:K8s 默认配置是"能跑起来"的配置,不是"能扛事"的配置。生产集群的安全加固不能凭感觉,要照着 CIS Benchmark 这份清单一条条过,用 kube-bench 做自动化验证,把"我觉得安全"变成"扫描报告证明安全"。

下一篇我们讲 NetworkPolicy,把网络层的收敛也补上,完成零信任的拼图。

思考题

  1. 你的集群--anonymous-auth=false配了吗?不配的话,攻击者拿到一台 Pod 能做什么?
  2. 开启 etcd 静态加密后,如果encryption-config.yaml里的密钥丢了,会发生什么?该怎么预防?
  3. PSS 的enforceaudit模式有什么区别?为什么不能一上来就enforce=restricted?
  4. readOnlyRootFilesystem: true配了之后,业务写日志到/var/log报错怎么办?

延伸阅读

  • CIS Kubernetes Benchmark v1.9.0 官方文档:https://www.cisecurity.org/benchmark/kubernetes
  • kube-bench 项目:https://github.com/aquasecurity/kube-bench
  • K8s Pod Security Standards:https://kubernetes.io/docs/concepts/security/pod-security-standards/
  • K8s 加密静态 Secret:https://kubernetes.io/docs/tasks/administer-cluster/encrypt-data/
  • Aqua 安全博客(定期更新 K8s 安全实践):https://blog.aquasec.com/

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询