★★ 中級

12. KMS(暗号化キー管理)

S3 / RDS / EBS / Secrets Manager すべての暗号化の中心にいるのが KMS(Key Management Service)。AWS 管理キーと顧客管理キー (CMK) の違い、いつ自前で持つかを整理します。

AWS 管理キー vs カスタマー管理キー

AWS 管理キー (aws/s3 等)カスタマー管理キー (CMK)
作成AWS が自動で作成自分で作成
料金無料$1/月/key + 利用 API 数
キーポリシー編集不可自由に編集
ローテーション自動(不可視)有効/無効を選択
削除不可(永続)7-30 日の猶予期間後に削除可
クロスアカウント共有不可可能

判断基準: 個人開発/検証なら AWS 管理キーで十分。本番運用・コンプライアンス要件・別アカウントとの共有が必要なら CMK。

CMK の作成

# Customer Managed Key(顧客管理キー)の最小例
resource "aws_kms_key" "app" {
  description             = "Customer-managed key for myapp"
  # 削除コマンド後の猶予期間(7〜30)。期間内なら CancelKeyDeletion で復活可
  deletion_window_in_days = 30
  # 年 1 回の自動キーローテーション。原則 true 推奨
  enable_key_rotation     = true

  tags = {
    Name = "myapp-cmk"
  }
}

deletion_window_in_days は 7〜30。削除コマンド後、この期間内なら復活可。本番は 30 日推奨。

エイリアスで参照しやすく

# 人間に読みやすい別名をキーにつける
# キー再作成で UUID が変わっても、alias を付け替えれば参照側のコードはそのまま
resource "aws_kms_alias" "app" {
  name          = "alias/myapp"                # alias/ で始める必須
  target_key_id = aws_kms_key.app.key_id
}

# 他リソースから「alias/myapp」で参照できる
resource "aws_s3_bucket_server_side_encryption_configuration" "data" {
  bucket = aws_s3_bucket.data.id
  rule {
    apply_server_side_encryption_by_default {
      sse_algorithm     = "aws:kms"             # AES256 ではなく KMS 暗号化に
      kms_master_key_id = aws_kms_alias.app.arn
    }
  }
}

自動ローテーション

enable_key_rotation = true1 年に 1 回自動でキーマテリアルが切り替わる。古いマテリアルは AWS が保持するので、過去に暗号化されたデータの復号も継続可。ON にして害はほぼないので原則 ON。

キーポリシー

キーポリシーは「このキーを使える人」を IAM とは独立に定義。デフォルトポリシーは「ルートユーザーが全権限」のみ。実用にはアプリやサービスからの利用権限を追加する必要があります。

# account_id を取るためのデータソース
data "aws_caller_identity" "current" {}

data "aws_iam_policy_document" "kms_app" {
  # ルートユーザーフル権限(必須・誤って権限を失わないため)
  # これを書かずに別の文だけ書くと「誰もこのキーを管理できない」状態になり詰む
  statement {
    sid     = "EnableIAMUserPermissions"
    effect  = "Allow"
    actions = ["kms:*"]
    resources = ["*"]
    principals {
      type        = "AWS"
      # root は「このアカウントの IAM 経路全てに権限委譲」の意味
      identifiers = ["arn:aws:iam::${data.aws_caller_identity.current.account_id}:root"]
    }
  }

  # ECS タスクロールに使用許可
  # アプリが暗号化/復号する操作だけ許可(管理操作は不可)
  statement {
    sid    = "AllowECSTaskRole"
    effect = "Allow"
    actions = [
      "kms:Encrypt",
      "kms:Decrypt",
      "kms:GenerateDataKey",     # データキー生成(包絡暗号化用)
      "kms:DescribeKey",
    ]
    resources = ["*"]
    principals {
      type        = "AWS"
      identifiers = [aws_iam_role.ecs_task.arn]
    }
  }

  # CloudWatch Logs に使用許可(KMS 暗号化されたロググループ用)
  # logs サービスがログを書く際にこの CMK を使うために必要
  statement {
    sid    = "AllowCloudWatchLogs"
    effect = "Allow"
    actions = [
      "kms:Encrypt*",
      "kms:Decrypt*",
      "kms:ReEncrypt*",
      "kms:GenerateDataKey*",
      "kms:Describe*",
    ]
    resources = ["*"]
    principals {
      type        = "Service"
      # リージョン名込みの service principal(KMS だけ独特の形)
      identifiers = ["logs.${data.aws_region.current.name}.amazonaws.com"]
    }
  }
}

# 上のポリシーを付けてキー作成
resource "aws_kms_key" "app" {
  description             = "myapp CMK"
  deletion_window_in_days = 30
  enable_key_rotation     = true
  policy                  = data.aws_iam_policy_document.kms_app.json
}

他リソースから使う

# RDS:ストレージ暗号化(作成後の変更不可)
resource "aws_db_instance" "main" {
  storage_encrypted = true
  kms_key_id        = aws_kms_key.app.arn
  # ...
}

# EBS:ボリュームの暗号化
resource "aws_ebs_volume" "data" {
  encrypted  = true
  kms_key_id = aws_kms_key.app.arn
  # ...
}

# CloudWatch Logs:ログデータの暗号化
resource "aws_cloudwatch_log_group" "app" {
  name              = "/myapp"
  retention_in_days = 30
  kms_key_id        = aws_kms_key.app.arn
}

# Secrets Manager(次章):シークレット値の暗号化
resource "aws_secretsmanager_secret" "db" {
  name       = "myapp/db"
  kms_key_id = aws_kms_key.app.arn
}

マルチリージョンキー

同じキーマテリアルを複数リージョンで使いたい時。DR / グローバル配信に。

# プライマリ:multi_region=true で「複製可能」状態に
# 同一のキーマテリアル ID(mrk-で始まる)を持つレプリカを他リージョンに作れる
resource "aws_kms_key" "primary" {
  description  = "Multi-region primary"
  multi_region = true
}

# 別リージョン(us-west-2)のレプリカ
# プライマリと同じ平文鍵を共有 → 一方で暗号化したものを他方で復号できる
resource "aws_kms_replica_key" "secondary" {
  provider                  = aws.us_west_2      # 別リージョンの provider が必要
  description               = "Replica in us-west-2"
  primary_key_arn           = aws_kms_key.primary.arn
  deletion_window_in_days   = 30
}