API Gatewayのリソースポリシーによるアクセス制御とPythonからのAPI呼び出しをやってみたい

はじめに

API Gatewayのアクセス制御にはいくつか方法がある。2018年4月にリリースされたリソースポリシーはその一つであり、IPもしくはIAMによるアクセス制御が行える。

この記事では、リソースポリシーの設定とLambdaからのAPI呼び出しの方法を説明する。Lambda FunctionはPython3.6で作成し、デプロイにはServerless Frameworkを使用した。

まずは結論を

  • リソースポリシーを使えばIAM{ユーザ|グループ|ロール}単位でAPI Gatewayの制御ができる。
    • クロスアカウントのアクセス制御も可能
    • 許可対象のIAMロールが付与されたLambdaからAPIを叩く際には、IAMロールの認証情報を明示的にリクエストに埋め込む必要がある

やったことと手順

ざっくりと以下を行った。

  • API Gatewayのリソースポリシーを使ったアクセス制御設定
  • 外部アカウントのLambdaにリソースポリシーで許可したIAMロールを付与し、LambdaからAPIを実行

事前準備

  • Serverless Framework をインストール (Serverless Frameworkドキュメント)
  • Dockerをインストールし、dockerdを起動しておく (手順はそこら中にあるので省略)

アカウントを2つ用意

クロスアカウントアクセスを試すためにアカウントを2個準備する。それぞれAccount-A, Account-Bと呼ぶ。Shared Credentialsを用意しておく。

  • Account-A: API Gatewayを使ってAPIをデプロイ
  • Account-B: APIを叩くLambda Functionをデプロイ

アカウントが1個しかなくてもAccount-BをAccountAに読み替えれば以降の手順を追えるはず(やってない)。

APIを作成する

Account-AでHolidays JPのような休日祝日情報を返すAPIを作成する。作り方はこちらのページを参考にしました。ありがとうございます。今回は簡易なAPIで良いので、S3バケットに以下のJSONファイルを配置し、api/v1/data.jsonメソッドに割り当てる。

data.json
{
    "2018-01-01": "元日",
    "2018-01-08": "成人の日",
    "2018-02-11": "建国記念の日",
    "2018-02-12": "休日",
    "2018-03-21": "春分の日",
    "2018-04-29": "昭和の日",
    "2018-04-30": "休日",
    "2018-05-03": "憲法記念日",
    "2018-05-04": "みどりの日",
    "2018-05-05": "こどもの日",
    "2018-07-16": "海の日",
    "2018-08-11": "山の日",
    "2018-09-17": "敬老の日",
    "2018-09-23": "秋分の日",
    "2018-09-24": "休日",
    "2018-10-08": "体育の日",
    "2018-11-03": "文化の日",
    "2018-11-23": "勤労感謝の日",
    "2018-12-23": "天皇誕生日",
    "2018-12-24": "休日",
}

API Gatewayのマネジメントコンソールはこんな表示になる。
API.png

デプロイしたAPIの動作を確認する。以下のようにJSON形式で値が返ってくるはず。

$ curl -s https://xxxxxxxxxx.execute-api.{Region}.amazonaws.com/{Stage}/api/v1/data.json
{
    "2018-01-01": "元日",
    "2018-01-08": "成人の日",
      ...(中略)...
    "2018-12-23": "天皇誕生日",
    "2018-12-24": "休日",
}

APIにリソースポリシーを設定

作成したAPIの「リソースポリシー」欄にIAMによるアクセス制御ポリシーを記載する。入力方法は公式ドキュメントの例を参考にしました。
このポリシーでは、Account-BのIAMロールに execute-api:Invoke を許可している。(許可対象のIAMロールは後ほどLambda Functionをデプロイした後に確認するので、現時点では設定しなくても良い気がする。)

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::{Account-B}:role/{IAM-Role}"
            },
            "Action": "execute-api:Invoke",
            "Resource": "arn:aws:execute-api:ap-northeast-1:{Account-A}:{API-Method}/{API-Path}"
        }
    ]
}

リソースポリシー設定後にAPIを叩くと、こんな感じのエラーが出て怒られる。

$ curl -s https://xxxxxxxxxx.execute-api.{Region}.amazonaws.com/{Stage}/api/v1/data.json
{"message":"Missing Authentication Token"}

APIを叩くLambda Functionをデプロイする

Serverless Frameworkを使ってAccount-BにLambda Functionをデプロイする

Serverless Framework プロジェクトを作成

sls コマンドでServerless Frameworkのプロジェクトを作成する

# バージョン確認
$ sls version
1.26.1

# slsプロジェクト作成
$ mkdir -p exeApiTest
$ cd ./exeApiTest
$ sls create -name exeApiTest -t "aws-python3"
Serverless: Generating boilerplate...
 _______                             __
