6
votes

I have the following Cloudformation template I am trying to deploy via SAM. This template correctly creates the DynamoDB table, an API Key, a Lambda function and the API Gateway, but I cannot figure out what I need to specify in the template to associate the API KEY with the API Gateway.

I have found plenty of snippets showing partial examples, but I am struggling to piece it all together.

Thank you in advance,

Denny

AWSTemplateFormatVersion: 2010-09-09
Transform: AWS::Serverless-2016-10-31
Parameters:
  TableName:
    Type: String
    Default: 'influencetabletest'
    Description: (Required) The name of the new DynamoDB table Minimum 3   characters
    MinLength: 3
    MaxLength: 50
    AllowedPattern: ^[A-Za-z-]+$
    ConstraintDescription: 'Required parameter. Must be characters only. No numbers allowed.'
  CorsOrigin:
    Type: String
    Default: '*'
    Description: (Optional) Cross-origin resource sharing (CORS) Origin. You can specify a single origin, all "*" or leave empty and no CORS will be applied.
    MaxLength: 250
Conditions:
  IsCorsDefined: !Not [!Equals [!Ref CorsOrigin, '']]
Resources:
  ApiKey:
    Type: AWS::ApiGateway::ApiKey
    DependsOn:
      - ApiGetter
    Properties:
      Name: "TestApiKey"
      Description: "CloudFormation API Key V1"
      Enabled: "true"
  ApiGetter:
    Type: AWS::Serverless::Api
    Properties:
      StageName: prd
      DefinitionBody:
        swagger: 2.0
        info:
          title:
            Ref: AWS::StackName
        paths:
          /getdynamicprice:
            post:
              responses: {}
              x-amazon-apigateway-integration:
                httpMethod: POST
                type: aws_proxy
                uri:
                  Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${LambdaGetter.Arn}/invocations
  LambdaGetter:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: ./index.js
      Handler: index.handler
      Runtime: nodejs8.10
      Environment:
        Variables:
          TABLE_NAME: !Ref TableName
          IS_CORS: IsCorsDefined
          CORS_ORIGIN: !Ref CorsOrigin
          PRIMARY_KEY: !Sub ${TableName}Id
      Policies:
        - DynamoDBCrudPolicy:
            TableName: !Ref TableName
      Events:
        Api:
          Type: Api
          Properties:
            Path: /getdynamicprice
            Method: POST
            RestApiId: !Ref ApiGetter
  DynamoDBTable:
    Type: AWS::DynamoDB::Table
    Properties:
      TableName: !Ref TableName
      AttributeDefinitions:
        -
          AttributeName: !Sub "${TableName}Id"
          AttributeType: "S"
      KeySchema:
        -
          AttributeName: !Sub "${TableName}Id"
          KeyType: "HASH"
      ProvisionedThroughput:
        ReadCapacityUnits: 1
        WriteCapacityUnits: 1
      StreamSpecification:
        StreamViewType: NEW_AND_OLD_IMAGES
Outputs:
  ApiKeyID:
    Value: !Ref ApiKey
  ApiUrl:
    Value: !Sub https://${ApiGetter}.execute-api.${AWS::Region}.amazonaws.com/prod/getdynamicprice
    Description: The URL of the API Gateway you invoke to get your dynamic pricing result.
  DynamoDBTableArn:
    Value: !GetAtt DynamoDBTable.Arn
    Description: The ARN of your DynamoDB Table
  DynamoDBTableStreamArn:
    Value: !GetAtt DynamoDBTable.StreamArn
    Description: The ARN of your DynamoDB Table Stream
2
Hi @Denny , how do you enabled APIkeyRequried property in SAM ? Because after attaching apikey to a apiplan you need to enable this property for sending the header string in the request right ?Private

2 Answers

9
votes

Edit (04/22/2020): there now seems to do all this using AWS SAM. Please see answer below

Here's a sample template where I have connected my API to a API key. But that's only been possible because I am using usage plans. I believe that is the primary purpose of an API key. API gateway usage plan

ApiKey: 
  Type: AWS::ApiGateway::ApiKey
  Properties: 
    Name: !Join ["", [{"Ref": "AWS::StackName"}, "-apikey"]]
    Description: "CloudFormation API Key V1"
    Enabled: true
    GenerateDistinctId: false
ApiUsagePlan:
  Type: "AWS::ApiGateway::UsagePlan"
  Properties:
    ApiStages: 
    - ApiId: !Ref <API resource name>
      Stage: !Ref <stage resource name>     
    Description: !Join [" ", [{"Ref": "AWS::StackName"}, "usage plan"]]
    Quota:
      Limit: 2000
      Period: MONTH
    Throttle:
      BurstLimit: 10
      RateLimit: 10
    UsagePlanName: !Join ["", [{"Ref": "AWS::StackName"}, "-usage-plan"]]
ApiUsagePlanKey:
  Type: "AWS::ApiGateway::UsagePlanKey"
  Properties:
    KeyId: !Ref <API key>
    KeyType: API_KEY
    UsagePlanId: !Ref ApiUsagePlan

There does not seem to be a way to do this without a usage plan.

5
votes

I did try the suggestion from ASR but ended up with a simpler approach. The AWS SAM (Serverless Application Model) contains prepackaged handling that doesn't necessitate the use of resources of the ApiGateway type.

To create an API Gateway with a stage that requires an authorization token in the header the following simplified code should do it for you :

Resources:
  ApiGatewayEndpoint:
    Type: AWS::Serverless::Api
    Properties:
      StageName: Prod
      Auth:
        ApiKeyRequired: true
        UsagePlan:
          CreateUsagePlan: PER_API
          UsagePlanName: GatewayAuthorization [any name you see fit]
  LambdaFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: lambda.handler
      Runtime: python3.7
      Timeout: 30
      CodeUri: .
      Events:
        PostEvent:
          Type: Api
          Properties:
            Path: /content
            Method: POST
            RequestParameters:
              - method.request.header.Authorization:
                  Required: true
                  Caching: true
            RestApiId:
              Ref: ApiGatewayEndpoint [The logical name of your gateway endpoint above]

The elements:

Auth:
   ApiKeyRequired: true
   UsagePlan:
     CreateUsagePlan: PER_API

is what does the trick. Cloudformation handles the plumbing for you, ie. the Api Key, UsagePlan and UsagePlanKey is automatically created and binded.

Although the docs are definitely not best in class they do provide some additional information: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-specification-resources-and-properties.html