By Wolfgang Unger
Lets have a look how to create a API Gateway with CDK (Python)
The first approach is using the RestApi Class and code the resources and methods.
The second by using a Swagger/Open API file.
Both APIs will use lambda integrations.
We will also see how to use Authorizers with Cognito and Custom Lambda.
RestApi CDK Class
To create a Rest API you basically can use the following code:
api = aws_apigateway.RestApi(
self,
f"{stage}-cdk-api",
deploy_options=deploy_options,
default_cors_preflight_options={
"allow_origins": aws_apigateway.Cors.ALL_ORIGINS,
"allow_methods": aws_apigateway.Cors.ALL_METHODS,
},
)
The deploy_options are not mandatory, if used like in this example you need to define them
first.
log_group = aws_logs.LogGroup(self, "CDK-Api-Logs")
deploy_options = aws_apigateway.StageOptions(
access_log_destination=aws_apigateway.LogGroupLogDestination(log_group),
access_log_format=aws_apigateway.AccessLogFormat.json_with_standard_fields(
caller=False,
http_method=True,
ip=True,
protocol=True,
request_time=True,
resource_path=True,
response_length=True,
status=True,
user=True,
),
metrics_enabled=True,
)
For the lambda integration you need of course a lambda function.
This function is not part of this tutorial.
Let's assume it was created ahead of this stack and the function is passed as
a parameter to the API stack:
lambda_integration = aws_apigateway.LambdaIntegration(
handler.fn
)
Now we can create our resources and methods.
First the base resources:
api_resource = api.root.add_resource("api")
v1_resource = api_resource.add_resource("v1")
Now the api specific resources we want to define using the lambda integraion:
items_resource = v1_resource.add_resource("items")
items_resource.add_method(
"GET",
lambda_integration,
authorizer=authorizer,
authorization_type=aws_apigateway.AuthorizationType.COGNITO,
)
Now we can add more resources and methods. We can also use path variables:
item_name_resource = items_resource.add_resource("{item_id}")
item_name_resource.add_method(
"GET",
lambda_integration,
authorizer=authorizer,
authorization_type=aws_apigateway.AuthorizationType.COGNITO,
)
This is basically the API, there is just one thing missing, as you might have noticed.
I am using a congnito authorizer here, of course we must initialize this class first.
user_pool = aws_cognito.UserPool.from_user_pool_id(
self, "auth-user-pool", "eu-west-1_xxxxx"
)
authorizer = aws_apigateway.CognitoUserPoolsAuthorizer(
self, id="api-authorizer", cognito_user_pools=[user_pool]
)
This is all we need to do to create the API by cdk code.
There is one problem here, if we have a bigger API with a lots of methods and resources,
the cdk code might grow and become less clear.
SpecRestApi CDK Class
To avoid this you can use the SpecRestApi Class and define your API resources and methods
with Swagger or Open API.
api = aws_apigateway.SpecRestApi(
self,
"api-swagger",
api_definition=aws_apigateway.ApiDefinition.from_asset(
os.path.abspath(
os.path.join(
os.path.dirname(__file__), "swagger/api-oas30-apigateway.yaml"
)
)
),
deploy_options = deploy_options
)
With this approach the cdk code becomes much shorter, the definition of the resources and
methods is no longer
required in the python cdk class.
It will be defined with the Swagger / OpenAPI json or yaml file.
Lets have a look on a OpenAPI 3.0 definition yaml.
openapi: "3.0.1"
info:
title: "api-swagger"
version: "2022-09-23T14:26:52Z"
servers:
- url: "https://juxxxxxxc65.execute-api.${AWS::Region}.amazonaws.com/{basePath}"
variables:
basePath:
default: "/prod"
paths:
/api/v1/items:
get:
security:
- AuthCustomAuthorizer: []
x-amazon-apigateway-integration:
type: "aws_proxy"
httpMethod: "POST"
uri:
"arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/arn:aws:lambda:${AWS::Region}:${AWS::Account}:function:item-handler/invocations"
passthroughBehavior: "when_no_match"
contentHandling: "CONVERT_TO_TEXT"
options:
responses:
"204":
description: "204 response"
content: {}
x-amazon-apigateway-integration:
type: "aws_proxy"
responses:
default:
statusCode: "204"
responseParameters:
method.response.header.Access-Control-Allow-Methods:
"'OPTIONS,GET,PUT,POST,DELETE,PATCH,HEAD'"
method.response.header.Access-Control-Allow-Headers:
"'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,X-Amz-User-Agent'"
method.response.header.Access-Control-Allow-Origin:
"'*'"
requestTemplates:
application/json: "{ statusCode: 200 }"
passthroughBehavior: "when_no_match"
There is aws specific code in this yaml (if you need lambda proxy integration), for example
x-amazon-apigateway-integration.
You can use also variables like ${AWS::Region} and ${AWS::Account}.
Also there is a authorizer used, this time a custom authorizer, not a cognito
authorizer.
It must be defined also in the yaml file:
components:
securitySchemes:
AuthCustomAuthorizer:
type: "apiKey"
name: "Authorization"
in: "header"
x-amazon-apigateway-authtype: "custom"
x-amazon-apigateway-authorizer:
type: "token"
authorizerUri:
"arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/arn:aws:lambda:${AWS::Region}:${AWS::Account}:my-function:-access-control-CustomAuthorizor/invocations"
authorizerResultTtlInSeconds: 300
Of yourse we must first create this Custom Authorizer in the CDK Code before we can use it
in
the API definition (please replace the region by a variable):
custom_authorizer_arn =
"arn:aws:lambda:eu-west-1:111111111111:function:my-api-access-control-CustomAuthorizor"
authorizer_uri="arn:aws:apigateway:{}:lambda:path/2015-03-31/functions/{}/invocations".format(
"eu-west-1", custom_authorizer_arn)
custom_authorizer_cfn = aws_apigateway.CfnAuthorizer(self, 'AuthCustomAuthorizer',
rest_api_id=api.rest_api_id,
name='AuthCustomAuthorizer',
type='TOKEN',
authorizer_uri=authorizer_uri,
identity_source='method.request.header.Authorization',
authorizer_result_ttl_in_seconds=300
)
This way you can create Rest APIs either with Swagger/OpenAPi or directly by cdk code and you can use
a Cognito Authorizer or also your own Custom Authorizer. Have fun with it !