API Gateway + lambda でマイクロサービスを作る時にサービス間での認証(と認可)をすることでセキュリティを強化できます。今回は Security Token Service(STS) を使って Lambda で認証情報を取得し、API Gateway の AWS_IAM の認証に利用します。
前提
- Lambda を利用した API Gateway の AWS_IAM 認証方法について主に紹介
- API Gateway + lambda の詳しいサービスの作り方説明しません
想定するフロー
- 公開されているサービスの API Gateway にリクエスト
- バックエンドの Lambda が他のサービスを API Gateway を利用して呼び出す
3. IAM の認証情報を Security Token Service(STS) で取得
3. API Gateway へのリクエストに IAM の認証情報を用いる
今回このような流れを想定した時のサービス間認証方法について紹介します。
大切なのは認証方法の部分なので、Public に公開された API Gateway は実装しません。
Lambda の作成
ロールの作成
ここが一番のポイントです。
呼び出し側の lambda に与えるロールにアタッチする IAM ポリシーを2つ作成します。1つは 呼び出したいサービスの API Gateway へのアクセスを許可するポリシー、もうひとつは STS でロールの一時認証情報を取得するためのポリシーです。
- API Gateway のアクセスを許可するポリシー
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"execute-api:Invoke",
"execute-api:InvalidateCache"
],
"Resource": "arn:aws:execute-api:us-east-1:account_id:restapis/*/*/*"
}
]
}
Resource の書き方は「API を管理するためのアクセスの制御 - Amazon API Gateway」を参照してください。ここには呼び出し側の Lambda でアクセスを許す API Gateway の ARN を指定します。
- STS で Role の一時認証情報を取得するためのポリシー
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Resource": "arn:aws:iam::account_id:role/service-role/roleName"
}
]
}
Resource には STS で一時認証情報を取得したいロールの ARN を指定します。
呼び出される側 Lambda ソースコード
def lambda_handler(event, context):
return "Success!!"
呼び出す側 Lambda ソースコード
説明はコードの後に記述。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import requests
import os
import boto3
from aws_requests_auth.aws_auth import AWSRequestsAuth
def lambda_handler(event, context):
client = boto3.client('sts')
IAM_ROLE_ARN = os.environ['IAM_ROLE_ARN']
IAM_ROLE_SESSION_NAME = 'test_session'
REGION_NAME = 'us-east-1'
# IAM の認証情報の取得
response = client.assume_role(
RoleArn=IAM_ROLE_ARN,
RoleSessionName=IAM_ROLE_SESSION_NAME
)
#print("Receive credential: ", end="")
#print(response)
credentials=response['Credentials']
# API のホスト名を指定
aws_host='XXXXXXXX.execute-api.us-east-1.amazonaws.com'
auth = AWSRequestsAuth(aws_access_key=credentials['AccessKeyId'],
aws_secret_access_key=credentials['SecretAccessKey'],
aws_host=aws_host,
aws_region='us-east-1',
aws_service='execute-api')
# additional credential
headers = {'x-amz-security-token':credentials['SessionToken']}
response = requests.get('https://' + aws_host + '/prod/functionA-1', auth=auth, headers=headers)
return "Call Function A -> code:" + str(response.status_code) + " content:" + response.text
IAM の一時的な認証情報を取得する
client = boto3.client('sts')
IAM_ROLE_ARN = os.environ['IAM_ROLE_ARN']
IAM_ROLE_SESSION_NAME = 'test_session'
REGION_NAME = 'us-east-1'
# IAM の認証情報の取得
response = client.assume_role(
RoleArn=IAM_ROLE_ARN,
RoleSessionName=IAM_ROLE_SESSION_NAME
)
STS を使って一時認証情報を取得しています。
Lambda の環境変数を使って IAM_ROLE_ARN を取得します。IAM_ROLE_ARN には呼び出したいサービスの API Gateway を Invoke できるロールを指定します。
参考:何故 additional credential を header に入れるのか
リクエストに署名する
credentials=response['Credentials']
# API のホスト名を指定
aws_host='XXXXXXXX.execute-api.us-east-1.amazonaws.com'
auth = AWSRequestsAuth(aws_access_key=credentials['AccessKeyId'],
aws_secret_access_key=credentials['SecretAccessKey'],
aws_host=aws_host,
aws_region='us-east-1',
aws_service='execute-api')
# additional credential
headers = {'x-amz-security-token':credentials['SessionToken']}
response = requests.get('https://' + aws_host + '/prod/functionA-1', auth=auth, headers=headers)
aws_requests_auth
を利用して署名を行います。
ポイントは headers で x-amz-security-token
を追加しているところです。IAMの一時認証情報を利用する場合はsecreat_access_key
をx-amz-security-token
として一緒に送らなければいけません。(REST リクエストの署名と認証 - Amazon Simple Storage Service)
API Gateway の作成
AWS_IAM 有効化
IAM での認証を有効化します。AMC ではメソッドリクエストから変更できますが、実際に量が増えると CLI でやらないとかなり面倒ですね。
呼び出し側の Lambda を動かしてみる
これで準備ができたのでテストで Lambda を呼び出してみます。
無事認証され、レスポンスを受け取れました!!
アクセスできないことも確かめる(おまけ)
そもそも認証がうまく働いているの?というのを確かめるために、API Gateway を呼び出すためのポリシーを呼び出し側のロールからとってみます。
アクセスが拒否されました。AWS_IAMで認証ができていたことがわかります。