API_CONFIGURATION_ERROR 为 AWS::Serverless::Api 使用 AWS_IAM 授权方时

API_CONFIGURATION_ERROR when using AWS_IAM authorizer for AWS::Serverless::Api

我正在尝试使用 sam 模板设置具有单个 lambda 函数的私有 API,但无论我做什么,API 网关都会记录 API_CONFIGURATION_ERROR。我不知道我做错了什么。 docs isn't very specific about it. According to this example 我认为我的模板看起来不错。

我的代码使用了 axios 和 aws4,我也尝试过使用 fetch 而不是 axios。我在使用 Postman 时也遇到了同样的错误,所以我真的不认为我的请求有问题。如果我只是禁用 IAM 授权,一切都很好。

这是我的 api 模板和我尝试调用的 lambda 函数。

AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Description: >
  iam-api-example
Parameters:
  SamplePrivateApiStageName:
    Type: String
    Default: endpoint
  SampleTableName:
    Type: String
    Default: SampleTable
Globals:
  Function:
    Runtime: nodejs14.x
    Timeout: 10
    MemorySize: 128
    Handler: app.lambdaHandler
Resources:

  SamplePrivateApi:
    Type: AWS::Serverless::Api
    Properties:
      Name: SamplePrivateApi
      StageName: !Ref SamplePrivateApiStageName
      Auth:
        DefaultAuthorizer: AWS_IAM
      AccessLogSetting:
        DestinationArn: !GetAtt SamplePrivateApiAccessLogs.Arn
        Format: '{ "requestId":"$context.requestId", "ip": "$context.identity.sourceIp", "requestTime":"$context.requestTime", "httpMethod":"$context.httpMethod","routeKey":"$context.routeKey", "path":"$context.path", "status":"$context.status","protocol":"$context.protocol", "responseLength":"$context.responseLength", "errorMessage":"$context.error.message", "responseType":"$context.error.responseType" }'
  SamplePrivateApiAccessLogs:
    Type: AWS::Logs::LogGroup

  GetQuestionFn:
    Type: AWS::Serverless::Function
    Properties:
      Policies:
        - AmazonDynamoDBFullAccess
      CodeUri: build/lambdas/private/advertisement/get-question
      Environment:
        Variables:
          TABLE_NAME: !Ref SampleTableName
      Events:
        GetQuestionFnEvent:
          Type: Api
          Properties:
            Path: /questions
            Method: GET
            RestApiId: !Ref SamplePrivateApi
            #Auth: 
              #Authorizer: "NONE" 

如果我通过在 lambda 函数中取消注释最后两个授权行(即使用 Authorizer:“NONE”)来停止使用 defaultAuthorizer,一切正常。所以只有当我让 defaultAuthorizer 负责时它才会失败。

我在 API 日志中得到的错误是:

{
    "requestId": "12311ec0-732d-4088-a641-71f05e4bd418",
    "ip": "x.xx.xxx.xxx",
    "requestTime": "16/Jul/2021:12:37:01 +0000",
    "httpMethod": "GET",
    "routeKey": "-",
    "path": "/endpoint/questions",
    "status": "500",
    "protocol": "HTTP/1.1",
    "responseLength": "36",
    "errorMessage": "Internal server error",
    "responseType": "API_CONFIGURATION_ERROR"
}

在 Postman 中,我使用 AWS 签名授权以及来自具有 AmazonAPIGatewayInvokeFullAccess 策略的 IAM 用户的访问密钥和密钥。区域也应该是正确的。

我在另一个 lambda 函数(也有 AmazonAPIGatewayInvokeFullAccess 策略)中使用的 axios 代码目前看起来像这样。

import AWS from 'aws-sdk'
const aws4 = require('aws4')
const fetchQuestion = async () => {
  // Host is domain without https://
  const urlParts = QUESTION_API_DOMAIN.split('://')
  const host = urlParts.length > 1 ? urlParts[1] : ''

  if (!host) {
    console.error('No host could be extracted.')
    return null
  }

  const request = {
    host,
    method: 'GET',
    url: `${QUESTION_API_DOMAIN}/endpoint/questions`,
    path: `/endpoint/questions`,
  }

  const secretAccessKey = AWS.config.credentials?.secretAccessKey
  const accessKeyId = AWS.config.credentials?.accessKeyId

  if (!secretAccessKey || !accessKeyId) {
    console.error('No credentials could be found.')
    return null
  }

  let signedRequest = aws4.sign(request, {
    secretAccessKey: secretAccessKey,
    accessKeyId: accessKeyId,
    sessionToken: AWS.config.credentials?.sessionToken,
  })

  // delete signedRequest.headers['Host']
  // delete signedRequest.headers['Content-Length']

  try {
    const response = await axios(signedRequest)
    return response.data
  } catch (err) {
    console.error('Error while retrieving question:', err)
  }
}

不知道哪里出了问题。

您的 API 网关需要分配一个角色,允许它调用 lambda 函数(授权方)

尝试在 API 验证部分 (https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-property-api-apiauth.html) 中使用 InvokeRole 属性。

---更新了----

调用角色 arn 必须指向角色 arn 而不是用户,请参见下面的示例

    Type: AWS::Serverless::Api
    Properties:
      Name: SamplePrivateApi
      StageName: !Ref SamplePrivateApiStageName
      Auth:
        DefaultAuthorizer: AWS_IAM
        InvokeRole: !GetAtt APIGatewayRole.Arn
      AccessLogSetting:
        DestinationArn: !GetAtt SamplePrivateApiAccessLogs.Arn
        Format: '{ "requestId":"$context.requestId", "ip": "$context.identity.sourceIp", "requestTime":"$context.requestTime", "httpMethod":"$context.httpMethod","routeKey":"$context.routeKey", "path":"$context.path", "status":"$context.status","protocol":"$context.protocol", "responseLength":"$context.responseLength", "errorMessage":"$context.error.message", "responseType":"$context.error.responseType" }'
  SamplePrivateApiAccessLogs:
    Type: AWS::Logs::LogGroup


APIGatewayRole:
    Type: "AWS::IAM::Role"
    Properties:
      Path: "/"
      ManagedPolicyArns:
        - "arn:aws:iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs"
      Policies:
        - PolicyName: "authorizerLambdaInvokeAccess"
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: "Allow"
                Action:
                  - lambda:InvokeAsync
                  - lambda:InvokeFunction
                Resource: !Sub ${AuthorizerLambdaFunction.Arn}

      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Sid: "AllowApiGatewayServiceToAssumeRole"
            Effect: "Allow"
            Action:
              - "sts:AssumeRole"
            Principal:
              Service:
                - "apigateway.amazonaws.com" ```