Set up AWS API GW with a Typescript authorizer and logging

2022-04-19



Introduction

A CloudFormation template to set up AWS API GW with a Typescript authorizer and logging

  • The trick to use SAM deploy for Typescript:
    Metadata:
      BuildMethod: esbuild
      BuildProperties:
        Minify: true
        Target: "es2020"
        Sourcemap: true
        EntryPoints:
          - TargetServiceSUTAuthorizer.ts

And to invoke SAM with sam build --beta-features && sam deploy

  • The trick to enable Cloudwatch logs on the API GW:

  ApiGwAccountConfig:
    Type: "AWS::ApiGateway::Account"
    Properties:
      CloudWatchRoleArn: !GetAtt "ApiGatewayLoggingRole.Arn"

  ApiGatewayLoggingRole:
    Type: "AWS::IAM::Role"
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - "apigateway.amazonaws.com"
            Action: "sts:AssumeRole"
      Path: "/"
      ManagedPolicyArns:
        - !Sub "arn:${AWS::Partition}:iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs"

Here’s the full CloudFormation file:


AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  This stack creates the target-service-sut resources.
  They are: An API GW with authorizer, and a microservice lambda that is called by the API GW

Parameters:
  myVPC:
    Type: String
  Subnet1:
    Type: String
  Subnet2:
    Type: String
  Environment:
    Type: String
  BuildNumber:
    Type: String
  Branch:
    Type: String
  AudienceList:
    Type: String

Globals:
  Function:
    Runtime: nodejs14.x
    Tracing: Active
    MemorySize: 1024
    ReservedConcurrentExecutions: 1
    Timeout: 2
    VpcConfig:
      SecurityGroupIds:
        - !Ref TargetServiceSUTSecurityGroup
      SubnetIds:
        - !Ref Subnet1
        - !Ref Subnet2
    Environment:
      Variables:
        ENVIRONMENT: !Ref Environment
        BUILD_NUMBER: !Ref BuildNumber
        BRANCH: !Ref Branch
        AUDIENCE_LIST: !Ref AudienceList
        REGION: ${AWS::Region}

