「密钥与敏感信息管理」

在 Terraform 工作流中管理密钥与敏感信息:Vault 集成与动态密钥、KMS 加密、后端与状态加密、密钥轮换与最小权限,以及 CI 中的密钥注入。

1. 敏感信息全景与威胁模型

一句话总结: 敏感信息在 Terraform 中会出现在变量、状态文件、输出与日志四个位置,先按「静态存储、传输、明文可见」三个维度盘点,再逐项封堵。

写进 main.tf 的密钥是「明文静态存储」;写进 terraform.tfvars 是「仓库泄露」;输出不标 sensitive 会被终端打印;state 文件不加密会把密钥落在 backend。威胁模型先列清楚这四条路径。

路径风险对策
代码明文仓库泄露变量 + 密钥系统注入
变量默认值误提交无默认值或标 sensitive
输出终端/日志泄露sensitive = true
state 文件后端泄露backend 加密
# 检查仓库是否已泄密
git log -p --all -S "AKIA" --oneline | head
grep -rE "password|secret|access_key" --include="*.tf" . | head

一句话:密钥管理的第一步不是「用哪个工具」,而是「盘点密钥可能出现的每个角落」,工具只是封堵的手段。

2. 变量 sensitive 与状态加密

一句话总结: sensitive 标记阻止明文输出与显示,但它不加密 state;真正的底线是把 state 放进加密后端,双管齐下。

变量与输出标 sensitive 后,terraform apply 不再打印明文,CLI 用 -no-color 与掩码保护日志。注意:sensitive 不阻止写入 state,后端加密才是最后防线。

variable "db_password" {
  type      = string
  sensitive = true
  description = "数据库密码,由 Vault 或 CI 密钥注入"
}

resource "aws_db_instance" "main" {
  engine         = "mysql"
  engine_version = "8.0"
  username       = var.db_username
  password       = var.db_password
  storage_encrypted = true
}

output "db_endpoint" {
  value     = aws_db_instance.main.endpoint
  sensitive = true
}

2.1 backend 加密

S3 backend 搭配 KMS 加密,或直接使用加密后端(如 GCS 的 KMS),保证 state 落盘不可读。

terraform {
  backend "s3" {
    bucket         = "platform-tfstate"
    key            = "prod/network/terraform.tfstate"
    region         = "ap-northeast-1"
    encrypt        = true
    kms_key_id     = "alias/terraform-state"
    dynamodb_table = "terraform-locks"
  }
}

一句话:sensitive 是「别打印」,加密是「读不出」,两者必须同时存在;state 里始终有明文,只能靠加密后端兜底。

3. Vault Provider 与动态密钥

一句话总结: Vault 通过 vault Provider 动态签发临时凭据,Terraform 只引用「取密文」而不持有明文,天然满足「用完即失效」。

Vault 的杀手锏是动态密钥:数据库、云厂商凭据按需签发,有 TTL,过时自动吊销。Terraform 侧用 vault_generic_secret 或专用 data source 读取。

terraform {
  required_providers {
    vault = {
      source  = "hashicorp/vault"
      version = "~> 3.0"
    }
  }
}

provider "vault" {
  address = var.vault_addr
}

data "vault_generic_secret" "db" {
  path = "secret/data/platform/db"
}

3.1 动态数据库凭据

更进阶的是让 Terraform 在 apply 时从 Vault 申请一组带 TTL 的数据库凭据,配合 vault_database_secret_backend_role 管理。

resource "vault_database_secret_backend_role" "app" {
  backend          = "database"
  name             = "app-readonly"
  db_name          = "mysql-main"
  default_ttl      = "1h"
  max_ttl          = "24h"
  creation_statements = [
    "CREATE USER '{{name}}'@'%' IDENTIFIED BY '{{password}}';",
    "GRANT SELECT ON app.* TO '{{name}}'@'%';",
  ]
}

data "vault_database_secret" "creds" {
  backend = "database"
  role    = vault_database_secret_backend_role.app.name
}

output "db_creds" {
  value = {
    username = data.vault_database_secret.creds.username
    password = data.vault_database_secret.creds.password
  }
  sensitive = true
}

一句话:Vault 的价值是「凭据生命周期化」——不是保管一把永久密钥,而是签发一批短命临时凭据。

4. 云厂商 KMS 集成

一句话总结: KMS 是云厂商的托管加密服务,Terraform 用它加密盘、桶、state 与密钥轮换,KMS key 本身由 Terraform 声明并纳入审计。

AWS KMS、Azure Key Vault、GCP KMS 都能被 Terraform 声明并与资源绑定。以 AWS 为例:S3 SSE-KMS、EBS 加密、Secrets Manager 都用 KMS key。

resource "aws_kms_key" "app" {
  description             = "应用加密主密钥"
  deletion_window_in_days = 30
  enable_key_rotation     = true
  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Sid       = "Enable IAM policies"
      Effect    = "Allow"
      Principal = { AWS = "*" }
      Action    = "kms:*"
      Resource  = "*"
    }]
  })
}

resource "aws_s3_bucket" "encrypted" {
  bucket = "encrypted-assets"
}

resource "aws_s3_bucket_server_side_encryption_configuration" "encrypted" {
  bucket = aws_s3_bucket.encrypted.id

  rule {
    apply_server_side_encryption_by_default {
      kms_master_key_id = aws_kms_key.app.arn
      sse_algorithm     = "aws:kms"
    }
  }
}

4.1 与 Secrets Manager 联动

应用配置用 aws_secretsmanager_secret 存 JSON,Terraform 读取后注入资源,避免密码写进代码。

resource "aws_secretsmanager_secret" "app" {
  name = "prod/app/rds"
}

