AWS Lambdaでカスタマー管理キーによる環境変数のKMS暗号化を無効にしたらKMSAccessDeniedExceptionが発生したので原因調査した話

ワークアラウンド:IAMロール名の変更または時間経過で解消するかも。
2020.07.21

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちは、CX事業本部の若槻です。

AWS Lambdaで関数に設定した環境変数が保管される際の暗号化には、既定ではAWSが管理するキーが使われますが、ユーザーがAWS KMSで作成した独自のキー(カスタマー管理キー)を使用することも可能です。

今回は、今までこのカスタマー管理キーによる環境変数の暗号化を行っていたLambdaを、AWS管理のキーを使用するように設定を戻したところ、Lambda実行時にKMSAccessDeniedExceptionが発生するようになり原因調査を行ったのでご紹介します。

構成の変更内容

まず、エラー発生の前後でシステムをどのように構成変更をしていたのかを、CloudFormationテンプレートとLambdaコードのパッチファイルで以下に示します。

CloudFormationテンプレート(の一部)変更内容

  • Lambdaの環境変数をデプロイ時にSSMパラメータから取得するようにしたため、パラメータaccessTokenを追加
  • カスタマー管理キーの使用をやめてAWS KMSキーが不要となったため、リソースKey00を削除
  • Lambdaの環境変数の暗号化でカスタマー管理キーの使用をやめたため、リソースFunction00Environment - Variables - ACCESS_TOKENの値をRef! accessTokenに変更
  Parameters:
    appName:
      Type: String

    bucketName:
      Type: String

+   accessToken:
+     Type: AWS::SSM::Parameter::Value<String>
+     Default: accessToken
+
  Resources:
    Role00:
      Type: AWS::IAM::Role
      Properties:
        RoleName: !Sub ${appName}-aws-role-00
        AssumeRolePolicyDocument:
          Version: 2012-10-17
          Statement:
            - Effect: Allow
              Principal:
                Service:
                  - lambda.amazonaws.com
              Action:
                - 'sts:AssumeRole'

    Policy00:
      Type: AWS::IAM::Policy
      Properties:
        PolicyName: !Sub ${appName}-aws-policy-00
        PolicyDocument: 
          Version: "2012-10-17"
          Statement: 
            - 
              Effect: "Allow"
              Action: 
                - "logs:CreateLogGroup"
                - "logs:CreateLogStream"
                - "logs:PutLogEvents"
              Resource: "*"
        Roles:
          - !Ref Role00

-   Key00:
-     Type: AWS::KMS::Key
-     Properties:
-       KeyPolicy:
-         Version: "2012-10-17"
-         Id: !Sub ${appName}-aws-apikey-00-keypolicy
-         Statement:
-           -
-             Sid: "Allow administration of the key"
-             Effect: "Allow"
-             Principal:
-               AWS: !Sub arn:aws:iam::${AWS::AccountId}:root
-             Action: "*"
-             Resource: "*"
-           -
-             Sid: "Allow use of the key"
-             Effect: "Allow"
-             Principal:
-               AWS: !GetAtt Role00.Arn
-             Action: 
-               - "kms:Encrypt"
-               - "kms:Decrypt"
-               - "kms:ReEncrypt*"
-               - "kms:GenerateDataKey*"
-               - "kms:DescribeKey"
-             Resource: "*"

    Function00:
      Type: AWS::Lambda::Function
      Properties:
        FunctionName: !Sub ${appName}-aws-function-00
        Code:
          S3Bucket: !Ref bucketName
          S3Key: function.zip
        Handler: lambda_function.lambda_handler
        MemorySize: 512
        Timeout: 60
        Role: !GetAtt Role00.Arn
        Runtime: python3.7
        Environment:
          Variables:
-           ACCESS_TOKEN: DummyAccessToken
+           ACCESS_TOKEN: Ref! accessToken
        KmsKeyArn: !GetAtt Key00.Arn

リソースFunction00で既存のEnvironment - Variables - ACCESS_TOKENの値をDummyAccessTokenとしていた理由は、デプロイ後に環境変数の実際の値の設定と、伝送中の暗号化のためのヘルパーの有効化をコンソールから手動で行っていたためです。

なお、伝送中の暗号化のためのヘルパーの有効化は、以前に次の記事で書いた通り、CloudFormationによる設定ができなかったため仕方なく手動で設定をしたという経緯があります。

Lambdaコード(の一部)変更内容

  • 環境変数の値の取得後のKMS復号化をしないように変更
    import os
    import boto3
    from base64 import b64decode

-   access_token = boto3.client('kms').decrypt(CiphertextBlob=b64decode(os.environ['ACCESS_TOKEN']))['Plaintext'].decode()
+   access_token = os.environ['ACCESS_TOKEN']

