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/SK | 90 天 | 双密钥交替,切换再吊销 |
| 数据库密码 | 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 生命周期里守得住。
延伸阅读
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。