IaC 安全与合规#
2021 年,某公司的一个 Terraform 配置被意外提交到 GitHub 公开仓库。这个配置包含了 AWS 密钥对、S3 bucket 权限,以及一个暴露的数据库连接字符串。
结果:攻击者利用这些凭证挖矿,造成了数万美元的损失。
这个故事告诉我们:基础设施代码中的安全问题,后果可能比应用代码更严重,因为它直接控制着云资源的访问权限。
#安全风险分类
#代码中的敏感信息
危险配置示例
# 错误:将密钥直接写在代码中
resource "aws_db_instance" "main" {
password = "super-secret-password-123" # 危险!
# ...
}
# 错误:硬编码访问密钥
provider "aws" {
access_key = "AKIAIOSFODNN7EXAMPLE"
secret_key = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
}flowchart LR
A[代码提交] --> B[Git 历史]
B --> C[永久保留]
C --> D[泄露风险]
E[敏感数据] --> F[Env 文件]
F --> G[加密]
G --> H[安全使用]#权限过度
危险权限
# 危险:管理员权限给所有资源
{
"Effect": "Allow",
"Action": "*",
"Resource": "*"
}
# 正确:最小权限
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject"
],
"Resource": "arn:aws:s3:::my-bucket/*"
}#不安全的网络配置
危险的网络配置
# 危险:开放所有端口
resource "aws_security_group" "bad" {
ingress {
from_port = 0
to_port = 65535
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
# 正确:精确的端口
resource "aws_security_group" "good" {
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}#敏感数据管理
#环境变量
环境变量注入
# 不推荐:.tfvars 文件
# variables.tfvars
db_password = "secret123"
# 推荐:命令行注入
export TF_VAR_db_password=$(vault kv get -field=password secret/db)
terraform apply
# 或 CI/CD 注入
env:
TF_VAR_db_password: ${{ secrets.DB_PASSWORD }}#Vault 集成
Vault
provider "vault" {
address = "https://vault.example.com"
}
data "vault_kv_secret_v2" "db_creds" {
mount = "secret"
name = "database"
}
resource "aws_db_instance" "main" {
username = data.vault_kv_secret_v2.db_creds.data.username
password = data.vault_kv_secret_v2.db_creds.data.password
}使用
# 安装 SOPS
brew install sops
# 创建加密文件
sops --encrypt secrets.yaml > secrets.enc.yaml
# 编辑加密文件
sops --edit secrets.enc.yaml.sops.yaml
creation_rules:
- path_regex: secrets\.yaml
kms: arn:aws:kms:us-east-1:123456789:key/xxx
encrypted_suffix: _secret#AWS Secrets Manager
Secrets
data "aws_secretsmanager_secret_version" "db" {
secret_id = "prod/myapp/database"
}
locals {
db_creds = jsondecode(data.aws_secretsmanager_secret_version.db.secret_string)
}
resource "aws_db_instance" "main" {
username = local.db_creds.username
password = local.db_creds.password
}#密钥管理
#不要在代码中存储密钥
密钥轮换流程
# 1. 在 Vault 中存储密钥
vault kv put secret/db username=admin password=new-secure-password
# 2. Terraform 读取
data "vault_kv_secret_v2" "db" {
mount = "secret"
name = "db"
}
# 3. 应用到 AWS
resource "aws_secretsmanager_secret_version" "db" {
secret_id = aws_secretsmanager_secret.db.id
secret_string = jsonencode({
username = data.vault_kv_secret_v2.db.data.username
password = data.vault_kv_secret_v2.db.data.password
})
}#IAM 角色
使用
provider "aws" {
region = "us-east-1"
# 不指定 access_key 和 secret_key
# 使用环境中的 IAM 角色或 AWS_PROFILE
}
# EC2 实例角色
resource "aws_iam_instance_profile" "ec2" {
name = "terraform-ec2-profile"
role = aws_iam_role.ec2.name
}
resource "aws_iam_role" "ec2" {
name = "terraform-ec2-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "ec2.amazonaws.com"
}
}
]
})
}#扫描工具
#tfsec
# 安装
brew install tfsec
# 扫描
tfsec .
# 输出
Result: ✗ [HIGH] Resource 'aws_db_instance.main' has no encryption at rest configured.
[SECURITY-TEST-001]
See https://tfsec.dev/docs/general/RDS encryption/tfsec
tfsec.yml#Checkov
# 安装
pip install checkov
# 扫描 Terraform
checkov -f main.tf
checkov -d ./terraform/
# 扫描 Plan
terraform plan -out=tfplan
checkov -f tfplan --framework terraform_plan# 扫描结果
Check: CKV_AWS_16: "RDS should have public snapshot disabled"
FAILED for resource: aws_db_instance.main
Check: CKV_AWS_42: "S3 Bucket has no lifecycle policy"
PASSED for resource: aws_s3_bucket.data
Check: CKV_AWS_123: "EC2 instance should not have public IP"
WARNING for resource: aws_instance.web#Terrascan
# 安装
brew install terrascan
# 扫描
terrascan scan -t aws -f main.tf#代码审查
#审查清单
SECURITY_REVIEW.md
# IaC 安全审查清单
## 敏感信息
- [ ] 没有硬编码的密码、密钥
- [ ] 使用 Vault/Secrets Manager 管理敏感数据
- [ ] .tfvars 文件在 .gitignore 中
- [ ] 没有 .tfstate 或 .tfbackup 提交
## IAM 权限
- [ ] 遵循最小权限原则
- [ ] 没有 `*` 权限
- [ ] S3 权限指定到对象级别
- [ ] 使用条件限制(如源 IP)
## 网络安全
- [ ] 安全组规则最小化
- [ ] 没有 0.0.0.0/0 的 SSH/RDP
- [ ] 数据库在私有子网
- [ ] 使用 VPC Endpoint
## 加密
- [ ] S3 启用加密
- [ ] RDS 启用加密
- [ ] 传输加密(TLS)
- [ ] 密钥使用 KMS
## 日志
- [ ] CloudTrail 开启
- [ ] VPC Flow Logs
- [ ] S3 访问日志#Pre-commit 钩子
.pre-commit-config.yaml
repos:
- repo: https://github.com/antonbabenko/pre-commit-terraform
rev: v1.86.0
hooks:
- id: terraform_fmt
- id: terraform_validate
- id: terraform_tflint
- id: tfsec
- repo: https://github.com/bridgecrewio/checkov
rev: v3.1.50
hooks:
- id: checkov
args: ["--directory", "."]
- repo: https://github.com/Yelp/detect-secrets
rev: v1.4.0
hooks:
- id: detect-secrets#合规框架
#CIS AWS 基准
CIS
# Checkov 内置 CIS 检查
checkov --check CKV2_AWS_* --compact
# 启用 CIS 基准
checkov --armor --benchmark CKS_AWS_1.4#SOC 2 控制
| SOC 2 控制 | IaC 实践 |
|---|---|
| CC6.1 | IAM 最小权限、密钥轮换 |
| CC6.3 | 网络隔离、安全组 |
| CC6.6 | 日志、审计 |
| CC7.2 | 入侵检测、监控 |
#PCI DSS
PCI
# PCI 要求加密
resource "aws_db_instance" "main" {
identifier = "pcidss-db"
# 必须启用加密
storage_encrypted = true
kms_key_id = aws_kms_key.pcidss.arn
# 必须私有子网
db_subnet_group_name = aws_db_subnet_group.main.name
vpc_security_group_ids = [aws_security_group.pcidss.id]
}
resource "aws_kms_key" "pcidss" {
description = "PCI DSS compliant KMS key"
deletion_window_in_days = 10
enable_key_rotation = true
policy = data.aws_iam_policy_document.kms.json
}#审计与日志
#变更追踪
Git
git log --oneline --all -- terraform/
git log -p -- terraform/main.tf | grep -A5 -B5 "password"
git blame terraform/main.tf | grep -i "secret"#Terraform Cloud 审计
Terraform
terraform {
backend "remote" {
organization = "myorg"
workspaces {
name = "production"
}
}
}审计日志
# Terraform Cloud 企业版
curl -X GET https://app.terraform.io/api/v2/organizations/myorg/audit-logs \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/vnd.api+json"#CloudTrail
CloudTrail
resource "aws_cloudtrail" "main" {
name = "terraform-audit"
s3_bucket_name = aws_s3_bucket.trail.id
is_multi_region_trail = true
is_organization_trail = true
include_global_service_events = true
event_selector {
read_write_type = "All"
include_management_events = true
data_resource {
type = "AWS::S3::Object"
values = ["arn:aws:s3:::my-terraform-state/*"]
}
}
}#自动化安全
#策略即代码
OPA
package terraform.security
deny[msg] {
input.resource[_].type == "aws_db_instance"
not input.resource[_].storage_encrypted == true
msg = "RDS must have encryption enabled"
}
deny[msg] {
input.resource[_].type == "aws_security_group"
inbound = input.resource[_].ingress[_]
inbound.from_port == 22
inbound.cidr_blocks[_] == "0.0.0.0/0"
msg = "SSH should not be open to the world"
}使用
# 安装
brew install conftest
# 运行
conftest test main.tf -p policy.rego#GitHub Actions 安全扫描
.github/workflows/security.yml
name: Security
on:
push:
branches: [main]
pull_request:
paths:
- '**/*.tf'
- '**/*.yaml'
- '**/*.yml'
jobs:
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run tfsec
uses: aquasecurity/tfsec-action@v1
- name: Run Checkov
uses: bridgecrewio/checkov-action@master
with:
directory: .
framework: terraform
output_format: sarif
- name: Run Trivy
if: false
uses: aquasecurity/trivy-action@master#最佳实践总结
#安全检查清单
- 敏感数据隔离:使用 Vault、Secrets Manager
- 最小权限:IAM 遵循最小权限
- 网络隔离:VPC、私有子网、最小安全组
- 加密:静态加密、传输加密
- 审计:CloudTrail、VPC Flow Logs
- 扫描:tfsec、Checkov 集成到 CI/CD
- 代码审查:所有 PR 必须安全审查
- 密钥轮换:定期轮换密钥和密码
下一步
想了解 IaC 的测试策略?请阅读 IaC 测试策略。