LoginSignup
4
6

More than 3 years have passed since last update.

IAM認証のAWS API GatewayにPythonからSigV4署名してアクセスするには

Last updated at Posted at 2020-07-22

AWSのAPI GatewayのAPIのメソッドの設定で、IAM認証を使っている場合、APIリクエスト時に署名が必要です。

API Gatewayのメソッド設定画面はこんな感じです。英語のマネジメントコンソールだと「Authorization」、日本語だと「認可」という欄です。

image.png

デフォルトは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#での記事も書きました。

4
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
6