I have a Lambda function that makes a GetObject
request to an S3 bucket.
However, I'm getting the following error:
AccessDenied: Access Denied
at deserializeAws_restXmlGetObjectCommandError (/node_modules/@aws-sdk/client-s3/dist-cjs/protocols/Aws_restXml.js:6284:41)
at processTicksAndRejections (internal/process/task_queues.js:95:5)
at /node_modules/@aws-sdk/middleware-serde/dist-cjs/deserializerMiddleware.js:6:20
at /node_modules/@aws-sdk/middleware-signing/dist-cjs/middleware.js:11:20
at StandardRetryStrategy.retry (/node_modules/@aws-sdk/middleware-retry/dist-cjs/StandardRetryStrategy.js:51:46)
at /node_modules/@aws-sdk/middleware-logger/dist-cjs/loggerMiddleware.js:6:22
at GetS3Data (/src/input.ts:21:26)
at Main (/src/main.ts:8:34)
at Runtime.run [as handler] (/handler.ts:6:9) {
Code: 'AccessDenied',
RequestId: '3K61PMQGW4825D3W',
HostId: '5PpmWpu2I4WZPx37Y0pRfDAcdCmjX8fchuE HLpUzy7uqoJirtb9Os0g96kWfluM/ctkn/mEC5o=',
'$fault': 'client',
'$metadata': {
httpStatusCode: 403,
requestId: undefined,
extendedRequestId: '5PpmWpu2I4WZPx37Y0pRfDAcdCmjX8fchuE HLpUzy7uqoJirtb9Os0g96kWfluM/ctkn/mEC5o=',
cfId: undefined,
attempts: 1,
totalRetryDelay: 0
}
}
I've given access to the Lambda function to make this request.
What is the issue?
serverless.ts
import type { AWS } from "@serverless/typescript";
const serverlessConfiguration: AWS = {
service: "affiliations",
frameworkVersion: "2",
custom: {
esbuild: {
bundle: true,
minify: false,
sourcemap: true,
exclude: ["aws-sdk"],
target: "node14",
define: { "require.resolve": undefined },
platform: "node",
},
},
plugins: ["serverless-esbuild"],
provider: {
name: "aws",
region: "us-east-2",
runtime: "nodejs14.x",
apiGateway: {
minimumCompressionSize: 1024,
shouldStartNameWithService: true,
},
environment: {
AWS_NODEJS_CONNECTION_REUSE_ENABLED: "1",
NODE_OPTIONS: "--enable-source-maps --stack-trace-limit=1000",
},
lambdaHashingVersion: "20201221",
vpc: {
securityGroupIds: ["<redacted>"],
subnetIds: ["redacted>"],
},
iam: {
role: {
statements: [
{
Effect: "Allow",
Action: ["s3:GetObject"],
Resource: "<redacted>",
},
],
},
},
},
useDotenv: true,
// import the function via paths
functions: {
run: {
handler: "handler.run",
timeout: 300,
events: [
{
sns: {
arn: "<redacted>",
},
},
],
},
},
};
module.exports = serverlessConfiguration;
s3.ts
export const GetS3Data = async (payload: GetObjectRequest) => {
try {
const response = await S3Service.getObject(payload);
const result = await new Promise((resolve, reject) => {
const data = [];
response.Body.on("data", (chunk) => data.push(chunk));
response.Body.on("err", reject);
response.Body.once("end", () => resolve(data.join("")));
});
return [result, null];
} catch (err) {
Logger.error({
method: "GetS3Data",
error: err.stack,
});
return [null, err];
}
};
package.json
"@aws-sdk/client-s3": "^3.36.0",
CodePudding user response:
Forgot to add /*
to the end of the resource
Resource: "<redacted>/*",
CodePudding user response:
Your 403 Access Denied
error is masking a 404 Not Found
error, as your code & Serverless config looks perfectly fine & should work as expected provided you've specified the resource correctly.
If you do not have correct s3:ListBucket
permissions, the S3 endpoint will not return a 404 Not Found
error if the object does not exist for the specified key.
GetObject
's API reference highlights this nuance:
If you have the s3:ListBucket permission on the bucket, Amazon S3 will return an HTTP status code 404 ("no such key") error.
If you don’t have the s3:ListBucket permission, Amazon S3 will return an HTTP status code 403 ("access denied") error.
This is to prevent attackers from enumerating public buckets & knowing what objects actually exist in the bucket.
The absence of a 404, in this case, is not allowing information to be leaked on if the object exists or not (just like an Invalid Credentials
message on a login page as opposed to Invalid Password
which indicates a user with the provided username exists).
Provide the Lambda with permission to carry out the s3:ListBucket
action to unmask the 404 error and/or ultimately double-check your GetObjectRequest
to make sure the key is being specified correctly for an object that does exist:
iam: {
role: {
statements: [
{
Effect: "Allow",
Action: ["s3:GetObject", "s3:ListBucket"],
Resource: "<redacted>",
},
],
},
}