尝试通过 Cloudformation 创建 AWS API 网关时出错

Posted

技术标签:

【中文标题】尝试通过 Cloudformation 创建 AWS API 网关时出错【英文标题】:Getting an error trying to create an AWS API Gateway via Cloudformation 【发布时间】:2018-11-14 11:42:09 【问题描述】:

我正在尝试制作一个简单的 Cloudformation 来创建一个托管在 S3 上并带有 API 网关后端的网站。据我所知,一切似乎都很好,但是在尝试创建 API 网关时出现错误:

导入期间发现错误:无法将路径“/proxy+”的资源的集成放在“ANY”上:用于集成的 AWS ARN 必须包含路径或操作(服务:AmazonApiGateway;状态代码:400;错误代码:BadRequestException ; 请求 ID:b28983d9-687c-11e8-8692-27df1db97456)

网关应该只是将所有内容发送到单个 lambda 的单个路由。应该超级简单。

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

【问题讨论】:

【参考方案1】:

您应该用来连接到 Lambda 的 URI 不是 Lambda 的 Arn,而是 API 网关调用 URI。此外,您需要将凭据行从ref 更改为执行角色的Arn

这里是更改部分的简短摘录:

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

【讨论】:

从哪里获得 Lambda 的 API 网关调用 URI? 您可以使用 jens walter 提供的模板构建它:“arn:aws:apigateway:$AWS::Region:lambda:path/2015-03-31/functions/$LambdaFunctionAPI. Arn/调用”

以上是关于尝试通过 Cloudformation 创建 AWS API 网关时出错的主要内容,如果未能解决你的问题,请参考以下文章

通过 cloudformation 为 fargate 启动类型任务创建 cloudwatch 事件规则的“目标”

CloudFormation - 为 DynamoDB 创建表启用 TTL

cloudformation 验证返回:无效的模板资源属性

如何在 cloudformation“Fn::Sub”中转义“$”

使用 Cloudformation 创建具有复合主键的 DynamoDB

通过 CloudFormation CLi 跨账户 S3 访问