Kubernetes 安全运营与生命周期治理
一次大促前的安全例会上,看板显示高危发现比上周下降了八成,业务负责人以为集中整改已经见效。值班工程师抽查后才发现,负责生成报告的控制器因私有仓库凭证过期连续失败,旧报告又被 TTL 清理;“没有报告”被查询层算成了“没有风险”。团队既找不到失败扫描的资产清单,也说不清最后一份有效证据是什么时候产生的,漂亮的下降曲线反而掩盖了采集链中断。
另一次发布中,一个支付工作负载申请临时放宽 runAsNonRoot 检查,审批单写着“下个版本修复”。数月后镜像、Deployment UID 和服务 owner 都已变化,例外仍按 namespace 与规则名继续生效。新镜像出现同类风险时没有告警,原审批人也已转岗;直到审计抽查,团队才意识到“忽略一条告警”实际上创建了一项需要到期、复核、撤销和追责的风险资产。
从看见告警走向经营证据
安全扫描器或运行时传感器只负责产生一种事实,运营系统负责证明这些事实持续覆盖了目标、失败能被看见、风险能到达有修改权的人,并在修复后由新证据关闭。初学者可以先记住四个对象:资产是被观察的 Kubernetes 对象、节点或镜像;规则是解释事实的版本化逻辑;证据是某次采集的原始结果;工作项是团队对发现项采取的处置状态。四者不能压成一个总分。
配置态势、镜像漏洞、节点基准和运行时事件的时间语义不同。配置报告可能随对象变化重建,漏洞结论会随数据库更新而变化,节点基准依赖目标主机与 benchmark,运行时事件则是不可重放的瞬时事实。安全运营层不应改写这些差异,而应保存来源、观察时间、扫描状态和原始引用,让查询者知道一个绿色结论究竟证明了什么。
最小闭环是:目标进入资产清单,采集器产生有效证据,归一层形成发现项,路由层绑定 owner 与处置期限,修复先改变声明态再进入集群,重扫生成新证据,复核者关闭工作项。任何一步失败都应留下独立状态,不能靠发现数量减少来推断成功。
先建立资产目录与责任边界
集群接入时先读取 API Server 中可公开的资源元数据,不需要把 Secret 值、完整环境变量或业务请求体导入中央平台。工作负载资产键至少包含 cluster_id、namespace、kind、name 和 Kubernetes uid;Pod 级事实还要沿 ownerReferences 回到 Deployment、StatefulSet、DaemonSet 或 Job。镜像必须记录 digest,节点必须记录节点 UID 与节点池,不能只依赖可复用名称。
uid 能区分同名对象删除后重建,稳定的业务服务键则用于跨 UID 追踪长期责任。两者应该同时存在:前者保证证据不串资产,后者保证重建后工单仍能交给正确团队。owner 不应来自自由文本,优先由受评审的 namespace 标签、服务目录或仓库 CODEOWNERS 映射产生;找不到 owner 时进入平台待分配队列,而不是默认交给安全团队。
读取资源元数据的服务账号只需 get/list/watch 目标资源与报告 CRD。它不需要读取 Secret 内容,也不应拥有修改工作负载的权限。下面是一个裁剪后的起点,实际 apiGroups 与报告资源名要按已安装工具的官方 CRD 核对:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: security-evidence-reader
rules:
- apiGroups: [""]
resources: ["namespaces", "nodes", "pods"]
verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
resources: ["deployments", "statefulsets", "daemonsets", "replicasets"]
verbs: ["get", "list", "watch"]
- apiGroups: ["aquasecurity.github.io"]
resources: ["vulnerabilityreports", "configauditreports"]
verbs: ["get", "list", "watch"]先用 kubectl auth can-i --as=system:serviceaccount:security-ops:evidence-reader list configauditreports.aquasecurity.github.io --all-namespaces 验证读取,再反向验证读取 Secret 值和修改 Deployment 均返回 no。如果反向验证得到 yes,应先修正 RoleBinding,不能带着越权身份接入中央平台。
用版本化证据合同接住不同工具
归一层接收的是机器可读事件,不是扫描器页面截图。每条记录至少保存资产键、规则键、工具与规则版本、观察时间、采集状态、原始证据引用和内容摘要。规则键推荐由 source / rule_id / ruleset_digest 组成;若工具只给 release 或 framework 版本,则把版本和规则配置摘要同时保存。升级后规则语义变化时,新旧发现才能被解释,而不是突然制造一批“修复”和“新增”。
schema_version: security.example.com/finding/v1
asset:
cluster_id: cluster-sandbox
namespace: checkout
kind: Deployment
name: api
uid: <resource-uid>
image_digest: sha256:<image-digest>
rule:
source: config-audit
id: KSV012
ruleset_digest: sha256:<ruleset-digest>
evidence:
observed_at: T0
valid_until: T+N
collection_state: succeeded
source_ref: configauditreport/<report-name>
payload_digest: sha256:<payload-digest>
routing:
owner: team-checkout
severity: high
due_class: high-defaultobserved_at 表示事实被观察的时刻,valid_until 是运营策略计算出的有效期,不是工具凭空承诺的真实性期限。collection_state 至少区分 pending、succeeded、failed、partial 与 unsupported。这样,托管控制面不可见会显示为 unsupported,registry 限流会显示为 failed,而不是被折算为通过。
Kubernetes 对象通用元数据说明了 UID、resourceVersion 与 owner references 等身份字段;Kubernetes RBAC 文档可用于核对最小读取权限;采用 Trivy Operator 时,应对照其报告 CRD 文档确认资源和字段,不要把示例合同误当产品 CRD。
报告时效必须与采集健康分开
一份报告只有在目标仍存在、规则版本可解释、采集成功且年龄未超过业务允许窗口时才有效。看板至少同时展示目标覆盖率、有效证据年龄和采集失败率。发现数是风险维度,不能兼任采集健康维度。零发现只有在目标数、成功扫描数、失败数和过期数都可见时才有意义。
推荐把证据年龄分成三个状态:fresh 可用于日常决策,stale 仍保留历史但必须触发补扫,unknown 表示从未成功或无法确定。阈值由发布频率、风险等级与容量预算决定;文档里的 T+N 只是占位语义,生产值要由 SLO 和基线测量给出。若报告 CRD 因 TTL 被删除,资产状态应转为 unknown 或等待新报告,不能自动关闭发现项。
对于 Operator,还要采集扫描 Job 失败、队列年龄、数据库更新时间和控制器调谐错误。对于运行时传感器,要观察节点覆盖、事件速率、丢弃计数、输出延迟和最后心跳。对于静态 CI,要保存 runner 结果、输入 digest 与退出码。不同入口共用同一健康语义,但不能假装底层指标相同。
正反实验验证“零发现”是否可信
在隔离 namespace 准备一个由团队评审过的合格 Deployment fixture,并让目标扫描器完成一次采集。正向路径应看到资产目录存在该 UID、collection_state=succeeded、报告年龄进入 fresh,且规则版本和原始引用可查询。随后修复一个已知配置反例并重扫,预期原发现进入 verified,复核记录引用新对象或新证据,而不是仅看到旧 CR 消失。
反向实验不需要制造真实漏洞,可以破坏采集链:在沙箱中暂时移除报告读取 RoleBinding,或让测试适配器拒绝一个 fixture。预期证据是采集失败率上升、目标进入 failed/unknown、看板不把它计入通过率,且告警到达平台 owner。恢复绑定后重新采集,积压应收敛,失败状态被新的成功证据覆盖但历史失败仍可审计。
kubectl auth can-i \
--as=system:serviceaccount:security-ops:evidence-reader \
list configauditreports.aquasecurity.github.io --all-namespaces
kubectl -n security-ops delete rolebinding evidence-reader-sandbox
# 预期:适配器出现 Forbidden,目标状态为 failed/unknown,而不是 passed。
kubectl -n security-ops apply -f evidence-reader-sandbox-rolebinding.yaml
# 预期:下一轮成功,队列年龄下降,并生成新的 observed_at/source_ref。清理时删除测试 namespace、fixture 工作项与测试专用 RoleBinding;保留脱敏的状态转换摘要。不要删除共享 ClusterRole,也不要用 kubectl delete crd 清理报告,因为那会级联影响整个集群的证据。
去重时保留多源事实
同一 Kubernetes 对象可能被静态清单、集群态势和多个规则集同时命中。去重的目标是减少重复处置,不是丢弃原始证据。只有资产语义、控制目标和规则版本映射一致时,多个观察才可以聚合为一个工作项;每个来源的 source_ref、观察时间与采集状态仍应作为子证据保存。
镜像 tag 相同但 digest 不同不是重复;同名 Deployment 的 UID 改变也不能直接继承“已修复”。规则 ID 相同但规则包 digest 变化时,应先比较语义。严重度冲突不适合取平均,可由暴露面、可利用路径、补偿控制和最高可信来源计算处置优先级,同时保留供应商原始等级。
去重键要有版本,例如 dedup_policy_version。改变映射策略时先离线回放样本,比较合并数、拆分数和 owner 变化,再灰度切换。否则一次归一规则升级就可能大面积关闭旧工单并创建新工单,制造虚假的整改吞吐量。
让 owner、SLA 与状态机形成约束
发现项进入 observed 后先检查资产仍存在、证据有效且规则适用,再进入 triaged。owner 必须是能修改事实的主体:应用配置交给业务仓库 owner,节点基准交给平台或集群供应方,托管控制面缺口由 provider 责任记录承接。安全团队负责规则与风险判断,不应成为所有工单的默认修复者。
SLA 应定义“何时开始计时、什么状态可暂停、什么证据算完成”。等待 owner 分配不能无限暂停;等待发布窗口可以有审批后的暂停原因;只有重扫通过或资产退出证据才能停止整改计时。按严重度给出示例等级可以帮助建模,但真实时限必须进入团队政策配置,避免把任意演示数字写成普适承诺。
observed -> triaged -> assigned -> fix_pending -> verified
| | |
| +-> exception_active -> exception_expired -> triaged
+-> false_positive_reviewed
+-> retired_with_asset_evidence每次转换保存操作者或服务身份、时间、原因、旧值、新值与证据引用。verified 不允许人工勾选绕过证据不变量;紧急情况下可以进入风险接受,但不能伪装成技术修复。
例外必须到期并重新证明
例外对象至少绑定资产键、规则键、风险原因、业务必要性、补偿控制、owner、审批人、到期条件和复核入口。中央系统应成为权威来源,再生成工具需要的 annotation、配置或流水线参数。若业务仓库、集群 annotation 与工单系统各自维护 ignore,同一风险很快会出现三个不同有效期。
到期不是发一封提醒邮件,而是状态自动转为 exception_expired,恢复规则评估并触发重扫。资产 UID、镜像 digest、规则包、暴露面或 owner 变化也应使原例外等待复核。对于特权工作负载之类高影响接受项,复核要实际检查 namespace、ServiceAccount、节点池、网络出口和镜像来源等补偿控制仍然存在。
误报和风险接受必须分开。误报需要可重复的反证并回馈规则 owner;风险接受承认事实成立但暂缓修复,需要期限与责任。二者都写成 ignore 会同时破坏规则质量指标和风险债务指标。
接入工单、GitOps 与发布复核
事件总线或批处理适配器把归一发现项写入中央存储,再按 owner 映射创建或更新工单。幂等键使用版本化 finding ID,工单回写只改变工作流字段,不覆盖扫描器原始证据。消息携带 schema version,消费者遇到未知版本应进入隔离队列并报警,不能静默丢字段。
整改 Kubernetes 配置时,先修改 Helm values、Kustomize patch 或声明清单,经 CI 渲染和规则检查后发布。集群侧确认新 generation、ReplicaSet、Pod template hash、UID 或镜像 digest,再触发对应采集器复核。手工 patch live object 只能作为受控应急动作,并必须回写仓库;否则下一次 GitOps 同步会撤销修复。
工单链接只保存最小定位字段和受控证据引用,不粘贴包含环境变量、Secret、内部镜像仓库地址或完整清单的原始报告。自动响应拥有写权限时,应使用独立服务账号、限定 namespace 与动作,并把审批、速率限制和熔断纳入故障设计。
敏感数据沿证据链最小化
安全报告可能暴露软件包、漏洞利用路径、RBAC 关系、节点配置、内部镜像名和 Secret 所在位置。采集层应在进入消息与工单前删除 Secret 值、token、Cookie、完整环境变量和不必要的请求内容,只保留定位所需的哈希、字段路径与资源引用。原始证据放入受控存储,查询结果按租户、namespace 和角色授权。
凭证分为集群读取身份、registry 身份、外部存储身份和通知身份,分别轮换与撤销。工作负载身份优于长期静态 token;确需 Secret 时限制可读主体,日志中只输出凭证引用。离职、服务迁移和工具退出都要验证旧身份请求被拒绝。
保留期按用途分层:集群内 CRD 保留当前态,短期日志支持排障,发现项与复核摘要支持整改审计。把所有报告永久留在 etcd 会增加控制面存储和 watch 压力;把完整 YAML 永久复制到工单则扩大敏感面。删除策略要与审计需求、对象存储成本和数据驻留要求一起评审。
容量、成本与高可用按链路核算
容量变量包括集群数、工作负载与容器数、节点数、规则数、报告类型、平均报告大小、发布频率、重扫频率、TTL、事件峰值和失败重试率。滚动发布、漏洞数据库或规则包更新会批量触发重评,平均 QPS 无法代表峰值。至少测量队列年龄、处理速率、API 限流、etcd/report 基数、对象存储增长与通知积压。
架构选型可以从故障半径开始。少量沙箱集群可用定时导出加事务库,组件少、成本低,但查询延迟和历史能力有限;生产集群适合在每个集群保留轻量采集与有限缓冲,再把归一、工单和长期证据放到中央服务,网络中断时仍能保存本地状态;强监管或多租户环境还要按区域或租户拆分消息、存储和密钥,牺牲集中查询便利来缩小越权与故障范围。选择条件不是集群数量一个数字,而是允许的证据年龄、数据驻留、断网缓冲、租户隔离和团队值守能力。
高可用不是多放几个 Dashboard 副本。采集控制器需要 leader election、幂等任务与队列恢复;中央消息与存储需要分区、备份和恢复目标;多集群网络中断时要明确本地缓冲上限、丢弃策略和恢复限速。安全平台不可用时,准入门禁是失败关闭还是记录后放行,也必须按风险面单独决定。
成本同时来自集群 CPU/内存、扫描 Job、registry 流量、漏洞数据库缓存、长期存储、搜索索引、SIEM 写入和工单座席。降低扫描频率会增加证据年龄,提高频率会挤压 API Server 与仓库。合适的方案是按风险分层 SLO,并让预算、时效和覆盖率在同一容量评审中可见。
升级、回滚与退出都要保留证据连续性
工具镜像、Chart、CRD、归一 schema、规则包和漏洞数据库都能改变结果。升级前用固定 fixture 与脱敏样本双跑旧版和新版,比较新增、消失、严重度、字段 schema、耗时和资源。旧版继续提供权威结果,新版先旁路;差异经规则 owner 评审后再切换。Kubernetes 官方的自定义资源文档解释了 CRD 存储和版本演进,删除或回退前应先确认转换与存储版本。
回滚包包含旧镜像、旧 Chart values、规则包、schema 适配器、凭证引用和 CRD 兼容判断。若存储已经前向迁移,回滚 Deployment 不一定能恢复;先验证读写兼容,再决定回退应用还是从备份恢复。回滚成功的证据是旧链路重新产生有效报告并且队列收敛,而不是 Pod 回到 Ready。
退出时冻结新例外和新规则,导出资产映射、发现项、原始证据索引、规则版本、owner、未结风险和审计摘要。替代方案双轨运行,逐证据面证明覆盖与失败状态可见。随后停止自动建单和通知,暂停采集,再按依赖顺序卸载 Controller、CronJob、DaemonSet、Webhook、CRD 与存储;删除 CRD 前必须先迁移其对象。
最后核销 ClusterRoleBinding、ServiceAccount、Secret、云身份、registry token、对象存储、缓存、日志索引与预算。用旧 token、旧 webhook 和旧流水线入口做拒绝实验,预期认证或服务发现失败;再用替代链路完成一次发现、分责、修复和复核。资源与身份都被证明不可再用,生命周期才真正结束。
用三个运营指标判断体系是否工作
第一,目标覆盖率回答应被观察的资产中有多少获得了适用证据,并显式列出 failed、partial 与 unsupported。第二,有效证据年龄回答当前结论是否仍可用于决策,按证据面展示分位数与超龄资产。第三,闭环质量回答发现项是否到达正确 owner、例外是否按期返回、修复是否由新证据复核。
不要用关闭工单数量奖励团队,因为规则升级、报告过期和资产重建都可能制造虚假关闭。更可靠的判断是:采集故障能否早于风险下降被发现,owner 是否拥有修改权,逾期与例外是否可解释,回滚和退出是否能撤销所有身份与资源。做到这些,Kubernetes 安全才从工具部署变成可持续运营能力。
