KMS CMK 使用時に、IAM ポリシーとキーポリシーで許可していないにもかかわらず、CMK の使用が拒否されない理由を教えてください

2022.08.29

困っていた内容

Lambda で環境変数の暗号化に、KMS CMK を使用しています。
CMK を使用するには、Lambda の 実行ロールに付与する IAM ポリシーまたは、CMK のキーポリシーで、CMK の使用を許可する必要があるという認識でしたが、どちらでも許可していないにもかかわらず、Lambda から CMK へのアクセスが拒否されず、CMK を使用できました。

CMK 使用時に、IAM ポリシーとキーポリシーで許可していないにもかかわらず、CMK の使用が拒否されない理由を教えてください。

どう対応すればいいの?

結論から説明すると、KMS の Grants によるアクセス許可により、Lambda から CMK へのアクセスが可能になっています。

AWS 公式ドキュメントより

A grant is a policy instrument that allows Amazon principals to use KMS keys in cryptographic operations. It also can let them view a KMS key (DescribeKey) and create and manage grants. When authorizing access to a KMS key, grants are considered along with key policies and IAM policies. Grants are often used for temporary permissions because you can create one, use its permissions, and delete it without changing your key policies or IAM policies.

上記ドキュメントの内容を要約すると、IAM ポリシー、キーポリシーとともに考慮される権限で、一時的なアクセス許可を作成するのが、Grants であると説明されています。
KMS でのアクセス許可は、IAM ポリシー、キーポリシー、Grants により決定されるため、IAM ポリシーとキーポリシーでアクセスを許可していなくても、Grants でアクセスが許可されていれば、CMK の使用は可能になります。

KMS によるアクセス許可については、以下の AWS 公式ドキュメントのフローチャートが非常にわかりやすいので、KMS での権限回りでのトラブルシューティングで活用できると思います。

Lambda などに Grants でのアクセスが許可されているかを確認するには、ListGrants をご利用ください。

やってみた

実際に Lambda の環境変数の暗号化に CMK を使用した際、どのように Grants でアクセス許可が付与されるのかを確認してみました。

まずは、検証用に KMS で CMK を作成しました。
CMK を削除できるよう、キー管理者を自分が使っている IAM ロールに指定しただけで、その他の設定はデフォルトです。
以下のようなキーポリシーとなりました。

{
    "Id": "key-consolepolicy-3",
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "Enable IAM User Permissions",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::{account-id}:root"
            },
            "Action": "kms:*",
            "Resource": "*"
        },
        {
            "Sid": "Allow access for Key Administrators",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::{account-id}:role/{role-name}"
            },
            "Action": [
                "kms:Create*",
                "kms:Describe*",
                "kms:Enable*",
                "kms:List*",
                "kms:Put*",
                "kms:Update*",
                "kms:Revoke*",
                "kms:Disable*",
                "kms:Get*",
                "kms:Delete*",
                "kms:TagResource",
                "kms:UntagResource",
                "kms:ScheduleKeyDeletion",
                "kms:CancelKeyDeletion"
            ],
            "Resource": "*"
        }
    ]
}

上記のキーポリシーからわかる通り、Lambda からのアクセスは許可していません。

続いて検証用にデフォルト設定で Lambda を作成しました。
Lambda をデフォルト設定で作成すると、以下のような権限を持った IAM ロールも自動的に作成されます。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "logs:CreateLogGroup",
            "Resource": "arn:aws:logs:ap-northeast-1:{account-id}:*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": [
                "arn:aws:logs:ap-northeast-1:{account-id}:log-group:/aws/lambda/{function-name}:*"
            ]
        }
    ]
}

上記 Lambda の実行ロールにも CMK へのアクセス権は付与されていません。

環境変数を設定する前に、AWS CLI の list-grants コマンドで、現在の Grants を確認してみます。

aws kms list-grants --key-id {my-cmk-key-id}

{
    "Grants": []
}

コマンドの結果、環境変数設定前では、Grants でのアクセス許可は付与されていないことがわかりました。

それでは、Lambda の環境変数を作成し、CMK で暗号化してみます。

この状態で、再度 list-grants を実行してみます。

aws kms list-grants --key-id {my-cmk-key-id}

{
    "Grants": [
        {
            "KeyId": "arn:aws:kms:ap-northeast-1:{account-id}:key/{my-cmk-key-id}",
            "GrantId": "3c50cfde06a737fc74d30c86984d19cea6a3aa083bc1027b83f64ab048c356fb",
            "Name": "Grants_test/498ac062-a8cf-4b80-a069-c2392472a2f7",
            "CreationDate": "2022-08-12T04:58:28+00:00",
            "GranteePrincipal": "arn:aws:sts::{account-id}:assumed-role/{lambda-role-name}/{function-name}",
            "RetiringPrincipal": "lambda.ap-northeast-1.amazonaws.com",
            "IssuingAccount": "arn:aws:iam::{account-id}:root",
            "Operations": [
                "Decrypt",
                "RetireGrant"
            ],
            "Constraints": {
                "EncryptionContextEquals": {
                    "aws:lambda:FunctionArn": "arn:aws:lambda:ap-northeast-1:{account-id}:function:{function-name}"
                }
            }
        }
    ]
}

環境変数設定後は、上記のように、DecryptRetireGrant の権限が付与されていました。
どうやら Lambda のコンソールから CMK を設定すると、自動的に Grants による権限が付与されるようです。

試しに Lambda から環境変数を使用するよう、デフォルトのコードに環境変数を出力する処理を追記してみました。

exports.handler = async (event) => {
    // TODO implement
    console.log(process.env.key); //追記した処理
    const response = {
        statusCode: 200,
        body: JSON.stringify('Hello from Lambda!'),
    };
    return response;
};

上記のコードでテスト実行したところ、環境変数の値を出力できることが確認できました。

CloudTrail でも、Lambda から CMK を使用した Decrypt が成功していることが記録されていました。

最後に、retire-grant で Grants によるアクセス権限を削除した際の挙動を確認してみます。

aws kms retire-grant \
--key-id arn:aws:kms:ap-northeast-1:{account-id}:key/{my-cmk-key-id} \
--grant-id {grant-id} 

# 戻り値なし

念のため再度 list-grants を実行してみます。

aws kms list-grants --key-id {my-cmk-key-id}

{
    "Grants": []
}

Lambda に付与されていた Grants によるアクセス権限が削除されていることが確認できました。
この状態で、再度 Lambda を実行すると、以下のエラーが発生しました。

API アクションの呼び出しに失敗しました。エラーメッセージ: Lambda was unable to decrypt the environment variables because KMS access was denied. Please check the function's KMS key settings. KMS Exception: AccessDeniedExceptionKMS Message: The ciphertext refers to a customer master key that does not exist, does not exist in this region, or you are not allowed to access.

Grants を削除したことにより、IAM ポリシー、キーポリシー、Grants のいずれでもアクセスが許可されていないため、上記のアクセス拒否エラーが発生している状態です。
CloudTrail でも AccessDenied の記録が残り、Lambda の実行ログも出力されません。

これで、Grants によるアクセス許可により、Lambda が CMK にアクセスできていたことがわかりました。

参考資料