AWSのAPI GatewayのAPIのメソッドの設定で、IAM認証を使っている場合、APIリクエスト時に署名が必要です。
API Gatewayのメソッド設定画面はこんな感じです。英語のマネジメントコンソールだと「Authorization」、日本語だと「認可」という欄です。
デフォルトはNONEという設定になっていますが、これをAWS_IAMにするとAPI Gatewayは届いたリクエストの署名を確認するようになります。リクエストする側が署名をする必要があります。
API Gatewayが署名を確認するようになるとAPI GatewayのリソースポリシーにてIAMユーザやIAMロールで制限をかけられるようになります。
Pythonにて署名を付けてAPIリクエストするサンプルコードを書いておきます。
前提
IAMユーザのアクセスキーが ~/.aws/credentials
に設定されていて、そのIAMユーザからIAMロールにスイッチするIAM権限があり、そのIAMロールでAPI Gatewayにアクセスするものとします。(~/.aws/credentials
があればよいので、以下のサンプルコードはGCPのCompute Engineインスタンスで動かしました)
Pythonコード
import boto3
from botocore.awsrequest import AWSRequest
from botocore.auth import SigV4Auth
import urllib.request
import sys
endpoint_host = "xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com"
endpoint = "https://" + endpoint_host + "/stage"
path = "/hello"
url = endpoint + path
# .aws/credentials または環境変数でアクセスキーが設定されているIAMユーザのセッションを生成。
session = boto3.session.Session()
# .aws/credentials に複数のIAMユーザがある場合は profile_name を指定。
#session = boto3.session.Session(profile_name = "foo")
sts_client = session.client("sts")
# IAMロールのARNを指定して assume_role 。
# IAMロールが不要でIAMユーザのままでAPIリクエストする場合は、ここは不要。
assume_role_response = sts_client.assume_role(
RoleArn = "arn:aws:iam::XXXXXXXXXXXX:role/ROLENAME",
RoleSessionName = "test")
# assume_role で得られたトークンなどからIAMロールでのセッションを生成。
# IAMロールが不要でIAMユーザのままでAPIリクエストする場合は、ここは不要。
session = boto3.session.Session(
aws_access_key_id = assume_role_response['Credentials']['AccessKeyId'],
aws_secret_access_key = assume_role_response['Credentials']['SecretAccessKey'],
aws_session_token = assume_role_response['Credentials']['SessionToken'])
# セッション情報からAPIリクエストに署名。
credentials = session.get_credentials()
awsreq = AWSRequest(method = "GET", url = url)
SigV4Auth(credentials, "execute-api", "ap-northeast-1").add_auth(awsreq)
# urllib.request.Request 生成。
# この4つのリクエストヘッダーが必須。
# IAMロールが不要でIAMユーザのままでAPIリクエストする場合は、X-Amz-Security-Token は不要。
req = urllib.request.Request(url, headers = {
"Authorization": awsreq.headers['Authorization'],
"Host": endpoint_host,
"X-Amz-Date": awsreq.context['timestamp'],
"X-Amz-Security-Token": assume_role_response["Credentials"]["SessionToken"]
})
# APIリクエスト実行
try:
with urllib.request.urlopen(req) as response:
# レスポンス出力
sys.stdout.buffer.write(response.read())
except urllib.error.HTTPError as err:
# 403などの場合はここに到達
# エラーを出力
print(err)
# レスポンスヘッダを出力
print(err.headers)
エラーメッセージの例
IAMユーザに assume_role する権限がないと assume_role を呼び出したところで
botocore.exceptions.ClientError: An error occurred (AccessDenied) when calling the AssumeRole operation
という例外が発生します。
X-Amz-Security-Token
が必要なのに足りてないと、403 Forbidden が返され、レスポンスヘッダに次のように書かれます。
x-amzn-ErrorType: UnrecognizedClientException
API GatewayのリソースポリシーでこのIAMロールからのリクエストを拒否していると、403 Forbidden が返され、レスポンスヘッダに次のように書かれます。
x-amzn-ErrorType: AccessDeniedException
なお、IAMロールにAPI GatewayにアクセスするIAM権限がなくても、API Gatewayのリソースポリシーで許可していれば、AccessDeniedException にはならずに正常に処理できるようです。
リンク
C#での記事も書きました。