Kubernetes 基础体系 · 第 73/83 篇。示例基于 Kubernetes 当前稳定 API;弃用、版本偏差、云厂商差异和生产风险会明确说明。

Kubernetes 事件响应:止损、证据、审计、凭据轮换和恢复

Kubernetes 事件响应不是“发现异常后删除 Pod”。一次完整响应必须同时处理五个问题:

  1. 止损:阻止攻击者继续访问、扩散或破坏数据。
  2. 证据:保留足以回答“谁在什么时间通过什么入口执行了什么操作”的材料。
  3. 审计:从 Kubernetes API Server 的请求记录中重建控制面行为。
  4. 凭据轮换:撤销或替换可能泄露的身份凭据,并验证旧凭据确实失效。
  5. 恢复:在确认环境可信后恢复工作负载、数据、DNS 和外部依赖。

这五件事存在冲突。例如,删除攻击者创建的 Pod 可以快速止损,但也可能丢失容器文件系统和进程状态;立刻轮换所有凭据可以缩短暴露窗口,但可能让依赖旧凭据的业务同时中断。因此,响应流程不是固定命令列表,而是一个受证据、影响范围和恢复目标约束的状态转换过程。


一、先建立事件响应的对象模型

1. Kubernetes Event 不等于审计事件

Kubernetes 中有一种名为 Event 的资源,通常通过以下命令查看:

kubectl get events -A --sort-by=.lastTimestamp

较新的集群使用 events.k8s.io/v1 API。一个 Event 通常描述某个 Kubernetes 对象附近发生的状态变化,例如:

apiVersion: events.k8s.io/v1
kind: Event
metadata:
  namespace: demo
  name: web-7d8f6d8b9f-x2abc.18f2e0e7
regarding:
  apiVersion: v1
  kind: Pod
  name: web-7d8f6d8b9f-x2abc
  uid: 8c2...
reason: FailedMount
note: 'MountVolume.SetUp failed...'
type: Warning
action: MountVolume
reportingController: kubelet

它适合回答:

  • Pod 为什么没有启动?
  • Deployment 是否持续创建副本?
  • 节点是否出现磁盘压力?
  • 某个控制器报告了什么状态?

但 Event 不能可靠回答:

  • 谁修改了 Deployment?
  • 谁读取了 Secret?
  • 请求来自哪个客户端证书或 ServiceAccount?
  • 请求体具体是什么?
  • 某条命令何时被执行?

Event 具有以下边界:

  • 通常只保留有限时间;
  • 同类事件可能被聚合;
  • 事件产生者是控制器、kubelet 等组件,不是完整的安全审计主体;
  • 事件丢失不会等价于操作没有发生;
  • Event 本身通常不是合规级别的不可抵赖证据。

因此,Event 是状态线索;API Server 审计日志才是重建控制面请求的主要证据来源。

2. 审计事件和 Kubernetes Event 的数据流不同

Kubernetes 审计发生在 API Server 处理 HTTP 请求的生命周期中。抽象流程如下:

flowchart LR
    C[客户端 kubectl 或控制器] --> A[kube-apiserver]
    A --> AU[审计策略匹配]
    AU --> S[审计事件生成]
    A --> R[认证与授权]
    R --> H[Admission]
    H --> E[持久化到 etcd 或转发]
    S --> L[日志 Backend]
    S --> W[Webhook Backend]
    K[kubelet / controller] --> EV[Event API]
    EV --> E2[Event 持久化]

一次 API 请求可能同时产生:

  • 审计记录;
  • Admission 审计注解;
  • 对象状态变化;
  • Event;
  • kubelet 或控制器日志;
  • 容器日志和节点日志。

这些记录的时间、对象名称和请求 UID 可以互相关联,但它们不是同一份数据。


二、响应生命周期:从发现到恢复

可以把事件响应表示为以下状态机:

stateDiagram-v2
    [*] --> Detected: 告警或线索
    Detected --> Scoped: 确认影响范围
    Scoped --> Contained: 限制访问和扩散
    Contained --> EvidencePreserved: 保存证据
    EvidencePreserved --> Eradicated: 清除持久化和恶意对象
    Eradicated --> CredentialsRotated: 轮换受影响凭据
    CredentialsRotated --> Recovered: 恢复可信工作负载
    Recovered --> Monitored: 持续观察
    Monitored --> Detected: 新证据表明仍被利用
    Scoped --> Recovered: 误报或无破坏且无需隔离

这里的顺序不是绝对的。实际操作常常是并行的:

  • 一组人限制入口;
  • 一组人保存审计和节点证据;
  • 一组人识别受影响凭据;
  • 另一组人准备干净环境。

但有两个原则不能颠倒:

  1. 在可能的情况下,先保存易消失证据,再执行破坏性动作。
  2. 恢复前必须确认恢复对象和凭据来源没有继承入侵状态。

