A curious use-case came up to me this week. We have a REST API in AWS API Gateway that integrates with a Lambda. This is set up using Serverless. This is a multi-tenant system and because a former client didn’t do their cleanup, we’re still receiving a lot of calls that basically return errors (because the tenant no longer exists on our side). In AWS Lambda, this means a lot of useless invocations and a higher bill at the end of the month. Here’s a way a returning the error in API Gateway, before your Lambda is invoked. In other words, let’s have the AWS infrastructure handle this for us.
The Resources
This screenshot of the AWS Console clarifies what we want to end up with:
Let’s break this down. Previously, we only had the /{proxy+}
resource. This means that the path is sent to our lambda and the lambda would handle the routing. You could choose to put your resources in API Gateway instead of using what is called Proxy Integration, but that’s not really the issue here (and also just a matter of preference in my opinion).When a request is made to https://example.com/goodtenant/whatever
, we want to invoke our Lambda and return the response. But if our bad tenant makes a request to https://example.com/badtenant/whatever
, we want to stop them short and immediately return an error.
So in the screenshot above, any request to /badtenant
or a sub-path of that should return an error.
The Integration
Now, we also need this badtenant
resource to point to a mock integration and return (in our case) a HTTP 403. This is what it should look like:
We need an integration of type “MOCK” and have it return a HTTP 403 for any result that the mock endpoint returns.In a mock endpoint, you can put some basic logic to return a response. Look at it like a mini-lambda. In our case, it doesn’t really matter what we return, because we’ll return a 403 in the end anyway.
The Serverless File
Now for the most important part, this is what you serverless.yml should look like (omitting the uninteresting parts):
functions:
main:
handler: lib/index.handler
name: myLambda
events:
- http: POST /{proxy+}
...
resources:
Resources:
ApiGatewayResourceBadTenant:
Type: AWS::ApiGateway::Resource
Properties:
ParentId:
Fn::GetAtt: ["ApiGatewayRestApi", "RootResourceId"]
PathPart: "badtenant"
RestApiId:
Ref: ApiGatewayRestApi
ApiGatewayResourceBadTenantProxyVar:
Type: AWS::ApiGateway::Resource
Properties:
ParentId:
Ref: ApiGatewayResourceBadTenant
PathPart: "{proxy+}"
RestApiId:
Ref: ApiGatewayRestApi
ApiGatewayResourceeBadTenantProxyVarAny:
Type: AWS::ApiGateway::Method
Properties:
HttpMethod: ANY
ResourceId:
Ref: ApiGatewayResourceBadTenantProxyVar
RestApiId:
Ref: ApiGatewayRestApi
Integration:
Type: MOCK
PassthroughBehavior: NEVER
RequestTemplates:
application/json: "{\"statusCode\":403}"
IntegrationResponses:
- SelectionPattern: .*
StatusCode: 403
MethodResponses:
- StatusCode: 403
AuthorizationType: NONE
In essence, what this does is define a new resource called ApiGatewayResourceBadTenant
. The parent (ApiGatewayRestApi
) is created by the Serverless framework and is always called ApiGatewayRestApi
. Under the resource, we create the proxy resource. And under that, we create the method.
The method contains a MOCK
integration and returns a 403.
Testing
After deploying this with serverless, the necessary resources should be created. If we make a call to our bad tenant, we see the 403:
This is pure API Gateway and not a single of our Lambda’s was invoked.
That’s It!
It took me a while to put the pieces together, but in the end it’s a fairly simple and elegant solution. We don’t need to do anything in the AWS console manually, which means everything is automated and remains in source control. But we can still stop the bad tenant from triggering our Lambda’s too much, which is good for our Amazon bill!
Top comments (0)