Justin808
Justin808

Reputation: 21522

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

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

Upvotes: 19

Views: 43459

Answers (2)

Adwait Mathkari
Adwait Mathkari

Reputation: 142

For those who are using terraform and getting this error, the following works (note that the uri parameter should not be the lambda arn, but the invoke arn which is different):

resource "aws_api_gateway_integration" "ordersIntegration" {
    rest_api_id             = aws_api_gateway_rest_api.msdApi.id
    resource_id             = aws_api_gateway_resource.orders.id
    http_method             = aws_api_gateway_method.ordersget.http_method
    integration_http_method = "POST"
    type                    = "AWS_PROXY"
    uri                     = "arn:aws:apigateway:${var.AWS_REGION}:lambda:path/2015-03-31/functions/${var.LAMBDA_FN_ARN}/invocations"
}

Where the LAMBDA_FN_ARN is simply "arn:aws:lambda:${var.AWS_REGION}:${var.AWS_ACCOUNT_ID}:function:${var.LAMBDA_NAME}" Also, 2015-03-31 is a constant value that needs to be used.

Upvotes: 2

jens walter
jens walter

Reputation: 14049

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

Upvotes: 33

Related Questions