S3のサーバー側の暗号化(SSE-KMS)の強制を試す
デフォルトの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-encryption
にaws: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の中の人が運営しているサークル「じゅ~しぃ~すくりぷと」の本のご案内
技術書コーナー
北海道の駅巡りコーナー