Kubernetes 基础体系 · 第 73/83 篇。示例基于 Kubernetes 当前稳定 API;弃用、版本偏差、云厂商差异和生产风险会明确说明。
Kubernetes 事件响应:止损、证据、审计、凭据轮换和恢复
Kubernetes 事件响应不是“发现异常后删除 Pod”。一次完整响应必须同时处理五个问题:
- 止损:阻止攻击者继续访问、扩散或破坏数据。
- 证据:保留足以回答“谁在什么时间通过什么入口执行了什么操作”的材料。
- 审计:从 Kubernetes API Server 的请求记录中重建控制面行为。
- 凭据轮换:撤销或替换可能泄露的身份凭据,并验证旧凭据确实失效。
- 恢复:在确认环境可信后恢复工作负载、数据、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. 止损的目标
止损的目标是降低攻击者未来可执行的动作集合。可以将某个身份 的能力表示为:
如果攻击者获得了身份 ,止损就是让有效权限集合从 降到 ,并尽量满足:
例如,删除一个 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
预期输出是 yes 或 no。如果响应人员不应拥有读取 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 等敏感资源,使用 Request 或 RequestResponse 会把敏感内容写入审计后端,风险很高。多数生产场景会对常规请求使用 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 的具体字段可能因版本和配置而略有差异,但常见字段包括:
auditIDstagerequestReceivedTimestampstageTimestampuser.usernameuser.groupssourceIPsverbobjectRefresponseStatusrequestURIannotations
可以用 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:可能持续接收后续对象变化;list或watch是否能读取具体值,还取决于响应内容和授权;- HTTP
200表示 API 请求成功,不等于调用者一定成功使用了凭据; - HTTP
403表示授权拒绝,但仍证明有人尝试了该操作。
审计日志能证明 API Server 处理了请求,不能单独证明容器内某个进程使用了 Secret 值访问外部系统。后者还需要结合应用日志、云审计、数据库审计、代理日志和网络流量。
六、第四阶段:凭据轮换不是“改一个 Secret”
1. 先分类凭据
Kubernetes 中常见凭据包括:
-
用户认证凭据
- OIDC 身份提供商令牌;
- 客户端证书;
- 云厂商 SSO 或访问密钥。
-
ServiceAccount 身份
- 投影的短期 Token;
- 旧式长期 Token Secret;
- 通过 TokenRequest API 获取的令牌。
-
应用 Secret
- 数据库用户名和密码;
- API Key;
- TLS 私钥和证书;
- 镜像仓库拉取凭据。
-
控制面凭据
- etcd 客户端证书;
- API Server、kubelet、控制器之间的证书;
- 加密配置和密钥;
- 云厂商控制面凭据。
-
集群外凭据
- 云 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 是否使用
hostNetwork、hostPID、hostPath; - 是否挂载了云厂商身份;
- 是否能访问节点元数据;
- 是否通过 Secret、环境变量或 CSI 驱动获取其他凭据。
3. 应用 Secret 的轮换顺序
假设应用使用数据库密码,正确轮换通常是:
- 在数据库侧创建新密码或新凭据;
- 验证新凭据能够连接;
- 更新 Kubernetes Secret;
- 让应用重新读取凭据;
- 验证应用健康状态和数据库连接;
- 撤销旧密码;
- 检查旧密码是否仍能访问。
更新 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 的镜像和命令;
initContainers和ephemeralContainers;- 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、用户、时间和补丁内容。
如果有审计日志,应查找:
create、patch、update、delete;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. 恢复顺序和验证点
一个更可控的恢复顺序是:
-
恢复或建立可信控制面
验证 API Server、etcd、认证和授权是否工作。 -
恢复基础对象
创建 Namespace、ResourceQuota、LimitRange、NetworkPolicy、RBAC 和必要的 CRD。 -
恢复安全控制器
包括 Admission Webhook、策略控制器、镜像验证和 Secret 管理集成。Webhook 不可用时可能阻塞资源创建,应先验证其证书、服务发现和故障策略。 -
恢复已轮换的凭据
不直接恢复可能泄露的旧 Secret;优先从外部密钥系统重新同步。 -
恢复数据层
按数据库、对象存储、消息系统和 PV 的一致性要求恢复,并检查恢复点。 -
恢复工作负载
使用可信镜像摘要,而不是只使用可变的latest标签:image: registry.example.com/payment@sha256:0123456789abcdef... -
恢复入口和 DNS
先做内部健康检查,再切换流量;DNS TTL 只影响缓存过期速度,不会撤销已经建立的连接。 -
验证业务和安全状态
包括 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/v1 和 events.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”,而是:
只满足其中一项,都不能称为完整恢复。Kubernetes 事件响应的核心,是用最小的破坏性动作控制风险,用独立证据还原事实,用凭据轮换切断旧信任,再从经过验证的控制面、资源和数据恢复业务。
系列导航与关联阅读
- 系列入口:Kubernetes 完整学习路线:从 Pod 与控制面到安全、运维和 Operator
- 上一篇:Kubernetes 节点排障:Kubelet、Runtime、DiskPressure、PID 和网络
- 下一篇:Kubernetes 灾难恢复:控制面、资源、数据、DNS、依赖和演练
- 延伸:Kubernetes 审计日志:Policy、Stage、Backend、性能和合规留存
官方资料
本文依据 Kubernetes、CNCF 与相关项目官方文档重新梳理;正文和生产清单由 WR BLOG 编写。

评论
0 条讨论