部署 AWS CloudFormation 模板时,我的堆栈出现故障。然后,我收到类似以下内容的错误:“Unable to validate the following destination configurations”。
简短描述
使用以下资源部署 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 事件源的 Lambda 权限资源添加 SourceAccount 属性。添加属性是因为 Amazon S3 的 Amazon Resource Name (ARN) 不包括账户 ID。SourceArn 属性足以满足大多数其他事件源的需求,但对于 Amazon S3 事件源,请考虑添加 SourceAccount 属性。这样可以防止以下情况:用户在您删除存储桶后重新创建存储桶,然后向新的存储桶拥有者授予调用您的 Lambda 函数的完全权限。
解决方法
您可以使用 Fn::Sub 内部函数与堆栈参数来避免循环依赖。您也可以使用 Fn::Join 组合字符串。
在以下示例模板中,S3 存储桶名称 BucketPrefix 是 AWS::S3::Bucket 和 AWS::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:*:*:*"
}
]
}
}
]
}
}
}
}
此模板可以避免循环依赖,因为它会按以下顺序创建资源:
- AWS Identity and Access Management (IAM) 角色
- Lambda 函数
- Lambda 权限
- S3 存储桶
现在,Amazon S3 可正常验证通知配置并创建存储桶。
您还可以尝试以下解决方法:
- 创建没有通知配置的 S3 存储桶,然后将该存储桶添加到下一次堆栈更新中。
- 创建约束较少的 Lambda 权限。例如,通过省略 SourceArn 来允许调用特定 AWS 账户。
- 创建自定义资源以在堆栈工作流程结束时运行。所有其他资源均已创建后,此资源会将通知配置添加到 S3 存储桶。
相关信息
如何避免在 AWS CloudFormation 中出现的“Unable to validate the following destination configurations”错误?
将 AWS Lambda 与 Amazon S3 事件结合使用
Ref
Fn::GetAtt