Search code examples
amazon-web-servicesamazon-s3aws-lambdaamazon-iamamazon-javascript-sdk

AWS Lambda s3.putObject throws Access Denied when setting ACL field


Update: Found the answer, thanks.


I'm setting up a lambda that periodically fetches some data, does some processing and saves a file to s3.

This is the function:

exports.handler = async function(event, context) {

  const data = await fetchXlsx();

  const uploaded = s3.putObject({
    Bucket: 'my-bucket',
    Key: 'current.json',
    ACL: 'public-read', // Works fine without this line
    ContentType: 'application/json',
    Body: JSON.stringify(data)
  }).promise()
  return uploaded
}

And this is the policy attached to the execution role:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "s3:PutObject",
                "s3:GetObject",
                "s3:PutObjectAcl"
            ],
            "Resource": "arn:aws:s3:::my-bucket/*"
        }
    ]
}

The code above works fine if I comment out the ACL property, but then the file isn't public, and I need it to be so.

Running the code as it is, it throws Access Denied:

{
  "errorType": "AccessDenied",
  "errorMessage": "Access Denied",
  "trace": [
    "AccessDenied: Access Denied",
    "    at Request.extractError (/var/runtime/node_modules/aws-sdk/lib/services/s3.js:831:35)",
    "    at Request.callListeners (/var/runtime/node_modules/aws-sdk/lib/sequential_executor.js:106:20)",
    "    at Request.emit (/var/runtime/node_modules/aws-sdk/lib/sequential_executor.js:78:10)",
    "    at Request.emit (/var/runtime/node_modules/aws-sdk/lib/request.js:688:14)",
    "    at Request.transition (/var/runtime/node_modules/aws-sdk/lib/request.js:22:10)",
    "    at AcceptorStateMachine.runTo (/var/runtime/node_modules/aws-sdk/lib/state_machine.js:14:12)",
    "    at /var/runtime/node_modules/aws-sdk/lib/state_machine.js:26:10",
    "    at Request.<anonymous> (/var/runtime/node_modules/aws-sdk/lib/request.js:38:9)",
    "    at Request.<anonymous> (/var/runtime/node_modules/aws-sdk/lib/request.js:690:12)",
    "    at Request.callListeners (/var/runtime/node_modules/aws-sdk/lib/sequential_executor.js:116:18)"
  ]
}

I've tried setting up the policy in many different ways, including trying to make it as permissible as possible:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "s3:*"
            ],
            "Resource": "*"
        }
    ]
}

But that didn't work.

On the bucket side, I haven't found any settings to mess with other than setting Block all public access to off but that didn't help either.

TLDR; I can upload a file from Lambda to S3, but how can I make it public?


Solution

  • Found the answer:

    On the bucket side, I haven't found any settings to mess with other than setting Block all public access to off but that didn't help either.

    I needed to unblock public access in both the account and the bucket settings.

    This is how it's currently set for both:

    Block all public access
    Off
    Block public access to buckets and objects granted through new access control lists (ACLs)
    Off
    Block public access to buckets and objects granted through any access control lists (ACLs)
    Off
    Block public access to buckets and objects granted through new public bucket or access point policies
    On
    Block public and cross-account access to buckets and objects through any public bucket or access point policies
    On
    

    And that is working with the original policy (second code block in the question).