Jordan
Jordan

Reputation: 2523

Attach bucket policy to bucket generated by serverless

I'm attempting to create an S3 bucket with serverless, which works, however in order to manipulate files in it I need a bucket policy. I'm having a hard time understanding where and how to add a policy that uses the generated S3bucket name created when serverless deploys for the first time

##serverless.yml##

service: vcc-nametags-api

# Use the serverless-webpack plugin to transpile ES6
plugins:
  - serverless-webpack
  - serverless-offline
  - serverless-ding

# serverless-webpack configuration
# Enable auto-packing of external modules
custom:
  # Our stage is based on what is passed in when running serverless
  # commands. Or fallsback to what we have set in the provider section.
  stage: ${opt:stage, self:provider.stage}
  # Set our DynamoDB throughput for prod and all other non-prod stages.
  # Load our webpack config
  webpack:
    webpackConfig: ./webpack.config.js
    includeModules: true
  environment: ${file(env.yml):${self:custom.stage}, file(env.yml):default}

provider:
  name: aws
  runtime: nodejs8.10
  stage: dev
  region: us-east-1

  # These environment variables are made available to our functions
  # under process.env.
  environment:
    S3DBBucketName:
      Ref: NametagsDatabaseBucket

functions:
  # Defines an HTTP API endpoint that calls the main function in create.js
  # - path: url path is /tags
  # - method: POST request
  # - cors: enabled CORS (Cross-Origin Resource Sharing) for browser cross
  #     domain api call
  # - authorizer: authenticate using the AWS IAM role
  create:
    handler: create.main
    events:
      - http:
          path: tags
          method: post
          cors: true

  get:
    # Defines an HTTP API endpoint that calls the main function in get.js
    # - path: url path is /tags/{id}
    # - method: GET request
    handler: get.main
    events:
      - http:
          path: tags/{id}
          method: get
          cors: true

  list:
    # Defines an HTTP API endpoint that calls the main function in list.js
    # - path: url path is /tags
    # - method: GET request
    handler: list.main
    events:
      - http:
          path: tags
          method: get
          cors: true

  update:
    # Defines an HTTP API endpoint that calls the main function in update.js
    # - path: url path is /tags/{id}
    # - method: PUT request
    handler: update.main
    events:
      - http:
          path: tags/{id}
          method: put
          cors: true

  delete:
    # Defines an HTTP API endpoint that calls the main function in delete.js
    # - path: url path is /tags/{id}
    # - method: DELETE request
    handler: delete.main
    events:
      - http:
          path: tags/{id}
          method: delete
          cors: true
# Create our resources with separate CloudFormation templates
resources:
  # S3DB
  - ${file(resources/s3-database.yml)}

##s3-database.yml##

Resources:
  NametagsDatabaseBucket:
    Type: AWS::S3::Bucket
    Properties:
      # Set the CORS policy
      CorsConfiguration:
        CorsRules:
          -
            AllowedOrigins:
              - '*'
            AllowedHeaders:
              - '*'
            AllowedMethods:
              - GET
              - PUT
              - POST
              - DELETE
              - HEAD
            MaxAge: 3000
  NametagsDatabaseBucketPolicy:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket:
        Ref: NametagsDatabaseBucket
      PolicyDocument:
        Statement:
          - Sid: PublicReadGetObject
            Effect: Allow
            Principal: "*"
            Action:
            - "s3:DeleteObject"
            - "s3:GetObject"
            - "s3:ListBucket"
            - "s3:PutObject"
            Resource:
              Fn::Join: [
                "", [
                  "arn:aws:s3:::",
                  {
                    "Ref": "NametagsDatabaseBucket"
                  },
                  "/*"
                ]
              ]

# Print out the name of the bucket that is created
Outputs:
  NametagsDatabaseBucketName:
    Value:
      Ref: NametagsDatabaseBucket

I've tried various combinations I've found on the internet as well as adding it to an iamroles property in the serverless.yml file but I can't seem to get anything to work

Upvotes: 13

Views: 9676

Answers (2)

Alessandro Oliveira
Alessandro Oliveira

Reputation: 2216

Since you are using a lambda to upload you should create an IAM Role for your Lambda and an IAM Policy with only the permissions required for operation. You might accomplish this by using the following excerpt in your cloud formation:

AWSTemplateFormatVersion: '2010-09-09'
Description: My Template
Resources:    
  LambdaRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Effect: Allow
          Principal:
            Service: lambda.amazonaws.com
          Action: sts:AssumeRole
      RoleName: !Sub ${AWS::StackName}-LambdaRole

  S3Policy:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: S3_Writer
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Effect: Allow
          Action:
            - s3:*
          Resource: !Sub
            - arn:aws:s3:::${BucketName}/*
            - BucketName: !Ref NametagsDatabaseBucket
      Roles:
        - !Ref TaskRole
Outputs:
  LambdaRole:
    Value: !Sub "${LambdaRole.Arn}"
    Export:
      Name: !Sub ${AWS::StackName}-LambdaRole

Then in your serverless.yml just refer to the task role created using something like this to reference the execution role:

service: vcc-nametags-api

provider:
  role: ${cf:${env:YOUR_STACK_ENV, 'YOUR_STACK_NAME'}.LambdaRole}

We have a setup like this working in several projects, I hope it works for you.

Upvotes: -1

Matt D
Matt D

Reputation: 3496

The Resource Reference Name seems to matter, I have always had to use the name of the bucket in the resource name. For example, a bucket with www.example.com needs a reference name of S3BucketWwwexamplecom.

However I also notice that the BucketName element is missing from your example.

This is from working example for a static website with a Bucket Policy:

resources:
  Resources:
    S3BucketWwwexamplecom:
      Type: AWS::S3::Bucket
      DeletionPolicy: Delete
      Properties:
        BucketName: ${self:custom.s3WwwBucket}
        CorsConfiguration:
          CorsRules:
            - AllowedMethods:
                - PUT
                - GET
                - POST
                - HEAD
              AllowedOrigins:
                - "https://${self:custom.myDomain}"
              AllowedHeaders:
                - "*"
        AccessControl: PublicRead
        WebsiteConfiguration:
          IndexDocument: index.html
    BucketPolicyWwwexamplecom:
      Type: 'AWS::S3::BucketPolicy'
      Properties:
        PolicyDocument:
          Statement:
            - Sid: PublicReadForGetBucketObjects
              Effect: Allow
              Principal: '*'
              Action:
                - 's3:GetObject'
              Resource: arn:aws:s3:::${self:custom.s3WwwBucket}/*
        Bucket:
          Ref: S3BucketWwwexamplecom

Upvotes: 9

Related Questions