I have created, as per the CDK example at this link, an HTTP API Gateway protected by IAM authorizers. However, I cannot get any requests to pass through it.
The producer lambda function simply returns a Harry Potter title and is omitted for brevity.
Here is the CDK code deployed to the producer account:
const authorizer = new authorizers.HttpIamAuthorizer();
const booksFunction = new lambda.Function(this, "BooksFunction", {
runtime: lambda.Runtime.NODEJS_18_X,
code: lambda.Code.fromAsset(
path.join(__dirname, "..", "handlers", "books")
),
handler: "books.handler",
});
const booksIntegration = new integrations.HttpLambdaIntegration(
"LambdaIntegration",
booksFunction
);
const principal = new iam.AccountPrincipal("CONSUMER_ACCOUNT_ID");
const httpApi = new apigwv2.HttpApi(this, "HttpProxyApi", {
createDefaultStage: true,
defaultAuthorizer: authorizer,
});
const routes = httpApi.addRoutes({
integration: booksIntegration,
methods: [apigwv2.HttpMethod.GET],
path: "/books",
// authorizer,
});
routes[0].grantInvoke(principal);
I tried adding a stage and CORS options to it, but no difference.
My lambda on the consumer account has the required privileges to invoke our API gateway. Here's the CDK code:
const lambdaRole = new iam.Role(this, "Role", {
roleName: "example-role",
assumedBy: new iam.ServicePrincipal("lambda.amazonaws.com"),
});
lambdaRole.addToPolicy(
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: ["execute-api:Invoke"],
resources: [
this.formatArn({
account: "PRODUCER_ACCOUNT_ID",
service: "execute-api",
resource: "API_NAME",
resourceName: "*",
}),
],
})
);
const booksFunction = new nodejs.NodejsFunction(this, "GetBookFunction", {
runtime: lambda.Runtime.NODEJS_18_X,
entry: path.join(__dirname, "..", "handlers", "get_book", "get_book.ts"),
handler: "handler",
role: lambdaRole,
bundling: {
nodeModules: ["@aws-crypto/sha256-js", "url"],
},
});
}
The consumer function signs requests with v4 signatures:
const v4 = new SignatureV4({
service: "execute-api",
region: process.env.AWS_DEFAULT_REGION || "",
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID || "",
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY || "",
sessionToken: process.env.AWS_SESSION_TOKEN || "",
},
sha256: Sha256,
});
const signRequest = async (request: HttpRequest): Promise<HttpRequest> => {
const signedRequest = await v4.sign(request);
return signedRequest;
};
export const handler = async (event, context) => {
const url = new URL(
"https://API_ID.execute-api.us-east-1.amazonaws.com/books"
);
const request = new HttpRequest({
hostname: url.host,
method: "GET",
headers: {
host: url.host,
},
path: url.pathname,
});
const results: Record<string, any> = {
error: {},
response: {},
};
try {
const signedRequest = await signRequest(request);
const response = await fetch(url.href, signedRequest);
const responseJson = await response.json();
results.response = responseJson;
} catch (e) {
results.error = e;
}
return results;
};
Calling producer's API Gateway from the consumer lambda returns:
{
"error": {},
"response": {
"message": "Forbidden"
}
}
This message means an exception didn't occur.
I can also confirm via logging that the API Gateway is being hit, but it returns a 403 forbidden error.
What am I doing wrong here? I'm at my wits' end.