三、第一阶段:止损,但不要把“删除资源”误当成隔离

1. 止损的目标

止损的目标是降低攻击者未来可执行的动作集合。可以将某个身份 II 的能力表示为:

P(I)={(verb,resource,namespace,resourceName)}P(I) = \{(verb, resource, namespace, resourceName)\}

如果攻击者获得了身份 II,止损就是让有效权限集合从 P(I)P(I) 降到 P(I)P'(I),并尽量满足:

P(I)P(I)P'(I) \subset P(I)

例如,删除一个 Pod 只减少了一个运行实例;如果攻击者仍拥有:

  • 创建 Pod 的权限;
  • 读取 Secret 的权限;
  • 修改 Deployment 的权限;
  • 访问云厂商元数据或外部凭据的能力;

那么攻击者仍然可以恢复执行。因此,止损必须优先处理身份和入口,而不只是处理当前进程。

2. 先确认当前身份和权限

响应人员必须明确自己执行命令时使用的身份,否则可能出现“以高权限账号调查,反而污染审计记录或扩大风险”。

kubectl auth whoami
kubectl auth can-i --list
kubectl config current-context
kubectl cluster-info

kubectl auth whoami 用于查看当前认证身份;kubectl auth can-i 用于询问授权结果。对具体动作进行验证更有意义:

kubectl auth can-i get secrets -n production
kubectl auth can-i patch deployments.apps -n production
kubectl auth can-i create pods -n production

预期输出是 yesno。如果响应人员不应拥有读取 Secret 的权限,却得到 yes,这本身就是需要记录的权限问题。

不要把以下命令当成安全隔离:

kubectl delete pod suspicious-pod -n production

它只删除 Pod 对象,且如果 Pod 由 Deployment、ReplicaSet、StatefulSet 或 Job 管理,控制器可能立即重新创建它。它还可能丢失:

  • 容器文件系统中的恶意脚本;
  • 内存中的进程状态;
  • 尚未发送到集中日志系统的日志;
  • 临时目录中的配置和工具。

3. 常见止损动作及其边界

暂停入口流量

可以先在入口层、网关、负载均衡器或云安全组限制外部流量。Kubernetes 内部可以临时修改 Ingress 或 Gateway 关联的路由,但必须确认控制器是否会继续从声明配置中恢复它。

更可靠的隔离通常包括:

  • 外部 WAF 或负载均衡器阻断;
  • 网络层限制攻击源;
  • 暂时撤销暴露的 LoadBalancer 或公网入口;
  • 对受影响命名空间施加临时 NetworkPolicy;
  • 从服务网格或 API 网关撤销路由。

NetworkPolicy 的限制必须明确:

  • 它通常只影响被网络插件执行的 Pod 流量;
  • 不一定限制控制面、节点管理网络或云厂商管理平面;
  • 没有 CNI 支持时,策略可能不会真正隔离;
  • 一条过于宽松的策略可能仍允许横向移动;
  • 一条过于严格的策略可能让响应人员失去诊断能力。

使恶意工作负载失去调度能力

可以暂停一个控制器的扩容或滚动行为,例如:

kubectl scale deployment compromised-api \
  --replicas=0 \
  -n production

这会停止该 Deployment 管理的副本,但不会:

  • 撤销已经泄露的 ServiceAccount Token;
  • 删除其关联 Secret;
  • 阻止攻击者从其他入口创建同名或其他 Pod;
  • 清除节点上的容器和文件;
  • 删除云资源或外部系统中的持久化。

如果需要限制节点上的新调度,可以使用污点或临时隔离节点,但应先确认业务拓扑和 DaemonSet 行为。给节点加污点并不等于停止已有 Pod,也不一定影响 DaemonSet。

隔离命名空间或身份

如果确认某个 ServiceAccount 被滥用,优先缩小其权限或停止使用它,而不是只删除某个 Pod:

kubectl get rolebinding,clusterrolebinding -A -o yaml > bindings-before.yaml
kubectl get serviceaccount -n production compromised-sa -o yaml > sa-before.yaml

保存完证据后,可以删除对应绑定:

kubectl delete rolebinding compromised-binding -n production
kubectl delete clusterrolebinding compromised-cluster-binding

删除 ClusterRoleBinding 的风险很高,因为它可能服务多个工作负载。应先记录绑定对象、被引用的 ServiceAccount,以及业务依赖。


四、第二阶段:证据保全

1. 证据要回答哪些问题

一份可用的证据链至少要回答:

  • 主体:哪个用户、ServiceAccount、客户端证书或 OIDC 身份发起请求?
  • 时间:请求何时到达、何时完成?时区和时钟是否可信?
  • 动作:访问了哪个 API Group、Resource、Verb、Namespace 和对象?
  • 结果:HTTP 状态码是什么?对象是否真的改变?
  • 路径:请求经过哪个 API Server、节点、入口或代理?
  • 影响:哪些 Pod、Secret、ConfigMap、节点、外部资源受到影响?
  • 完整性:证据是否在采集后被修改?

