こしあん
2024-12-08

S3のサーバー側の暗号化(SSE-KMS)の強制を試す


12{icon} {views}


デフォルトのSSE-S3ではなく、特定のカスタマーマネージドキー(KMSキー)を使用してS3バケットでサーバーサイド暗号化を強制する方法を試してみた。Terraformとバケットポリシーを活用し、その特定のKMSキーによる暗号化のみを許可し、他の暗号化やキーを使用したアップロードを拒否する設定を行う。

はじめに

SCSの勉強していて、KMSを使って、S3バケットに対してサーバーサイドの暗号化の強制の例が出てきたので試してみた。

前提

普通にS3を作ると、「Amazon S3 マネージドキーを使用したサーバー側の暗号化 (SSE-S3)」が付与されており、何も考えずにアップロードすれば自動的にサーバー側の暗号化がかかる。

しかし、SSE-S3は本当にマネージド状態で、鍵の権限管理やローテーションなどの柔軟性が低いので、これが必要になったらKMSを明示して暗号化する必要がある。

KMSの作成(カスタマーマネージドキー)

基本的にTerraformで作りたいが、KMSをTerraformで作ってしまうとterraform destroyをしたときに削除ではなく「削除保留状態」になってしまい、再度terraform applyすると「同名のキーがある」とエラーになってしまう。Secrets Managerではrecovery_window_in_days=0にすればこの問題を回避できるが、KMSではこの回避策は使えない。

一旦ブラウザからカスタマー管理キー(カスタマーマネージドキー)を作成し、必要な権限を付与しておく。このエイリアスをexample-cmk-awsとしておく。

TerraformでのS3バケット作成と暗号化強制の設定

  • デフォルトの暗号化は、aws_s3_bucket_server_side_encryption_configurationで設定し、ここに先ほど作成したKMSを設定する
  • 暗号化を強制するようにバケットポリシーで、暗号化していない状態(s3:x-amz-server-side-encryptionaws:kmsがない場合)にアップロードを明示的に拒否るようなポリシーを追加する
  • ただ、これだとKMSなら何でも許可してしまう設定になり、AWS CLIから--sse aws:kmsでアップロードしたときに、AWSマネージドキーのKMSが使われてしまう。作成したKMSのみに限定するために、2個目のポリシー(s3:x-amz-server-side-encryption-aws-kms-key-idによるARN指定)を追加する。
# 既存のKMSキーをデータソースとして取得
data "aws_kms_key" "example" {
  key_id = "alias/example-cmk-aws"
}

# S3バケットの作成
resource "aws_s3_bucket" "encrypted_bucket" {
  bucket        = "s3-encrypted-bucket-by-example-cmk"
  force_destroy = true
}

# サーバーサイド暗号化の設定
resource "aws_s3_bucket_server_side_encryption_configuration" "example" {
  bucket = aws_s3_bucket.encrypted_bucket.id

  rule {
    apply_server_side_encryption_by_default {
      sse_algorithm     = "aws:kms"
      kms_master_key_id = data.aws_kms_key.example.arn
    }
  }
}

# バケットポリシーで暗号化を強制
resource "aws_s3_bucket_policy" "encrypted_bucket_policy" {
  bucket = aws_s3_bucket.encrypted_bucket.id

  policy = jsonencode({
    Version = "2012-10-17",
    Statement = [
      # SSEがaws:kms以外の場合を拒否
      {
        Sid       = "DenyUnEncryptedObjectUploads",
        Effect    = "Deny",
        Principal = "*",
        Action    = "s3:PutObject",
        Resource  = "${aws_s3_bucket.encrypted_bucket.arn}/*",
        Condition = {
          StringNotEquals = {
            "s3:x-amz-server-side-encryption" = "aws:kms"
          }
        }
      },
      # 指定したCMK以外を使用する場合を拒否
      {
        Sid       = "DenyUseOfUnspecifiedKMSKey",
        Effect    = "Deny",
        Principal = "*",
        Action    = "s3:PutObject",
        Resource  = "${aws_s3_bucket.encrypted_bucket.arn}/*",
        Condition = {
          StringNotEquals = {
            "s3:x-amz-server-side-encryption-aws-kms-key-id" = data.aws_kms_key.example.arn
          }
        }
      }
    ]
  })
}

実行後、バケットのプロパティを見ると暗号化タイプがSSE-KMSになっているのがわかる。

ブラウザからアップロード

何も考えずにマネジメントコンソールのS3の画面からアップロードすると当然失敗する。これはデフォルトの暗号化タイプである、SSE-KMSが設定されていない状態でアップロードしたためで、バケットポリシーに拒否られている。

アップロード時の設定で以下のように明示的に暗号化キーを指定すればOK。先ほど作成したKMSのポリシーが付与されていればアップロード成功する。

AWS CLIからのアップロード

–sse aws:kmsを指定しない場合:失敗

同様のことはAWS CLIからアップロードした場合も同様で、普通にアップロードしてしまうと、暗号化キーを指定してないので、以下のようなエラーになる。

> aws s3 cp sample_file/test1.txt s3://s3-encrypted-bucket-by-example-cmk --profile hogehoge
upload failed: sample_file\test1.txt to s3://s3-encrypted-bucket-by-example-cmk/test1.txt An error occurred (AccessDenied) when calling the PutObject operation: ...

–sse aws:kmsのみ指定した場合:失敗

--sse aws:kmsのみ指定してアップロードすると、KMSは使われるもののカスタマーマネージドキーではなく、AWS管理のSSE-KMSが使われてしまう。2個目のポリシーにかかり失敗する。

# --sse-kms-key-idを明示しない(AWS管理キーを使用):アップロード失敗
aws s3 cp sample_file/test1.txt s3://s3-encrypted-bucket-by-example-cmk --sse aws:kms  --profile hogehoge

–sse aws:kmsと–sse-kms-key-idの両方を指定:成功

--sse aws:kmsを指定し、先程作成したexample-cmk-awsのエイリアスを指定してアップロードすれば初めて成功する。

# --sse-kms-key-idを明示:アップロード成功
aws s3 cp sample_file/test1.txt s3://s3-encrypted-bucket-by-example-cmk --sse aws:kms --sse-kms-key-id alias/example-cmk-aws --profile hogehoge


Shikoan's ML Blogの中の人が運営しているサークル「じゅ~しぃ~すくりぷと」の本のご案内

技術書コーナー

北海道の駅巡りコーナー


Add a Comment

メールアドレスが公開されることはありません。 が付いている欄は必須項目です