AWS の API を理解しよう !
中級編 ~ リクエストの署名や CLI/SDK の中身を覗いてみる
Author : 杉本 圭太
こんにちは ! テクニカルトレーナーの杉本圭太です !
最近読んで面白かった漫画は「ブレス」(著 : 園山ゆきの) です。
先月に引き続き AWS の API や AWS Command Line Interface (AWS CLI)、AWS SDK に関する内容の記事をお届けします !
今回は “中級編” として、AWS は利用しているよという方を対象に、より踏み込んだ内容にしてみました。
最後には記事の内容にまつわるクイズも用意していますのでお楽しみに ( ・∀・)=b
このシリーズ記事のその他の記事はこちら
- 選択
- 初級編 ~ API の仕組みと利用方法を理解しよう
- 中級編 ~ リクエストの署名や CLI/SDK の中身を覗いてみる
- 上級編 ~ 新しい発見があるかも ? いろんな機能をご紹介 !
はじめに
前回の記事 では AWS の API がどのようなものかを紹介しました。
しかし前回の知識のみで AWS のエンドポイントへリクエストを送れば誰でも AWS のサービスを利用できるのかというと、実はできません。それだけだと AWS のアカウントを作成せずに AWS のサービスが利用できたり、勝手に他人のリソースを操作できてしまうからです。そのため AWS の API には「リクエストの署名」と呼ばれる、どのアカウントの誰がリクエストを送信したかを判断できる仕組みがあります。
では「リクエストの署名」の仕組みとはどのようなものなのでしょうか。言葉自体初めて聞いた方もいるかもしれません。なぜなら AWS マネジメントコンソール、AWS CLI 、AWS SDK があればリクエストの署名を意識しなくても AWS を利用できるからです。
ですが今回の記事では、AWS の API を利用する上で欠かせないけどあまり表に出ることはないリクエストの署名が、どんな仕組みなのかをポイントを絞って解説します。
それだけではなく、AWS マネジメントコンソール、AWS CLI、AWS SDK を利用したときに何が起きているのかも、簡単に紹介します !
リクエストの署名とは
今回の一番のテーマである「リクエストの署名」とは何なのか?を解説していきます。理解するためのキーポイントは「認証情報」と「署名バージョン 4 (SigV4)」です。それぞれどんなものかを確認しましょう !
認証情報
リクエストの署名でまず必要になるのが、どの AWS アカウントの誰かを証明するための情報である認証情報です。AWS では AWS Identity and Access Management (IAM) が認証と認可を提供しており、IAM ユーザーの認証情報で AWS の API をリクエストする場合には「アクセスキー ID とシークレットアクセスキー」を利用します。
- アクセスキー IDの例: AKIAIOSFODNN7EXAMPLE
- シークレットアクセスキーの例: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
※ IAM ロールを利用した一時的な認証情報の場合は、この 2 つに加えて セッショントークン も含まれます。
アクセスキー ID とシークレットアクセスキーは API のリクエストでそのまま利用するのではなく、リクエストの署名プロセスのために利用します。リクエストの署名プロセスを行うことで、AWS のサービスは「誰からのリクエストか」「改ざんされていないか」などを判断 できます。
現在 AWS の API を利用するほとんどの場合、署名プロセスには 署名バージョン 4 (SigV4) を利用しますので、今回の記事も SigV4 について解説をします。
署名バージョン 4 (SigV4)
SigV4 を利用したリクエストには必要な要素 があり、その中でも認証情報が関わってくるのが「認証パラメータ」という要素です。認証パラメータは以下の 4 つのパラメータのセットです。パラメータの中にはアクセスキー ID やシークレットアクセスキーが必要になるものも含まれており、先ほど紹介した認証情報はここで活用されています。
▼ 認証パラメータに含まれる 4 つのパラメータ
パラメータ | 説明 | 値の例 |
Algorithm | 「署名」を作成するために利用するアルゴリズム名 | AWS4-HMAC-SHA256 |
Credential scope | アクセスキー ID、タイムスタンプ、リージョン、サービス名、"aws4_request"という固定値を順番にスラッシュ ("/") 区切りで連結させた文字列 | AKIDEXAMPLE/20150830/us-east-1/iam/aws4_request |
SignedHeaders | 「署名」に含める HTTP/S ヘッダーのセミコロン (";") で区切られたリスト | content-type;host;x-amz-date |
Signature | リクエストに利用する情報とシークレットアクセスキーから作成する「署名」と呼ばれる文字列 | 5d672d79c15b13162d9279b0855cfba6789a8edb4c82c400e06b5924a6f2b5d7 |
この認証パラメータを HTTP/S のリクエストで利用する方法は Authorization ヘッダーに追加するか、クエリ文字列に追加するかの 2 通り ありますが、署名プロセスはどちらも同じであるため、今回は Authorization ヘッダーに追加する場合の形式で説明します。
Authorization ヘッダーの構成は決まっており、ヘッダーの値には認証パラメータの 4 つのパラメータがそれぞれ入ります。
Authorization: <Algorithm> Credential=<Credential scope>, SignedHeaders=<SignedHeaders>, Signature=<Signature>
▼ 認証パラメーターの値が入った Authorization ヘッダーの例
Authorization: AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/iam/aws4_request, SignedHeaders=content-type;host;x-amz-date, Signature=5d672d79c15b13162d9279b0855cfba6789a8edb4c82c400e06b5924a6f2b5d7
つまりこのヘッダーをリクエストに付与することでやっと、AWS の API が利用できます。
今回の記事では、リクエストの署名とは「認証情報を利用して、SigV4 のプロセスでリクエストに署名を付与すること」と考えましょう。
ここまででリクエストを送るため最終的に必要な形式は把握できましたが、現時点では認証パラメータの Signature の値である「署名」が何なのかは謎に包まれています。ではその謎を解明するために、リクエストを送るときの署名プロセスを確認して「署名」がどのようにつくられるのかを見ていきましょう !
リクエストの署名プロセス
リクエストの署名プロセスは公式ドキュメントの「AWS リクエストへの署名」で、以下の 4 つのタスクに分けて記載されています。
- タスク 1 : 署名バージョン 4 の正規リクエストを作成する
- タスク 2 : 署名バージョン 4 の署名文字列を作成する
- タスク 3 : AWS 署名バージョン 4 の署名を計算する
- タスク 4 : HTTP リクエストに署名を追加する
今回はその中からポイントを抜粋して「署名」とはどのような情報を使ってどのように出来上がるのかを、ドキュメントの例にある通り IAM の ListUsers のリクエストに SigV4 の署名プロセスを行う場合で確認していきます。
▼ 署名プロセスを行うリクエスト (*1)
GET https://iam.amazonaws.com/?Action=ListUsers&Version=2010-05-08 HTTP/1.1
Host: iam.amazonaws.com
Content-Type: application/x-www-form-urlencoded; charset=utf-8
X-Amz-Date: 20150830T123600Z
今はまだこのリクエストにリクエストの署名はされていませんが、読み進めていくと最後に、このリクエストへ付与するための Authorization ヘッダーができあがります。
タスク 1 : 署名バージョン 4 の正規リクエスト (Canonical request) を作成する
まず最初に HTTP リクエストの内容を以下のような擬似コードの処理をして、「正規リクエスト (Canonical request)」という形式に変換します。
▼ 正規リクエスト変換のための疑似コード
CanonicalRequest =
HTTPRequestMethod + '\n' +
CanonicalURI + '\n' +
CanonicalQueryString + '\n' +
CanonicalHeaders + '\n' +
SignedHeaders + '\n' +
HexEncode(Hash(RequestPayload))
ここのポイントはリクエストに含まれる要素を改行で区切り、RequestPayload は認証パラメータの Algorithm で指定した方法 (SHA-256 など) でハッシュ化して小文字の16進数の文字列に変換することです。
▼ 「リクエスト (*1)」を正規リクエスト形式にした値 (*2)
GET
/
Action=ListUsers&Version=2010-05-08
content-type:application/x-www-form-urlencoded; charset=utf-8
host:iam.amazonaws.com (http://iam.amazonaws.com/)
x-amz-date:20150830T123600Z
content-type;host;x-amz-date
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
さらになんとこの正規リクエスト全体も、正規リクエストの一部である RequestPayload に実施したのと同じ方法でハッシュ化して 16 進数の文字列にします。
▼ 「正規リクエスト形式の値 (*2)」をハッシュ化した値 (*3)
f536975d06c0309214f805bb90ccff089219ecd68b2577efef23edd43b7e1a59
これで Signature に利用するような形式の値ができあがりましたが、実はまだまだ署名のプロセスは完了してはいません。この値は、次のタスク 2 で利用します。
タスク 2 : 署名バージョン 4 の署名文字列を作成する
次に「署名」を作成するためのインプットとなる「署名対象の文字列」を、以下のような擬似コードを利用して用意します。
※ 現在のドキュメントの日本語訳では「Create a string to sign for Signature Version 4」が「署名バージョン 4 の署名文字列を作成する」と訳されていますが、「署名バージョン 4 の署名対象の文字列を作成する」とした方が理解しやすいかと思いますので、今回はそのように記載しています。
▼ 「署名対象の文字列」を用意するための疑似コード
StringToSign =
Algorithm + \n +
RequestDateTime + \n +
CredentialScope + \n +
HashedCanonicalRequest
ここでのポイントは、署名対象の文字列を用意するための擬似コードにある CredentialScope は認証パラメータで紹介した Credential scope の値と違ってアクセスキー ID が含まれないことや、タスク 1 で用意した「正規リクエストをハッシュ化した値 (*3)」が利用されることです。
▼ 署名対象の文字列 (*4)
AWS4-HMAC-SHA256
20150830T123600Z
20150830/us-east-1/iam/aws4_request
f536975d06c0309214f805bb90ccff089219ecd68b2577efef23edd43b7e1a59
この「署名対象の文字列 (*4)」はタスク 3 で利用します。
タスク 3 : AWS 署名バージョン 4 の署名を計算する
認証パラメータの Signature にあたる「署名」を作るためには、まず「署名キー」の作成が必要です。署名キーの作成には、一連のハッシュベースのメッセージ認証コード (HMAC) を用意します。
以下の擬似コードでは HMAC(key, data) が key と data を引数にして出力をバイナリ形式で返す HMAC-SHA256 関数を表しています。
▼ 署名キーを作成するための疑似コード
kSecret = your secret access key
kDate = HMAC("AWS4" + kSecret, Date)
kRegion = HMAC(kDate, Region)
kService = HMAC(kRegion, Service)
kSigning = HMAC(kService, "aws4_request")
ここでのポイントは AWS の認証情報のシークレットアクセスキーを利用することです。それ以外にもリージョンやサービス名なども含まれていますね。
今回の例だと、具体的に値を入れると以下のような式で署名キーができます。
kSecret = "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY"
kDate = HMAC("AWS4" + kSecret, "20150830")
kRegion = HMAC(kDate, "us-east-1")
kService = HMAC(kRegion, "iam")
kSigning = HMAC(kService, "aws4_request")
▼ 署名キーの値 (*5)
c4afb1cc5771d871763a393e44b703571b55cc28424d1a5e86da6ed3c154a4b9
これでやっと署名を作成する準備ができました!
最後に「署名対象の文字列 (*4)」と「署名キー (*5)」の値をキー付きハッシュ関数の入力すると「署名」に利用する文字列になります。
▼ 署名を作成するための疑似コード
HexEncode(HMAC("署名キー (*5)", "署名対象の文字列 (*4)"))
単純な計算式ですが、あえてポイントを挙げるならハッシュ関数のキーとデータがそれぞれどちらかを間違えないようにしましょう。
▼ 署名の値 (*6)
5d672d79c15b13162d9279b0855cfba6789a8edb4c82c400e06b5924a6f2b5d7
認証パラメータの Signature である「署名」は完成です ! あとはリクエストに付与するだけです。
タスク 4 : HTTP リクエストに署名を追加する
タスク 4 では、署名を含む認証パラメータをリクエストに付与する方法が記載されています。この記事ではすでに紹介した通り、ヘッダを利用する方法であれば「署名の値 (*6)」を Authorization ヘッダーの Signature パラメーターの値にすることで、AWS の API を SigV4 の署名付きリクエストで送信できます。クエリ文字列に署名情報を追加したい場合は こちら をご確認ください。
あらためて Authorization ヘッダーを見てみましょう。無事にリクエストに署名を追加することができました。 ✧٩(。•̀ᴗ-)人(-ᴗ´•。)۶✧
Authorization: AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/iam/aws4_request, SignedHeaders=content-type;host;x-amz-date, Signature=5d672d79c15b13162d9279b0855cfba6789a8edb4c82c400e06b5924a6f2b5d7
※ IAM ロールを利用した一時的な認証情報の場合は Authorization ヘッダーに加えて、セッショントークンを値に設定した X-Amz-Security-Token ヘッダーもリクエストに必要です。
「さあ、これでリクエストの署名も理解したので、あとは AWS の API を利用するときはこの手順に従ってリクエストを送ってください !」と言われたらどうでしょうか ? さすがにこれを自分で実施するのは大変ですよね・・・。
ここでまたまたマネジメントコンソール、CLI、SDKの便利さを実感する時がきました ! マネジメントコンソールならパスワードでログインする、AWS CLI や AWS SDK なら認証情報を決められた場所に設定 (プロファイルや環境変数を利用) することで、利用者は AWS の操作をしたときにリクエストの署名について考える必要がなくなっているのです。
ではマネジメントコンソール、CLI、SDK がどうやって API を送っているのかをそれぞれ覗いてみましょう( 👁🗨ω👁🗨 )
AWS マネジメントコンソール
AWS マネジメントコンソール は 2009 年に発表された 機能です。なんと AWS で最初から利用できたわけではないんですね。詳細は AWS マネジメントコンソール入門ガイド をご確認ください。
AWS マネジメントコンソールの基本
お好みのブラウザや、AWS コンソールモバイルアプリ で AWS のサービスを操作できるツールです。コンソールへアクセスしたり画面をクリックすることで、その画面や操作ごとに必要となる AWS の API が呼び出され、リソースの操作や情報の表示をしてくれます。
AWS アカウントまたは IAM の認証情報を使用して AWS マネジメントコンソールにログインすると、ログインセッションを利用 して AWS のサービスを利用できます。
コンソールを操作すると
Web ブラウザを利用した場合、どのように AWS のサービスを利用しているのか確認します。
※ 執筆時点での情報を記載しています。
コンソールの URL も、初級編で紹介した AWS のサービスエンドポイント一覧 の中の マネジメントコンソールのサービスエンドポイント に記載されていて、ブラウザはこのエンドポイントにアクセスして画面を表示しています。東京リージョンの場合は ap-northeast-1.console.aws.amazon.com です。
マネジメントコンソールはサービスごとにページが用意されていて、サービスの画面や操作ごとに動作は異なりますが、EC2 のトップページを開いた場合の画面はこちらです。作成された EC2 インスタンスや EBS ボリュームなどの数が表示されています。これらの情報を取得するには、複数の AWS API の利用が必要です。
この画面を開いた時に、どのような通信が発生しているか見てみます。ブラウザのツールで確認すると、サービスエンドポイントに多くのリクエストが送られていました。
この画面の場合は、https://ap-northeast-1.console.aws.amazon.com/ec2/alma/ec2-proxy などの URL へリクエストを送っています。
このようにマネジメントコンソールでは、マネジメントコンソールのサービスエンドポイント へアクセスし、AWS リソースの情報取得・操作のために直接 EC2 のサービスエンドポイント などの各サービスのエンドポイントへアクセスしていない場合もあります。そして認証情報はログインセッションがあるため、ユーザーはリクエスト署名などを意識することなく AWS の操作が可能です !
AWS CLI
AWS CLI は 2012年に発表された ツールです。AWS CLI の詳細を知りたい場合は、AWS CLI のユーザーガイド や AWS CLI Command Reference をご覧ください。また現時点で AWS CLI の最新メジャーバージョンであるバージョン 2 を前提としています。
AWS CLI の基本
まずは簡単に CLI の利用方法について確認しましょう。
AWS CLI の構造 は <command> に AWS のサービス、<subcommand> にはサービスに対する操作を指定します。
aws <command> <subcommand> [options and parameters]
▼ セキュリティグループを作成するコマンド例
aws ec2 create-security-group —group-name my-sg —description "My security group"
コマンドにはサービスエンドポイントのそれぞれの Actions に 1:1 で対応する <command> <subcommand> が用意されています。また、一部のサービスでは複雑な API を簡略化する高レベルコマンドもあります。
コマンドを実行すると
ここからが今回の記事でお伝えしたいポイントです。
AWS CLI でコマンドを実行したときには何が起きているのでしょうか ?
実は AWS CLI は Python で実装されたツール で、コマンドを実行すると Python のプログラムが実行されます。そしてこのプログラムの中では botocore というパッケージを利用しており、コマンドの入力情報から AWS のサービスエンドポイントや API アクションに対応する HTTP/S リクエストを組み立て、リクエストの送信などをしています。
AWS の API ではリクエストの署名が必要だとこの記事の前半で解説しましたが、AWS CLI の利用する認証情報の優先順位 に基づいて、プログロムの中でリクエストへ署名を付与してくれています。
つまり、AWS CLI でコマンドを実行すると、以下のように HTTP リクエストが送られているということです。
▼ Amazon DynamoDB のテーブル一覧を取得するコマンド例
aws dynamodb list-tables --limit 5 --region ap-northeast-1
▼ 送信される HTTP リクエストの例 (一部情報を省略しています)
POST / HTTP/1.1
Host: dynamodb.ap-northeast-1.amazonaws.com
X-Amz-Target: DynamoDB_20120810.ListTables
Content-Type: application/x-amz-json-1.0
User-Agent: aws-cli/2.Y.Z XXXXXX
X-Amz-Date: 20221001T091500Z
Authorization: AWS4-HMAC-SHA256 Credential=XXX, SignedHeaders=YYY, Signature=ZZZ
Content-Length: 12
{"Limit": 5}
実際に自身で確認したい場合は、以下のように --debug オプションを付けてコマンドを実行すると、どのような HTTP/S リクエストを送信しているかがわかるので試してみてください !
aws dynamodb list-tables --limit 5 --region ap-northeast-1 --debug
ちなみに AWS CLI で利用されている Python の botocore パッケージは、AWS SDK for Python (Boto3) でも利用されています。このコードがどのような処理を実装しているかは、この後の SDK の説明と合わせてお伝えします。
AWS SDK
例えばデータベースに DynamoDB を利用したり、サービス同士の連携に SQS キューを利用したりなど、AWS のサービスと統合するアプリケーションを開発する場合、Tools to Build on AWS に記載されている開発言語であれば AWS SDK を利用できます。各言語の AWS SDK の詳細を知りたい場合は、それぞれのドキュメントをご確認ください。
AWS SDK の基本
AWS の API の利用方法を知っていれば、AWS SDK を利用しなくても AWS のサービスへ HTTP/S のリクエスを送るコードを書くことは可能です。では AWS SDK を利用すると何が嬉しいのでしょうか ?
AWS の API を利用するためにどんな処理をプログラムのコードに書かないといけないのか、いくつか挙げてみましょう。
- 利用したい API ごとによって多種多様に定められた形式で、HTTP/S のリクエストを組み立てる処理を実装する
- 認証情報からリクエストの署名を付与する処理を実装する
- HTTP/S の通信を確立してデータを送受信する処理をライブラリを利用して実装する
- エラーがあった場合のハンドリングやリトライ処理を実装する
さらにテストをしたり、AWS の機能に更新があれば、それに追従した処理を都度書く必要もあるかもしれません。
ただし AWS SDK を活用してコードを書けば、今挙げたような処理は AWS SDK のコードに含まれているため、自分達で実装する必要がなくなります。
例えば AWS SDK for Python (Boto3) を利用した場合、これだけのコード で先程挙げた処理を実装することなく AWS のサービスエンドポイントへリクエストが送れます。
import boto3
# Create SQS client
sqs = boto3.client('sqs')
# List SQS queues
response = sqs.list_queues()
AWS SDK のコードを実行すると
AWS SDK に任せている処理はどのように実装されているか AWS SDK for Python (Boto3) でいくつか見てみましょう。執筆時点の Boto3 の最新バージョンである 1.27.64 を前提にしており、HTTP/S リクエストの組み立てなどは botocore パッケージ (執筆時点の最新バージョンは 1.24.64) のコードを利用しています。
- 認証情報を取得しているコードの一部はこちら »
Provider という名前が末尾に付いているいくつかのクラスで、環境変数・設定ファイル・インスタンスプロファイルから、アクセスキー ID とシークレットアクセスキーなどの認証情報を取得しているコードが書かれています。 - リクエスト署名をしているコードの一部はこちら »
Auth という名前が末尾に付いているいくつかのクラスで、認証情報からハッシュ関数を利用して、リクエストの署名プロセスを行っているコードが書かれています。 - リクエストを送ってリトライ処理などをしているコードの一部はこちら »
Endpoint クラスの _send_request メソッドで、リクエストの送信やリトライなどをするコードが書かれています。
これ以外にも SDK には多くのコードが含まれていて、自分達で実装しないで良い部分の多さを感じられますね ! このような処理が、AWS SDK が提供されている各プログラミング言語ごとに実装されていますので、興味のある方は自分が利用している言語の SDK を調査してみると新たな発見があるかもしれませんよ (๑•̀ᴗ- )✩
まとめ
普段はあまり意識することのない、AWS API を支えてくれているリクエスト署名や、AWS のツールを使うことの便利さを再認識できたところで、“中級編” はここまでです。次回は “上級編” として、AWS の API に関する小ネタをいくつか紹介する予定ですのでお楽しみに !
またプログラミングはある程度できるけど、今回紹介した AWS SDK の使い方を学んでみたいなという方は、Developing on AWS というトレーニングコースを受講してみてください。AWS SDK の基本的な使い方や AWS のサーバーレスサービスの基本を学び、演習では AWS SDK を利用したアプリケーションを構築していく内容なのでおすすめです !
最後に今回もクイズで知識の確認や腕試しをしてみましょう !
クイズ
問題
もしかすると知っていれば役に立つかもしれない、そんな内容のクイズを AWS CLI バージョン 2 の範囲で作ってみました ! 問題の下をスクロールすると正解がありますので、少し考えてから確認してみてください !
- AWS CLI の出力結果をクライアント側でフィルタリングするオプションは何でしょうか ?
- AWS CLI の --output で指定できる形式を全て挙げると何があるでしょうか ?
- AWS CLI の再試行 (retry) モードで指定できる形式を全て挙げると何があるでしょうか ?
↓ (正解はこちら) ↓
正解
1. --query
AWS CLI のクライアントフィルタリングについてはこちら
JMESPath 構文 を利用することで、かなり柔軟に出力結果から必要な情報だけを絞ることができるため、使いこなせるとシェルスクリプトを書くときなどに便利です !
2. json、yaml、yaml-stream、text、table
AWS CLI の出力形式についてはこちら
現時点では 5 つあります、用途に合わせて適切なものを選択しましょう!text 形式は awk コマンドと組み合わせるときに役立ちます!
3. レガシー、標準、アダプティブ
AWS CLI のリトライについてはこちら
レガシーと標準で、デフォルトの最大再試行回数や、再試行対象のエラー / 例外が異なります。AWS CLI バージョン 2 のデフォルトは標準再試行モードです。
このシリーズ記事のその他の記事はこちら
- 選択
- 初級編 ~ API の仕組みと利用方法を理解しよう
- 中級編 ~ リクエストの署名や CLI/SDK の中身を覗いてみる
- 上級編 ~ 新しい発見があるかも ? いろんな機能をご紹介 !
筆者プロフィール
杉本 圭太
アマゾン ウェブ サービス ジャパン合同会社
トレーニングサービス本部 テクニカルトレーナー
新しいことを知るのが好きなので、多くの人に「AWS を知るのは面白い ! もっと学んでみよう ! 活用しよう !」と思っていただけるよう工夫しながらテクニカルトレーナーをしています。
最近は面白い漫画が増えすぎていて、時間が足りなくて困っています。
AWS を無料でお試しいただけます