AWS SAMでAPI Gatewayのリソースポリシーを設定してみた

2018.07.26

おつかれさまです。サーバーレス開発部の新井です。

今回は、3ヶ月程前に発表されたAPI GatewayのリソースポリシーAWS SAMから設定する方法を紹介したいと思います。API Gatewayのリソースポリシーでは、 特定のAWSアカウントのユーザーに対するアクセス制限や特定のIPアドレス範囲内でのアクセス制限を設定することことができます。S3のバケットポリシーの設定とほぼ同じと考えれば、イメージがつかみやすいかと思います。

また、前回のブログの一部でカスタムオーソライザーによるIP制限を紹介しましたが、今回紹介するAPI Gatewayのリソースポリシーを利用する方がより簡単に実装することができます。

それでは、早速始めていきたいと思います!

  • 今回参考にさせて頂いたサイト
  1. https://dev.classmethod.jp/cloud/aws/api-gateway-resource-policy/
  2. https://aws.amazon.com/about-aws/whats-new/2018/04/amazon-api-gateway-supports-resource-policies/
  3. https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/apigateway-resource-policies-create-attach.html

構成図

下準備

まずは、SAM CLIから必要なリソースを作成していきます。 使ってるバージョンは以下の通り

$ sam --version
SAM CLI, version 0.4.0

例のごとく以下のコマンドを実行

sam init --runtime python3.6

作成されたフォルダの中身はこんな感じ

$ tree -L 1
.
├── hello_world
├── README.md
├── requirements.txt
├── sam-app
├── template.yaml
└── tests

hello_world/app.pyでrequestsモジュールを使っているので、インストールしてあげる

pip install -r requirements.txt -t hello_world/

何も考えずデプロイ

aws s3 mb s3://

aws cloudformation package --template-file template.yaml --output-template-file output.yaml --s3-bucket

aws cloudformation deploy --template-file output.yaml --stack-name --capabilities CAPABILITY_IAM

※ initで作成したtemplate.yamlがそのままだとpathの設定がおかしいと怒られたので、CodeUriの部分をhello_world/build/→hello_worldに修正しました。

疎通が出来ることを確認

$curl -v https:///Prod/hello
{"message": "hello world", "location": "xxx.xxx.xxx.xxx"}

LambdaのGIPを返してくれているみたいですね。

API Gatewayのリソースポリシーを設定

余談ですが、当初AWS SAMがこの新しい機能をサポートしているのか、そもそもCloud Formationがサポートしてるのか結構あやしい状態で、色々検索してみたのですが、いっこうにそれらしい記事やサンプルなども見つからずほぼ諦めかけていました。

初心に戻り、上記の参考サイト3を上から下までちゃんと読んでみたところ、「API Gateway リソースポリシーをアタッチする Swagger の例」にそれっぽいことが書いてあったので、試してみたらできたというオチでした。一次ソースはちゃんと読まないとダメですね。。。

それでは、本題に入ります。

template.yamlのAPIをSwagger定義からインポートするように書換えます。変化がわかるように過去の元の部分はコメントアウトして載せています。

template.yaml

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  sam-app

  Sample SAM Template for sam-app

# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst
Globals:
  Function:
    Timeout: 3

Resources:
  HelloWorldFunction:
    Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
    Properties:
      CodeUri: hello_world
      Handler: app.lambda_handler
      Runtime: python3.6
      Environment: # More info about Env Vars: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#environment-object
        Variables:
          PARAM1: VALUE
          # Events:
          #     HelloWorld:
          #         Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
          #         Properties:
          #             Path: /hello
          #             Method: get

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

  HelloWorldApi:
    Type: AWS::Serverless::Api
    Properties:
      StageName: Prod
      DefinitionBody:
        swagger: "2.0"
        info:
          title: sam-apig-resource-policy-api
        schemes:
          - https
        paths:
          /hello:
            get:
              summary: "hello api"
              description: "hello api"
              produces:
              - "application/json"
              x-amazon-apigateway-integration:
                  responses:
                    default:
                      statusCode: "200"
                  uri:
                    Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HelloWorldFunction.Arn}/invocations
                  passthroughBehavior: "when_no_match"
                  httpMethod: "POST"
                  contentHandling: "CONVERT_TO_TEXT"
                  type: "aws_proxy"
        x-amazon-apigateway-policy:
          Version: "2012-10-17"
          Statement:
          - Effect: Allow
            Principal: "*"
            Action: execute-api:Invoke
            Resource:
              - "execute-api:/*"
            Condition:
              IpAddress:
                aws:SourceIp:
                  - "xxx.xxx.xxx.xxx/32"

Outputs:
  # HelloWorldApi:
  #   Description: "API Gateway endpoint URL for Prod stage for Hello World function"
  #   Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/"

  HelloWorldFunction:
    Description: "Hello World Lambda Function ARN"
    Value: !GetAtt HelloWorldFunction.Arn

  HelloWorldFunctionIamRole:
    Description: "Implicit IAM Role created for Hello World function"
    Value: !GetAtt HelloWorldFunctionRole.Arn

API Gatewayのポリシードキュメントを設定している箇所は、x-amazon-apigateway-policyになりますが、この記述をDefinitionBodyのSwagger内に書く必要がある関係で、他の部分を修正、追加したりと多少面倒でした。

再度デプロイを行い、今度はIP制限により疎通ができなくなっていることを確認する。

$curl -v https:/// {"Message":"User: anonymous is not authorized to perform: execute-api:Invoke on resource: arn:aws:execute-api:ap-northeast-1::/Prod/GET/hello"}

マネージメントコンソールから設定を確認

ちゃんと設定されていますね。

SourceIpの部分に許可したいIPアドレスを入力すれば、ホワイトリスが設定できます。さらに、EffectをDenyにしたり、CondtionをNotIpAddressにするなどして、IPアドレスのブラックリストを設定することも可能です。

まとめ

いかがだったでしょうか。

やっていることは大したことないですが、まだ新しい機能ということもあって情報が少ないかと思いますので、どなたかの参考になれば幸いです。

また、現在サーバーレス開発部では一緒に働くメンバーを募集中です!!ご興味がある方は採用フォームからお問い合わせいただければと思います!