はじめに
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
メソッドに割り当てる。
{
"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の動作を確認する。以下のように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.yml
とhandler.py
が作成される。serverless.yml
はデプロイ設定。handler.py
はデフォルトのLambda用コード。
Serverless Framework のデプロイ設定
serverless.yml
にはLambda Functionと一緒にデプロイするリソースを設定できる。例えば、IAMロールやCloudWatch Eventsなど。ここではAccount-Aで作成したAPIの実行権限をIAMロールに付与する。(試してないけど、Resource: "arn:aws:execute-api:*:*:*"
でいいかもしれない)
provider:
iamRoleStatements:
- Effect: "Allow"
Action:
- "execute-api:Invoke"
Resource: "arn:aws:execute-api:ap-northeast-1:{Account-A}:{API-Method}/{API-Path}"
その他諸々を書いて、最終的に以下のような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を作成する。
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関連
-
Control Access to an API with Amazon API Gateway Resource Policies
- API Gateway リソースポリシーの公式ドキュメント
-
奥さんがフェイスエステしている間に日本の祝日や休日を JSON で返す Web サービスを API Gateway と Amazon S3 そして Python で作ってみました
- API Gatewayの設定方法を参考にしました。
-
[小ネタ] botocoreのAWS APIリクエストの署名プロセスのみを利用する
- 認証情報をつけてAPIを呼び出す方法。こちらのコードをほぼ全て使わせていただきました。
- 上の記事ではbotocoreのメソッドをそのまま使っているので、以下のモジュールを使ったほうが安全かもしれない。
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を使っている場合はこのやり方でデプロイパッケージを小さくできる。