[小ネタ] botocoreのAWS APIリクエストの署名プロセスのみを利用する

2015.11.19
  • 2017/02/02 修正 : PreserveAuthSession が BotocoreHTTPSession に変更されていました

はじめに

藤本です。

先日、Amazon Elasticsearch ServiceのIAM Roleによるアクセス制御というエントリを投稿し、IAMによるアクセス制御が設定されたAmazon Elasticsearch Serviceに対してAWSリクエストの署名をしたREST APIに発行するために、botoを利用しました。
どうせならAWS Lambda for Pythonで標準ライブラリとして持っているbotocoreの署名プロセスを利用したくなりました。botocoreのソースコードを追いかけ、署名のみを利用する事ができましたので今回はそちらのコードをご紹介します。

概要

AWSへのリクエストには署名が必要です。AWS公式ドキュメントに記載がある署名プロセスを使えば、AWSへのリクエストが可能です。ただ、実装するのは面倒です。

botocoreはawscliやboto3の土台となるLowレベルインタフェースです。こんなに信用できるライブラリはありません。活用して、楽しましょう。

今回はAWS Lambda for Pythonを使って、IAMによるアクセス制御が設定されたAmazon Elasticsearch Serviceにbotocoreライブラリを利用して任意のAPIに署名し、リクエストを発行します。

botocoreの署名プロセス

やることは4つです。

  1. Credentialsインスタンス生成
    アクセスキー、シークレットキー、セッショントークン(必須ではない)を引数にCredentialsクラスのインスタンスを作成します。追加の小ネタですがAWS Lambdaではクレデンシャル情報は環境変数に設定されています。
  2. AWSRequestインスタンス生成
    API発行したいURL、HTTPメソッド、リクエストボディ(必須ではない)を引数にAWSリクエストクラスのインスタンスを作成します。
  3. AWSリクエスト署名(署名バージョン4)
    生成したCredentials、AWSRequestから署名バージョン4の署名ヘッダを作成します。現在は署名バージョン4が推奨されていますが、今後、新しい署名が出来た時にはそちらに移行することを推奨します。詳しくはauthモジュールを見てみてください
  4. API発行

ソースコードは以下のようになります。

import os
from botocore.awsrequest import AWSRequest
from botocore.auth import SigV4Auth
from botocore.endpoint import BotocoreHTTPSession
from botocore.credentials import Credentials

### 1. Credential生成  
credentials = Credentials(os.environ["AWS_ACCESS_KEY_ID"], os.environ["AWS_SECRET_ACCESS_KEY"], os.environ["AWS_SESSION_TOKEN"])
### 2. AWSRequest生成
request = AWSRequest(method=<METHOD>, url=<URL>, data=<DATA>)
### 3. AWSリクエスト署名
SigV4Auth(credentials, <SERVICE_NAME>, os.environ["AWS_REGION"]).add_auth(request)
### 4. API発行
BotocoreHTTPSession().send(request.prepare())

やってみた

それでは上記の実装を利用して、IAMによるアクセス制御を行ったAmazon ESに対してAPIを発行します。
まずは署名なしで実行して拒否されることを確認します。

import urllib2

def lambda_handler(event, context):
    url = "http://search-**************************.ap-northeast-1.es.amazonaws.com/"
    try:
        urllib2.urlopen(url)
    except urllib2.URLError, e:
        return e.read()

AWS_Lambda

はい、拒否されました。

次は、署名を行ったリクエストを発行します。

import os
from botocore.awsrequest import AWSRequest
from botocore.auth import SigV4Auth
from botocore.endpoint import BotocoreHTTPSession
from botocore.credentials import Credentials

def lambda_handler(event, context):
    url = "http://search-**************************.ap-northeast-1.es.amazonaws.com/"
    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, "es", os.environ["AWS_REGION"]).add_auth(request)
    response = BotocoreHTTPSession().send(request.prepare())
    return response.json()

AWS_Lambda

はい、成功しました。

次は、リクエストボディを指定したPOSTも行ってみます。

import os
from botocore.awsrequest import AWSRequest
from botocore.auth import SigV4Auth
from botocore.endpoint import BotocoreHTTPSession
from botocore.credentials import Credentials

def lambda_handler(event, context):
    url = "http://search-**************************.ap-northeast-1.es.amazonaws.com/hoge/hoge/hoge"
    body = '{"name": "fujimoto"}'
    credentials = Credentials(os.environ["AWS_ACCESS_KEY_ID"], os.environ["AWS_SECRET_ACCESS_KEY"], os.environ["AWS_SESSION_TOKEN"])
    request = AWSRequest(method="POST", url=url, data=body)
    SigV4Auth(credentials, "es", os.environ["AWS_REGION"]).add_auth(request)
    response = BotocoreHTTPSession().send(request.prepare())
    return response.json()

AWS_Lambda

うん。

まとめ

楽できるところは楽して、やるべきことに時間を使っていきましょう。