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.1IAM 最小权限、密钥轮换
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 测试策略