2. 先采集只读状态

以下命令会读取集群状态,但其读取行为本身可能进入 API Server 审计日志,因此应使用专门的调查身份:

mkdir -p evidence/cluster

date -u +"%Y-%m-%dT%H:%M:%SZ" | tee evidence/cluster/collected-at.txt
kubectl version -o yaml > evidence/cluster/kubectl-version.yaml
kubectl cluster-info dump --all-namespaces \
  --output-directory=evidence/cluster/cluster-dump
kubectl get events.events.k8s.io -A -o yaml \
  > evidence/cluster/events-v1.yaml
kubectl get events -A -o yaml \
  > evidence/cluster/events-core.yaml

cluster-info dump 可能产生大量数据,也可能受到权限、版本和集群规模影响。它不应被视为完整取证工具;还需要单独保存:

kubectl get nodes -o yaml > evidence/cluster/nodes.yaml
kubectl get namespaces -o yaml > evidence/cluster/namespaces.yaml
kubectl get pods -A -o yaml > evidence/cluster/pods.yaml
kubectl get deployments,statefulsets,daemonsets,jobs,cronjobs -A -o yaml \
  > evidence/cluster/controllers.yaml
kubectl get roles,rolebindings,clusterroles,clusterrolebindings -A -o yaml \
  > evidence/cluster/rbac.yaml

不要用不必要的命令读取所有 Secret 内容。多数调查只需要元数据:

kubectl get secrets -A -o json \
  | jq 'del(.items[].data, .items[].stringData)' \
  > evidence/cluster/secrets-metadata.json

这仍可能暴露 Secret 名称、类型和标签,但不会保存值。若确实需要保存 Secret 值,必须将其视为高度敏感证据,使用加密介质和严格访问控制。

3. 容器日志和进程证据

在删除 Pod 前,先保存日志和描述信息:

kubectl describe pod suspicious-pod -n production \
  > evidence/cluster/suspicious-pod.describe.txt

kubectl logs suspicious-pod -n production --all-containers \
  --timestamps > evidence/cluster/suspicious-pod.logs.txt

kubectl logs suspicious-pod -n production --all-containers \
  --previous --timestamps \
  > evidence/cluster/suspicious-pod.previous.logs.txt

--previous 只有在容器曾经重启且 kubelet 仍保留上一实例日志时才可能有输出。日志不一定包含:

  • 进程内存;
  • 被删除文件;
  • 节点内核日志;
  • 容器运行时事件;
  • 未写入标准输出的应用日志。

如果怀疑节点被入侵,单纯删除 Pod 不足以恢复可信状态。应按照节点取证流程保留:

  • kubelet 日志;
  • 容器运行时日志;
  • 系统日志和内核日志;
  • 节点进程、网络连接、挂载点;
  • 云厂商实例审计和磁盘快照。

直接在可疑节点上执行清理命令可能改变时间线和文件元数据。生产环境通常需要由平台安全团队决定是现场采集、隔离节点,还是立即销毁并从可信镜像重建。

4. 计算证据哈希

保存文件后可以计算哈希,证明之后拿到的文件与采集版本一致:

find evidence -type f -print0 \
  | sort -z \
  | xargs -0 sha256sum \
  > evidence/SHA256SUMS

哈希只能证明“文件内容是否变化”,不能证明文件最初是否真实,也不能替代访问控制、时间源、采集人和存储介质记录。完整的证据记录还应包括:

  • 采集时间;
  • 采集主机;
  • 使用的身份;
  • 命令和工具版本;
  • 文件来源;
  • 传输和存储过程;
  • 后续访问记录。

五、第三阶段:用审计日志重建控制面事实

1. 审计策略的四个层级

Kubernetes 审计策略通过规则决定哪些请求记录、记录到什么程度。主要审计级别是:

Level 记录内容
None 不记录
Metadata 请求元数据、身份、资源、响应状态等,不记录请求体和响应体
Request 额外记录请求体,不记录响应体
RequestResponse 记录请求体和响应体

对于 Secret、Token、ConfigMap 等敏感资源,使用 RequestRequestResponse 会把敏感内容写入审计后端,风险很高。多数生产场景会对常规请求使用 Metadata,只对经过严格评估的特定操作提升级别。

审计事件的 stage 描述请求处理阶段:

  • RequestReceived:API Server 收到请求;
  • ResponseStarted:已开始返回响应,主要适用于长时间请求;
  • ResponseComplete:请求处理完成;
  • Panic:处理过程中发生异常。

一个请求可能产生多个 stage。不能把同一请求的每条 stage 当成多个独立操作。

2. 一个最小审计策略示例

