For context, I'm using serverless framework so a few things happening:

  • A role is created with some attached policies and a lambda function gets that assigned as execution role. The S3 policies are (listing more relevant ones):
    • ListBucket to the bucket ARN as resource
    • GetObject, PutObject DeleteObjet to the bucket ARN as resource prefixed by /*
  • I'm creating the bucket using Cloudformation resources syntax and I set a public read policy in order to use it as a static website host.
  • I'm also including another policy statement in the S3 bucket policy where I assign the write operations and set the execution role ARN as principal.

I got the following iamRoleStatements section:

- Effect: "Allow"
  Action:
    - "s3:ListBucket"
  Resource:
    - Fn::Join:
        - ""
        - - "arn:aws:s3:::"
          - Ref: StaticSiteBucket
- Effect: "Allow"
  Action:
    - "s3:GetObject"
    - "s3:PutObject"
    - "s3:DeleteObject"
    - "s3:GetObjectVersionTagging"
    - "s3:PutObjectVersionTagging"
  Resource:
    - Fn::Join:
        - ""
        - - "arn:aws:s3:::"
          - Ref: StaticSiteBucket
          - "/*"

I can actually confirm that it produces the following policy to the generated role:

        {
            "Action": [
                "s3:ListBucket"
            ],
            "Resource": [
                "arn:aws:s3:::bucket-name-here"
            ],
            "Effect": "Allow"
        },
        {
            "Action": [
                "s3:GetObject",
                "s3:PutObject",
                "s3:DeleteObject",
                "s3:GetObjectVersionTagging",
                "s3:PutObjectVersionTagging"
            ],
            "Resource": [
                "arn:aws:s3:::bucket-name-here/*"
            ],
            "Effect": "Allow"
        }

Having this, I ran the lambda function to perform a putObject over that bucket. Getting an 'Access denied error', so I thought what if the bucket itself needs to allow writes, so I included a write statement to the bucket policy:

StaticSiteBucket:
  Type: AWS::S3::Bucket
  Properties:
    AccessControl: PublicRead
    BucketName: ${self:service}-static-site-${self:provider.stage}
    WebsiteConfiguration:
      IndexDocument: index.html
StaticSiteBucketPolicy:
  Type: AWS::S3::BucketPolicy
  Properties:
    Bucket:
      Ref: StaticSiteBucket
    PolicyDocument:
      Statement:
        - Sid: PublicReadGetObject
          Effect: Allow
          Principal: "*"
          Action:
          - s3:GetObject
          Resource:
            Fn::Join: [
              "", [
                "arn:aws:s3:::",
                {
                  "Ref": "StaticSiteBucket"
                },
                "/*"
              ]
            ]
        - Sid: AllowLambdaRoleWrite
          Effect: Allow
          Action:
            - "s3:GetObject"
            - "s3:PutObject"
            - "s3:DeleteObject"
            - "s3:GetObjectVersionTagging"
            - "s3:PutObjectVersionTagging"
          Principal:
            AWS:
              - Fn::GetAtt: [ IamRoleLambdaExecution, Arn ]
          Resource:
            Fn::Join:
              - ""
              - - "arn:aws:s3:::"
                - Ref: StaticSiteBucket
                - "/*"

Which generates the following policy at S3 bucket:

{
    "Version": "2008-10-17",
    "Statement": [
        {
            "Sid": "PublicReadGetObject",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::bucket-name-here/*"
        },
        {
            "Sid": "AllowLambdaRoleWrite",
            "Effect": "Allow",
            "Principal": {
                "AWS": "<role-arn>"
            },
            "Action": [
                "s3:GetObject",
                "s3:PutObject",
                "s3:DeleteObject",
                "s3:GetObjectVersionTagging",
                "s3:PutObjectVersionTagging"
            ],
            "Resource": "arn:aws:s3:::bucket-name-here/*"
        }
    ]
}

So, in a few words I tried:

  • Allowing the role to have write permissions, no luck
  • Attaching write permissions to that role from the bucket policy, no luck

What am I missing?

2 Answers

0
diegoaguilar On Best Solutions

So in my case what I was missing:

  • I had to include getObjectTagging and putObjectTagging actions.

For anyone with a similar problem. Read this below!

Learning from the lesson (I spent literally one day and a half stuck on this) and aware that Stackoverflow is a repository of experiences and knowledge.

I'd point out:

  • Identify which API operations your code is doing, and then go to the docs and check it. Some operations act on buckets while other on objects
  • In my case, I could drop any additional policy statements on the bucket, in other words,
  • the execution role is the one getting the powers that be.
  • I couldn't achieve this, but if there would be a way to have a more explicit error log, at least on aws-sdk node's library, would save tons of time. Stacktrace was not enough. Suggestions welcomed.

This is what my StaticSiteBucket resource declaration looks like now:

- Effect: "Allow"
  Action:
    - "s3:ListBucket"
  Resource:
    - Fn::Join:
        - ""
        - - "arn:aws:s3:::"
          - Ref: StaticSiteBucket
- Effect: "Allow"
  Action:
    - "s3:GetObject"
    - "s3:PutObject"
    - "s3:DeleteObject"
    - "s3:GetObjectTagging"
    - "s3:PutObjectTagging"
  Resource:
    - Fn::Join:
        - ""
        - - "arn:aws:s3:::"
          - Ref: StaticSiteBucket
          - "/*"
1
Gonfva On

Some ideas to consider:

  1. The principal (lambda.amazonaws.com) shouldn't be needed in the policy. The principal already appears in the role that serverless creates (at least by default). Most probably now it works because you gave it all the permissions.
  2. You may need ListBucket. Not because it is strictly needed, but because it may be masking another problem. See this excellent response. In fact, when you put the name of the bucket without the asterisk in the resource section, is probably because of this.
  3. Is the file small? For big files you maybe triggering multipart and that requires other put permissions as well.
  4. Are you tagging or putting additional ACL permissions or owner? That also requires additional permissions.