Skip to content

Instantly share code, notes, and snippets.

@thoroc
Last active April 16, 2024 10:14
Show Gist options
  • Save thoroc/cc0dc26da1d5caef2dc2b8f3d8358ea2 to your computer and use it in GitHub Desktop.
Save thoroc/cc0dc26da1d5caef2dc2b8f3d8358ea2 to your computer and use it in GitHub Desktop.
Api Gateway with CDK (Python)

Api Gateway with CDK

source: https://www.sccbrasil.com/blog/aws/cdk-api.html

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 integration:

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
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment