Solving problems with API Gateway and the Python CDK
Tags: apigateway, aws, python, cdk
Published: 2022-12-09 16:53:00

So you decided to use the Python flavor of CDK to build your API Gateway infrastructure. Great! Python CDK is my favorite tool to manage Infrastructure as Code in AWS. Anyone who knows Python and has created resources in the AWS console can get up to speed almost instantly with Python CDK. But now you've hit an incredibly hard to debug issue: you've specified an existing API Gateway in a different CDK Stack, and are now trying to use `aws_apigateway.LambdaRestApi.from_rest_api_attributes` to get the existing API Gateway and add new paths to it using add_resource and `add_method`.

Everything is going great, your new stack deploys, and then you try to invoke your new API Gateway->Lambda endpoint. You get

```
Authorization header requires 'Credential' parameter. Authorization header requires 'Signature' parameter. Authorization header requires 'SignedHeaders' parameter. Authorization header requires existence of either a 'X-Amz-Date' or a Date' header.
```

Oh no. That generic error could mean anything. So you Google around for a while, trying to figure out what could be going wrong. You look at the API Gateway in the AWS console. You realize that even though the resource paths are being added to your Gateway, they're not being deployed to a `Stage`!

The LambdaRestApi class should do that automatically, right? Yes, it will, but unfortunately not if you're using the `from_rest_api_attributes` function to get an existing Gateway. This is a known issue: https://github.com/aws/aws-cdk/issues/12417

I'd encourage you to upvote the github issue above so that the CDK team prioritizes it, but here's the workaround with Python CDK:

```
import uuid
from aws_cdk import aws_apigateway

api = aws_apigateway.LambdaRestApi.from_rest_api_attributes(self, [gateway id], rest_api_id=[api id], root_resource_id=[gateway root resource id])
r = api.root.add_resource("hello-world")
r.add_method("ANY", aws_apigateway.LambdaIntegration([your previously specified Lambda CDK object])
deployment = aws_apigateway.Deployment(self, f"deployment-{uuid.uuid4().hex}', api=api)
stage = aws_apigateway.Stage(self, [stage id (some string)], deployment=deployment, stage_name = [some string])
api.deployment_stage = stage
```

And now your new endpoint deploys properly. As you can see, there are many parameters that you need to create yourself (I've put those in square brackets), but the main thing to note is that I've manually added a Deployment and Stage to the API Gateway, and added a hash to the deployment ID that changes every time the stack is deployed. This forces the deployment to be pushed out, and the solution won't work without it.

If you need to add multiple resources/methods in different stacks (my own current use case!), the best way I've found is just to put the deployment into a separate stack and build it after you add/update any of your resource stacks. It's a little gross, but prevents you from having to add multiple stage names.

If this one had you beating your head against a wall for a while, you're not alone. I hope the CDK team addresses this issue soon, but in the meantime you now have a fix.