AWS CloudFormation의 Lambda 이벤트 알림과 관련된 "Unable to validate the following destination configurations" 오류를 방지하려면 어떻게 해야 합니까?

최종 업데이트 날짜: 2020년 1월 16일

AWS CloudFormation 템플릿을 배포할 때 스택이 실패합니다. 그리고 "Unable to validate the following destination configurations"와 비슷한 오류가 표시됩니다.

간략한 설명

다음 리소스를 사용하여 AWS CloudFormation 템플릿을 배포할 때 이 오류가 발생합니다.

Amazon S3는 버킷을 생성할 때 알림 구성을 검증해야 합니다. 검증은 버킷에 Lambda 함수로 이벤트를 푸시할 권한이 있는지 여부를 확인하여 수행됩니다. 권한 리소스(이 검사를 통과하려면 존재해야 함)에는 버킷 이름이 필요합니다. 즉, 권한 리소스는 버킷에 의존하고 버킷은 권한 리소스에 의존합니다.

참고: 다음 코드 예제와 비슷한 DependsOn 리소스 속성을 구현하여 이 문제를 해결하려고 하면 "Circular dependency between resources" 오류가 표시됩니다.

다음 코드 예제는 S3 버킷 리소스와 Lambda 권한 리소스의 SourceArn 속성 간에 순환 종속성을 보여줍니다.

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

참고: Amazon S3의 Amazon 리소스 이름(ARN)에는 계정 ID가 포함되지 않으므로 SourceAccount 속성을 Amazon S3 이벤트 소스에 대한 Lambda 권한 리소스에 추가하는 것이 모범 사례입니다. SourceArn 속성은 대부분의 다른 이벤트 소스에 적합하지만 Amazon S3 이벤트 소스에 대해 SourceAccount 속성을 추가하는 것이 좋습니다. 이렇게 하면 다른 사람이 버킷을 다시 생성한 것을 찾을 때만 S3 버킷을 삭제하여 버킷의 새 소유자에게 Lambda 함수를 호출할 수 있는 전체 권한을 부여하는 시나리오가 방지됩니다.

해결 방법

스택 파라미터와 함께 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": "nodejs8.10"
      }
    },
    "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 버킷에 알림 구성을 추가합니다.