|   _   .-----.----.--.--.-----.----|  .-----.-----.-----.
|   |___|  -__|   _|  |  |  -__|   _|  |  -__|__ --|__ --|
|____   |_____|__|  \___/|_____|__| |__|_____|_____|_____|
|   |   |             The Serverless Application Framework
|       |                           serverless.com, v1.26.1
 -------'

Serverless: Successfully generated boilerplate for template: "aws-python3"

exeApiTestディレクトリにserverless.ymlhandler.pyが作成される。serverless.ymlはデプロイ設定。handler.pyはデフォルトのLambda用コード。

Serverless Framework のデプロイ設定

serverless.ymlにはLambda Functionと一緒にデプロイするリソースを設定できる。例えば、IAMロールやCloudWatch Eventsなど。ここではAccount-Aで作成したAPIの実行権限をIAMロールに付与する。(試してないけど、Resource: "arn:aws:execute-api:*:*:*"でいいかもしれない)

IAMロールに付与するポリシーの設定例
provider:
  iamRoleStatements:
    - Effect: "Allow"
      Action:
        - "execute-api:Invoke"
      Resource: "arn:aws:execute-api:ap-northeast-1:{Account-A}:{API-Method}/{API-Path}"

その他諸々を書いて、最終的に以下のようなserverless.ymlができる。

serverless.ymlの例
service: exeApiTest
provider:
  name: aws
  runtime: python3.6
  region: ap-northeast-1
  iamRoleStatements:
    - Effect: "Allow"
      Action:
        - "logs:CreateLogGroup"
        - "logs:CreateLogStream"
        - "logs:PutLogEvents"
      Resource: "arn:aws:logs:*:*:*"
    - Effect: "Allow"
      Action:
        - "execute-api:Invoke"
      Resource: "arn:aws:execute-api:ap-northeast-1:{Account-A}:{API-Method}/{API-Path}"

custom:
  exclude:
    - node_modules/**
    - package.json

functions:
  runner:
    handler: handler.runner
    memorySize: 128
    timeout: 60

APIを叩くLambda Functionを作成する

こちらを参考に、APIを叩くLambda Functionを作成する。

handler.pyの例
import json
import os
import logging
from urllib.request import Request, urlopen
from urllib.error import URLError, HTTPError
from botocore.awsrequest import AWSRequest
from botocore.auth import SigV4Auth
from botocore.endpoint import BotocoreHTTPSession
from botocore.credentials import Credentials

# ログレベル設定
logger = logging.getLogger()
logLevel = logging.INFO
logger.setLevel(logLevel)

def runner(event, context):
    url = 'https://xxxxxxxxxx.execute-api.{Region}.amazonaws.com/{Stage}/api/v1/data.json'
    credentials = Credentials(os.environ["AWS_ACCESS_KEY_ID"],
                              os.environ["AWS_SECRET_ACCESS_KEY"],
                              os.environ["AWS_SESSION_TOKEN"])
    request = AWSRequest(method="GET", url=url)
    SigV4Auth(credentials, 'execute-api',
              os.environ["AWS_REGION"]).add_auth(request)
    response = BotocoreHTTPSession().send(request.prepare())
    return response.json()

Lambda Functionをデプロイ

Serverless Frameworkでデプロイする

$ pwd
/path-to-project/exeApiTest

# Serverless Frameworkでデプロイ
$ sls deploy --aws-profile={Account-B}

デプロイした後にLambdaに振られたIAMロールのARNを確認し、API Gatewayのリソースポリシーに設定しておく。

Lambda Functionを実行

↑でデプロイしたLambda Functionを叩くと、休日祝日情報が取得できる。(実行もslsコマンドでできるので楽。Serverless強い。)

$ sls invoke --aws-profile={Account-B} --function=runner
{
    "2018-01-01": "元日",
    "2018-01-08": "成人の日",
      ...(中略)...
    "2018-12-23": "天皇誕生日",
    "2018-12-24": "休日",
}

以上。こんな感じでAPIへのアクセスを制御できたけど、もっとうまい方法がある気がする。。。

参考情報

AWS API関連

Serverless Framework

試していく過程で、Lambdaの実行環境にデフォルトで入っていないPythonライブラリ(Requestsとか)を使っていたので、そのようなライブラリをServerless Frameworkでデプロイする方法も紹介する。

  • serverless-python-requirements
    • requestsなど、Lambda実行環境にデフォルトで入っていないライブラリを入れるためのServerless用プラグイン
  • lambci/docker-lambda
    • AWS Lambda の実行環境をエミュレートするDockerイメージ。serverless-python-requirements がPythonのモジュールをコンパイルするために使っている。
    • デプロイするときにあらかじめ docker pull しておくと安心できる。
  • How to Handle your Python packaging in Lambda with Serverless plugins
    • serverless-python-requirements の解説記事. exeApiTestのserverless.ymlはこれを参考にした。
  • AWS - Packaging
    • Serverless Frameworkでファイルを指定してデプロイする方法。Virtualenvを使っている場合はこのやり方でデプロイパッケージを小さくできる。
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.