Skip to content

Terraform State 与 IaC 凭证防御:加密远端状态、零密钥输出与暴露阻断 ​

1. 威胁模型与 IaC 暴露根因 ​

Terraform 在运行期会将资源属性、output 以及部分 Provider 元数据写入状态文件(.tfstate)和计划文件(tfplan)。一旦发生以下任一疏漏,攻击者无需攻破云控制台即可获得高价值凭据与完整资产拓扑:

  1. 静态/Web 路径暴露:terraform.tfstate、terraform.tfstate.backup、*.tfvars、.terraform/ 或 crash.log 被打包进镜像、静态站点或遗留在 Git 历史中。
  2. 远端 Backend 权限与加密缺失:S3/GCS Backend Bucket 未启用公开访问阻止(Public Access Block)、缺少 KMS 静态加密及最小 IAM Policy,或 CI 产物公开下载包含了 terraform plan / output 明文日志。
  3. 明文密钥持久化入 State:通过 random_password 或资源属性直接生成凭证并写入 state,即使标记 sensitive = true,CLI 仅隐藏终端回显而 state JSON 内部仍保存明文。

2. 分层防御与 IaC 安全工程规范 ​

防线层级控制措施具体要求防御效果
Git 与构建排除.gitignore、.dockerignore 与 pre-commit 钩子强制拦截 *.tfstate*、*.tfvars、*.tfplan接入 gitleaks/trufflehog 门禁阻止状态文件与变量文件进入仓库或容器镜像
远端 Backend 加密隔离启用 S3/GCS 私有访问阻止、encrypt = true(SSE-KMS CMK)及版本控制/锁表仅专用部署 Role 可读写特定环境 prefix消除状态文件被越权枚举或未加密存储
State 零密钥设计Terraform >= 1.10/1.11 优先使用 ephemeral 资源与 write-only(*_wo)属性,或通过 AWS Secrets Manager 外部生成轮换禁止将数据库密码、Token 直接作为 output 导出让状态文件本身不落地持久明文秘密
CI Plan 与日志隔离PR 阶段将 tfplan 加密保存于受限存储,日志禁止打印 terraform show -json 明文细节应用与部署分离,仅受保护分支允许 apply防止 CI 日志与公开 Artifact 泄露敏感属性
Web 边界兜底反向代理统一拒绝访问 *.tfstate*、*.tfvars、*.hcl、.terraform/*返回 404/403阻断自动化路径探测直接下载状态文件

安全远端 Backend 与 Ephemeral 凭据示例(HCL) ​

hcl
terraform {
  required_version = ">= 1.11.0"
  backend "s3" {
    bucket       = "org-prod-tfstate-private"
    key          = "prod/core/terraform.tfstate"
    region       = "us-east-1"
    encrypt      = true
    kms_key_id   = "arn:aws:kms:us-east-1:111122223333:key/example-cmk-id"
    use_lockfile = true
  }
}

# 使用 ephemeral 资源生成密码并配合 write-only 属性,不写入 tfstate
ephemeral "random_password" "db_admin" {
  length           = 32
  override_special = "!#$%&*()-_=+[]{}<>:?"
}

Nginx 兜底拦截规则 ​

nginx
location ~* \.(tfstate|tfstate\.backup|tfvars|tfplan|hcl)$ {
    return 404;
}
location ^~ /.terraform {
    return 404;
}

防御工作流 ​

  1. 暴露面排查与紧急轮换:核查 Web 根目录、Git 历史、CI Artifact 与对象存储是否存在公开可达的 tfstate/tfvars/crash.log;一旦发现历史暴露,假定其中所有数据库密码、API Key 与角色均已失密并立即轮换。
  2. Backend 迁入加密私有存储:将本地 state 迁移至启用 Public Access Block、SSE-KMS 独立密钥与访问日志的远端 Backend,删除本地遗留 .tfstate 和 .backup 文件。
  3. 代码与 CI 改造:升级 Terraform 至支持 ephemeral / write-only 的版本,逐步移除明文 output 和静态密钥变量,将 .gitignore/.dockerignore 与扫描门禁写入仓库基线。
  4. 持续验证:定期对产物目录、公开域名及仓库运行扫描脚本与 http_probe 检查,确认零状态文件泄露。

3. Python IaC 状态暴露与敏感字段检测脚本 ​

python
#!/usr/bin/env python3
"""Terraform 状态文件暴露与敏感属性留存检查工具。"""

import json
from pathlib import Path

SENSITIVE_ATTR_HINTS = ("password", "secret", "token", "private_key", "connection_string", "access_key")
FORBIDDEN_PUBLISH_FILES = {"terraform.tfstate", "terraform.tfstate.backup", "terraform.tfvars", "crash.log"}


def scan_tfstate_for_plaintext_secrets(state_obj: dict) -> list[str]:
    findings: list[str] = []
    for out_name, out_val in state_obj.get("outputs", {}).items():
        if any(h in out_name.lower() for h in SENSITIVE_ATTR_HINTS) or out_val.get("sensitive"):
            findings.append(f"output '{out_name}' stored in state (avoid sensitive outputs in tfstate)")
    for res in state_obj.get("resources", []):
        rtype = res.get("type", "unknown")
        rname = res.get("name", "unknown")
        for idx, inst in enumerate(res.get("instances", [])):
            attrs = inst.get("attributes", {})
            hits = [k for k, v in attrs.items() if v and any(h in k.lower() for h in SENSITIVE_ATTR_HINTS)]
            if hits:
                findings.append(f"resource {rtype}.{rname}[{idx}] retains sensitive attrs: {hits}")
    return findings


def check_gitignore_coverage(gitignore_text: str) -> list[str]:
    required_patterns = ("*.tfstate", "*.tfvars", ".terraform")
    return [pat for pat in required_patterns if pat not in gitignore_text]


if __name__ == "__main__":
    sample_state = {
        "outputs": {"db_password": {"value": "placeholder", "sensitive": True}},
        "resources": [
            {"type": "aws_db_instance", "name": "main", "instances": [{"attributes": {"password": "example-value"}}]}
        ],
    }
    hits = scan_tfstate_for_plaintext_secrets(sample_state)
    missing_ignore = check_gitignore_coverage("*.tfstate\n*.tfvars\n.terraform/\n")
    print("state hits:", hits)
    print("missing gitignore:", missing_ignore)
    assert len(hits) == 2
    assert missing_ignore == []

验证与判定标准 ​

检查项判定为已加固(PASS)判定为存在风险(FAIL)验证方式
Web/仓库暴露面所有 .tfstate*、.tfvars、crash.log 均未入库且 Web 端返回 404任意暴露路径返回 200 或存在于 Git 历史http_probe + Git 历史密钥与文件扫描
远端 Backend 安全强制 encrypt = true(CMK KMS)、开启 Public Access Block 与版本控制使用本地 state 或 S3 Backend 无加密/权限过宽检查 backend HCL 配置与 Bucket 策略
State 敏感字段控制新建密钥采用 ephemeral/write-only 且无明文秘密 outputtfstate 明文留存大量高权 DB/API 密码scan_tfstate_for_plaintext_secrets 核查
CI 日志与产物Plan 产物加密且仅限受保护分支,日志不回显敏感输出PR 日志或公开 Artifact 可下载完整 Plan/State检查 CI Workflow 与 Artifact 保留策略

MCP 工具映射 ​

防御步骤MCP 工具说明
检索 IaC 防护策略kb_router按 Terraform / tfstate / secrets 搜索相关防护文档
核对泄露路径清单kb_read_file读取 ctf-website/10-cloud/terraform-state-secrets 对照常见暴露面
Web 暴露面复核http_probe验证公开域名对 /terraform.tfstate 等路径返回 404

Apache-2.0 · 仅供授权环境下的学习与防御性研究使用 · 免责声明