下面的策略只记录元数据,并显式排除高频、低价值的请求:

apiVersion: audit.k8s.io/v1
kind: Policy
omitStages:
  - "RequestReceived"
rules:
  - level: None
    resources:
      - group: ""
        resources: ["events"]

  - level: None
    users:
      - system:kube-proxy
    verbs: ["watch"]

  - level: Metadata
    resources:
      - group: ""
        resources: ["secrets", "configmaps"]
      - group: "apps"
        resources: ["deployments", "daemonsets", "statefulsets"]
      - group: "rbac.authorization.k8s.io"
        resources: ["roles", "rolebindings", "clusterroles", "clusterrolebindings"]

  - level: Metadata

这个例子表达了几个重要事实:

  • events 通常噪声很大,可以不记录;
  • Secret 的 Metadata 仍能回答“谁访问了哪个 Secret”,但不会记录 Secret 值;
  • RBAC 变更必须记录,因为它可能扩大权限;
  • 最后的通配规则是兜底规则;
  • Kubernetes 审计策略采用首个匹配规则,规则顺序会改变结果。

omitStages: RequestReceived 减少日志量,但会失去“请求刚到达 API Server 时”的记录。对于完整取证和延迟分析,是否省略该阶段要根据吞吐、成本和合规要求决定。

3. 审计 Backend 和可靠性

审计事件可以写入:

  • 日志 Backend:写到 API Server 所在节点或指定文件,再由日志系统采集;
  • Webhook Backend:发送到远程审计服务。

Backend 不是“选一个就永久可靠”。需要明确:

  • 日志文件是否会轮转、丢失或被节点删除;
  • Webhook 不可用时 API Server 是阻塞请求还是批量缓冲;
  • 缓冲区满时丢弃、阻塞还是失败;
  • 审计日志是否独立于被调查集群保存;
  • 多 API Server 的时间和事件顺序如何统一;
  • 审计后端本身是否有访问审计和不可变留存。

具体参数和默认行为随 Kubernetes 版本、发行版和部署方式变化。应以所运行版本的 kube-apiserver 文档和实际启动参数为准,而不是把某个发行版的默认值当成规范保证。

一个常见的静态配置示意是:

kube-apiserver \
  --audit-policy-file=/etc/kubernetes/audit-policy.yaml \
  --audit-log-path=/var/log/kubernetes/audit.log

这不是通用安装命令。托管 Kubernetes 中通常无法直接修改 API Server 启动参数,需要使用云厂商提供的审计能力;云厂商的字段映射、保留周期、收费、脱敏和导出方式也可能不同。

4. 从审计记录中定位 Secret 读取

审计 JSON 的具体字段可能因版本和配置而略有差异,但常见字段包括:

  • auditID
  • stage
  • requestReceivedTimestamp
  • stageTimestamp
  • user.username
  • user.groups
  • sourceIPs
  • verb
  • objectRef
  • responseStatus
  • requestURI
  • annotations

可以用 jq 筛选读取 Secret 的请求:

