削除予定のAWS KMS鍵が利用されたら通知する
不要になったAWS Key Management Service (AWS KMS) のカスタマーマスターキー(CMK)は無効にしたり、削除することができます。
ここで気をつけないといけないのは、不要と判断した鍵が実は利用されていた場合です。
そこで
- CloudTrail
- CloudWatch Logs
- CloudWatch Alarm
を組み合わせて、削除予定・無効な CMK が利用されたときに、通知する仕組みを作ってみます。
CMK の無効化・削除処理の仕様について
仕様を簡単にまとめます。
無効処理について
CMK は即座に無効・無効解除できます。
無効状態の鍵で Encrypt/Decrypt すると DisabledException
エラーが発生します。
$ aws kms encrypt \ --key-id arn:aws:kms:us-east-1:123456789012:key/XXX \ --plaintext foo An error occurred (DisabledException) when calling the Encrypt operation: arn:aws:kms:us-east-1:123456789012:key/XXX is disabled.
削除処理について
AWS KMS で作成した CMK を削除するには、7日〜30日の削除保留期間が必ず発生します。 保留期間中はいつでも削除を解除でき、一方で、保留期間がすぎて削除されると、鍵は復元できなくなります。当然、過去に暗号化されたデータは復号できなくなります。
鍵を利用していないことを100%確認が持てないときは、削除ではなく、無効化を検討してください。
削除保留状態の鍵で Encrypt/Decrypt すると KMSInvalidStateException
エラーが発生します。
$ aws kms encrypt \ --key-id arn:aws:kms:us-east-1:123456789012:key/XXX \ --plaintext foo An error occurred (KMSInvalidStateException) when calling the Encrypt operation: arn:aws:kms:us-east-1:123456789012:key/XXX is pending deletion.
なお、インポートしたキーマテリアルを削除すると、CMK は即座に使用できなくなります。CMK を再び使用するには、同じキーマテリアルを再インポートする必要があります。
参考 https://docs.aws.amazon.com/kms/latest/developerguide/importing-keys-delete-key-material.html
CMK のステート
鍵の無効や削除に伴い CMK のステートは次のように推移します。
※鍵をインポートした場合は別途「PendingImport」というステートが存在します。
キーステータスがカスタマーマスターキーの使用に与える影響 も合わせてご確認ください。
有効でないCMKを検知するシステム
次のドキュメントに従い仕組みを作ります。
https://docs.aws.amazon.com/kms/latest/developerguide/deleting-keys-creating-cloudwatch-alarm.html
大まかには、以下の流れで作業します。
- CloudWatch Logs への CloudTrail ログファイル配信を設定
- エラーログを検出するメトリックフィルタを作成
- エラーが検出されたら通知する CloudWatch アラームを作成
検知で取りこぼしがないよう、KMS の API 呼び出しでエラーが発生しているもの全体を通知対象とします。
CloudTrail の有効化
CloudTrail はユーザーアクティビティと API 使用状況の追跡するサービスです。 まだ有効になっていない場合は、次のドキュメントに従い有効化してください。
http://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-create-and-update-a-trail.html
CloudWatch Logs への CloudTrail ログファイル配信を設定する
CloudTrail から CloudWatch Logs にイベントを送信するよう設定します。
管理コンソールを利用する場合、Trail の Configuration 画面から配信するロググループを指定します。
メトリックフィルターの作成
CloudWatch Logs 画面で配信先に指定したロググループを選択し、"Create Metric Filter" をクリックします。
Define Pattern
Filter Pattern に次のパターンを指定します。
{ $.eventSource = kms.amazonaws.com && ($.errorMessage = "*" || $.errorCode = "*") }
KMS サービスに対する API 呼び出しで、なにか例外が発生したログをフィルタ対象としています。
CloudTrail ログに errorMessage
または errorCode
の属性が存在する場合、エラーと判定しています。
Assign Metric
- Filter Name : eventSource-kms-errorCode-Exception
- Metric Namespace : CloudTrailLogMetrics
- Metric Name : KMSErrorCount
と入力して、フィルタを作成します。
フィルタ一覧画面にある "Create Alarm" リンクから CloudWatch Alarm の作成画面に移動します。
アラームの作成
先程作成したメトリック(KMSErrorCount
)は選択済みです。
このメトリックに対して
Whenever KMSErrorCound >= 1 for 1 out of 1 datapoints
のしきい値でアラームを設定します。
欠落データの扱い("Treat missing data as") は good (not breaching threshold) を選択します。 今回のように、異常時にしかメトリックデータが発生しないようなときに向いています。
詳細はドキュメントをご確認ください。
通知先は Actions で指定します。
個人検証では、メールアドレスに通知する SNS を利用しました。
動作確認
以上で準備が整いました。
CMK を無効・削除して KMS::Encrypt API を呼び出してエラーを発生させ、アラームが機能していることを確認します。
有効状態の CMK の動作確認
$ KEYID=arn:aws:kms:us-east-1:123456789012:key/XXX $ aws kms describe-key --key-id $KEYID { "KeyMetadata": { "KeyManager": "CUSTOMER", "KeyId": "XXX", "Description": "CMK For S3", "CreationDate": 1522329556.173, "KeyUsage": "ENCRYPT_DECRYPT", "Arn": "arn:aws:kms:us-east-1:123456789012:key/XXX", "KeyState": "Enabled", "AWSAccountId": "123456789012", "Origin": "AWS_KMS", "Enabled": true } }
- Enabled : true
- KeyState : Enabled
となっています。
この状態で Encrypt
を呼び出します
$ aws kms encrypt \ --key-id $KEYID \ --plaintext foo { "KeyId": "arn:aws:kms:us-east-1:123456789012:key/XXX", "CiphertextBlob": "AQICAHjm+..." }
成功し、base64 エンコードされた ciphertext を返します。
CMK を無効化して API 呼び出し
次に、この CMK を無効化します。
$ aws kms disable-key --key-id $KEYID $ aws kms describe-key --key-id $KEYID { "KeyMetadata": { "KeyUsage": "ENCRYPT_DECRYPT", "AWSAccountId": "123456789012", "Arn": "arn:aws:kms:us-east-1:123456789012:key/XXX", "Enabled": false, "KeyId": "XXX", "Description": "CMK For S3", "KeyManager": "CUSTOMER", "Origin": "AWS_KMS", "KeyState": "Disabled", "CreationDate": 1522329556.173 } }
- Enabled : true → false
- KeyState : Enabled → Disabled
に変わっています。
この状態で Encrypt
を呼び出します。
$ aws kms encrypt \ --key-id $KEYID \ --plaintext foo An error occurred (DisabledException) when calling the Encrypt operation: arn:aws:kms:us-east-1:123456789012:key/XXX is disabled.
DisabledException
エラーが発生しました。
CMK を削除してAPI呼び出し
次に、この CMK を削除します。
保留期間(--pending-window-in-days
) は10日とします。
$ aws kms schedule-key-deletion --key-id $KEYID \ --pending-window-in-days 10 { "KeyId": "arn:aws:kms:us-east-1:123456789012:key/XXX", "DeletionDate": 1534291200.0 } $ aws kms describe-key --key-id $KEYID { "KeyMetadata": { "Enabled": false, "KeyId": "XXX", "DeletionDate": 1534291200.0, "Origin": "AWS_KMS", "CreationDate": 1522329556.173, "Arn": "arn:aws:kms:us-east-1:123456789012:key/XXX", "KeyUsage": "ENCRYPT_DECRYPT", "AWSAccountId": "123456789012", "KeyManager": "CUSTOMER", "Description": "CMK For S3", "KeyState": "PendingDeletion" } }
- Enabled : false
- DeletionDate : 削除される UNIX TIME
- KeyState : PendingDeletion
に変わっています。
この状態で Encrypt
を呼び出します。
$ aws kms encrypt \ --key-id $KEYID \ --plaintext foo An error occurred (KMSInvalidStateException) when calling the Encrypt operation: arn:aws:kms:us-east-1:123456789012:key/XXX is pending deletion.
KMSInvalidStateException
エラーが発生しました。
CloudWatch Alarm の確認
CloudTrail → CloudWatch Logs → CloudWatch Alarm が発火していることを確認します。
管理コンソール
エラー発生件数が記録されており、しきい値を超えているために ALARM ステートになっています。
期待通りです。
メール
SNS 経由で アラームメールが送信されています。
期待どおりです。
CloudTrail ログの確認
CloudTrail での KMS::Encrypt API 呼び出しのログを確認します
メトリックフィルターの条件になっている属性を確認します。
$ aws cloudtrail lookup-events \ --lookup-attributes AttributeKey=EventName,AttributeValue=Encrypt \ --max-items 3 | jq '.Events[].CloudTrailEvent | fromjson | {errorCode, errorMessage}' { "errorCode": "KMSInvalidStateException", "errorMessage": "arn:aws:kms:us-east-1:123456789012:key/XXX is pending deletion." } { "errorCode": "DisabledException", "errorMessage": "arn:aws:kms:us-east-1:123456789012:key/XXX is disabled." } { "errorCode": null, "errorMessage": null }
API 呼び出し時と同じです。
参考までに、CloudTrail の CMK 無効化時のログ全体は以下のようになっています。
{ "eventVersion": "1.05", "userIdentity": { ... }, "eventTime": "2018-08-04T11:00:07Z", "eventSource": "kms.amazonaws.com", "eventName": "Encrypt", "awsRegion": "us-east-1", ... "errorCode": "DisabledException", "errorMessage": "arn:aws:kms:us-east-1:123456789012:key/XXX is disabled.", "requestParameters": { "keyId": "arn:aws:kms:us-east-1:123456789012:key/XXX" }, "responseElements": null, "requestID": "8c9cbb6e-97d5-11e8-a0a4-0d492c69e0cf", "eventID": "63d702bd-52b4-45ad-b72d-88ada860d9f0", "readOnly": true, "resources": [ { "ARN": "arn:aws:kms:us-east-1:123456789012:key/XXX", "accountId": "123456789012", "type": "AWS::KMS::Key" } ], "eventType": "AwsApiCall", "recipientAccountId": "123456789012" }
AWS Secrets Manager への応用
2018年4月にはシークレットを管理するマネージドサービス AWS Secrets Manager が発表されました。 このサービスで管理するシークレットは、CMK と同じく、即時に削除状態とはならず、保留期間が設けられています。
今回と同様の仕組みにより、削除予定のシークレットが利用された場合は、検知することができます。
具体的な手順は次のドキュメントを参照ください。
まとめ
無効・削除予定の AWS KMS CMK が利用されたときに、検知する仕組みを紹介しました。
一旦削除されると復元できなくなるため、100%不要であると確信が持てない限り CMK を削除しないようにしてください。
今回はエラー検知の取りこぼしがないように Filter Pattern を { $.eventSource = kms.amazonaws.com && ($.errorMessage = "*" || $.errorCode = "*") }
としました。
特定の errorMessage
だけを対象にするなど、要件に合わせてカスタマイズしてください。
今回紹介した仕組みは AWS Secrets Manager のシークレットにも活用できます。