2. IaC代码安全威胁建模
基础设施即代码(IaC)把网络、计算、存储、身份与密钥策略都变成了可版本化、可复用的代码。这一转变带来可审计与可回滚的优势,同时也把传统运行时的攻击面前移到了代码仓库、模板参数、模块依赖和流水线执行环境。对IaC做威胁建模,核心不是罗列所有可能的漏洞,而是围绕“代码如何变成真实资源”这条链路,识别攻击者能在哪些位置施加影响、影响会沿什么路径放大、最终破坏哪一类安全属性。
本节采用STRIDE作为分类骨架。STRIDE的六类威胁分别是:Spoofing(仿冒)、Tampering(篡改)、Repudiation(抵赖)、Information Disclosure(信息泄露)、Denial of Service(拒绝服务)、Elevation of Privilege(权限提升)。在IaC语境下,它们并不抽象:仿冒对应伪造提交者身份或冒充可信模块来源;篡改对应修改模板、变量或状态文件;抵赖对应缺少不可否认的变更记录;信息泄露对应明文密钥、输出值或状态文件暴露;拒绝服务对应删除关键资源或制造配额耗尽;权限提升对应把过宽的角色、策略或信任关系写入代码。
结合基础设施代码的典型攻击面,可以归纳出四条主要威胁路径:配置漂移、权限提升、数据暴露、供应链投毒。配置漂移指代码声明的期望状态与真实环境状态发生偏离,攻击者可能利用漂移留下的“灰色地带”长期驻留;权限提升指IaC中定义的IAM策略、角色信任关系或Kubernetes RBAC被写成过宽授权,使低权限主体获得高权限;数据暴露指密钥、连接串、证书、状态文件或输出值以明文形式进入仓库、日志或制品;供应链投毒指第三方模块、Provider、镜像或CI插件被替换或注入恶意逻辑,在流水线执行时获得凭据与资源控制权。
把四条攻击面与STRIDE对齐后,威胁清单就不再是泛泛而谈。下面给出一份可直接用于评审的威胁清单示例。清单中的“示例参数”均为教学假设,用于说明判断依据,不代表任何真实环境的实测结果。
| 威胁编号 | 攻击面 | STRIDE 类别 | 典型场景 | 关键判断依据 | 建议缓解措施 |
|---|---|---|---|---|---|
| T-01 | 配置漂移 | 篡改 / 抵赖 | 安全组被手工放通 0.0.0.0/0,代码中未记录 | terraform plan 显示无差异,但云上实际规则与代码不一致 | 启用 drift detection,定期执行 plan 并告警;禁止控制台直改 |
| T-02 | 权限提升 | 权限提升 | IAM 策略使用 "Action": "*" 且 "Resource": "*" |
策略模拟显示可执行 iam:CreateUser、iam:AttachUserPolicy | 最小权限拆分,使用权限边界与 Access Analyzer 校验 |
| T-03 | 数据暴露 | 信息泄露 | 数据库密码以明文写入 variables.tf 并提交 | 仓库扫描命中高熵字符串;state 文件中出现明文密码 | 接入密钥管理服务,使用动态凭据;state 加密并限制访问 |
| T-04 | 供应链投毒 | 仿冒 / 篡改 | 引用未锁定版本的第三方模块,上游被替换 | 模块 source 未固定 commit,校验和不匹配 | 锁定版本与哈希,使用私有仓库镜像并做签名校验 |
| T-05 | CI/CD 执行环境 | 权限提升 / 信息泄露 | 流水线以长期高权限凭据执行不可信 PR 的代码 | PR 触发即注入生产凭据;日志中打印环境变量 | 使用 OIDC 短期凭据,隔离 PR 与生产执行环境 |
| T-06 | 配置漂移 | 拒绝服务 | 误删关键负载均衡或数据库实例,代码未做保护 | 资源缺少 prevent_destroy;删除操作无审批 | 关键资源加生命周期保护,删除走审批与备份 |
威胁清单的价值在于驱动具体动作。以T-02为例,判断一条IAM策略是否过宽,不能只看是否出现星号,而要看“谁能扮演该角色、该角色能对哪些资源做什么”。实际操作中,可以先导出策略JSON,再用策略模拟器验证:假设主体为低权限开发者,尝试执行 iam:CreateUser、iam:AttachUserPolicy、sts:AssumeRole,若返回 allowed,则说明存在权限提升路径。修复时把 "Action": "*" 拆成实际需要的动作集合,把 "Resource": "*" 收敛到具体资源ARN,并加上 Condition 限制来源VPC或MFA。
再以T-03为例,数据暴露往往不是单一位置,而是沿“变量 → 计划 → 状态 → 输出 → 日志”扩散。一个可执行的检查顺序是:先用 gitleaks detect --source . --verbose 扫描仓库历史;再检查 terraform.tfstate 是否包含明文敏感值;然后确认 output 是否标记了 sensitive = true;最后检查CI日志是否打印了 TF_VAR_ 环境变量。若state必须保留敏感值,应使用加密后端并限制读取权限,而不是把state提交到仓库。
常见误区:把 sensitive = true 当作加密。它只控制CLI输出显示,不改变state中的存储形态。真正的机密性依赖加密后端、访问控制与密钥轮换。
供应链投毒的判断重点在“来源是否可验证、版本是否可复现”。例如模块声明 source = "git::https://example.com/modules/network.git?ref=v1.2",其中 ref 指向可变标签,上游可移动该标签指向恶意提交。更稳妥的做法是固定到commit SHA,并记录校验和;对Provider和镜像同样锁定版本与摘要。流水线中还应限制第三方代码能接触的凭据范围,避免“一个PR就能拿到生产密钥”。
完成威胁清单后,需要把它落到可复查的节奏上:每次IaC变更评审时对照清单检查对应威胁项;每周执行一次漂移检测与密钥扫描;每月复核一次高权限角色与信任关系。这样STRIDE就不再是一次性的建模练习,而是持续治理IaC安全的具体抓手。