Getting an error trying to create an AWS API Gateway via Cloudformation

19,853

The URI you should use to connect to the Lambda is not the Arn of the Lambda, but an API gateway invocation URI. Additionally, you need to change the credential line from a ref to the Arn of the execution role.

Here a short excerpt of the changed section:

x-amazon-apigateway-integration:
  type: aws_proxy
  httpMethod: POST
  uri: !Sub  "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${LambdaFunctionAPI.Arn}/invocations"
  credentials: !GetAtt APIGatewayExecutionRole.Arn
Share:
19,853
Justin808
Author by

Justin808

Just some guy on the interwebs.

Updated on June 13, 2022

Comments

  • Justin808
    Justin808 almost 2 years

    I'm trying to make a simple Cloudformation to create a website hosted on S3 with an API Gateway backend. Everything seems OK as far as I can tell but I get errors when trying to create the API Gateway:

    Errors found during import: Unable to put integration on 'ANY' for resource at path '/{proxy+}': AWS ARN for integration must contain path or action (Service: AmazonApiGateway; Status Code: 400; Error Code: BadRequestException; Request ID: b28983d9-687c-11e8-8692-27df1db97456)

    The gateway should just be a single route that sends everything to a single lambda. Should be super simple.

    ---
    AWSTemplateFormatVersion: '2010-09-09'
    Description: Website S3 Hosted, API Gateway Backend
    Parameters:
      DomainName:
        Type: String
        Description: The DNS name of an Amazon Route 53 hosted zone e.g. server.com
        AllowedPattern: '(?!-)[a-zA-Z0-9-.]{1,63}(?<!-)'
        ConstraintDescription: must be a valid DNS zone name.
    Mappings:
      S3RegionMap:
        us-east-1:
          S3HostedZoneId: Z3AQBSTGFYJSTF
          S3WebsiteEndpoint: s3-website-us-east-1.amazonaws.com
        us-west-1:
          S3HostedZoneId: Z2F56UZL2M1ACD
          S3WebsiteEndpoint: s3-website-us-west-1.amazonaws.com
        us-west-2:
          S3HostedZoneId: Z3BJ6K6RIION7M
          S3WebsiteEndpoint: s3-website-us-west-2.amazonaws.com
        eu-west-1:
          S3HostedZoneId: Z1BKCTXD74EZPE
          S3WebsiteEndpoint: s3-website-eu-west-1.amazonaws.com
        ap-southeast-1:
          S3HostedZoneId: Z3O0J2DXBE1FTB
          S3WebsiteEndpoint: s3-website-ap-southeast-1.amazonaws.com
        ap-southeast-2:
          S3HostedZoneId: Z1WCIGYICN2BYD
          S3WebsiteEndpoint: s3-website-ap-southeast-2.amazonaws.com
        ap-northeast-1:
          S3HostedZoneId: Z2M4EHUR26P7ZW
          S3WebsiteEndpoint: s3-website-ap-northeast-1.amazonaws.com
        sa-east-1:
          S3HostedZoneId: Z31GFT0UA1I2HV
          S3WebsiteEndpoint: s3-website-sa-east-1.amazonaws.com
    Resources:
      LambdaExecutionRole:
        Type: AWS::IAM::Role
        Properties:
          AssumeRolePolicyDocument:
            Statement:
            - Effect: Allow
              Principal:
                Service: lambda.amazonaws.com
              Action:
              - sts:AssumeRole
          Path: '/'
          Policies:
          - PolicyName: execution
            PolicyDocument:
              Statement:
              - Effect: Allow
                Action:
                - logs:CreateLogGroup
                - logs:CreateLogStream
                - logs:PutLogEvents
                Resource: '*'
              - Effect: Allow
                Action:
                - s3:GetObject
                - s3:PutObject
                - s3:ListBucket
                Resource: '*'
              - Effect: Allow
                Action:
                - ec2:DescribeNetworkInterfaces
                - ec2:CreateNetworkInterface
                - ec2:DeleteNetworkInterface
                Resource: '*'
              - Effect: Allow
                Action:
                - cognito-idp:AdminGetUser
                - cognito-idp:AdminUpdateUserAttributes
                Resource: '*'
      APIGatewayExecutionRole:
        Type: AWS::IAM::Role
        Properties:
          AssumeRolePolicyDocument:
            Statement:
            - Effect: Allow
              Principal:
                Service: apigateway.amazonaws.com
              Action:
              - sts:AssumeRole
          Path: '/'
          Policies:
          - PolicyName: execution
            PolicyDocument:
              Statement:
              - Effect: Allow
                Action:
                - logs:CreateLogGroup
                - logs:CreateLogStream
                - logs:PutLogEvents
                Resource: '*'
              - Effect: Allow
                Action:
                - lambda:InvokeFunction
                Resource: '*'
      LambdaFunctionAPI:
        Type: AWS::Lambda::Function
        Properties:
          Code:
            ZipFile: exports.handler = function (event, context, callback) { callback(null, event); };
          Handler: index.handler
          MemorySize: 128
          Role: !GetAtt LambdaExecutionRole.Arn
          Runtime: nodejs4.3
          Timeout: 30
      APIGateway:
        Type: AWS::ApiGateway::RestApi
        Properties:              
          FailOnWarnings: true
          Name: !Join ['-', !Split ['.', !Join ['.', ['api', !Ref DomainName]]]]
          Body:
            swagger: '2.0'
            info:
              version: 0.0.1
              title: !Join [' ', ['API route for', !Ref DomainName]]
            basePath: '/api'
            paths:
              '/{proxy+}':
                options:
                  summary: CORS support
                  description: |
                    Enable CORS by returning correct headers
                  consumes:
                    - application/json
                  produces:
                    - application/json
                  tags:
                    - CORS
                  x-amazon-apigateway-integration:
                    type: mock
                    requestTemplates:
                      application/json: |
                        {
                          "statusCode" : 200
                        }
                    responses:
                      "default":
                        statusCode: "200"
                        responseParameters:
                          method.response.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key'"
                          method.response.header.Access-Control-Allow-Methods: "'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'"
                          method.response.header.Access-Control-Allow-Origin: "'*'"
                        responseTemplates:
                          application/json: |
                            {}
                  responses:
                    '200':
                      description: Default response for CORS method
                      headers:
                        Access-Control-Allow-Headers:
                          type: "string"
                        Access-Control-Allow-Methods:
                          type: "string"
                        Access-Control-Allow-Origin:
                          type: "string"
                x-amazon-apigateway-any-method:
                  produces:
                  - "application/json"
                  responses:
                    '200':
                      description: "200 response"
                      schema:
                        $ref: "#/definitions/Empty"
                  x-swagger-router-controller: main
                  x-amazon-apigateway-integration:
                    type: aws_proxy
                    httpMethod: POST
                    uri: !GetAtt LambdaFunctionAPI.Arn
                    credentials: !Ref APIGatewayExecutionRole
    
            definitions:
              Empty:
                type: "object"
                title: "Empty Schema"
      APIDeployment:
        Type: AWS::ApiGateway::Deployment
        Properties:
          RestApiId: !Ref APIGateway
          Description: Deploy for live
          StageName: Live
      WebsiteBucket:
        Type: AWS::S3::Bucket
        Properties:
          BucketName:
            Ref: DomainName
          AccessControl: PublicRead
          WebsiteConfiguration:
            IndexDocument: index.html
            ErrorDocument: 404.html
          Tags:
          - Key: Name
            Value: !Join ['_', ['WebsiteBucket', !Ref 'AWS::StackName']]
          - Key: Domain
            Value: !Ref DomainName
        DeletionPolicy: Retain
      WWWBucket:
        Type: AWS::S3::Bucket
        Properties:
          BucketName: !Join ['.', ['www', !Ref DomainName]]
          AccessControl: PublicRead
          WebsiteConfiguration:
            RedirectAllRequestsTo:
              HostName: !Ref WebsiteBucket
          Tags:
          - Key: Name
            Value: !Join ['_', ['WWWBucket', !Ref 'AWS::StackName']]
          - Key: Domain
            Value: !Ref DomainName
      WebsiteBucketPolicy:
        Type: AWS::S3::BucketPolicy
        Properties:
          Bucket: !Ref WebsiteBucket
          PolicyDocument:
            Statement:
            - Action:
              - s3:GetObject
              Effect: Allow
              Resource: !Join ['', ['arn:aws:s3:::', !Ref WebsiteBucket, '/*']]
              Principal: '*'
      WWWBucketPolicy:
        Type: AWS::S3::BucketPolicy
        Properties:
          Bucket: !Ref WWWBucket
          PolicyDocument:
            Statement:
            - Action:
              - s3:GetObject
              Effect: Allow
              Resource: !Join ['', ['arn:aws:s3:::', !Ref WWWBucket, '/*']]
              Principal: '*'
      DNS:
        Type: AWS::Route53::HostedZone
        Properties:
          HostedZoneConfig:
            Comment: !Join [' ', ['Hosted zone for', !Ref DomainName]]
          Name: !Ref DomainName
          HostedZoneTags:
          - Key: Application
            Value: Blog
      DNSRecord:
        Type: AWS::Route53::RecordSetGroup
        DependsOn: DNS
        Properties:
          HostedZoneName:
            Fn::Join: ['', [!Ref DomainName, '.']]
          Comment: Zone records.
          RecordSets:
          - Name: !Ref DomainName
            Type: A
            AliasTarget:
              HostedZoneId: !FindInMap [S3RegionMap, !Ref 'AWS::Region', S3HostedZoneId]
              DNSName: !FindInMap [S3RegionMap, !Ref 'AWS::Region', S3WebsiteEndpoint]
          - Name: !Join ['.', ['www', !Ref DomainName]]
            Type: A
            AliasTarget:
              HostedZoneId: !FindInMap [S3RegionMap, !Ref 'AWS::Region', S3HostedZoneId]
              DNSName: !FindInMap [S3RegionMap, !Ref 'AWS::Region', S3WebsiteEndpoint]
    Outputs:
      S3WebsiteURL:
        Value: !GetAtt WebsiteBucket.WebsiteURL
        Description: URL for website hosted on S3