2. 云原生应用攻击面建模
云原生应用不是“一个跑在云上的程序”,而是一条由源码、依赖、构建产物、运行时工作负载、编排控制面、服务网格数据面以及底层云资源共同组成的交付与运行链。攻击面建模的目标,是把这条链上所有可能被外部或内部攻击者触达、影响、利用的入口、资产与信任关系显式地列出来,并标注它们之间的可达路径。只有先看清“有哪些资产、谁信任谁、谁能到谁”,后续的漏洞优先级排序、策略收敛和运行时检测才有依据。
一、攻击面建模的六个层次
工程上通常把云原生攻击面拆成六个层次,每一层的资产类型、暴露方式和典型风险都不同:
- 代码与依赖层:源码仓库、CI 流水线、第三方包(npm、PyPI、Maven、Go module)、构建脚本。风险集中在供应链投毒、硬编码凭据、依赖混淆与构建环境被篡改。
- 镜像与制品层:容器镜像、Helm Chart、SBOM、镜像仓库。风险集中在基础镜像过期、镜像层中残留密钥、镜像签名缺失、仓库可被匿名拉取或推送。
- 容器与运行时层:容器进程、挂载卷、命名空间、capabilities、seccomp/AppArmor 配置。风险集中在特权容器、宿主机目录挂载、容器逃逸。
- Kubernetes 控制面与工作负载层:API Server、etcd、kubelet、ServiceAccount、RBAC、Ingress、NetworkPolicy、Secret/ConfigMap。风险集中在过度授权、匿名访问、Secret 明文、缺少网络隔离。
- 服务网格层:Sidecar、mTLS 证书、VirtualService、AuthorizationPolicy、网关。风险集中在 mTLS 未强制、授权策略过宽、控制面证书泄露。
- 云基础设施层:VPC、安全组、IAM Role、对象存储、KMS、负载均衡、托管 Kubernetes 控制面。风险集中在公网暴露、IAM 权限过大、跨账号信任链被滥用。
这六层不是并列的,而是存在明确的“上游影响下游”关系:代码层的依赖漏洞会进入镜像,镜像配置会决定容器权限,容器权限会影响能否触达 Kubernetes API,Kubernetes 中的 ServiceAccount 又可能映射到云 IAM Role。因此攻击面建模必须沿着这条链做可达性分析,而不是孤立地扫描每一层。
二、资产建模:从“清单”到“关系图”
很多团队的攻击面梳理停留在“资产清单”阶段:列出有多少个镜像、多少个 Namespace、多少个 IAM Role。但清单无法回答“从公网 Ingress 到数据库 Secret 需要经过几跳”。资产建模要做的是把清单升级为带属性的关系图。每个节点至少应包含以下字段:
| 字段 | 含义 | 示例 |
|---|---|---|
| asset_id | 全局唯一标识 | k8s://prod/ns-payment/deploy-checkout |
| asset_type | 资产类型 | Deployment / Image / IAMRole / ServiceAccount |
| exposure | 暴露面等级 | internet / intranet / cluster-internal / none |
| privilege | 权限等级 | cluster-admin / namespace-admin / read-only |
| data_class | 数据敏感度 | PII / PCI / internal / public |
| trust_edges | 信任关系边 | SA→IAMRole、Ingress→Service、Sidecar→CA |
其中 trust_edges 是攻击面建模的核心。以 Kubernetes 为例,一条典型的信任边是:ServiceAccount → IAM Role(通过 IRSA 或 Workload Identity)。如果某个 ServiceAccount 被绑定到 s3:GetObject 且资源为 *,那么任何能创建使用该 SA 的 Pod 的攻击者,就等价于获得了跨账号读取对象存储的能力。这条边在清单里看不见,但在关系图里必须显式画出。
三、可达性分析:把“能到”变成“能利用”
有了关系图之后,攻击面建模的第二步是计算从“入口节点”到“高价值目标节点”的可达路径。入口节点包括:公网 Ingress、LoadBalancer Service、NodePort、被允许匿名访问的 API Server 端点、可被外部推送的镜像仓库。高价值目标包括:持有云 IAM 凭据的 ServiceAccount、etcd 数据、KMS 密钥、含 PII 的数据库 Secret。
可达性分析可以用图遍历实现。下面是一段简化的 Python 示例,用 NetworkX 在资产关系图上查找从公网入口到敏感资产的最短路径:
import networkx as nx
G = nx.DiGraph()
# 节点:资产;边:信任/可达关系
G.add_edge("internet", "ingress-nginx", exposure="internet")
G.add_edge("ingress-nginx", "svc-checkout", exposure="cluster-internal")
G.add_edge("svc-checkout", "sa-checkout", privilege="namespace-admin")
G.add_edge("sa-checkout", "iam-role-payment", privilege="s3:GetObject *")
G.add_edge("iam-role-payment", "s3-pii-bucket", data_class="PII")
# 查找从公网到 PII 存储的可达路径
paths = list(nx.all_simple_paths(G, "internet", "s3-pii-bucket"))
for p in paths:
print(" -> ".join(p))
# 输出:internet -> ingress-nginx -> svc-checkout -> sa-checkout -> iam-role-payment -> s3-pii-bucket
这条路径一旦被识别出来,就可以针对性地做收敛:给 sa-checkout 去掉 s3:GetObject *,改为只允许访问特定前缀;或者在 svc-checkout 与 iam-role-payment 之间增加 NetworkPolicy 与 IAM Condition 限制。攻击面建模的价值不在于“画出一张图”,而在于让每一条高风险路径都有明确的收敛动作和验证方式。
四、建模的落地节奏与验证
实际工程中,攻击面建模不需要一次性覆盖全部六层。建议按“先入口、后纵深”的顺序推进:第一轮先梳理公网暴露面(Ingress、LoadBalancer、NodePort、公网 API Server),第二轮补全 Kubernetes RBAC 与 ServiceAccount 到云 IAM 的信任边,第三轮再覆盖镜像仓库、CI 流水线与服务网格证书。每一轮结束后,用一条可复现的验证命令确认收敛效果,例如:
# 检查是否仍有 ServiceAccount 绑定到 cluster-admin
kubectl get clusterrolebindings -o json | \
jq '.items[] | select(.roleRef.name=="cluster-admin") | .subjects[]'
# 检查是否有 LoadBalancer Service 直接暴露到公网
kubectl get svc -A -o json | \
jq '.items[] | select(.spec.type=="LoadBalancer") | {ns:.metadata.namespace, name:.metadata.name}'
需要强调的是,攻击面是动态的:一次新的 Deployment、一次 RBAC 变更、一次 IAM Policy 调整,都可能新增一条从公网到敏感数据的路径。因此建模不是一次性项目,而应嵌入 CI/CD 与集群准入控制中,在变更发生时自动重算可达性并告警。只有这样,云原生应用保护才从“事后扫描”转向“持续收敛”。