resource "aws_secretsmanager_secret_version" "app" {
  secret_id     = aws_secretsmanager_secret.app.id
  secret_string = jsonencode({
    username = var.db_username
    password = var.db_password
  })
}

data "aws_secretsmanager_secret_version" "app" {
  secret_id = aws_secretsmanager_secret.app.id
}

一句话:KMS 管「加密」,Secrets Manager 管「存放」,Vault 管「签发」,三者职责不同,组合起来覆盖静态与动态两类密钥。

5. 后端与状态加密

一句话总结: 状态文件含全部资源属性(含密码),必须加密落盘;后端选择按团队基础设施来定,S3+GCS+azurerm 都能加密与锁。

不同后端的安全配置差异主要在「加密」与「锁」两点。统一原则:加密必开、锁必开、权限最小化。

# AWS S3 backend(KMS 加密 + DynamoDB 锁)
terraform {
  backend "s3" {
    bucket         = "tfstate-prod"
    key            = "terraform.tfstate"
    encrypt        = true
    kms_key_id     = "alias/tfstate-key"
    dynamodb_table = "tf-locks"
  }
}
# GCP GCS backend(加密默认 + 锁)
terraform {
  backend "gcs" {
    bucket = "tfstate-prod"
    prefix = "terraform/state"
  }
}
# Azure azurerm backend
terraform {
  backend "azurerm" {
    resource_group_name  = "rg-tfstate"
    storage_account_name = "sttfstate"
    container_name       = "tfstate"
    key                  = "prod.tfstate"
  }
}

5.1 状态加密验证

# 直接从后端拉取状态,确认不可读明文
aws s3 cp s3://tfstate-prod/terraform.tfstate - | grep -c password
# 输出 0 表示落盘已被 KMS 加密(下载后仍是密文)

一句话:后端加密是「最后一道门」——不管代码层做了多少防护,只要 state 明文落盘,一切归零。

6. 密钥轮换与最小权限

一句话总结: 轮换解决「密钥长期有效」的风险,最小权限解决「密钥泄露影响面」的风险,两者共同压缩攻击窗口。

轮换的痛点是「轮换过程不能导致服务中断」。策略:先写入新密钥,再切换引用,最后吊销旧密钥;Terraform 用数据源动态读取即可平滑过渡。

resource "aws_iam_access_key" "app" {
  user = aws_iam_user.app.name
}

resource "aws_iam_user" "app" {
  name = "app-svc"
}

# 最小权限:只允许列桶与读对象
resource "aws_iam_user_policy" "app" {
  name = "min-s3-read"
  user = aws_iam_user.app.name

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect   = "Allow"
        Action   = ["s3:ListBucket"]
        Resource = ["arn:aws:s3:::app-assets"]
      },
      {
        Effect   = "Allow"
        Action   = ["s3:GetObject"]
        Resource = ["arn:aws:s3:::app-assets/*"]
      },
    ]
  })
}

6.1 轮换清单

密钥类型轮换频率做法
云厂商 AK/SK90 天双密钥交替,切换再吊销
数据库密码30 天Vault 动态签发替代静态密码
KMS key自动开 enable_key_rotation
自签证书每年提前 30 天签发新证书

一句话:没有轮换的密钥是「定时炸弹」,没有最小权限的密钥是「核弹」——轮换压时间,权限缩范围。

7. CI 中的密钥注入

一句话总结: CI 里密钥经环境变量或托管 secret 注入,禁止写进仓库与日志,TF_VAR_ 前缀让 Terraform 自动读取。

流水线是密钥泄露的高发区。规范做法:CI 平台托管 secret → 映射为环境变量 → 以 TF_VAR_ 前缀喂给 Terraform;日志脱敏默认开启。

# GitHub Actions 中注入密钥
env:
  TF_VAR_db_password: ${{ secrets.DB_PASSWORD }}
  VAULT_ADDR: ${{ secrets.VAULT_ADDR }}
  VAULT_TOKEN: ${{ secrets.VAULT_TOKEN }}
# 本地开发等价写法
export TF_VAR_db_password="$(vault kv get -field=password secret/db)"
export TF_LOG_PROVIDER=""   # 避免 provider 日志带出明文
terraform apply -auto-approve

7.1 密钥守卫

# pre-commit 检查是否有疑似密钥
grep -rE "AKIA[0-9A-Z]{16}|BEGIN RSA PRIVATE KEY" . --include="*.tf"
# 阻断含敏感词的 diff
git diff --cached | grep -iE "password\s*=" && exit 1

一句话:CI 密钥注入的核心是「源头托管、传输加密、日志脱敏」三件事,让密钥只在运行时短暂存在。

8. 总结

密钥与敏感信息管理的完整防线,从变量到状态逐层收紧:

环节手段目标
变量sensitive 标记 + 无默认值不打印、不误提交
输出sensitive = true终端/日志不泄露
动态密钥Vault 签发临时凭据用完即失效
静态加密KMS / 云托管密钥盘、桶、state 不可读
后端加密 + 锁 + 最小权限状态落盘安全
轮换定期交替 + 吊销压缩攻击窗口
CI托管 secret + 脱敏流水线不泄露

一句话收尾:密钥管理不是「选一个密码库」,而是「让明文只在运行时短暂存在」。从 sensitive 标记、KMS 加密、Vault 动态签发到后端加密,每一层都在压缩明文的存在空间与存在时间;配齐这四层,敏感信息才能在整个 IaC 生命周期里守得住。

延伸阅读

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「terraform」更多文章

  1. Helm Provider 与应用发布:值注入与回滚
  2. 模块注册表与分发:版本、文档与测试
  3. DNS 与证书编排:托管区域与自动验证