如何避免 CloudFormation 中的 Lambda 事件通知發生「無法驗證以下目標組態」錯誤?

2 分的閱讀內容
0

當我部署 AWS CloudFormation 範本時,我的堆疊會失敗。然後,我收到類似以下內容的錯誤: 「無法驗證以下目的地組態。」

簡短說明

當您使用下列資源部署 CloudFormation 範本時,您會收到這個錯誤:

Amazon S3 在建立儲存貯體時必須驗證通知組態。驗證是透過檢查儲存貯體是否具有將事件推送至 Lambda 函數的權限來完成。權限資源 (必須存在才能通過此檢查) 需要儲存貯體名稱。這意味著權限資源取決於儲存貯體,並且儲存貯體取決於權限資源。

**注意:**如果您嘗試透過實作類似下列程式碼範例的 DependsOn 資源屬性來解決這個問題,就會收到「資源之間的循環相依性」錯誤。

下列範例顯示 S3 儲存貯體資源與 Lambda 權限資源的 SourceArn 屬性之間的循環相依性。

"Resources": {
  "MyS3BucketPermission": {
    "Type": "AWS::Lambda::Permission",
    "Properties": {
      "Action": "lambda:InvokeFunction",
      ...
      ...
      "SourceArn": {
        "Ref": "MyS3Bucket"
      }
    }
  },
  "MyS3Bucket": {
    "DependsOn" : "MyS3BucketPermission",
    ...
    ...

**重要:**最佳做法是將 SourceAccount 屬性新增至 Amazon S3 事件來源的 Lambda 權限資源。您新增屬性是因為 Amazon S3 的 Amazon Resource Name (ARN) 不包含帳戶 ID。SourceArn 屬性適用於大多數其他事件來源,但是請考慮為 Amazon S3 事件來源新增 SourceAccount 屬性。這樣可防止使用者重新建立您刪除的儲存貯體,然後授予新儲存貯體所有者完整權限來調用 Lambda 函數。

解決方法

您可以使用 Fn::Sub 內在函數搭配堆疊參數來避免循環相依性。您也可以使用 Fn::Join 來合併字串。

在下列範例範本中,S3 儲存貯體名稱儲存貯體 BucketPrefixAWS::S3::BucketAWS::Lambda::Permission 資源的參數。

**注意:**下列範例假設儲存貯體名稱先前未用於您的 AWS 帳戶。如果您想要重複使用含有此程式碼片段的範本,則每次都必須提供不同的儲存貯體前綴。

{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Parameters": {
    "BucketPrefix": {
      "Type": "String",
      "Default": "test-bucket-name"
    }
  },
  "Resources": {
    "EncryptionServiceBucket": {
      "DependsOn": "LambdaInvokePermission",
      "Type": "AWS::S3::Bucket",
      "Properties": {
        "BucketName": {
          "Fn::Sub": "${BucketPrefix}-encryption-service"
        },
        "NotificationConfiguration": {
          "LambdaConfigurations": [
            {
              "Function": {
                "Fn::GetAtt": [
                  "AppendItemToListFunction",
                  "Arn"
                ]
              },
              "Event": "s3:ObjectCreated:*",
              "Filter": {
                "S3Key": {
                  "Rules": [
                    {
                      "Name": "suffix",
                      "Value": "zip"
                    }
                  ]
                }
              }
            }
          ]
        }
      }
    },
    "LambdaInvokePermission": {
      "Type": "AWS::Lambda::Permission",
      "Properties": {
        "FunctionName": {
          "Fn::GetAtt": [
            "AppendItemToListFunction",
            "Arn"
          ]
        },
        "Action": "lambda:InvokeFunction",
        "Principal": "s3.amazonaws.com",
        "SourceAccount": {
          "Ref": "AWS::AccountId"
        },
        "SourceArn": {
            "Fn::Sub": "arn:aws:s3:::${BucketPrefix}-encryption-service"
        }
      }
    },
    "AppendItemToListFunction": {
      "Type": "AWS::Lambda::Function",
      "Properties": {
        "Handler": "index.handler",
        "Role": {
          "Fn::GetAtt": [
            "LambdaExecutionRole",
            "Arn"
          ]
        },
        "Code": {
          "ZipFile": {
            "Fn::Join": [
              "",
              [
                "var response = require('cfn-response');",
                "exports.handler = function(event, context) {",
                " var responseData = {Value: event.ResourceProperties.List};",
                " responseData.Value.push(event.ResourceProperties.AppendedItem);",
                " response.send(event, context, response.SUCCESS, responseData);",
                "};"
              ]
            ]
          }
        },
        "Runtime": "nodejs12.x"
      }
    },
    "LambdaExecutionRole": {
      "Type": "AWS::IAM::Role",
      "Properties": {
        "AssumeRolePolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Principal": {
                "Service": [
                  "lambda.amazonaws.com"
                ]
              },
              "Action": [
                "sts:AssumeRole"
              ]
            }
          ]
        },
        "Path": "/",
        "Policies": [
          {
            "PolicyName": "root",
            "PolicyDocument": {
              "Version": "2012-10-17",
              "Statement": [
                {
                  "Effect": "Allow",
                  "Action": [
                    "logs:*"
                  ],
                  "Resource": "arn:aws:logs:*:*:*"
                }
              ]
            }
          }
        ]
      }
    }
  }
}

該範本可避免循環相依性,因為它會按照以下順序建立資源:

  1. 建立 AWS Identity and Access Management (IAM) 角色
  2. Lambda 函數
  3. Lambda 權限
  4. S3 儲存貯體

現在,Amazon S3 可以驗證其通知組態並建立儲存貯體,而不會出現任何問題。

您也可以嘗試以下解決方案:

  • 在沒有通知設定的情況下建立 S3 儲存貯體,然後在下一次堆疊更新中新增儲存貯體。
  • 建立不受限制的 Lambda 權限。例如,透過省略 SourceArn,允許針對特定 AWS 帳戶調用。
  • 建立要在堆疊工作流程結束時執行的自訂資源。此資源會在建立所有其他資源後,將通知組態新增至 S3 儲存貯體。

相關資訊

如何避免 AWS CloudFormation 中出現「無法驗證以下目的地組態」錯誤?

將 AWS Lambda 與 Amazon S3 事件搭配使用

Ref

Fn::GetAtt

AWS 官方
AWS 官方已更新 3 年前