Search code examples
amazon-web-servicesserverless-frameworkaws-cloudformation-custom-resource

Event trigger using serverless framwork on an existing S3 bucket


I'm trying to set a lambda to trigger when an object is created in an S3 bucket.

My serverless.yml includes this:

  handleNewRawObjectInS3:
    handler: lambdas/handleNewRawObjectInS3/handleNewRawObjectInS3.handleS3Event
    events:
      - s3:
          bucket: ${file(../evn.${opt:stage, 'dev'}.json):RAW_IMAGE_BUCKET}
          event: s3:ObjectCreated:*

This results in an error:

Error:
CREATE_FAILED: S3Bucketxxxxxxxxxrawimages (AWS::S3::Bucket)
xxxxxxxxx-raw-images already exists in stack arn:aws:cloudformation:us-east-1:xxxx:stack/s3-xxxxxxxxx-raw-images/310e7010-xxx-xxx-xxxx-12f066874c93

I already have the bucket created -- created via uploading a cloudformation template directly (our corporate version of AWS doesn't allow us to use serverless framework to create a bucket). How to add S3 trigger event on AWS Lambda function using Serverless framework? indicates this was not possible with older versions of serverless, but following the rabbit hole, you can see a feature request ... and later an answer that shows you need to add 'existing: true'.

So I add that to my serverless framework setup:

service: my-service-events

provider:
  name: aws
  runtime: nodejs14.x
  region: ${file(../evn.${opt:stage, 'dev'}.json):REGION}
  stage: ${opt:stage, 'dev'}

  deploymentBucket: #must name manually-created bucket for deployment because enterprise doesn't allow automated bucket creation
    name: ${file(../evn.${opt:stage, 'dev'}.json):DEPLOYMENT_BUCKET}
  iam: #must name a role because enterprise doesn't allow automated role creation
    role: myServerlessRole  # This is a reference to the resource name from the role created in the resources -> iam roles section
    deploymentRole:  ${file(../evn.${opt:stage, 'dev'}.json):DEPLOYMENT_ROLE}
resources:
  - ${file(../iam-roles.${opt:stage, 'dev'}.yml)}

functions:
  handleNewRawObjectInS3:
    handler: lambdas/handleNewRawObjectInS3/handleNewRawObjectInS3.handleS3Event
    events:
      - s3:
          bucket: ${file(../evn.${opt:stage, 'dev'}.json):RAW_IMAGE_BUCKET}
          event: s3:ObjectCreated:*
          existing: true

The IAM file/role referenced above looks like this:

Resources:
  myServerlessRole:
    Type: AWS::IAM::Role
    Properties:
      PermissionsBoundary: arn:aws:iam::xxx:policy/csr-Developer-Permissions-Boundary
      Path: /my/default/path/
      RoleName: myServerlessRole-${self:service}
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole
      Policies:
        - PolicyName: myPolicyName
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                Resource:
                  - 'Fn::Join':
                    - ':'
                    -
                      - 'arn:aws:logs'
                      - Ref: 'AWS::Region'
                      - Ref: 'AWS::AccountId'
                      - 'log-group:/aws/lambda/*:*:*'
    
              - Effect: "Allow"
                Action:
                  - "s3:*"
                Resource: "arn:aws:s3:::*" #
              - Effect: "Allow"
                Action:
                  - "lambda:*"
                Resource: "*"               
              - Effect: "Allow"
                Action:
                  - cloudfront:CreateDistribution
                  - cloudfront:GetDistribution
                  - cloudfront:UpdateDistribution
                  - cloudfront:DeleteDistribution
                  - cloudfront:TagResource
                Resource: "arn:aws:cloudfront:::*"             

Trying to deploy this gets me the error:

Error:
CREATE_FAILED: CustomDashresourceDashexistingDashs3LambdaFunction (AWS::Lambda::Function)
Resource handler returned message: "The role defined for the function cannot be assumed by Lambda. (Service: Lambda, Status Code: 400, Request ID: 812c5384-1c26-42c9-bdef-1ce4a59f2be4)" (RequestToken: 9cdbb5af-3bc7-d6bf-384b-5126d1048ccd, HandlerErrorCode: InvalidRequest)

How can I deploy this lambda triggered by an s3 event?


Solution

  • The issue comes from the fact that CustomDashresourceDashexistingDashs3LambdaFunction lambda runs as the deployementRole and not under the defined role (which is the default role that lambdas run under). Given the deployment role doesn't normally need to assumeRole my deployment role did not have the assumeRole permission.

    The fix for this is to ensure that the sts:assumeRole trust relationship has been applied to the deploymentRole like so:

    {
            "Effect": "Allow",
            "Principal": {
                "Service": "lambda.amazonaws.com"
            },
            "Action": "sts:AssumeRole"
        }