Kubernetes 安全证据分层与工具选型
一个支付服务的镜像在 CI 中通过了漏洞与 Secret 扫描,签名验证也确认它来自批准的流水线。部署请求经过准入策略,privileged=false、只读根文件系统和资源限制全部合格。容器启动后,一个原本正常的诊断子进程被输入驱动,开始枚举服务账号目录并连接异常地址。前三层证据都真实,却没有任何一层观察启动后的进程、文件和网络行为。
另一支团队部署了节点基准、配置态势、漏洞报告和运行时告警四套工具。周会上总分下降,告警数量却上升;同一个 Deployment 在四个平台里用了三种名字,旧 Pod 的事件还挂在新版本名下。值班人员无法回答发现来自哪个对象 UID、哪次规则更新、哪个内核、哪份漏洞库,也不知道修复后该重跑哪一层。工具越多,事实反而越难确认。
先问四个问题再看产品名
第一问是观察对象:源码与依赖、镜像摘要、Kubernetes 请求、渲染清单、live object、节点配置,还是内核行为。第二问是观察时点:提交、构建、准入、周期扫描、对象变更或行为发生瞬间。第三问是执行位置:开发机、CI、API Server、controller/Job、节点 DaemonSet、eBPF hook 或 LSM。第四问是失败语义:返回非零、生成报告、拒绝 API 请求、发出告警、终止进程,还是以内核错误码阻止操作。
这四个答案相同,工具才可能互为候选。Falco 与 kube-bench 都能产生“失败”,但前者的核心输入是运行时事件,后者读取节点和控制面配置;把两者放进统一功能评分,会隐藏部署权限、覆盖盲点和故障半径。选型表应比较证据合同,而不是比较勾选数量。
用八层坐标放置证据
开发扫描读取仓库、依赖、文件系统、SBOM 或镜像,适合快速反馈和 CI 门禁。制品信任把摘要、构建身份、Provenance、签名和信任根连接起来。准入在 API Server 持久化前评价请求与对象。需要深入这些执行面时,可继续阅读开发侧漏洞扫描、供应链证据模型和授权策略证据模型。
静态清单层读取渲染后的 YAML,能发现缺失的安全上下文与资源建议,却不知道目标集群是否默认补字段。集群态势层通过 API 观察 live object、RBAC、镜像与报告,能发现绕过 CI 或长期漂移的对象。节点基准层读取 kubelet、控制面和主机配置,托管控制面不可见时会留下明确缺口。
运行时检测层从系统调用、tracepoint、kprobe、LSM hook 或内核审计得到进程、文件与网络事实;执行约束层进一步施加 signal、override、LSM policy 等动作。检测能看到行为不等于能原子阻止,阻止进程也不等于回滚已经发生的副作用。选型时必须逐个事件验证“观察点位于操作之前还是之后”。
source/dependency --build--> digest + attestations
manifest --admission--> persisted live object
live object --controller/job--> posture and vulnerability reports
node files/config --benchmark--> node/control-plane findings
process action --kernel hook--> runtime event --policy action--> result资产身份决定证据能否相遇
统一数据模型不能从 resource_name 开始。名称可复用,Deployment 会产生 ReplicaSet,Pod 会重建,容器会重启,Tag 会移动。工作负载主键至少保留 cluster_id / namespace_uid / workload_kind / workload_uid / pod_uid / container_name / image_digest。节点证据再加入 node_uid / kernel_release / kernel_build / container_runtime / os_image。
每条发现保存 evidence_type、collector/version、rule ID/revision、observed_at、原始事实摘要、coverage state 和不可变 source reference。运行时事件还需要 event ID、进程身份、父进程、策略 revision、动作与返回结果;漏洞报告需要数据库更新时间、包身份与 image digest;节点基准需要 target、读取路径和访问错误。
{
"asset": {
"cluster_id": "sandbox-cluster",
"namespace_uid": "opaque-namespace-uid",
"workload_uid": "opaque-workload-uid",
"pod_uid": "opaque-pod-uid",
"container": "api",
"image_digest": "sha256:<digest>"
},
"evidence": {
"type": "runtime.process_exec",
"collector": "<name>",
"collector_version": "<locked-version>",
"rule_id": "unexpected-shell",
"rule_revision": "<immutable-revision>",
"coverage": "observed",
"observed_at": "T+N"
}
}coverage 必须是一等字段。observed 表示采集链可用,not_applicable 表示目标不具备该检查,unavailable 表示托管控制面或权限使其不可见,failed 表示采集器执行失败。缺报告不能自动转成 pass;旧报告也不能因为对象同名就继承给新 UID。
在隔离集群建立最小观察台
选型实验需要一个可销毁的 Linux Kubernetes 集群、Helm、kubectl 和能够安装 cluster-scoped 资源的临时管理员。运行时工具还要提前采集每个节点的内核版本、BTF、LSM、架构和容器运行时。管理员只用于安装 CRD、ClusterRole、DaemonSet 与必要的 host access;日常读取改用独立只读身份。
kubectl config current-context
kubectl get nodes -o custom-columns='NODE:.metadata.name,UID:.metadata.uid,KERNEL:.status.nodeInfo.kernelVersion,ARCH:.status.nodeInfo.architecture,RUNTIME:.status.nodeInfo.containerRuntimeVersion'
kubectl auth can-i create customresourcedefinitions.apiextensions.k8s.io
kubectl auth can-i create clusterroles.rbac.authorization.k8s.io
kubectl create namespace security-lab
kubectl label namespace security-lab security.example.com/purpose=evidence-lab预期结果是明确的 context、节点兼容矩阵、两个授权判断和带测试标签的 namespace。任何节点信息缺失都先记录为候选工具的兼容风险。不要在生产节点临时启用 BTF、修改 LSM 启动参数或加载未知内核模块;这些动作可能重启节点或扩大宿主权限,应在专用节点池按正式变更处理。
安装候选工具时固定 Chart 版本、应用版本与镜像 digest,先保存 helm show chart、默认 values、渲染清单和 RBAC diff,再创建 release。Kubescape、kube-bench、Trivy Operator、Polaris、Falco、Tetragon、Tracee 与 KubeArmor 的具体命令分别由对应产品文章维护。这里的共同准入条件是:版本可追溯、权限可解释、节点覆盖可计算、卸载对象可枚举。
正向实验要证明每层真的看见
创建一个安全配置完整的 Deployment,固定镜像摘要,并给它加 test_case_id 与 release revision。先对渲染清单运行静态工具,再创建对象并等待态势控制器完成观察。节点基准对测试节点运行一次,保存 target 与不可见项。最后在容器里执行一个无副作用的受控命令,让运行时工具产生预期事件。
正向判据不是所有工具都返回 pass,而是每层输出符合自身语义:静态层能定位清单字段,集群层绑定 live UID,节点层声明读取到哪些目标,运行时层绑定 Pod/container/process。执行约束候选先运行 audit/monitor 模式,业务动作应成功且出现事件;只有在事件字段、selector 与节点兼容性稳定后才进入阻断测试。
kubectl -n security-lab get deployment evidence-positive -o json > live-positive.json
kubectl -n security-lab get pod -l app=evidence-positive \
-o custom-columns='POD:.metadata.name,UID:.metadata.uid,IMAGE_ID:.status.containerStatuses[0].imageID'
kubectl -n security-lab exec deployment/evidence-positive -- /bin/sh -c 'printf "evidence-positive\n"'预期 live-positive.json 包含 Deployment UID 与 generation,Pod 输出包含 Pod UID 和 image digest,受控命令返回零。运行时侧应按候选产品的输出入口找到对应 exec 事件;找不到时先检查传感器是否位于该 Pod 所在节点、规则是否加载、事件是否被过滤以及丢事件指标,不能把空输出写成“没有风险”。
反向实验要稳定暴露错误机制
配置反例可以使用缺失 runAsNonRoot、允许提权或增加 capability 的测试清单。预期静态与集群态势层命中同一字段,但规则 ID 和严重度可以不同。准入若处于拒绝模式,对象可能根本不会持久化;此时集群态势没有 live finding 是正常结果,必须保留 API 拒绝证据,而不是说态势工具漏报。
运行时反例使用无敏感内容的临时路径和明确测试进程。检测候选应产生事件;执行候选先观察,再灰度到 enforce。阻断判据必须同时检查命令返回码、目标对象状态与安全事件。只看到进程收到 SIGKILL 不能证明写入未发生;使用 override 或 LSM deny 时也要验证目标内核与 hook 支持。
apiVersion: v1
kind: Pod
metadata:
name: evidence-negative
namespace: security-lab
labels:
app: evidence-negative
security.example.com/test-case: posture-negative
spec:
restartPolicy: Never
containers:
- name: probe
image: busybox:1.37
command: ["sh", "-c", "sleep 3600"]
securityContext:
allowPrivilegeEscalation: true
capabilities:
add: ["NET_RAW"]预期配置工具指出 allowPrivilegeEscalation 或 capability 风险。若某个工具不检查该字段,应记录规则覆盖差异,而不是强行调严重度制造一致。实验标签使报告和事件可以定向清理;它不是安全豁免标签,准入策略需要显式排除专用 namespace,并由短期授权和审计保护。
工具选择从证据缺口开始
当主要问题是 kubelet、etcd 或控制面组件配置是否符合 CIS 风格基准,优先评估 kube-bench,并验证发行版 target、配置映射与主机路径。托管控制面不可见时,它会留下真实盲区。若问题是多框架态势、live object、节点风险和持续报告,评估 Kubescape;若需要 Kubernetes 风格的漏洞、配置、RBAC 与 Secret 报告 CRD,评估 Trivy Operator,同时接受扫描 Job、数据库、etcd 与 CRD 生命周期成本。
Polaris 适合围绕工作负载配置建议建立 CLI、Dashboard、Webhook 或 controller 工作流;kube-score 适合对渲染清单做轻量静态反馈。它们不能替代节点基准、镜像身份或运行时事件。开发团队已经使用 Trivy CLI 时,也不能由此推断 Trivy Operator 没有新增成本:后者引入控制器、Job、集群 RBAC、报告读取与保留。
运行时侧,Falco 适合成熟规则驱动的检测与告警链;Tracee 把 eBPF 事件采集与签名检测组合起来;Tetragon 提供深层 TracingPolicy、事件导出和受内核能力约束的执行动作;KubeArmor 更强调 KSP/CSP/HSP 与 BPF-LSM、AppArmor 等执行器。选择依据应是事件语义、内核矩阵、阻断原子性、数据输出和团队值班能力,而不是“是否使用 eBPF”这一项。
相邻安全系统如何交接
制品信任交付的是不可变 digest、生产者身份、Provenance、签名与验证结果。运行时安全链以 digest 关联工作负载,但不会从运行时事件反推构建器可信。若发现异常进程,事件响应可把 digest 交给供应链证据图计算受影响部署;两边保留各自 verifier 与规则 revision。
准入交付 API 请求、策略/Binding、决定、audit ID 与是否持久化。对象被允许后,态势扫描继续检查 live state,运行时传感器观察行为。准入拒绝不产生 live object;运行时告警也不能证明准入策略失效。开发侧扫描交付仓库 revision、清单或镜像 digest、数据库版本和退出码;集群侧再补实际 UID、默认化字段、节点与运行事件。
工作负载故障排查交付 Pod status、Event、日志、EndpointSlice 与受控 exec 证据,详见日志、事件、Exec 与端口转发。安全事件可以引用这些证据,却不能把业务 CrashLoop 自动判为攻击,也不能用安全告警代替容器退出码和 kubelet Event。
项目接入要分开事实库与工作队列
原始证据保存不可变事件或报告引用,规范化层生成统一资产键和低基数 finding,工单系统负责 owner、SLA、例外和整改动作。看板只是查询视图。若工单关闭会反向删除原始报告,或看板分数成为唯一事实,后续规则升级就无法重算历史影响。
推荐的状态机是 observed -> triaged -> assigned -> mitigated -> verification_pending -> closed。closed 必须引用同一资产或明确继任资产上的复扫结果;对象已删除则记录 deletion UID、时间和控制器级联结果。例外保存 reason、owner、批准者、目标、规则 revision、到期时间和补偿措施,到期后重新进入 triage,而不是永久压制。
导出管道按来源保存 checkpoint。CRD watch 使用 resourceVersion 并处理过期重列,事件流保存 source event ID 与消费位点,周期扫描保存 run ID 与覆盖分母。下游不可用时使用有界缓冲并暴露 drop/backpressure;无限重试会拖垮 controller,无声丢弃会制造“风险下降”的假象。
权限模型按采集面拆开
静态清单工具通常不需要集群凭证。集群态势 reader 可按 namespace 获取 workload 与报告,cluster-scoped RBAC 和 node 则由独立角色读取。扫描 Job 若要读取私有镜像,需要受限的 registry 凭证;不能把所有 namespace 的 imagePullSecret 复制给一个共享扫描账号。节点基准的 hostPath 只读挂载仍然能暴露证书、配置和宿主细节。
运行时 agent 可观察宿主进程并加载 BPF 或 LSM 程序,属于节点高权限组件。关闭 privileged 并不自动变成低权限,capability、host namespace、挂载和内核接口仍要逐项审查。Operator、agent、relay、exporter 与读报告用户使用不同 ServiceAccount;管理规则、读取原始事件和删除历史数据也应分权。
安装后保存以下授权证据,并在升级时做 diff:
kubectl auth can-i --list \
--as=system:serviceaccount:<security-namespace>:<collector-service-account>
kubectl get clusterrole,clusterrolebinding -l app.kubernetes.io/instance=<release> -o yaml
kubectl get daemonset,deployment -n <security-namespace> -o yaml预期能解释每个 cluster-scoped verb、hostPath、capability 与网络出口。通配符权限、读取 Secret、patch node/namespace 或创建任意 workload 都需要独立理由。删除一个无关权限后若工具完全不可用,说明权限合同需要在测试环境继续拆解,而不是立即恢复管理员权限并停止分析。
安全数据也需要最小化与销毁
运行时事件可能含命令参数、路径、网络地址、用户 ID 与容器环境线索;漏洞和 RBAC 报告会暴露组件版本、角色与攻击路径;节点基准会披露主机配置。采集配置应优先保留规则判断必需字段,对 argv、环境、文件内容和 payload 使用拒绝列表或允许列表,并验证文件 sink 与 gRPC/HTTP sink 是否应用同一过滤规则。
多租户读取以 namespace 和资产 owner 为主,平台安全团队使用单独审计角色。原始证据、规范化 finding、工单和聚合指标采用不同保留期;删除流程覆盖 CRD、对象存储、消息系统、搜索索引、缓存和备份。向外部 SaaS 发送前核对数据驻留、传输身份、租户隔离、删除 API 与退出导出格式。
容量与成本用两条公式思考
扫描类的日负载近似由“资产变化量 × 报告类型 × 重扫频率”决定,再叠加漏洞库更新、失败重试和滚动发布峰值。成本落在 Job CPU/内存、镜像拉取、数据库缓存、API 请求、etcd/外部存储和报告索引。报告 TTL 缩短会提高新鲜度,也会触发更多重扫;TTL 拉长会降低成本,却扩大陈旧窗口。
运行时类的入口负载近似由“每节点事件率 × 启用事件比例 × 平均事件字节”决定,规则求值、进程缓存、内核 buffer、用户态队列、relay 和 SIEM 逐层放大。高基数 argv 与路径不仅增加带宽,也增加索引费用和泄露面。容量验证必须包含正常基线、发布洪峰、攻击样本和下游断连四种阶段。
共同指标至少包含覆盖节点/目标数、采集失败数、报告年龄、扫描队列年龄、API throttling、规则加载失败、丢事件、导出失败、缓冲占用和下游确认延迟。指标为零也要有语义:没有事件、传感器失联和 exporter 停止不能共用同一条零线。
高可用先定义故障发生在哪一层
节点 agent 的故障域是单节点,DaemonSet 覆盖率比中央副本数更重要。Operator/controller 的故障域是新策略、扫描编排和 CRD 调谐,leader election 负责接管但不保证无重复。Relay/exporter 的故障域是集中可见性与下游交付,是否重放取决于产品和外部消息系统,不能从 replicas 推断 exactly-once。
将检测与执行分开做故障注入。停止 controller 后,既有节点规则可能继续工作,但新工作负载未必加载;停止 relay 后,节点阻断可能继续,但 SIEM 出现空窗;停止 agent 后,该节点可能既失去事件也失去约束。每次演练保存进入故障、第一缺口、接管、补偿完成与数据对账五个时点。
升级、迁移与退出是一组证据事务
升级先冻结候选版本的 Chart、镜像、CRD、规则和漏洞库,复制一组黄金资产与正反事件。新旧版本并行时比较 coverage、finding identity、严重度、事件字段、阻断结果和资源成本;差异按预期变更、规则变更、能力丢失和采集失败分类。只比较 Pod Ready 或告警总数不足以批准升级。
回滚前检查 CRD schema、存储版本、内核程序和 LSM profile。Helm history 不能证明 CRD 可降级,也不能清除 pin 在宿主的 BPF 对象。迁移到另一工具时先建立双轨关联,确认新工具覆盖同一资产与故障场景,再停止旧规则写入;禁止让两套执行器同时对同一高风险动作施加不同约束。
清理从实验 namespace 开始:删除测试工作负载与报告,撤销临时 RoleBinding,终止 export consumer,再按产品顺序卸载 release。最后逐节点验证 agent、内核对象、LSM profile 和宿主文件,逐集群检查 CRD、webhook、ClusterRoleBinding、Secret 与 finalizer,逐外部系统检查 topic、index、bucket、API token 和账单。退出完成的判断是旧系统不再观察、不再执行、不再持有凭证,也不再产生费用。
用第一证据处理常见误判
“零发现”先看覆盖分母、最后成功扫描、规则加载和丢事件;“同名对象风险复活”先比 UID 与 image digest;“准入拒绝但态势无报告”先确认对象是否持久化;“Block 策略已创建但动作仍成功”先查 Active LSM、策略加载状态、目标节点和执行器能力;“事件存在但 SIEM 没有”沿 agent、socket/gRPC、relay、队列和 sink checkpoint 分段定位。
“升级后告警暴增”先区分规则 revision、默认规则变化、事件字段变化和真实行为变化;“托管集群基准全绿”先检查不可见 target 是否被排除;“运行时工具 CPU 突增”先看事件洪峰、selector 宽度、参数采集、buffer 丢失和下游背压。每一种现象都有更靠近事实的第一证据,先分型再改规则,才能避免用静默忽略换取漂亮分数。
Kubernetes 官方的 审计、RBAC 与 Pod Security Standards可用于核对基础平台语义。工具能力与目标版本应分别查阅 kube-bench、Kubescape、Trivy Operator、Falco、Tetragon、Tracee 与 KubeArmor的一手文档,并在目标内核和 Kubernetes 发行版上重跑同一组证据合同。
