入社2週間でAWS SAMと格闘してハマったところ

サーバーレス開発部@大阪の岩田です。早いものでクラスメソッドに入社して2週間が経過しました。

先週から実際にプロジェクトに入って、SAMテンプレートをガリガリ書いているのですが、何かとハマることが多く、トライ&エラーを繰り返していました。

概ねテンプレートが作成できたので、備忘を兼ねてハマった箇所をまとめてみます。

概要

API GatewayからLambdaを起動し、DynamoDBにアクセスするという、至ってシンプルなREST APIを作成する案件です。

APIの仕様書をSwaggerで管理したいという要件があったため、APIの詳細についてはSAMテンプレートからSwaggerの定義ファイルを読み込む様な作りで作成しています。

SAMテンプレートでSwaggerを利用すると、AWS::Serverless::FunctionのEventだけでは定義できない様な詳細まで定義できるのですが、その分記述が複雑になり色々とハマってしまいました。

Invalid permissions on Lambda function

デプロイ完了後に、マネジメントコンソールからテストを行ったところ、 Invalid permissions on Lambda function というエラーが発生し、テストに失敗しました。

Tue May 15 09:03:02 UTC 2018 : Sending request to https://lambda.ap-northeast-1.amazonaws.com/2015-03-31/functions/arn:aws:lambda:ap-northeast-1:xxxxxx:function:xxxxxx/invocations
Tue May 15 09:03:02 UTC 2018 : Execution failed due to configuration error: Invalid permissions on Lambda function Tue May 15 09:03:02 UTC 2018 : Method completed with status: 500

こちらに記載されているように、API GatewayにLambda の InvokeFunctionのアクセス許可が必要になります。 SAMテンプレートの中で、

  SomeFunction:
    Type: AWS::Serverless::Function
    Properties:
      #略
      Events:
        Get:
          Type: Api
          Properties:
            RestApiId: !Ref SomeApi
            Path: /hogehoge
            Method: GET

の様にEventsの中にType: Apiを記述していると、SAMがよしなにやってくれるのですが、Eventsを定義する際にPathやMethodを記述するのはSwaggerの定義と重複して冗長だな〜と思っていたので、Eventsを定義していませんでした。 こちらの本で紹介されているのと同じ書き方になります。↓

【書評】「サーバーレスアプリケーション 開発ガイド」は初心者から経験者まで AWS でアプリを作るお供にしたい

GitHubに上がっているSAMのソースコードを確認したところ、下記の様になっていました。イベントソースにAPIが指定されている場合は勝手にパーミッション作成までやってくれる様です。

    
    class Api(PushEventSource):
    """Api method event source for SAM Functions."""
    resource_type = 'Api'
    principal = 'apigateway.amazonaws.com'
    #略...
    def to_cloudformation(self, **kwargs):
        """If the Api event source has a RestApi property, then simply return the Lambda Permission resource allowing
        API Gateway to call the function. If no RestApi is provided, then additionally inject the path, method, and the
        x-amazon-apigateway-integration into the Swagger body for a provided implicit API.
        :param dict kwargs: a dict containing the implicit RestApi to be modified, should no explicit RestApi \
                be provided.
        :returns: a list of vanilla CloudFormation Resources, to which this Api event expands
        :rtype: list
        """
        resources = []
    #略...

SAMテンプレートに下記の様な記述を追加し、API GatewayにLambda の InvokeFunctionを許可することで解決しています。

 
  LambdaPermissionxxxx:
    Type: "AWS::Lambda::Permission"
    Properties:
      Action: lambda:InvokeFunction
      FunctionName: !Ref SomeLambdaFunction
      Principal: apigateway.amazonaws.com

Execution failed due to configuration error: Malformed Lambda proxy response

上記の対応でPOSTのAPIについてはテストが成功する様になったのですが、今度はGETのAPIで Execution failed due to configuration error: Malformed Lambda proxy respons というエラーが発生し、テストに失敗します。

こちらのドキュメントに記載があるのですが、 Swagger のAPI Gateway 拡張を利用してAPI GatewayからLambdaの呼び出しを設定する場合、API Gateway integrationオブジェクトのhttpMethodにはPOSTを設定する必要があります。

SAMテンプレートを下記の様に修正

x-amazon-apigateway-integrationのhttpMethodをGETからPOSTに変更することでエラーが改善しました。

 
      x-amazon-apigateway-integration:
          uri:
            Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${SomeLambdaFunction.Arn}/invocations
          responses:
            default:
              statusCode: "200"
          passthroughBehavior: "when_no_match"
          #httpMethod: "GET"
          httpMethod: "POST"
          contentHandling: "CONVERT_TO_TEXT"
          type: "aws_proxy"

API Gatewayのオーソライザーが設定されない

Cognitoで認証済みのユーザーのみAPIを利用させたい箇所があり、API GatewayのオーソライザーにCognitoユーザープールを指定しようとしていました。 Swaggerの定義は下記の様な内容です。

  /xxxx/{id}:
    get:
      summary: "hogehoge"
      description: "hogehoge"
      security:
      - Cognito_Authorizer: []
#略
securityDefinitions:
  Cognito_Authorizer:
    type: "apiKey"
    name: "Authorization"
    in: "header"
    x-amazon-apigateway-authtype: cognito_user_pools
    x-amazon-apigateway-authorizer:
      providerARNs:
        - "arn:aws:cognito-idp:ap-northeast-1:xxxxxxxxxxx:userpool/ap-northeast-1_xxxxxxxx"
      type: cognito_user_pools
      

が、デプロイしてみると一向にオーソライザーが反映されません。 オチとしては type: "apiKey"と書くべきところを、 type: "apikey"と"k"を小文字で書いていたことが原因でした。 ただ、SAMでデプロイした際に、特にエラー等出なかったので、中々気付くことができませんでした。

この事象ですが、マネジメントコンソールから「Swaggerからインポート」を試すことでの原因を切り分け分けることができました。 SAMのデプロイではエラーが出なかったのですが、マネジメントコンソールからだと、下記の様なエラーメッセージが表示されたため、Swaggerの定義を微調整しながら原因を切り分けることができました。

まとめ

いかがだったでしょうか。 経験不足もあって、かなり苦戦しました。 同じ様な境遇のSAM初心者の方の参考になれば幸いです。