amazon-web-servicesaws-lambdaaws-api-gatewayamazon-iam

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


I am trying to set up a private API with a single lambda function using a sam template, but whatever I do the API gateway logs API_CONFIGURATION_ERROR. I have no idea what I do wrong. The docs isn't very specific about it. According to this example I think my template looks alright.

My code uses axios and aws4, I have also tried fetch instead of axios. I'm also getting the same error when I use Postman so I don't really think it is something wrong with my request. Everything works perfectly fine if I just disable the IAM authorization.

This is my template for the api and the lambda function I try to invoke.

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" 

If I stop using the defaultAuthorizer by uncommenting the last two auth-lines (i.e. using Authorizer: "NONE") in the lambda function everything works fine. So it only fails when I leave the defaultAuthorizer in charge.

The error I get in the API log is:

{
    "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"
}

In Postman, I use AWS signature authorization and the access key and secret key from an IAM user that has AmazonAPIGatewayInvokeFullAccess policy. The regions should be correct as well.

enter image description here

My axios code I use in another lambda function (which also has AmazonAPIGatewayInvokeFullAccess policy) currently looks like this.

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

No idea left what could be wrong.


Solution

  • Your API Gateway needs to have a role assigned to it which allows it to invoke the lambda function (authorizer)

    Try using the InvokeRole property in the API Auth section (https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-property-api-apiauth.html).

    --- Updated ----

    The invoke role arn must point to a role arn and not to a user see example below

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