jq -c '
  select(
    .objectRef.resource == "secrets" and
    (.verb == "get" or .verb == "list" or .verb == "watch")
  )
  | {
      time: (.stageTimestamp // .requestReceivedTimestamp),
      user: .user.username,
      groups: .user.groups,
      verb: .verb,
      namespace: .objectRef.namespace,
      name: .objectRef.name,
      uri: .requestURI,
      code: .responseStatus.code,
      auditID: .auditID
    }
' audit.log

解释时必须区分:

  • get secret/name:访问单个 Secret;
  • list secrets:可能暴露整个命名空间中的多个 Secret;
  • watch secrets:可能持续接收后续对象变化;
  • listwatch 是否能读取具体值,还取决于响应内容和授权;
  • HTTP 200 表示 API 请求成功,不等于调用者一定成功使用了凭据;
  • HTTP 403 表示授权拒绝,但仍证明有人尝试了该操作。

审计日志能证明 API Server 处理了请求,不能单独证明容器内某个进程使用了 Secret 值访问外部系统。后者还需要结合应用日志、云审计、数据库审计、代理日志和网络流量。


六、第四阶段:凭据轮换不是“改一个 Secret”

1. 先分类凭据

Kubernetes 中常见凭据包括:

  1. 用户认证凭据

    • OIDC 身份提供商令牌;
    • 客户端证书;
    • 云厂商 SSO 或访问密钥。
  2. ServiceAccount 身份

    • 投影的短期 Token;
    • 旧式长期 Token Secret;
    • 通过 TokenRequest API 获取的令牌。
  3. 应用 Secret

    • 数据库用户名和密码;
    • API Key;
    • TLS 私钥和证书;
    • 镜像仓库拉取凭据。
  4. 控制面凭据

    • etcd 客户端证书;
    • API Server、kubelet、控制器之间的证书;
    • 加密配置和密钥;
    • 云厂商控制面凭据。
  5. 集群外凭据

    • 云 IAM 角色和访问密钥;
    • DNS Provider Token;
    • CI/CD、镜像仓库、数据库和消息系统凭据。

轮换顺序应由信任关系决定。例如,若攻击者可能读取应用 Secret,先轮换数据库密码,再恢复应用;若攻击者拥有集群管理员权限,则必须评估其是否读取过所有可见 Secret,并考虑集群外凭据也已泄露。

2. ServiceAccount Token 的实际边界

现代 Kubernetes 通常使用 TokenRequest API 和投影卷提供短期 ServiceAccount Token。它们会由 kubelet 刷新,应用需要从文件重新读取或由客户端库处理刷新。

但以下情况不能混淆:

  • 短期 Token 过期后通常自然失效;
  • Token 是否已经过期取决于集群配置、Token 生命周期和签发方式;
  • Kubernetes 没有一个适用于所有认证方式的统一“全局撤销按钮”;
  • 旧式 Secret 中保存的长期 ServiceAccount Token 可能长期有效;
  • 删除或重建 ServiceAccount 会改变其 UID,通常可使绑定到旧身份的 Token 无法继续通过校验,但必须结合实际签发方式和 API Server 配置验证;
  • 外部 OIDC、云 IAM 凭据不由 Kubernetes 单独控制。

检查工作负载使用的身份:

kubectl get pod suspicious-pod -n production \
  -o jsonpath='{.spec.serviceAccountName}{"\n"}'

kubectl get serviceaccount compromised-sa -n production -o yaml
kubectl get rolebinding,clusterrolebinding -A -o yaml \
  | grep -n -C 3 'compromised-sa'

不要只检查 Pod 的 serviceAccountName。还应检查:

  • 绑定到该 ServiceAccount 的 Role 和 ClusterRole;
  • Pod 是否使用 hostNetworkhostPIDhostPath
  • 是否挂载了云厂商身份;
  • 是否能访问节点元数据;
  • 是否通过 Secret、环境变量或 CSI 驱动获取其他凭据。

3. 应用 Secret 的轮换顺序

假设应用使用数据库密码,正确轮换通常是:

  1. 在数据库侧创建新密码或新凭据;
  2. 验证新凭据能够连接;
  3. 更新 Kubernetes Secret;
  4. 让应用重新读取凭据;
  5. 验证应用健康状态和数据库连接;
  6. 撤销旧密码;
  7. 检查旧密码是否仍能访问。

更新 Secret 的示例:

kubectl create secret generic app-db \
  -n production \
  --from-literal=username=app \
  --from-literal=password='NEW_VALUE' \
  --dry-run=client -o yaml \
  | kubectl apply -f -

命令行参数可能进入 shell 历史、进程列表或 CI 日志,因此生产环境更适合从受保护文件或 Secret 管理系统生成清单。例如:

umask 077
printf '%s' 'NEW_VALUE' > /tmp/db-password
kubectl create secret generic app-db \
  -n production \
  --from-literal=username=app \
  --from-file=password=/tmp/db-password \
  --dry-run=client -o yaml \
  | kubectl apply -f -
rm -f /tmp/db-password

这里的更新只改变 Kubernetes 中的 Secret 对象,不保证应用已经使用新值:

  • 通过环境变量注入的 Secret 通常在容器启动时读取,更新 Secret 不会改变现有进程环境;
  • 通过普通 Secret volume 挂载的文件通常会由 kubelet 最终更新,但应用必须重新读取文件;
  • 使用 subPath 挂载时,更新通常不会反映到已挂载文件;
  • 某些 CSI Secret Store 驱动有自己的刷新和轮换语义;
  • 应用可能缓存凭据,必须通过 reload、滚动更新或重启使其生效。

因此,更新后需要验证:

kubectl rollout restart deployment/app -n production
kubectl rollout status deployment/app -n production --timeout=5m
kubectl get pods -n production -l app=app

rollout restart 会触发滚动重启,不是无影响操作。必须确认副本数、PodDisruptionBudget、启动时间和数据库连接上限能够承受它。

4. 验证旧凭据失效

轮换最容易失败的地方是“新凭据可用,但旧凭据仍然可用”。验证应在隔离环境进行:

# 使用新凭据执行一次最小权限测试
curl --fail-with-body \
  -H "Authorization: Bearer ${NEW_TOKEN}" \
  https://example.internal/api/health

# 使用旧凭据测试,预期应返回 401 或等价的拒绝结果
curl -i \
  -H "Authorization: Bearer ${OLD_TOKEN}" \
  https://example.internal/api/health

预期失败状态因外部系统不同而不同,不能仅以 HTTP 401 作为通用规范。还要检查:

  • 云 IAM 审计是否显示旧 Access Key 被拒绝;
  • 数据库旧用户是否被禁用;
  • DNS API 旧 Token 是否撤销;
  • 镜像仓库旧凭据是否不能拉取;
  • 应用日志是否出现凭据刷新失败。

七、第五阶段:清除持久化和确认根因

攻击者可以通过 Kubernetes 对象持久化,不一定需要修改容器镜像。应检查:

  • Deployment、StatefulSet、DaemonSet 的镜像和命令;
  • initContainersephemeralContainers
  • CronJob、Job;
  • Admission Webhook;
  • Mutating/Validating 配置;
  • RoleBinding 和 ClusterRoleBinding;
  • 新增的 ServiceAccount;
  • Secret、ConfigMap 和镜像拉取凭据;
  • Ingress、Gateway、Service;
  • 节点上的 DaemonSet 和系统组件;
  • 云厂商负载均衡、DNS、IAM 和存储资源。

例如,检查最近修改的工作负载:

kubectl get deploy -A -o json \
  | jq -r '
      .items[]
      | [
          .metadata.namespace,
          .metadata.name,
          (.metadata.managedFields[-1].time // ""),
          (.metadata.annotations["kubectl.kubernetes.io/last-applied-configuration"] // "")
        ]
      | @tsv'

managedFields 的最后一项不一定代表“攻击者最后一次修改”,因为不同客户端和控制器会写入不同的管理字段。可靠判断仍需要结合审计日志中的 auditID、用户、时间和补丁内容。

如果有审计日志,应查找:

  • createpatchupdatedelete
  • impersonate
  • RBAC 资源变更;
  • ServiceAccount TokenRequest;
  • Secret 的 get/list/watch
  • Webhook、APIService 和节点相关对象;
  • 来自异常 IP、异常 User-Agent 或异常时间段的请求。

“清除恶意对象”前要记录其完整 YAML,尤其不能只保存当前对象,因为删除后对象状态可能无法从 API 查询恢复。


八、恢复:先恢复可信控制面,再恢复业务

1. 恢复不等于重新部署 Pod

Kubernetes 灾难恢复至少涉及以下层次:

层次 典型内容
控制面 API Server、etcd、Controller Manager、Scheduler、证书和配置
集群资源 Namespace、RBAC、CRD、Webhook、Workload、Service、Ingress
持久数据 PV、数据库、对象存储、消息队列
网络与发现 DNS、入口、负载均衡、NetworkPolicy、服务网格
外部依赖 IAM、镜像仓库、DNS Provider、数据库、CI/CD、监控和密钥系统

etcd 快照只能恢复 Kubernetes API 对象的一部分,不能自动恢复:

  • PV 中的数据;
  • 外部数据库;
  • 云厂商 LoadBalancer 的所有状态;
  • DNS Provider 中的记录;
  • 镜像仓库;
  • 外部身份和凭据;
  • 依赖系统中的业务状态。

反过来,恢复业务数据库也不能恢复 Kubernetes 的 RBAC、Webhook 或 Secret。

2. etcd 恢复的核心约束

etcd 保存 Kubernetes 的控制面状态。恢复 etcd 时必须确认:

  • 快照来自哪个集群和时间点;
  • 快照是否可读、可校验;
  • 证书和成员配置是否匹配;
  • 是原地恢复还是建立新控制面;
  • API Server 是否会连接到恢复后的 etcd;
  • 恢复点之后发生的资源变更如何处理;
  • 恢复出的 Secret 是否包含已经泄露的凭据。

如果攻击者拥有管理员权限,不能简单地把“被入侵集群的最新 etcd 快照”当成可信备份。它可能包含:

  • 恶意 RBAC;
  • 恶意 Webhook;
  • 被修改的镜像和命令;
  • 新增的持久化资源;
  • 已泄露的 Secret;
  • 恶意 CRD 或控制器配置。

恢复时应优先建立干净控制面或可信隔离环境,再从经过验证的备份中恢复资源,并重新生成或注入凭据。

3. 恢复顺序和验证点

一个更可控的恢复顺序是:

  1. 恢复或建立可信控制面
    验证 API Server、etcd、认证和授权是否工作。

  2. 恢复基础对象
    创建 Namespace、ResourceQuota、LimitRange、NetworkPolicy、RBAC 和必要的 CRD。

  3. 恢复安全控制器
    包括 Admission Webhook、策略控制器、镜像验证和 Secret 管理集成。Webhook 不可用时可能阻塞资源创建,应先验证其证书、服务发现和故障策略。

  4. 恢复已轮换的凭据
    不直接恢复可能泄露的旧 Secret;优先从外部密钥系统重新同步。

  5. 恢复数据层
    按数据库、对象存储、消息系统和 PV 的一致性要求恢复,并检查恢复点。

  6. 恢复工作负载
    使用可信镜像摘要,而不是只使用可变的 latest 标签:

    image: registry.example.com/payment@sha256:0123456789abcdef...
    
  7. 恢复入口和 DNS
    先做内部健康检查,再切换流量;DNS TTL 只影响缓存过期速度,不会撤销已经建立的连接。

  8. 验证业务和安全状态
    包括 RBAC、网络策略、审计、日志、告警、凭据旧值失效和关键业务事务。


九、一个从线索到恢复的完整算例

假设监控发现 production 命名空间中的 api Deployment 在凌晨被修改,Pod 运行了未知命令,并且数据库出现异常访问。

第一步:确认对象变化

kubectl get deployment api -n production -o yaml \
  > evidence/api-deployment-current.yaml

kubectl get pods -n production -l app=api -o wide \
  > evidence/api-pods.txt

kubectl describe deployment api -n production \
  > evidence/api-deployment.describe.txt

先保存当前状态,再进行隔离。不能先执行 kubectl rollout undo,否则可能覆盖当前恶意配置,虽然审计日志仍可能保留修改请求。

第二步:查找控制面请求

在审计日志中筛选该 Deployment:

jq -c '
  select(
    .objectRef.namespace == "production" and
    .objectRef.resource == "deployments" and
    .objectRef.name == "api"
  )
  | {
      time: (.stageTimestamp // .requestReceivedTimestamp),
      stage,
      user: .user.username,
      verb,
      uri: .requestURI,
      code: .responseStatus.code,
      auditID: .auditID,
      sourceIPs,
      userAgent: .userAgent
    }
' audit.log

假设结果显示:

{
  "time": "2025-02-18T01:13:04Z",
  "stage": "ResponseComplete",
  "user": "system:serviceaccount:ci:deploy-bot",
  "verb": "patch",
  "uri": "/apis/apps/v1/namespaces/production/deployments/api",
  "code": 200,
  "auditID": "b3...",
  "sourceIPs": ["10.20.3.15"],
  "userAgent": "kubectl/v1.31.0"
}

这说明 API Server 接受了来自 deploy-bot 的 Patch,但仍不能说明:

  • deploy-bot 的 Token 从何处泄露;
  • 10.20.3.15 是否是攻击者主机;
  • Patch 的完整内容是什么;
  • 被修改的命令是否已经执行;
  • 数据库访问是否来自这个 Pod。

因此还要关联:

  • CI/CD 审计;
  • 10.20.3.15 对应的节点或 Runner;
  • Pod 创建和更新记录;
  • 容器日志;
  • 数据库审计;
  • ServiceAccount Token 使用情况。

第三步:止损并保存身份关系

kubectl get serviceaccount deploy-bot -n ci -o yaml \
  > evidence/deploy-bot.yaml

kubectl get rolebinding,clusterrolebinding -A -o yaml \
  > evidence/all-bindings.yaml

如果确认 CI 暂时不可信,应暂停相关流水线,并删除或禁用其高权限绑定。不能仅删除 api Pod,因为被修改的 Deployment 会重新创建同样的 Pod。

第四步:回滚到可信版本

回滚前需要确认 ReplicaSet 历史中存在可信版本:

kubectl rollout history deployment/api -n production
kubectl rollout history deployment/api -n production --revision=3

如果 revision 3 的镜像和命令已被确认可信:

kubectl rollout undo deployment/api \
  -n production --to-revision=3

kubectl rollout status deployment/api \
  -n production --timeout=5m

如果历史对象本身也可能被攻击者修改,回滚并不可靠。此时应从版本控制系统、镜像签名系统和发布制品库重新生成清单,而不是信任集群当前保存的历史。

第五步:轮换 CI、ServiceAccount 和数据库凭据

至少应评估:

  • deploy-bot 的 Token;
  • CI 系统中的云凭据;
  • 它能读取的 Kubernetes Secret;
  • api 能读取的数据库和外部 API 凭据;
  • 镜像仓库凭据;
  • 数据库中与该应用相关的密码。

轮换后逐一验证新值和旧值,不能用“Pod 已经重启”代替外部系统的撤销确认。

第六步:恢复和持续监控

恢复后检查:

kubectl get pods -n production -l app=api
kubectl get events.events.k8s.io -n production \
  --sort-by=.eventTime
kubectl auth can-i --as=system:serviceaccount:ci:deploy-bot \
  patch deployments.apps -n production

最后一条命令预期应为 no,如果 CI 仍需要发布,则应改为只允许必要 Namespace 和必要资源的最小权限,而不是恢复原来的广泛绑定。


十、常见误解和失败表现

误解一:kubectl get events 没有异常,所以没有攻击

错误原因是 Event 不是完整审计。攻击者读取 Secret、修改 RBAC 或执行成功的 API 请求,未必产生明显 Warning Event。应检查 API Server 审计、身份提供商、云审计和应用日志。

误解二:删除 Pod 就能清除攻击

如果 Pod 由控制器管理,控制器会重新创建;如果攻击者拥有创建权限,也可以重新部署;如果节点已被入侵,Pod 删除更可能只是删除了表面症状。应先查控制器、绑定、Webhook、节点和外部身份。

误解三:更新 Secret 后应用立即使用新密码

环境变量注入通常不会自动更新,应用也可能缓存文件内容。必须确认注入方式、应用 reload 机制和滚动更新结果。

误解四:etcd 备份等于完整灾备

etcd 只覆盖 Kubernetes 控制面对象,不覆盖外部数据库、PV 内容、DNS、IAM 和第三方服务。恢复演练必须验证整个依赖链,而不是只验证 kubectl get nodes

误解五:有审计日志就能看到 Secret 的值

使用 Metadata 级别不会记录对象内容;这是降低敏感信息暴露的设计。即使使用 RequestResponse,也不应默认对 Secret 开启,因为日志后端可能成为新的泄露面。调查 Secret 是否被读取,通常先依赖元数据审计,再从外部系统证明凭据是否被使用。

误解六:HTTP 403 表示没有风险

403 表示该次请求未获授权,但攻击者可能已经通过另一身份成功访问,或者已经从 Pod 环境变量、文件、日志和外部系统获得凭据。失败请求也值得保留,因为它可以揭示扫描和权限探测行为。


十一、生产环境需要明确的边界

1. 规范保证、实现行为和经验建议必须分开

规范或 API 层面的保证包括:

  • API Server 可根据审计策略生成审计事件;
  • Kubernetes 使用认证和授权机制决定请求身份与权限;
  • RBAC 通过 Role、ClusterRole 及其 Binding 表达授权关系;
  • Event 是 Kubernetes API 中用于描述状态变化的资源。

常见实现行为包括:

  • 控制器会为被其管理的 Pod 维持副本数;
  • kubelet 会处理投影 Token 和部分 Secret volume 的更新;
  • 事件可能被聚合并在有限时间后清理;
  • 不同部署方式会把审计日志发送到不同后端。

经验建议包括:

  • 将审计日志导出到独立、受保护且不可变的存储;
  • 对敏感资源优先使用 Metadata 审计;
  • 对节点和外部依赖建立独立取证与备份流程;
  • 定期演练 Token、数据库凭据、etcd 和 DNS 的恢复。

不能把某个版本或云厂商的默认值当成所有 Kubernetes 集群的规范保证。

2. 版本和云厂商差异

本文示例使用当前稳定 API 形式,例如 audit.k8s.io/v1events.k8s.io/v1。但以下内容经常存在偏差:

  • kubectl 与 API Server 版本不一致时,命令输出和可用字段可能变化;
  • 审计日志参数由自建控制面启动方式决定;
  • 托管 Kubernetes 可能只提供云厂商审计导出;
  • ServiceAccount Token 的签发和生命周期受 API Server 配置影响;
  • Secret Store CSI、工作负载身份和云 IAM 属于额外组件,不是 Kubernetes 核心 API 的统一行为;
  • etcd 快照恢复由发行版、拓扑和证书配置决定。

执行响应命令前,应先确认集群版本、发行版、控制面管理方式、CNI、存储驱动、身份集成和云厂商能力。


十二、把响应能力变成可验证的系统

事件响应真正成熟的标志,不是有一份“出事后执行的命令清单”,而是平时已经能够验证以下链路:

  • 能否从审计日志定位某次 RBAC 或 Secret 访问;
  • 审计日志是否覆盖所有 API Server 实例;
  • 审计后端故障时,API 请求会阻塞还是丢失审计;
  • 是否能在不读取 Secret 值的情况下调查访问行为;
  • 是否知道每个工作负载使用了哪些 ServiceAccount 和外部凭据;
  • Secret 更新后应用能否无中断或可控地重新加载;
  • 旧凭据是否有明确的撤销验证;
  • 是否能从可信来源重建镜像、清单和策略;
  • etcd、PV、数据库、DNS 和外部身份是否分别可恢复;
  • 恢复后是否重新启用审计、告警和最小权限校验。

最终要验证的不是“Pod 已经 Running”,而是:

恢复成功=业务可用旧凭据失效恶意持久化已清除控制面可信证据完整\text{恢复成功} = \text{业务可用} \land \text{旧凭据失效} \land \text{恶意持久化已清除} \land \text{控制面可信} \land \text{证据完整}

只满足其中一项,都不能称为完整恢复。Kubernetes 事件响应的核心,是用最小的破坏性动作控制风险,用独立证据还原事实,用凭据轮换切断旧信任,再从经过验证的控制面、资源和数据恢复业务。


系列导航与关联阅读

官方资料

本文依据 Kubernetes、CNCF 与相关项目官方文档重新梳理;正文和生产清单由 WR BLOG 编写。