この記事は「2026 Japan AWS Jr. Champions 真夏のQiitaリレー」の32日目の記事となります。
過去の投稿(リンク集)は以下リンクからご覧ください。
https://qiita.com/ys-yoshida/private/6f7c7f85155a993e2c86
AWS Jr. ChampionsのQiitaリレー、まだまだ続きます。涼しい日が続いて真夏感は薄いですが…。
はじめに
SQSでは、SQS管理キーまたはKMS管理キーを用いてメッセージを暗号化することが可能です。
AWS Certified Security - Specialtyの勉強中でこの辺りを調べているとき、ドキュメントによってproducer(メッセージ送信側)に必要なKMS権限の記述が違うことに気づきました。
SQSのキーマネジメントに関するページでは、producerに次の2つの権限が必要とされています。
[
"kms:GenerateDataKey",
"kms:Decrypt"
]
Amazon SQS メッセージを暗号化するための KMS キーを変更する場合、古い KMS キーで暗号化した既存のメッセージはそのキーで暗号化されたままになることに注意してください。これらのメッセージを復号するには、古い KMS キーを保持していて、そのキーポリシーで kms:Decrypt および kms:GenerateDataKey へのアクセス許可を Amazon SQS に付与していることを確認する必要があります。
一方で、SQSのドキュメント履歴を見ると、2024年7月24日にSendMessageからkms:Decryptが不要になったと書かれています。
Amazon SQS では SendMessage API 用の kms:Decrypt アクセス許可が不要になりました。キューの暗号化に必要なのは、KMS キーに対する kms:GenerateDataKey アクセス許可のみとなりました。ただし、ReceiveMessage を呼び出すには引き続き kms:Decrypt アクセス許可が必要です。
リリースノートに従えば、現在のproducerにはkms:Decryptが不要です。それでもキーマネジメントのページには古い説明が残っています。
kms:GenerateDataKeyだけで本当に送信できるのか。さらに、SQS内部ではDecrypt自体を呼ばなくなったのか。
せっかくなので実際にSQSキューを作って確認してみました。
検証構成
CloudFormationでSSE-KMSを有効化したSQSキューとCustomer managed KMS keyを作りました。
producer用のLambdaにはkms:GenerateDataKeyだけを付けます。比較用として、kms:GenerateDataKeyとkms:Decryptの両方を持つLambdaも用意しました。
SQSのデータキー再利用期間は、最小値の60秒にしています。
TargetQueue:
Type: AWS::SQS::Queue
Properties:
KmsMasterKeyId: !GetAtt SqsKmsKey.Arn
KmsDataKeyReusePeriodSeconds: 60
producer側のKMS権限はこれだけです。
- Effect: Allow
Action: kms:GenerateDataKey
Resource: !GetAtt SqsKmsKey.Arn
Condition:
StringEquals:
kms:ViaService: !Sub sqs.${AWS::Region}.amazonaws.com
kms:CallerAccount: !Ref AWS::AccountId
GenerateDataKeyだけで送ってみる
次の3パターンでSendMessageしました。
-
GenerateDataKeyだけを持つproducerから1回目の送信 - 65秒待ってから同じproducerでもう一度送信
-
GenerateDataKeyとDecryptを持つ比較用producerから送信
3回とも成功しました。レスポンスはこんな形です。
{
"ok": true,
"case": "gdk-only",
"message_id": "63c82d94-ccbd-4899-beac-00728d9d7271"
}
もし新しいデータキーを取得するときにDecryptが必要なら、ここで失敗するはずです。しかし、Decryptを持たないproducerでも普通に送れました。
KMS API実行履歴の確認
送信した時間帯のKMSイベントをCloudTrailで確認しました。
13:29:18Z GenerateDataKey (gdk-only, 1回目)
13:30:24Z GenerateDataKey (gdk-only, 再利用期間経過後)
13:30:26Z GenerateDataKey (gdk-decrypt, 比較用ロール)
3回ともGenerateDataKeyです。
イベントの中身は次のようになっていました。
{
"eventSource": "kms.amazonaws.com",
"eventName": "GenerateDataKey",
"userIdentity": {
"type": "AssumedRole",
"invokedBy": "sqs.amazonaws.com"
},
"requestParameters": {
"encryptionContext": {
"aws:sqs:arn": "arn:aws:sqs:ap-northeast-1:...:sqs-kms-gdk-test"
},
"keySpec": "AES_256"
}
}
invokedByはsqs.amazonaws.comで、encryption contextには対象キューのARNが入っています。LambdaがKMSを直接呼んだイベントではなく、SQS経由の呼び出しだと分かります。
同じ時間帯でDecryptも検索しましたが、該当イベントは0件でした。
以前のDecryptは何をしていたのか
以降の内容はドキュメントの記述と検証結果に基づく推測を含みます。
以前producerに必要だったkms:Decryptは、新しいデータキーの整合性を確認するために使われていました。これはSQSのドキュメントにも記載されています。
データキー再利用期間が終了した場合、プロデューサーによる次回のSendMessageまたはSendMessageBatchの呼び出しでは、kms:Decryptとkms:GenerateDataKeyの呼び出しもトリガーされます。kms:Decryptの呼び出しでは、新しいデータキーを使用する前に整合性を検証します。
GenerateDataKeyを呼ぶと、KMSから平文データキーと暗号化済みデータキーが返ります。
GenerateDataKey
├─ 平文データキー
└─ 暗号化済みデータキー
旧仕様では、新しいデータキーを使う前にDecryptを呼んで整合性を確認していました。
具体的な内部実装は公開されていませんが、ドキュメントの記述より返却された暗号化済みデータキーをKMSキーで復号して平文データキーと比較する処理だったと考えられます。
GenerateDataKey → plaintext key A / encrypted key E
Decrypt(E) → plaintext key A'
2024年7月24日の変更後はSendMessageにkms:Decryptが不要になり、今回のCloudTrailでもGenerateDataKeyだけが記録されました。
なおAWSは整合性検証自体をなくしたのか、別の方法に変えたのかまでは公開情報からは読み取ることができませんでした。
コストへの影響
SQSはKMSから取得したデータキーを一定期間再利用します。デフォルトは300秒で、60〜86,400秒の範囲で設定できます。
現在のSQSドキュメントには、KMSリクエスト数の概算式として次の式が載っています。
$$
R = \frac{B}{D}(2P + C)
$$
ここで、
- $B$: 対象期間(秒)
- $D$: データキー再利用期間(秒)
- $P$: producer数
- $C$: consumer数
です。
producerが再利用期間ごとにGenerateDataKeyとDecryptを1回ずつ、consumerがDecryptを1回呼ぶ前提です。
今回CloudTrailで確認した挙動を同じ形に当てはめると、現在は次のように見積もることができると推測されます。
$$
R \approx \frac{B}{D}(P + C)
$$
まとめ
SCSの勉強でキーマネジメントのページだけを見ていたら、producerにもkms:Decryptが必要だとそのまま覚えていたと思います。
ドキュメント同士の記述が違ったので気になって試しただけでしたが、CloudTrailまで見ると挙動がかなりはっきりしました。
GenerateDataKeyだけのproducerでも、データキー再利用期間を超えた後を含めてSendMessageは成功します。そして送信時にCloudTrailへ記録されたKMS APIはGenerateDataKeyだけでした。
SCSの試験対策として調べ始めた内容でしたが、検証をする癖は実務でも持っておいたほうがよさそうです。