I am attempting to send a Scan command to DynamoDB via API Gateway and a Lambda function written in Javascript (ES6 formatting). Every time I do so, I get 403 ERROR The request could not be satisfied. I have put the aws-sdk dependencies into a Lambda layer.
Lambda function:
import { DynamoDBClient, ScanCommand } from "@aws-sdk/client-dynamodb";
import { unmarshall } from '@aws-sdk/util-dynamodb';
const REGION = process.env.AWS_REGION;
const dynamo = new DynamoDBClient({region: REGION});
const tableName = process.env.MOVIE_TABLE;
//get table name from MOVIE_TABLE environment variable
/**
*
* Event doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format
* @param {Object} event - API Gateway Lambda Proxy Input Format
*
* Context doc: https://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-context.html
* @param {Object} context
*
* Return doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html
* @returns {Object} object - API Gateway Lambda Proxy Output Format
*
*/
//test with command: sam local start-api --port 8080 --log-file logfile.txt
export const lambdaHandler = async (event, context) => {
let respBody;
let sCode = 200;
if (event.httpMethod !== 'GET') {
throw new Error(`GET method only accepts GET method, you tried: ${event.httpMethod}`);
}
//All log statements are written to CloudWatch
console.info('received request for get:', event);
console.info('received context:', context);
try {
const params = {
TableName: tableName,
};
const command = new ScanCommand(params);
console.info(`params Tablename: ${params.TableName}`);
console.info(`Region: ${REGION}`);
respBody = await dynamo.send(command);
respBody = respBody.Items;
respBody = respBody.map((i) => unmarshall(i));
} catch (err) {
sCode = err.statusCode;
respBody = err.message;
var stack = err.stack;
//const { requestId, cfId, extendedRequestId } = err.$$metadata;
console.info('Error stacktrace: \n');
console.info(stack);
//console.info('Error metdata: \n');
//console.log({ requestId, cfId, extendedRequestId });
} finally {
respBody = JSON.stringify(respBody);
console.info(`About to return status: ${sCode}, respBody: ${respBody}`);
}
const response = {
statusCode: sCode,
body: respBody
};
return response;
};
template.yaml:
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
movie-crud-app
Globals:
Function:
Timeout: 3
Resources:
GetItemsFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: lambda-handlers/get-items/
Handler: get-items.lambdaHandler
Runtime: nodejs18.x
Description: A simple function to get items
Policies:
#Give Create/Read/Update/Delete permissions to MovieTable
- DynamoDBCrudPolicy:
TableName: !Ref MovieTable
Environment:
Variables:
#Make table name accessible as environment variable from function code during execution
MOVIE_TABLE: !Ref MovieTable
Architectures:
- x86_64
Layers:
- !Ref DependenciesLayer
Events:
GetItems:
Type: Api
Properties:
Path: /items
Method: get
DependenciesLayer:
Type: AWS::Serverless::LayerVersion
Properties:
LayerName: sam-app-dependencies
Description: Dependencies for movie crud app (aws-sdk/client-dynamodb)
ContentUri: dependencies/
CompatibleRuntimes:
- nodejs18.x
LicenseInfo: 'MIT'
RetentionPolicy: Retain
MovieTable:
Type: AWS::DynamoDB::Table
Properties:
AttributeDefinitions:
- AttributeName: year
AttributeType: N
- AttributeName: title
AttributeType: S
KeySchema:
- AttributeName: year
KeyType: HASH #Partition key
- AttributeName: title
KeyType: RANGE #Sort key
ProvisionedThroughput:
ReadCapacityUnits: 10
WriteCapacityUnits: 10
TableName: "MovieTable"
I am 100% certain that the API Gateway is reaching the Lambda function. Logs from AWS Cloudwatch:
ERROR Invoke Error
{
"errorType": "Error",
"errorMessage": "No valid endpoint provider available.",
"stack": [
"Error: No valid endpoint provider available.",
" at /var/runtime/node_modules/@aws-sdk/middleware-serde/dist-cjs/serializerMiddleware.js:10:15",
" at /var/runtime/node_modules/@aws-sdk/lib-dynamodb/dist-cjs/baseCommand/DynamoDBDocumentClientCommand.js:11:20",
" at /opt/nodejs/node_modules/@aws-sdk/middleware-logger/dist-cjs/loggerMiddleware.js:5:28",
" at /var/runtime/node_modules/@aws-sdk/lib-dynamodb/dist-cjs/commands/ScanCommand.js:28:28",
" at DynamoDBDocumentClient.send (/var/runtime/node_modules/@aws-sdk/smithy-client/dist-cjs/client.js:20:20)",
" at Runtime.lambdaHandler [as handler] (file:///var/task/get-items.mjs:60:29)",
" at Runtime.handleOnceNonStreaming (file:///var/runtime/index.mjs:1085:29)"
]
}
I get an Internal Server Error if I use curl.
This app was deployed via AWS sam with: sam deploy --guided
I have tried sending a scan command to my dynamoDB table with a simple python script that uses boto3. That seems to have worked, so I thought that there might be an issue with permissions or roles that I may have missed.
Roles assigned to my lambda function:
JSON for GetItemsFunctionRolePolicy:
{
"Statement": [
{
"Action": [
"dynamodb:GetItem",
"dynamodb:DeleteItem",
"dynamodb:PutItem",
"dynamodb:Scan",
"dynamodb:Query",
"dynamodb:UpdateItem",
"dynamodb:BatchWriteItem",
"dynamodb:BatchGetItem",
"dynamodb:DescribeTable",
"dynamodb:ConditionCheckItem"
],
"Resource": [
"arn:aws:dynamodb:us-east-1:[mytablename]",
"arn:aws:dynamodb:us-east-1:[mytablename]/index/*"
],
"Effect": "Allow"
}
]
}
Perhaps the issue may have something to do with the integration request being LAMBDA_PROXY?
2023-03-06 Update: Apparently any Get or Scan commands sent to an empty data table will automatically give a "No valid endpoint provider" error. Once I filled the table with mock data, I no longer got that error. However, I still get the 403 Forbidden error if I try to call the API using Postman. If I use curl or a web browser, the function works. My next step is to fix the issue with Postman.