Resources:

  ApiGwAccountConfig:
    Type: "AWS::ApiGateway::Account"
    Properties:
      CloudWatchRoleArn: !GetAtt "ApiGatewayLoggingRole.Arn"

  ApiGatewayLoggingRole:
    Type: "AWS::IAM::Role"
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - "apigateway.amazonaws.com"
            Action: "sts:AssumeRole"
      Path: "/"
      ManagedPolicyArns:
        - !Sub "arn:${AWS::Partition}:iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs"

  TargetServiceSUTFunctionApi:
    Type: AWS::Serverless::Api
    Description: API GW fronting the TargetServiceSUT function
    Properties:
      StageName: Prod
      Name: target-service-sut
      Description: API GW fronting the TargetServiceSUT function
      TracingEnabled: true
      EndpointConfiguration:
        Type: PRIVATE
      Auth:
        DefaultAuthorizer: LambdaRequestAuthorizer
        ApiKeyRequired: false
        Authorizers:
          LambdaRequestAuthorizer:
            FunctionArn: !GetAtt TargetServiceSUTAuthorizerFunction.Arn
            FunctionPayloadType: REQUEST
            Identity:
              Headers:
                - Authorization
              ValidationExpression: Bearer.*
              ReauthorizeEvery: 3600

        ResourcePolicy:
          CustomStatements:
            [
              {
                "Effect": "Allow",
                "Principal": "*",
                "Action": "execute-api:Invoke",
                "Resource": "execute-api:/Prod/GET/*"
              }
            ]
        UsagePlan:
          CreateUsagePlan: PER_API

      MethodSettings:
        - LoggingLevel: INFO
          ResourcePath: '/*'
          HttpMethod: '*'
          MetricsEnabled: 'true'
          DataTraceEnabled: 'true'

  TargetServiceSUTFunction:
    Type: AWS::Serverless::Function
    Description: Target Service SUT
    Properties:
      FunctionName: target-service-sut
      Tracing: Active
      CodeUri: target-service/src
      Handler: app.lambdaHandler
      Role: !GetAtt TargetServiceSUTFunctionExecutionRole.Arn
      Events:
        Healthcheck:
          Type: Api
          Properties:
            RestApiId: !Ref TargetServiceSUTFunctionApi
            Path: /sut
            Method: get

  TargetServiceSUTAuthorizerFunction:
    Type: AWS::Serverless::Function
    Description: API GW Authorizer function
    Properties:
      FunctionName: target-service-sut-authorizer
      Tracing: Active
      CodeUri: authorizer/src
      Handler: TargetServiceSUTAuthorizer.authorize
      Role: !GetAtt TargetServiceSUTAuthorizerFunctionExecutionRole.Arn
    Metadata:
      BuildMethod: esbuild
      BuildProperties:
        Minify: true
        Target: "es2020"
        Sourcemap: true
        EntryPoints:
          - TargetServiceSUTAuthorizer.ts

  TargetServiceSUTFunctionExecutionRole:
    Type: AWS::IAM::Role
    Description: The execution role of the function
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Principal:
              Service:
                - "lambda.amazonaws.com"
            Action:
              - "sts:AssumeRole"
      RoleName: TargetServiceSUTFunctionExecutionRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/CloudWatchLogsFullAccess
        - arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess
        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
        - arn:aws:iam::aws:policy/service-role/AWSLambdaRole
        - !Ref TargetServiceSUTFunctionEC2NetworkPolicy
        - !Ref TargetServiceSUTFunctionSSMPolicy

  TargetServiceSUTFunctionSSMPolicy:
    Type: AWS::IAM::ManagedPolicy
    Description: Policy allowing the lambda to access SSM
    Properties:
      ManagedPolicyName: !Sub "TargetServiceSUTFunctionAllowSSMOperations"
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Sid: TargetServiceSUTFunctionSSMPolicy
            Effect: Allow
            Action:
              - ssm:GetParameter
            Resource: "*"

  TargetServiceSUTFunctionEC2NetworkPolicy:
    Type: AWS::IAM::ManagedPolicy
    Description: Policy allowing the lambda to be attached to our VPC
    Properties:
      ManagedPolicyName: !Sub "TargetServiceSUTFunctionAllowEC2NetworkOperations"
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Sid: TargetServiceSUTFunctionAllowEC2NetworkPolicy
            Effect: Allow
            Action:
              - ec2:CreateNetworkInterface
              - ec2:DeleteNetworkInterfacePermission
              - ec2:DescribeNetworkInterfaces
              - ec2:CreateNetworkInterfacePermission
              - ec2:ResetNetworkInterfaceAttribute
              - ec2:ModifyNetworkInterfaceAttribute
              - ec2:DeleteNetworkInterface
              - ec2:AttachNetworkInterface
            Resource: "*"

  TargetServiceSUTSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: "TargetServiceSUT SG"
      GroupName: TargetServiceSUTSecurityGroup
      VpcId: !Ref myVPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 443
          ToPort: 443
          CidrIp: 0.0.0.0/0
      SecurityGroupEgress:
        - IpProtocol: -1
          FromPort: -1
          CidrIp: 0.0.0.0/0

  TargetServiceSUTAuthorizerFunctionExecutionRole:
    Type: AWS::IAM::Role
    Description: The execution role of the function
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Principal:
              Service:
                - "lambda.amazonaws.com"
                - "apigateway.amazonaws.com"
            Action:
              - "sts:AssumeRole"
      RoleName: TargetServiceSUTAuthorizerFunctionExecutionRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/CloudWatchLogsFullAccess
        - arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess
        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
        - arn:aws:iam::aws:policy/service-role/AWSLambdaRole
        - !Ref TargetServiceSUTFunctionEC2NetworkPolicy

Outputs:
  TargetServiceSUTFunctionApi:
    Description: "API Gateway endpoint URL for Prod stage for a function"
    Value: !Sub "https://${TargetServiceSUTFunctionApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/target-service-sut"

Happy coding!



Other Tags

API GW
AWS
ActiveRecord
Agile
Alexa
Analysis
Ansible
BDD
BLE
C
CAB
CloudFormation
CloudFront
CloudWatch
Cross-compile
Cucumber
DevOps
Devops
DotNet
Embedded
Fitbit
GNU
GitHub Actions
Governance
How-to
Inception
IoT
Javascript
Jest
Lambda
Mac OS X
MacRuby
Metrics
MySQL
NetBeans
Objective-C
PMO
Product Management
Programme management
Project Management
Quality Assurance
Rails
Raspberry Pi
Remote compilation
Remote debugging
Remote execution
Risk Assessment
Route 53
Ruby
S3
SPA
Self Organising Teams
SpecFlow
TDD
Unit testing
VSM
Value
arm
contract testing
inception
nrf51
pact
planning
rSpec
ruby
ssh