My AWS CloudFormation stack creation fails if I deploy a template with the following resources:

  1. An AWS Lambda function resource.
  2. An Amazon S3 bucket resource with a NotificationConfiguration property that references the Lambda function.
  3. A Lambda permission resource with FunctionName and SourceArn properties that match the Lambda function and the S3 bucket.
    Note: It's a best practice to add the SourceAccount property to the Lambda permission resource for S3 event sources, because an S3 Amazon Resource Name (ARN) does not include an account ID. Although the SourceArn property is adequate for most other event sources, consider adding the SourceAccount property for S3 event sources to guard against a scenario where you delete the bucket only to find that someone else has re-created the bucket, granting the new owner of the bucket full permissions to invoke your Lambda function.

If the stack fails, an error similar to this occurs:

Unable to validate the following destination configurations

When creating the bucket, S3 must validate the notification configuration by checking whether the bucket has permission to push events to the Lambda function. The permission resource (which must exist for this check to pass) requires the bucket name. Therefore, the permission resource depends on the bucket, and the bucket depends on the permission resource.

Note: If you attempt to resolve this issue by implementing a DependsOn resource attribute similar to the following, you receive an error:

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

DependsOn resource attribute error:

Circular dependency between resources

In this example, there is a circular dependency between the S3 bucket resource and the SourceArn property of the Lambda permission resource because neither exists and one can't be created without the other.

If this problem occurs, you can avoid circular dependencies by using the Fn::Join intrinsic function along with stack parameters.

Consider this sample template where bucket name "BucketPrefix" is provided as a parameter to "AWS::S3::Bucket" and "AWS::Lambda::Permission" resources.

Note: This example assumes that the bucket name wasn't used previously with your AWS accounts. If you want to reuse a template with this code snippet, you must provide a different bucket prefix every time.

{
  "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::Join": [
            "",
            [
              "arn:aws:s3:::",
              {
                "Fn::Sub": "${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": "nodejs4.3"
      }
    },
    "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:*:*:*"
                }
              ]
            }
          }
        ]
      }
    }
  }
}

This avoids circular dependency because it creates the resources in this order:

  1. IAM role
  2. Lambda function
  3. Lambda permission
  4. S3 bucket

This approach allows S3 to verify its notification configuration and create the bucket without any issues.

Other possible workarounds include:

  • Create the bucket without notification configuration, and then add it in the next stack update.
  • Create a less-constrained Lambda permission. For example, allow invocations for a specific AWS account by omitting the SourceArn.
  • Create a custom resource to run at the end of the stack workflow. This resource adds the notification configuration to the bucket after all other resources are created.

Did this page help you? Yes | No

Back to the AWS Support Knowledge Center

Need help? Visit the AWS Support Center

Published: 2017-10-03

Updated: 2018-09-06