変更後に発生した事象

前述の構成変更を行った上で同じLambdaを呼び出したところ次のKMSAccessDeniedExceptionエラーが発生するようになりました。

[ERROR] KMSAccessDeniedException: An error occurred (KMSAccessDeniedException) when calling the Invoke operation (reached max retries: 4): Lambda was unable to decrypt the environment variables because KMS access was denied. Please check the function's KMS key settings. KMS Exception: UnrecognizedClientExceptionKMS Message: The security token included in the request is invalid.

このエラーは、次のドキュメントによるとキーが存在しないかキーへのアクセス権がない場合に発生するとのことです。

The ciphertext references a key that doesn't exist or that you don't have access to.

しかし、CloudFormationスタックではIAMロールからKMSキーへの参照は削除し、LambdaからもKMS暗号化の設定を削除していたため、KMSキーが使われるはずがなく、なぜこのエラーが発生するのか分かりませんでした。

調査

Serverless FrameworkのGitHub Repositoryで次のIssueが上がっているのを見つけました。

Serverless FrameworkでデプロイしたLambdaで今回と同様のエラーが発生しているという内容です。そしてIssueのスレッドの中でやけにリアクションを集めている次の投稿が目に留まりました。

I had this issue and after some head banging found out it was due to deleting an IAM policy and creating using the same name, simply changing the IAM of the lambda to something else, saving and then changing back fixed it.

事象の原因はIAMポリシーを削除して同じ名前で作成していたことで、IAMを別の名前に変更したら解消したとのことです。

思い返せば、わたしはシステムの変更前に、一度CloudFormationスタックの削除の操作を行った(理由は忘れました)後に、スタックテンプレートおよびLambdaコードを変更してcloudformation create-stackをしたら、そのLambdaで今回の事象が発生するようになったので、条件は酷似しています。

そこで私も倣ってスタックテンプレートでIAMロールの物理名を次のように変更してスタックcloudformation update-stackしたところ、Lambdaはエラーなく実行できるようになりました。

  Resources:
    Role00:
      Type: AWS::IAM::Role
      Properties:
-       RoleName: !Sub ${appName}-aws-role-00
+       RoleName: !Sub ${appName}-aws-role-01
        AssumeRolePolicyDocument:
          Version: 2012-10-17
          Statement:
            - Effect: Allow
              Principal:
                Service:
                  - lambda.amazonaws.com
              Action:
                - 'sts:AssumeRole'

結論(原因の推測とワークアラウンド)

さらに、ロール名変更により一旦は事象が解消した後に、同Issue内での次の投稿にも目が留まりました。

I believe this is because the lambda references the identifier of the IAM role to use, not the ARN of the IAM role. Read more about identifiers here : https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_identifiers.html#identifiers-unique-ids

Is there any update on this? This happens to us fairly consistently when doing a remove followed shortly by a re-deploy. The issue usually resolves itself within 5-10 minutes. Is there anything we can add to our deployment to speed that up?

意訳すると次のようになります。

  • AWSドキュメントによると、IAMを同じ名称で再作成したとしても、IAMはAIDAJQABLZS4A3QDU576Qのような一意の識別子により管理されるため、以前のアクセス権などの情報が残ることはない
  • しかし、今回のエラーはIAM再作成から5~10分程度で発生した後に自然に解消する動作となった

この投稿がヒントとなり、私は今回の事象は次の仕様およびオペレーションが原因となり発生したと推測しました。

  1. 同じ名称で再作成したIAMロールは、AWSシステム内でIAMの識別子が変更されるまでにタイムラグがある。
  2. よって、再作成直後にLambdaがそのIAMロールを使用したため、以前の識別子のIAMとして削除済みのKMSキーを参照した。
  3. しかし、参照されたKMSキーは削除済みであったためLambdaの実行がKMSAccessDeniedExceptionとなった。

よって、今回の事象が発生した際は次のようなワークアラウンドにより解消できると考えられます。

  • IAMロールの名称(物理名)を変更する
  • IAMロールの識別子がAWSシステム内で切り替わるまで待つ

以上が今回の調査により私が結論づけた原因(推測)とワークアラウンドとなります。

おわりに

AWS Lambdaでカスタマー管理キーによる環境変数のKMS暗号化を無効にしたらKMSAccessDeniedExceptionが発生するようになり原因調査を行ったのでご紹介しました。

原因についてはあくまで他のユーザーや今回の私の動作状況を元にした推測ではあるのですが、同じ事象が発生した方は今回ご紹介したワークアラウンドを実施してみると解消するかもしれません。

参考

以上