上周安全部门丢过来一张表格密密麻麻列着集群版本多少、API Server有没有开匿名访问、etcd是否启用了TLS、kubelet的只读端口关没关……要求一周内交一份基线合规报告。以前遇到这种事基本都是对着官方文档一项一项人工核对一个中型集群几十个配置参数眼睛看花不说还容易漏项。后来我把Kube-Bench捡起来配合CIS Benchmark对照检查才慢慢把这件事从“人工核对”变成了“定时自动化”。这篇文章就把我怎么落地Kube-Bench做K8s安全基线扫描的完整过程写出来包括它到底怎么工作、集群里怎么跑、扫描报告怎么解读以及怎么把扫描接入自动化流程。如果你是运维、安全工程师或者正在被各种安全合规问题追着跑这篇应该能帮你省不少时间。我默认你已经有一个可以操作的Kubernetes集群不需要提前装任何东西Kube-Bench可以临时下载也可以直接用容器运行。下面从原理讲到实操最后把我踩过的坑一并列出来。1. 先搞清楚一件事K8s安全七分靠基线条三分靠工具Kubernetes的默认配置设计初衷是“能用、好用”优先级排在“默认安全”之前。早期很多集群甚至直接允许匿名访问API Serverkubelet的只读端口也开着etcd没有配TLS这类问题在内部测试环境可能无伤大雅一旦集群暴露到生产环境就是实打实的突破口。安全基线的作用就是给“集群应该处于什么样的安全状态”定一个最低标准。你不会想在生产环境里突然被提醒“etcd没有开认证”更不会想靠记忆去确认每一个组件参数是否合规。基线把这一切固化下来让安全状态变得可量化、可审计、可追踪。1.1 为什么需要一条可量化的安全基线K8s的安全配置散落在很多地方控制面组件的启动参数、etcd的证书配置、kubelet的服务配置、RBAC的权限绑定、Pod的安全上下文。这些配置分布在不同的配置文件里人工检查不但效率低而且不同人检查的标准还不一样。今天A管理员觉得“这个参数不用管”明天B管理员可能觉得“这个必须改”最后集群配置全凭个人经验。有了基线标准之后所有检查项都是白纸黑字每一条都有编号、有描述、有修复建议。不管谁来检查标准是一致的结果是可以横向对比的。这也是为什么等保、客户尽调、内部安全审计都会要求提供基线合规报告——他们不关心你集群跑得多快只关心你是否达到了一个公认的安全基线水平。另一个价值在于基线把很多“听起来很专业”的安全议题拆成了具体、可操作的检查点。比如“防止未授权访问”听起来很虚但落到基线里就是“API Server是否设置了--anonymous-authfalse”“是否启用了RBAC授权模式”这样明确的技术动作。这种从抽象到具体的拆解对团队协作特别重要。1.2 CIS Benchmark到底检查哪些东西CIS Kubernetes Benchmark把K8s的安全配置划分成十几个大块核心检查范围包括控制面组件、etcd、工作节点组件和集群策略。每个大块下面又有细化的检查项每一条都对应一个具体的配置要求。控制面组件里API Server的检查项最多因为它几乎是整个集群的入口涉及匿名访问、授权模式、TLS配置、审计日志、ServiceAccount令牌等。etcd的检查项集中在是否启用了TLS认证、是否限制了访问来源。工作节点方面kubelet的只读端口、认证授权方式、Pod运行权限都是重点。集群策略部分主要看Pod Security Admission、网络策略、RBAC默认策略等是否生效。这里有一个很现实的问题——K8s版本更新很快不同版本的默认配置和参数名都会有变化所以CIS Benchmark也会跟着迭代。Kube-Bench自身会区分版本运行时会根据你指定的K8s版本去套对应的检查规则。版本匹配对不上扫描报告就没有参考意义这一块后面我会详细说。2. Kube-Bench的工作方式它凭什么判断你的集群“违规”Kube-Bench本质上是一个配置合规检查器不是一个攻防渗透工具。它不会去尝试利用漏洞也不会产生攻击流量只做一件事读取集群组件的配置文件、检查运行参数和文件权限然后和CIS Benchmark里的规则一条一条做比对输出PASS、FAIL、WARN、INFO。理解这个定位很重要。很多第一次接触Kube-Bench的人会误以为它像漏洞扫描器一样能“扫出漏洞”跑完发现报告里全是配置项反而一脸疑惑。实际上它回答的是“你的集群配置是否符合标准”而不是“你的集群能不能被攻破”。2.1 检查项从哪来版本对应关系与配置文件Kube-Bench的检查规则以yaml文件形式存放在工具目录下里面按照章节和组织结构维护了所有CIS控制项及其检查方法。不同K8s版本对应不同的规则文件规则和版本错配是最常见的误报来源。从K8s 1.20开始CIS Benchmark的版本和K8s版本基本形成了一一对应的关系。比如K8s 1.20到1.22对应CIS Kubernetes Benchmark v1.61.23到1.25对应v1.71.26以后对应v1.8。如果你的集群是1.31版本却用针对1.20时代的默认规则去扫描大概率会扫出很多“不存在”的问题或者漏掉新版里的新检查项。Kube-Bench检查的配置来源也很固定主要读这几类文件控制面组件比如kube-apiserver、kube-controller-manager、kube-scheduler它们的启动参数位于/etc/kubernetes/manifests目录下的静态Pod yaml文件etcd的配置同样在静态Pod里kubelet的配置文件一般在/var/lib/kubelet/config.yaml启动参数则可能分布在/etc/kubernetes/kubelet.env或系统服务文件里。Kube-Bench还检查文件权限和目录归属比如证书文件是否对组可读、容器运行时目录是否被普通用户写权限等。这些检查不需要连接的集群完全依赖本地文件系统所以它必须运行在集群节点上而不能跑在集群外。2.2 一个失败项的产生过程检查逻辑实例拆解以API Server的匿名访问检查为例对应CIS规则编号一般是1.2.1或类似条目要求是“确保匿名请求被禁止”。Kube-Bench的执行逻辑分三步第一步定位api-server的静态Pod配置文件默认路径是/etc/kubernetes/manifests/kube-apiserver.yaml。第二步在配置文件中找到command或args段提取所有启动参数。第三步检查是否存在--anonymous-authfalse。如果参数存在就返回PASS不存在或者被显式配成true就返回FAIL。看起来很简单但实际场景里有一个很常见的坑有些集群通过systemd管理组件或者参数配置在单独的env文件里Kube-Bench默认路径读不到这些自定义位置。这种情况下它会诚实地报告WARN表示“我知道有这个检查项但我没法确认”而不是直接判FAIL。再比如etcd的TLS配置检查Kube-Bench会同时检查--cert-file和--key-file两个参数是否存在并且还会检查这两个参数是否指向有效的证书文件。有些集群只配置了cert-file而漏了key-fileKube-Bench一样判FAIL。这种小细节人工检查很容易漏。理解了检查原理再看后面的操作就会清晰很多。Kube-Bench不是一个黑盒工具它的每条规则都是可追踪的你完全可以知道它为什么给某个检查项打了FAIL。3. 实操把Kube-Bench在集群里跑起来Kube-Bench支持三种常见运行方式直接在节点上跑二进制、用容器临时跑、或者作为Job在集群里跑。不同方式适用不同场景我建议你三个都会按实际情况切换。需要先提醒一点无论哪种方式Kube-Bench都必须能读取节点的宿主目录包括/etc、/var、/opt和/usr/bin。因为它要检查的是节点上的组件配置、二进制文件权限和证书文件不是集群内部运行的东西。3.1 本地二进制方式最快能出结果如果你想在单个master节点上快速验证一下结果本地二进制方式最直接。# 从GitHub Releases下载对应版本 curl -L https://github.com/.../releases/download/v0.9.5/kube-bench_0.9.5_linux_amd64.tar.gz -o kube-bench.tar.gz tar -xzf kube-bench.tar.gz # 赋予执行权限并查看帮助 chmod x kube-bench sudo ./kube-bench --help # 指定K8s版本运行比如集群是1.28 sudo ./kube-bench run --version 1.28命令执行过程中Kube-Bench会逐个读取组件配置并实时输出当前检查项的结果。全部跑完后会汇总出一个结果统计表按检查项分组显示PASS、FAIL、WARN、INFO的数量。二进制方式的优点是简单直接缺点是它会下载二进制到节点里而且需要root权限因为这涉及读取配置文件。对于一次性的排查工作这种方式够用了。注意命令里的--version参数这里的版本指K8s版本不是Kube-Bench自身版本。3.2 容器与Job方式不污染宿主机才是正解如果不想在节点上留任何东西容器方式更合适。Kube-Bench官方提供了容器镜像运行方式如下docker run --rm \ -v /etc:/etc:ro \ -v /var:/var:ro \ -v /opt:/opt:ro \ -v /usr/bin:/usr/bin:ro \ -v /usr/local/bin:/usr/local/bin:ro \ -v /etc/kubernetes:/etc/kubernetes:ro \ kube-bench镜像 run这里把所有需要检查的宿主目录以只读方式挂载进容器容器内部执行检查后直接退出。这种方式的优势非常明显不需要在节点上安装任何二进制不会留下临时文件挂载全部只读对宿主机几乎零侵入。如果用集群内Job方式运行思路是一样的只是把容器挂到了Kubernetes的调度体系里。核心优势是可以通过定制的镜像和调度策略实现批量扫描多个节点。一个经验做法是用NodeSelector把扫描Pod调度到目标节点上比如给master节点加一个专用标签然后通过CronJob定时在master节点上运行扫描。这里有个细节控制面组件和etcd只存在于master节点或控制面节点所以针对控制面的扫描Pod必须调度到这些节点上。而worker节点的检查需要跑在所有工作节点上通常会用DaemonSet方式在每个节点起一个扫描Pod。如果集群规模大这会生成大量扫描结果需要注意结果的汇总和去重。4. 一屏看懂扫描报告哪些是真风险哪些是误判Kube-Bench的默认输出是人类可读的文本格式每个检查项显示编号、描述和结果状态。但这个输出结果用于自动化处理并不友好所以官方还支持JSON、JUnit XML等结构化输出格式。我建议自动化管道统一用JSON手工查看时用文本格式。一个真实集群的扫描报告通常会有几十个FAIL项。看到这么多红色标记别慌不是所有FAIL都意味着集群马上要被攻破了需要按风险级别和影响面来分类处理。4.1 输出格式与结果解读Kube-Bench的结果状态分四种PASS表示配置满足基线要求FAIL表示配置不满足存在风险WARN表示工具无法准确判断需要人工核对INFO表示仅仅提供信息不判定合规性。处理WARN项要特别谨慎。它不代表安全只代表“Kube-Bench没法确认”。常见的导致WARN的情况是组件启动参数不在默认路径里或者检查项依赖的条件在当前环境不适用。这种状态不处理审计的人可能会追问。用JSON输出方便后续解析sudo ./kube-bench run --json /tmp/kube-bench-result.jsonJSON里每一项包含rule_id、desc、remediation、status和实际检测到的配置值。拿到这份JSON后续无论是做数据统计、告警判断还是报告生成都很方便。4.2 失败项的修复优先级判断以我实际扫描过的一批集群为例下面这几个是出现频率最高的FAIL项修复优先级也最高检查项编号风险描述常见修复方式说明1.2.6 / 匿名访问API Server允许匿名请求在kube-apiserver启动参数中加--anonymous-authfalse风险极高被扫到基本等于裸奔1.2.1 / 授权模式API Server使用AlwaysAllow改为--authorization-modeNode,RBAC需要确认现有RBAC策略是否完备2.2 / etcd权限etcd目录存在不安全文件权限chmod 700 或 chown etcd:etcd防止非授权用户读取证书和密钥4.2.1 / kubelet匿名访问kubelet允许匿名请求kubelet配置中设置authentication.anonymous.enabled: false高危容易被横向移动4.2.2 / kubelet授权模式kubelet使用AlwaysAllow改为Webhook模式让kubelet与API Server的RBAC联动1.2.22 / 审计日志API Server未启用审计日志配置--audit-log-path与--audit-log-maxsize需要规划日志存储容量防止磁盘打满从经验看修复顺序应该遵循“先网络边界、再身份认证、后审计追溯”的逻辑。匿名访问、弱授权模式这类问题直接决定集群对未授权请求的暴露面必须最先处理。审计日志这类属于追踪能力虽然不直接影响被不被攻击但没有日志意味着出问题后没有任何线索同样不能拖太久。这里还需要注意每一项修复都不能脱离业务现状。直接把--authorization-mode改成RBAC之前要确保集群里已有的ServiceAccount和RBAC绑定关系是完整的否则改完后现有工作负载可能全部失去权限造成大面积故障。这种“修了一个安全项炸了整个集群”的事故我在实际运维里见过不止一次。5. 别让扫描变成一次性行为自动化闭环设计手工跑一次扫描容易难的是让它持续运转。K8s集群是动态系统配置可能在一次升级、一次节点重建后回退也可能因为某人的一次临时调试而引入新的安全风险。所以自动化的目标不是“扫一次”而是“每次配置变更后都能自动扫且结果能及时送达负责人”。我落地过的自动化方案有三层定时扫描、结果采集、触发告警。5.1 定时任务方案CronJob 结果落库定时扫描最常用的载体就是Kubernetes CronJob。下面是个可以直接参考的示例它会在每天凌晨2点扫描控制面节点并把JSON结果写入持久化存储。apiVersion: batch/v1 kind: CronJob metadata: name: kube-bench-master namespace: security spec: schedule: 0 2 * * * jobTemplate: spec: template: spec: serviceAccountName: kube-bench-sa hostPID: true restartPolicy: Never nodeSelector: node-role.kubernetes.io/master: tolerations: - key: node-role.kubernetes.io/master operator: Exists effect: NoSchedule containers: - name: kube-bench image: kube-bench:latest args: - run - --json - --version - 1.28 volumeMounts: - name: etc mountPath: /etc - name: var mountPath: /var - name: opt mountPath: /opt - name: usr-bin mountPath: /usr/bin - name: usr-local-bin mountPath: /usr/local/bin - name: etc-kubernetes mountPath: /etc/kubernetes volumes: - name: etc hostPath: path: /etc - name: var hostPath: path: /var - name: opt hostPath: path: /opt - name: usr-bin hostPath: path: /usr/bin - name: usr-local-bin hostPath: path: /usr/local/bin - name: etc-kubernetes hostPath: path: /etc/kubernetes这里有几个容易踩的坑。第一扫描容器的hostPID必须开否则Kube-Bench无法读取节点的进程信息部分检查项会变成WARN。第二CronJob的Pod默认会被调度到任意节点必须用nodeSelector和toleration把扫描Pod固定到目标节点上否则结果没有意义。第三很多集群的master节点默认有NoSchedule污点不加toleration的话调度根本不会成功。扫描结果如果直接打印在Pod日志里每次查看都要去翻历史日志。更好的做法是把JSON结果通过脚本发送到对象存储或企业内部的数据接收服务这样可以跨时间维度统计风险趋势。我通常的做法是让扫描Pod多挂一个脚本容器把JSON解析后发送到消息队列后面接一个数据管道做入库和可视化。5.2 接入CI流水线让配置变更先过关再上线定时扫描只能发现“已经存在的风险”真正的安全自动化还应该在“变更发生前”拦截风险。具体来说就是在集群升级、组件配置变更、应用大版本发布之前用Kube-Bench做一次增量检查把基线扫描塞进CI流程。很多团队用Jenkins或Argo CD做集群配置的持续部署。可以在流水线中加入一个Kube-Bench扫描阶段流水线生成的集群偷数先在临时环境或目标节点上跑一次基线扫描如果高危FAIL项数量超过阈值流水线直接中断变更不会进生产。这个方案的实现难点在于Kube-Bench是静态检查工具它不能自动理解“当前变更可能影响哪些检查项”。我的做法是将扫描结果做diff——上一次扫描的基线结果和本次扫描结果对比新增的FAIL项就是本次变更引入的风险需要对它做特别审查。接入CI的另一个价值是让扫描结果“有人负责”。定时扫描的结果如果没人看等于白扫。而CI流水线中扫描不过就发布失败会迫使配置变更的所有者去处理安全项责任自然落到对应团队头上。5.3 结果可视化与告警联动自动化扫描除了结果落库还需要具备实时告警能力。我配置过一个简单的告警链路扫描完成之后脚本解析JSON结果统计高危FAIL项的数量通过与Prometheus格式兼容的方式暴露成指标再配置告警规则。比如设置一个“API Server匿名访问处于开启状态”的告警一旦扫描发现这个配置项不是PASS状态就立刻通过企业IM或邮件通知安全团队。这类告警的优先级应该比普通的资源类告警高因为配置回退往往发生在无意的变更之后越早发现越容易定位到是哪次操作改坏了配置。告警阈值也不要只设置成“有FAIL就告警”否则每天都能收到几十条告警团队很快就麻木了。更合理的做法是对FAIL项分级高危项匿名访问、AlwaysAll授权、etcd无TLS一旦出现就告警中低危项累积到一定数量才告警或者合并成日报周报发送。6. 实际运行一段时间后我总结的注意点自动化扫描跑起来之后真正的麻烦才刚刚开始。工具部署本身不复杂复杂的是让它持续输出有价值的报告而不是变成一堆没人看的日志。下面这几个问题是我在落地过程中遇到过的写出来免得你重复踩坑。6.1 版本匹配是最大的坑Kube-Bench和K8s版本必须严格匹配这一点怎么强调都不过分。我遇到过最典型的情况是集群已经从1.24升级到1.30但CronJob里的--version参数还停留在1.24扫描结果里出现大量莫名其妙的FAIL项。规则文件不匹配会带来的问题有两种。一种是旧规则检查的参数在新版本里已经改名或废弃导致误报另一种是新版本新增的检查项没有被规则包覆盖导致漏报。误报会让团队对报告失去信任漏报更严重会让安全团队误以为集群很安全。建议把扫描版本和集群版本的对应关系维护在配置管理平台里集群升级流程里强制包含“更新Kube-Bench版本参数”这一条防止升级后扫描规则还挂着旧版本。另外Kube-Bench的规则文件在它的GitHub仓库里持续更新但镜像发布可能会有延迟是不是要用最新镜像建议先验证一下是否包含对应的规则文件。6.2 托管集群、自建集群的扫描边界如果你用的是托管集群比如云厂商提供的控制面服务那你对控制面节点基本没有访问权限。这种情况下Kube-Bench扫描控制面组件和etcd的检查项是跑不了的只能在worker节点范围内执行扫描。我建议把扫描计划分成两部分托管侧能扫的worker节点配置、以及控制面配置参数的手动记录。对于无法自动扫描的部分可以向云厂商索取他们的安全基线报告一般托管服务商都会提供现成的合规文件。千万别拿着Kube-Bench结果直接甩给老板说“集群不安全”得先讲清楚扫描边界在哪里。自建集群虽然能完全掌控但也意味着所有修复都要自己处理。控制面组件的yaml文件修改、etcd证书轮换、kubelet配置调整每一步都可能影响集群稳定性。如果你在一个生产集群上第一次做扫描和修复建议先在测试环境完整走一遍流程避免在业务高峰期动控制面参数。6.3 避免修了一个项又带崩了集群修复扫描发现的FAIL项比运行扫描本身需要注意更多事情。我见过最严重的一次事故是某集群的API Server授权模式一直是AlwaysAllow扫描判FAIL后运维直接改成RBAC结果部署在集群里的旧控制器全部同时失去权限瞬间大量Pod重建整个服务雪崩。所以修复高危项之前一定要评估影响面。改授权模式、关匿名访问这类操作最好先在一个非核心业务集群上验证同时准备好回滚方案。Kube-apiserver的配置如果在静态Pod里改完文件后kubelet会自动重启API Server这个重启过程本身就是一次小的集群中断业务量大的时候要谨慎。另外两个细节也容易出错一是etcd目录权限的调整要确认etcd进程的用户和组chown错了会把整个集群的读写权限破坏掉二是审计日志的开启它会在短时间内产生大量日志数据如果没有配置日志轮转策略很可能直接把宿主机磁盘写满反而引发更严重的问题。6.4 扫描结果的价值在于持续跟进不在于一次报告最后说一点比较“软”的经验。Kube-Bench单独跑一次的结果价值非常有限它只是某个时间点的快照。安全基线的真正价值在于趋势变化——这周比上周多出了哪几个FAIL项哪个节点反复出现同样的问题哪些检查项在变更之后被无意关掉了。我把这些变化跟踪下来之后发现很多安全问题其实是重复发生的过程性问题而不只是技术问题。比如kubelet的只读端口被打开很有可能是某次排障时顺手开的API Server的匿名访问被打开可能是某个调试脚本干的。这些问题如果不持续跟踪今天修完下周又会冒出来。所以我在实际落地时会把自动扫描结果和周会同步结合起来每周固定时间汇总一次风险趋势让相关团队看一眼新增的高危项比一次性堆出几十条告警有效得多。安全扫描不是目的把修复动作持续落到日常运维节奏里才是这件事真正的价值所在。