★★ 中級

13. Secrets Manager / SSM Parameter Store

API キー・DB パスワード・設定値を コードに書かず 安全に保管する仕組み 2 種。料金・自動回転・容量で使い分けます。

どちらを使うか

Secrets ManagerSSM Parameter Store
料金$0.40/secret/月 + API $0.05/10kStandard 無料 / Advanced $0.05/parameter/月
容量上限64 KB4 KB (Standard) / 8 KB (Advanced)
自動回転あり(Lambda 連携)なし
クロスリージョンレプリありなし
適した用途DB / API キーで自動回転したい環境設定値、軽い秘密、エンドポイント等

原則: パスワード/API トークンは Secrets Manager、設定値・接続文字列・URL は SSM Parameter Store

Secrets Manager

resource "random_password" "db" {
  length  = 32
  special = true
  override_special = "!#$%&*()-_=+[]{}<>:?"
}

resource "aws_secretsmanager_secret" "db" {
  name                    = "myapp/db/master"
  description             = "Master credentials for myapp DB"
  kms_key_id              = aws_kms_key.app.arn
  recovery_window_in_days = 7    # 削除後 7 日間は復旧可(即座に消すなら 0)
}

resource "aws_secretsmanager_secret_version" "db" {
  secret_id = aws_secretsmanager_secret.db.id
  secret_string = jsonencode({
    username = "admin"
    password = random_password.db.result
    host     = aws_db_instance.main.address
    port     = aws_db_instance.main.port
    dbname   = aws_db_instance.main.db_name
  })
}

自動回転

Secrets Manager は「N 日ごとに Lambda を呼んでパスワードを変える」を組み込みでサポート。RDS なら AWS 提供の rotation Lambda がそのまま使えます。

resource "aws_secretsmanager_secret_rotation" "db" {
  secret_id           = aws_secretsmanager_secret.db.id
  rotation_lambda_arn = aws_lambda_function.rotation.arn

  rotation_rules {
    automatically_after_days = 30
  }
}

SSM Parameter Store

# 平文(設定値、URL など)
resource "aws_ssm_parameter" "api_endpoint" {
  name  = "/myapp/api/endpoint"
  type  = "String"
  value = "https://api.example.com/v1"
}

# 暗号化(API キー、Webhook シークレット)
resource "aws_ssm_parameter" "api_key" {
  name   = "/myapp/external/stripe-key"
  type   = "SecureString"
  key_id = aws_kms_alias.app.arn   # CMK で暗号化(省略すると aws/ssm デフォルト)
  value  = var.stripe_key

  lifecycle {
    ignore_changes = [value]   # 値は外部で更新する運用なら
  }
}

# 階層構造で複数並べる
locals {
  config = {
    "/myapp/feature/dark_mode" = "true"
    "/myapp/feature/new_ui"    = "false"
    "/myapp/limit/rate_per_min" = "100"
  }
}

resource "aws_ssm_parameter" "feature" {
  for_each = local.config
  name     = each.key
  type     = "String"
  value    = each.value
}
階層命名のコツ /myapp/feature/... のようにスラッシュ区切りで名前付けすると、IAM ポリシーで arn:...:parameter/myapp/* のようにまとめて権限制御できます。

アプリから読む

# AWS CLI から
aws secretsmanager get-secret-value --secret-id myapp/db/master --query SecretString --output text

aws ssm get-parameter --name /myapp/api/endpoint --query Parameter.Value --output text
aws ssm get-parameters-by-path --path /myapp/feature/ --recursive

# ECS タスク定義から直接環境変数に注入
# secrets = [
#   { name = "DB_PASSWORD", valueFrom = aws_secretsmanager_secret.db.arn }
# ]

Terraform から読む(data ソース)

data "aws_secretsmanager_secret_version" "db" {
  secret_id = "myapp/db/master"
}

locals {
  db = jsondecode(data.aws_secretsmanager_secret_version.db.secret_string)
}

# 使い方: locals.db.username, locals.db.password

data "aws_ssm_parameter" "api_endpoint" {
  name = "/myapp/api/endpoint"
}

resource "aws_lambda_function" "x" {
  environment {
    variables = {
      API_ENDPOINT = data.aws_ssm_parameter.api_endpoint.value
    }
  }
}
state に平文で残る data ソースで取得した値は state に平文で書かれます。state を S3 に置く時は必ず KMS 暗号化+IAM 制限。最も安全なのは「Terraform で値を読まず、ARN/名前だけ渡してアプリ側で取得」する方式。