概要
AWS-SDKをbashで作りました。
AWS-SDKやプログラミング環境の用意できないPCでも、AWSが扱えるようになります。
作るにあたって、以下の条件を守るようにしています。
- プレーンなbash(今回はgitbash)を利用、標準のgitbashにないものは使わないこと
- プログラミング言語は使わない。awk, sed, bashで解決すること
- プロファイル情報はAWSと同じものを利用すること(~/.aws/credentials)
サービスごとに記事を開閉できるようにしていますので、必要なサービスだけ読んでいただければ。
構成図
できあがったもの
SDKの全機能の動作確認はできないため、記事では以下の動作確認にとどめます。
- DynamoDBのデータを操作
- Lambdaを直接実行
- SQSのデータを操作
- STSから自身のプロファイル情報を取得
※なお、S3は違いが大きいため、今回のスクリプトでは対応しません。
簡単な動作確認以上の試験はしていないので、利用は自己責任の範囲でお願いします。
利用の前に:プロファイルの用意
利用の際は、~/.aws/credentialsにプロファイル情報を作成しておいてください。
形式はテキスト、拡張子は不要です。
[default]
aws_access_key_id = AKIA**************************
aws_secret_access_key = *****************************
動作確認:DynamoDBのデータを操作
同じリクエストをAWS-SDK(boto3)で書いた場合 引数を変えて、データの登録、スキャン操作もできることを確認 リクエストパラメータの詳細DynamoDBを操作する手順(クリックで開きます)
./aws-sdk-bash.sh "default""ap-northeast-1""dynamodb""/"""\"content-type:application/x-amz-json-1.0;host:@;x-amz-date:@;x-amz-target:DynamoDB_20120810.GetItem"\"{\"TableName\": \"target_table\", \"Key\": {\"id\": {\"S\": \"key\"}}}"{"Item":{"entity":{"S":"string_data"},"id":{"S":"key"}}}
fromboto3importSessionSession(profile_name="default",region_name="ap-northeast-1").client(service_name="dynamodb").get_item(TableName="target_table",Key={"id":{"S":"key"}})
# データを入れる
./aws-sdk-bash.sh "default""ap-northeast-1""dynamodb""/"""\"content-type:application/x-amz-json-1.0;host:@;x-amz-date:@;x-amz-target:DynamoDB_20120810.PutItem"\"{\"TableName\": \"target_table\", \"Item\": {\"id\": {\"S\": \"newData\"}, \"entity\":{\"S\":\"new_string_data\"}}}"{}# データを入れた後、Scanで件数が増えていることを確認する
./aws-sdk-bash.sh "default""ap-northeast-1""dynamodb""/"""\"content-type:application/x-amz-json-1.0;host:@;x-amz-date:@;x-amz-target:DynamoDB_20120810.Scan"\"{\"TableName\": \"target_table\"}"{"Count":2,"Items":[{"entity":{"S":"new_string_data"},"id":{"S":"newData"}},{"entity":{"S":"string_data"},"id":{"S":"key"}}],"ScannedCount":2}
パラメータ名 上記リクエスト例の内容 備考 プロファイル名 default 認証名 リージョン ap-northeast-1 リージョン情報 サービス名 dynamodb サービス名。DynamoDB URIパス / エンドポイントのパス、DynamoDBではルートパス URLクエリ DynamoDBでは使用しない HTTPヘッダ content-type:application/x-amz-json-1.0;
host:@;
x-amz-date:@;
x-amz-target:DynamoDB_20120810.GetItem@はスクリプト側で付与する
x-amz-targetで実施する処理を指定できる。
取得(GetItem)
追加(PutItem)
スキャン(Scan)…etcPOSTペイロード {"TableName":"target_table", "Key": {"id" : "S" : "key"}}
動作確認:Lambdaを直接実行
同じリクエストをAWS-SDK(boto3)で書いた場合 リクエストパラメータLambdaを操作する手順(クリックで開きます)
./aws-sdk-bash.sh "default""ap-northeast-1""lambda""/2015-03-31/functions/sample_lambda/invocations"""\"host:@;x-amz-content-sha256:@;x-amz-date:@;x-amz-invocation-type:RequestResponse;x-amz-user-agent:aws-sdk-js/2.668.0 callback"\"{\"Message\":\"Hello\"}"{"statusCode": 200, "body": "\"Hello from Lambda! I am SAMPLE\""}
fromboto3importSessionimportjsonSession(profile_name="default",region_name="ap-northeast-1").client(service_name="lambda").invoke(FunctionName="sample_lambda",InvocationType="RequestResponse",Payload=json.dumps({"Message":"Hello"}))
パラメータ名 上記リクエスト例の内容 備考 プロファイル default 認証名 リージョン ap-northeast-1 リージョン情報 サービス名 lambda サービス名。Lambda URIパス /2015-03-31/functions/sample_lambda/invocations 実施する操作
ラムダ名「sample_lambda」に「invoke」コマンドを発行するURLクエリ Lambdaでは使用しない HTTPヘッダ host:@;
x-amz-content-sha256:@;
x-amz-date:@;
x-amz-invocation-type:RequestResponse;
x-amz-user-agent:aws-sdk-js/2.668.0 callback@のものはスクリプト側で付与する
応答あり同期処理のためRequestResponseを指定
UAはJavaScript版のUAを借用POSTペイロード {"Message":"Hello"}
動作確認:SQSのデータを操作
メッセージの送信をboto3で書いた場合 リクエストパラメータSQSを操作する手順(クリックで開きます)
# メッセージを送信
./aws-sdk-bash.sh "default""ap-northeast-1""sqs""/"""\"host:@;x-amz-content-sha256:@;x-amz-date:@;x-amz-user-agent:aws-sdk-js/2.668.0 callback"\"Action=SendMessage&MessageBody=%7B%22id%22%3A%22NewMessage%22%7D&QueueUrl=https%3A%2F%2Fsqs.ap-northeast-1.amazonaws.com%2F***********************%2Fsqs-send-request-test-0424&Version=2012-11-05"<?xml version="1.0"?><SendMessageResponse xmlns="http://queue.amazonaws.com/doc/2012-11-05/"><SendMessageResult><MessageId>013a849d-f344-4d90-a6be-e37302c6b029</MessageId><MD5OfMessageBody>abc832604cb20908715bca6f197c8945</MD5OfMessageBody></SendMessageResult><ResponseMetadata><RequestId>01e07779-a593-5162-b698-2050d59e1524</RequestId></ResponseMetadata></SendMessageResponse>
# 送信したメッセージを受信
./aws-sdk-bash.sh "default""ap-northeast-1""sqs""/"""\"host:@;x-amz-content-sha256:@;x-amz-date:@;x-amz-user-agent:aws-sdk-js/2.668.0 callback"\"Action=ReceiveMessage&QueueUrl=https%3A%2F%2Fsqs.ap-northeast-1.amazonaws.com%2F*****************%2Fsqs-send-request-test-0424&Version=2012-11-05"<?xml version="1.0"?><ReceiveMessageResponse xmlns="http://queue.amazonaws.com/doc/2012-11-05/"><ReceiveMessageResult><Message><MessageId>6335c486-d1b6-4478-84c4-40b92f5d655b</MessageId><ReceiptHandle>***********</ReceiptHandle><MD5OfBody>abc832604cb20908715bca6f197c8945</MD5OfBody><Body>{"id":"NewMessage"}</Body></Message></ReceiveMessageResult><ResponseMetadata><RequestId>9e05f85b-4a27-5d6f-97c4-af723bb68a70</RequestId></ResponseMetadata></ReceiveMessageResponse>
fromboto3importSessionimportjsonSession(profile_name="default",region_name="ap-northeast-1").client(service_name="sqs").send_message(QueueUrl="https://sqs.ap-northeast-1.amazonaws.com/**************/sqs-send-request-test-0424",MessageBody=json.dumps({"id":"NewMessage"}))
パラメータ名 上記リクエスト例の内容 備考 プロファイル default 認証名 リージョン ap-northeast-1 リージョン情報 サービス名 sqs サービス名。SQS URIパス / SQSでは使用しない URLクエリ SQSでは使用しない HTTPヘッダ host:@;
x-amz-content-sha256:@;
x-amz-date:@;
x-amz-user-agent:aws-sdk-js/2.668.0 callback@のものはスクリプト側で付与する
UAはJavaScript版のUAを借用POSTペイロード Action=SendMessage&MessageBody=%7B%22id%22%3A%22NewMessage%22%7D&QueueUrl=https%3A%2F%2Fsqs.ap-northeast-1.amazonaws.com%2F***********************%2Fsqs-send-request-test-0424&Version=2012-11-05 SQSはフォームデータ形式で送信される。
送信、受信などの操作の切り替えはActionで行う。
動作確認:STSから自身のプロファイル情報を取得
同じ処理をboto3で書いた場合 リクエストパラメータSTSを操作する手順(クリックで開きます)
# 自身の情報を取得する
./aws-sdk-bash.sh "default""ap-northeast-1""sts""/"""\"host:@;x-amz-content-sha256:@;x-amz-date:@;x-amz-user-agent:aws-sdk-js/2.668.0 callback"\"Action=GetCallerIdentity&Version=2011-06-15"<GetCallerIdentityResponse xmlns="https://sts.amazonaws.com/doc/2011-06-15/"><GetCallerIdentityResult>
<Arn>arn:aws:iam::*****************:user/**************</Arn>
<UserId>*****************</UserId>
<Account>*****************</Account>
</GetCallerIdentityResult>
<ResponseMetadata>
<RequestId>1703851c-5b55-49b5-8bcf-130ab4dbb6f2</RequestId>
</ResponseMetadata>
</GetCallerIdentityResponse>
fromboto3importSessionSession(profile_name="default",region_name="ap-northeast-1").client(service_name="sts").get_caller_identity()
パラメータ名 上記リクエスト例の内容 備考 プロファイル default 認証名 リージョン ap-northeast-1 リージョン情報 サービス名 sts サービス名。STS URIパス / SQSでは使用しない URLクエリ SQSでは使用しない HTTPヘッダ host:@;
x-amz-content-sha256:@;
x-amz-date:@;
x-amz-user-agent:aws-sdk-js/2.668.0 callback@のものはスクリプト側で付与する
UAはJavaScript版のUAを借用POSTペイロード Action=GetCallerIdentity&Version=2011-06-15 STSはフォームデータ形式で送信される。
操作の切り替えはActionで行う。
ソースコード
動かす場合は、aws-sdk-bash.shのファイル名で任意の場所に保存してください。
実行するコマンドは「動作確認」を参照してください。
#!/bin/bash# プロファイルのパスは固定CREDENTIALS_FILE=~/.aws/credentials
# アルゴリズムは署名v4、hmacのSHA256ALGORITHM='AWS4-HMAC-SHA256'# スクリプトの引数_INPUT_PROFILE_NAME=$1_INPUT_REGION=$2_INPUT_SERVICE=$3_INPUT_CANONICAL_URI=$4_INPUT_CANONICAL_QUERY_STRING=$5_INPUT_OPTIONAL_HEADERS=$6_INPUT_PAYLOAD=$7#エンドポイントの作成METHOD=POST
PROTOCOL=https
HOST_NAME=${_INPUT_SERVICE}.${_INPUT_REGION}.amazonaws.com
# .awsファイルからプロファイル情報を取得する# 入力 プロファイルのパス、プロファイル名、プロファイルのキー名、ファイルの読込行数
get_credentials (){_CREDENTIALS_FILE=$1;_PROFILE_NAME=$2;_KEY_NAME=$3;_READ_LENGTH=$4;# PROFILE_NAMEの行番号を取得するPROFILE_IDX=`nl$_CREDENTIALS_FILE | grep$_PROFILE_NAME | head-n 1 | awk'{print $1}'`PROFILE_IDX_END=`expr$PROFILE_IDX + $_READ_LENGTH`# アクセスキーIDを取得するRESULT=`cat$CREDENTIALS_FILE | sed-n"${PROFILE_IDX},${PROFILE_IDX_END}p" | grep"=" | grep${_KEY_NAME} | \tr-d" " | sed"s/=/ /g" | awk'{print $2}' | \head-n 1`echo-n$RESULT}# SHA256でハッシュを作成する# 入力 メッセージ:ファイル、キー:なし# 出力 ハッシュ:hex形式
create_digest_from_file (){cat$1 | openssl dgst -sha256 | grep stdin | awk'{print $2}'}# HMAC-SHA256でハッシュを作成する# 入力 メッセージ:テキスト、キー:テキスト# 出力 ハッシュ:hex形式
sign_from_string (){echo-n$2 | openssl dgst -sha256-hmac$1 | grep stdin | awk'{print $2}'}# HMAC-SHA256でハッシュを作成する# 入力 メッセージ:テキスト、キー:hex形式# 出力 ハッシュ:hex形式
sign_from_string_with_hex_key (){echo-n$2 | openssl dgst -sha256-mac hmac -macopt hexkey:$1 | grep stdin | awk'{print $2}'}# HMAC-SHA256でハッシュを作成する# 入力 メッセージ:ファイル、キー:hex形式# 出力 ハッシュ:hex形式
sign_from_file_with_hex_key (){cat$2 | openssl dgst -sha256-mac hmac -macopt hexkey:$1 | grep stdin | awk'{print $2}'}# 署名v4の基本情報(アクセスキーID、送信日時、リージョン、サービス名)をHMAC-SHA256でハッシュ化する
get_signature_key (){TEMP_DATE=`sign_from_string AWS4$1$2`TEMP_REGION=`sign_from_string_with_hex_key $TEMP_DATE$3`TEMP_SERVICE=`sign_from_string_with_hex_key $TEMP_REGION$4`
sign_from_string_with_hex_key $TEMP_SERVICE'aws4_request'}# アクセスキーIDを取得するACCESS_KEY_ID=`get_credentials $CREDENTIALS_FILE${_INPUT_PROFILE_NAME} aws_access_key_id 2`# シークレットアクセスキーを取得するSECRET_ACCESS_KEY=`get_credentials $CREDENTIALS_FILE${_INPUT_PROFILE_NAME} aws_secret_access_key 2`# UTC日時を取得する(フォーマット例:2020/12/31T12:34:50のとき AMZ_DATE:20201231T123450Z DATE_STAMP:20201231)UTC_DATE=`date-Iseconds-u | sed"s/+/ /g" | awk'{print $1 "Z"}'`AMZ_DATE=`echo-n$UTC_DATE | sed"s/-//g" | sed"s/://g"`DATE_STAMP=`echo-n$UTC_DATE | sed"s/-//g" | sed"s/T/ /g" | awk'{print $1}'`# 一時ファイルを作成、一時ファイルは終了時に削除するTEMP_HEADERS=`mktemp`TEMP_CANONICAL_REQUEST=`mktemp`TEMP_STRING_TO_SIGN=`mktemp`TEMP_PAYLOAD=`mktemp`trap"rm -f $TEMP_HEADERS; rm -f $TEMP_CANONICAL_REQUEST; rm -f $TEMP_STRING_TO_SIGN; rm -f $TEMP_PAYLOAD" EXIT
# ペイロードを一時ファイルに書き写すecho-n${_INPUT_PAYLOAD}>$TEMP_PAYLOAD# 送信するBODYデータからSHA256のハッシュ(キーなし)を作成するPAYLOAD_HASH=`create_digest_from_file $TEMP_PAYLOAD`# 送信ヘッダを設定するecho-n"${_INPUT_OPTIONAL_HEADERS}" | sed"s#x-amz-content-sha256:@#x-amz-content-sha256:${PAYLOAD_HASH}#" | sed"s#host:@#host:${HOST_NAME}#" | sed"s#x-amz-date:@#x-amz-date:${AMZ_DATE}#" | xargs -d";"-r-I @ echo @ >>$TEMP_HEADERSSIGNED_HEADERS=`echo-n"${_INPUT_OPTIONAL_HEADERS}" | xargs -d";"-r-I @ echo";@" | sed's/:.*//'`SIGNED_HEADERS=`echo-n$SIGNED_HEADERS | sed's/ //g' | sed's/^;//'`# 正規リクエストを作成する# リクエストの情報を標準化する# 参考情報:タスク 1: 署名バージョン 4 の正規リクエストを作成する# https://docs.aws.amazon.com/ja_jp/general/latest/gr/sigv4-create-canonical-request.htmlecho$METHOD>$TEMP_CANONICAL_REQUESTecho$_INPUT_CANONICAL_URI>>$TEMP_CANONICAL_REQUESTecho$_INPUT_CANONICAL_QUERY_STRING>>$TEMP_CANONICAL_REQUESTcat$TEMP_HEADERS>>$TEMP_CANONICAL_REQUESTecho"">>$TEMP_CANONICAL_REQUESTecho$SIGNED_HEADERS>>$TEMP_CANONICAL_REQUESTecho-n$PAYLOAD_HASH>>$TEMP_CANONICAL_REQUEST# 署名文字列を作成する# 参考情報:タスク 2: 署名バージョン 4 の署名文字列を作成する# https://docs.aws.amazon.com/ja_jp/general/latest/gr/sigv4-create-string-to-sign.htmlCANONICAL_REQUEST_HASH=`create_digest_from_file $TEMP_CANONICAL_REQUEST`CREDENTIAL_SCOPE=${DATE_STAMP}/${_INPUT_REGION}/${_INPUT_SERVICE}/"aws4_request"# 署名のアルゴリズム、署名の有効範囲を設定するecho${ALGORITHM}>$TEMP_STRING_TO_SIGNecho${AMZ_DATE}>>$TEMP_STRING_TO_SIGNecho${CREDENTIAL_SCOPE}>>$TEMP_STRING_TO_SIGNecho-n${CANONICAL_REQUEST_HASH}>>$TEMP_STRING_TO_SIGN# 署名のため、Signatureを計算する# 参考情報:タスク 3: AWS署名バージョン 4 の署名を計算する# https://docs.aws.amazon.com/ja_jp/general/latest/gr/sigv4-calculate-signature.htmlSIGNING_KEY=`get_signature_key ${SECRET_ACCESS_KEY}${DATE_STAMP}${_INPUT_REGION}${_INPUT_SERVICE}`SIGNATURE=`sign_from_file_with_hex_key ${SIGNING_KEY}${TEMP_STRING_TO_SIGN}`# 署名をHTTPリクエストのヘッダに設定する# 参考情報:タスク 4: HTTP リクエストに署名を追加する# https://docs.aws.amazon.com/ja_jp/general/latest/gr/sigv4-add-signature-to-request.htmlAUTHORIZATION_HEADER="${ALGORITHM} Credential=${ACCESS_KEY_ID}/${CREDENTIAL_SCOPE}, SignedHeaders=${SIGNED_HEADERS}, Signature=${SIGNATURE}"# HTTPリクエストを送信する。QUERY_STRING=${_INPUT_CANONICAL_QUERY_STRING}if[!$QUERY_STRING=""];then
QUERY_STRING="?${QUERY_STRING}"fi
curl -s-X POST ${PROTOCOL}://${HOST_NAME}${_INPUT_CANONICAL_URI}${QUERY_STRING}-d @$TEMP_PAYLOAD-H @$TEMP_HEADERS-H"Authorization: ${AUTHORIZATION_HEADER}"
ソースコードの解説
処理の流れは以下の通りです。
様々な言語のために様々なSDKがありますが、この4つさえ実装すればAWS-SDKと同じことができます。
\def\の{\unicode[serif]{x306E}}
\bbox[8px, border: 2px solid gray]{\rlap{\tt 1.\quad プロファイル情報\の取得}\hspace{80mm}}
\triangledown
\def\の{\unicode[serif]{x306E}}
\bbox[8px, border: 2px solid gray]{\rlap{\tt 2.\quad パラメータ\の取得と作成}\hspace{80mm}}
\triangledown
\bbox[8px, border: 2px solid gray]{\rlap{\tt 3.\quad パラメータをSHA256でハッシュ化}\hspace{80mm}}
\triangledown
\bbox[8px, border: 2px solid gray]{\rlap{\tt 4.\quad curlでHTTPリクエストを送信}\hspace{80mm}}
バージョン4の署名については、AWS公式の「完全な 署名バージョン 4 署名プロセスの例 (Python)」を参考にしてください。
以降、それぞれの処理について解説します。
1. プロファイル情報の取得
~/.aws/credentialsからプロファイル情報を取得する処理です。
\def\の{\unicode[serif]{x306E}}
\begin{array}{l|l}
\hline
コマンド & 目的 \\
\hline
{\tt nl} & 行番号 + ファイル\の内容を表示します。\\
& 行番号\の出るcatです。\\
\hdashline
{\tt sed\;\text{-n}} & 指定行\のテキストを取得します。\\
\hline
\end{array}
「nl + grep プロファイル名」で、取得するプロファイル名の行数を取得します。
「sed -n」でプロファイル名の後ろの行が取れるため、そこからアクセスキーIDとシークレットアクセスキーを取ります。
# .awsファイルからプロファイル情報を取得する# 入力 プロファイルのパス、プロファイル名、プロファイルのキー名、プロファイルの読込行数
get_credentials (){_CREDENTIALS_FILE=$1;_PROFILE_NAME=$2;_KEY_NAME=$3;_READ_LENGTH=$4;# PROFILE_NAMEの行番号を取得するPROFILE_IDX=`nl$_CREDENTIALS_FILE | grep$_PROFILE_NAME | head-n 1 | awk'{print $1}'`PROFILE_IDX_END=`expr$PROFILE_IDX + $_READ_LENGTH`# アクセスキーIDを取得するRESULT=`cat$CREDENTIALS_FILE | sed-n"${PROFILE_IDX},${PROFILE_IDX_END}p" | grep"=" | grep${_KEY_NAME} | \tr-d" " | sed"s/=/ /g" | awk'{print $2}' | \head-n 1`echo-n$RESULT}
# アクセスキーIDを取得するACCESS_KEY_ID=`get_credentials $CREDENTIALS_FILE${_INPUT_PROFILE_NAME} aws_access_key_id 2`# シークレットアクセスキーを取得するSECRET_ACCESS_KEY=`get_credentials $CREDENTIALS_FILE${_INPUT_PROFILE_NAME} aws_secret_access_key 2`
2.パラメータの取得と作成
日時の取得
日時はUTCで取得します。
時分秒を含むフォーマットと、時分秒を含まないフォーマットの文字列を作成します。
\def\の{\unicode[serif]{x306E}}
\begin{array}{l|l}
\hline
値 & フォーマット \\
\hline
{\tt \text{AMZ_DATE}} & {\tt YYYYMMDDTHHMMSS}\のフォーマットで指定します。\\
& 後ろには{\tt UTC}を指す{\tt Z}をつけます。\\
& コロン、ハイフン\のような記号は入れません。\\
\hdashline
{\tt \text{DATE_STAMP}} & {\tt YYYYMMDD}\のフォーマットで指定します。\\
& 日付だけを指定します。\\
& コロン、ハイフン\のような記号は入れません。\\
\hline
\end{array}
# UTC日時を取得する(フォーマット例:2020/12/31T12:34:50のとき AMZ_DATE:20201231T123450Z DATE_STAMP:20201231)UTC_DATE=`date-Iseconds-u | sed"s/+/ /g" | awk'{print $1 "Z"}'`AMZ_DATE=`echo-n$UTC_DATE | sed"s/-//g" | sed"s/://g"`DATE_STAMP=`echo-n$UTC_DATE | sed"s/-//g" | sed"s/T/ /g" | awk'{print $1}'`
テキストを扱う
文字列は一時ファイルに書き出して扱います。
一時ファイルはプロセスが終了したタイミングで削除されます。
bashの変数で扱うこともできますが、ファイルで文字列を扱うほうが実装しやすくなります。
署名情報は、空行、末尾の改行、データの出現順序が厳密に決まっており、一つでもずれると署名が通らなくなります。
\def\の{\unicode[serif]{x306E}}
\begin{array}{l|l}
\hline
コマンド & 目的 \\
\hline
{\tt mktemp} & /tmpに一時ファイルを作成します。\\
& ファイル名は重複なくつけられ、\\
& アクセス権限も適切に設定されます。\\
\hdashline
{\tt trap} & 特定\のタイミングでコマンドを実行します。\\
& プロセス終了時に一時ファイルを削除します。\\
\hline
\end{array}
# 一時ファイルを作成、一時ファイルは終了時に削除するTEMP_HEADERS=`mktemp`TEMP_CANONICAL_REQUEST=`mktemp`TEMP_STRING_TO_SIGN=`mktemp`TEMP_PAYLOAD=`mktemp`trap"rm -f $TEMP_HEADERS; rm -f $TEMP_CANONICAL_REQUEST; rm -f $TEMP_STRING_TO_SIGN; rm -f $TEMP_PAYLOAD" EXIT
パラメータの置換
リクエストの時にユーザーが意識しないパラメータがあります。
そういったパラメータは、@で値を省略することで、スクリプト側に設定させます。
\begin{array}{ll}
ユーザーが設定するパラメータ & {\tt \text{x-amz-date:@}} \\
実際に送信されるパラメータ & {\tt \text{x-amz-date:20200504T145432Z}}\\
& \\
\end{array}
\def\の{\unicode[serif]{x306E}}
\begin{array}{l|l}
\hline
ヘッダ\のキー & 設定される値 \\
\hline
{\tt \text{x-amz-content-sha256}} & ペイロード\のハッシュ値\\
\hdashline
{\tt host} & {\tt AWSエンドポイント\のURL}\\
\hdashline
{\tt \text{x-amz-date}} & リクエストした日時\\
\hline
\end{array}
# 送信するBODYデータからSHA256のハッシュ(キーなし)を作成するPAYLOAD_HASH=`create_digest_from_file $TEMP_PAYLOAD`# 送信ヘッダを設定するecho-n"${_INPUT_OPTIONAL_HEADERS}" | sed"s#x-amz-content-sha256:@#x-amz-content-sha256:${PAYLOAD_HASH}#" | sed"s#host:@#host:${HOST_NAME}#" | sed"s#x-amz-date:@#x-amz-date:${AMZ_DATE}#" | xargs -d";"-r-I @ echo @ >>$TEMP_HEADERSSIGNED_HEADERS=`echo-n"${_INPUT_OPTIONAL_HEADERS}" | xargs -d";"-r-I @ echo";@" | sed's/:.*//'`SIGNED_HEADERS=`echo-n$SIGNED_HEADERS | sed's/ //g' | sed's/^;//'`
3.ハッシュ化
HMAC-SHA256の取得は、Pythonでは以下のように書きます。
defsign(key,msg):returnhmac.new(key,msg.encode("utf-8"),hashlib.sha256).digest()defgetSignatureKey(key,dateStamp,regionName,serviceName):kDate=sign(("AWS4"+key).encode("utf-8"),dateStamp)kRegion=sign(kDate,regionName)kService=sign(kRegion,serviceName)kSigning=sign(kService,"aws4_request")returnkSigning
同じことをbashで実現するには、opensslを使います。
\def\の{\unicode[serif]{x306E}}
\begin{array}{l|l}
\hline
コマンド & 目的 \\
\hline
{\tt openssl\;dgst\;\text{-}sha256} & テキストを{\tt SHA256}でハッシュ化します。\\
& 結果は16進数になります。\\
\hdashline
{\tt openssl\;dgst\;\text{-}sha256} & テキストを{\tt SHA256}でハッシュ化します。\\
\qquad{\tt \text{-}hmac\;(key)} & プレーンテキスト\のキーを指定します。\\
& 結果は16進数になります。\\
\hdashline
{\tt openssl\;dgst\;\text{-}sha256} & テキストを{\tt SHA256}でハッシュ化します。\\
\qquad{\tt \text{-}mac\;hmac} & 16進数\のキーを指定します。\\
\qquad{\tt \text{-}macopt\;hexkey:(key)}& 結果は16進数になります。\\
\hline
\end{array}
# SHA256でハッシュを作成する# 入力 メッセージ:ファイル、キー:なし# 出力 ハッシュ:hex形式
create_digest_from_file (){cat$1 | openssl dgst -sha256 | grep stdin | awk'{print $2}'}# HMAC-SHA256でハッシュを作成する# 入力 メッセージ:テキスト、キー:テキスト# 出力 ハッシュ:hex形式
sign_from_string (){echo-n$2 | openssl dgst -sha256-hmac$1 | grep stdin | awk'{print $2}'}# HMAC-SHA256でハッシュを作成する# 入力 メッセージ:テキスト、キー:hex形式# 出力 ハッシュ:hex形式
sign_from_string_with_hex_key (){echo-n$2 | openssl dgst -sha256-mac hmac -macopt hexkey:$1 | grep stdin | awk'{print $2}'}# HMAC-SHA256でハッシュを作成する# 入力 メッセージ:ファイル、キー:hex形式# 出力 ハッシュ:hex形式
sign_from_file_with_hex_key (){cat$2 | openssl dgst -sha256-mac hmac -macopt hexkey:$1 | grep stdin | awk'{print $2}'}# 署名v4の基本情報(アクセスキーID、送信日時、リージョン、サービス名)をHMAC-SHA256でハッシュ化する# PythonのgetSignatureKeyと同じ処理
get_signature_key (){TEMP_DATE=`sign_from_string AWS4$1$2`TEMP_REGION=`sign_from_string_with_hex_key $TEMP_DATE$3`TEMP_SERVICE=`sign_from_string_with_hex_key $TEMP_REGION$4`
sign_from_string_with_hex_key $TEMP_SERVICE'aws4_request'}
AWS-SDKのリクエストの調べ方
AWS-SDKが投げるペイロードとヘッダの形式は、サービスによってバラバラです。
公式にドキュメントはありませんので、自分で調べるしかありません。
サービス | メソッドの指定方法 | ペイロード |
---|---|---|
Lambda | URLパス | JSON |
DynamoDB | ヘッダ | JSON |
SQS | ペイロード | Form形式 |
STS | ペイロード | Form形式 |
boto3+Wiresharkで調べる方法
そのまま通信すると暗号化されるので、ちょっと小細工を入れます。
importboto3client=boto3.client("dynamodb",use_ssl=False)print(client.get_item(TableName="target_table",Key={"id":{"S":"key"}}))
use_sslをFalseにすると80ポートへの送信になります。
平文で通信するので、Wiresharkで読めるようになります。
Wiresharkを見ると、送信しているデータが以下の通りだとわかります。
POST / HTTP/1.1
Host: dynamodb.ap-northeast-1.amazonaws.com
Accept-Encoding: identity
X-Amz-Target: DynamoDB_20120810.GetItem
Content-Type: application/x-amz-json-1.0
User-Agent: Boto3/1.12.43 Python/3.8.2 Windows/10 Botocore/1.15.43
X-Amz-Date: 20200501T213154Z
Authorization: AWS4-HMAC-SHA256 Credential=AKIA**********/20200501/ap-northeast-1/dynamodb/aws4_request, SignedHeaders=content-type;host;x-amz-date;x-amz-target, Signature=***********************************************************
Content-Length: 58
{"TableName": "target_table", "Key": {"id": {"S": "key"}}}
同じリクエストが飛ぶようにデータを設定すればOKです。
なお、一部のサービスではuse_sslが使えません。たとえばLambdaやMQTTがそうです。
HTTPSにしか対応しないサービスでポート80につなごうとすると、レスポンスが返らずにタイムアウトします。
※SSL無しで通信できるかどうかは、AWSに「サービス エンドポイントとクォータの表」があります。
エンドポイントのプロトコルがHTTP or HTTPSになっているものは通信できます。
ブラウザの検証機能で調べる方法
SSLが必要なサービスでペイロードを見る場合は、javascript版のSDKを使います。
<scriptsrc="https://sdk.amazonaws.com/js/aws-sdk-2.668.0.min.js"></script>
<scripttype="text/javascript">AWS.config.update({accessKeyId:'AKIA******************',secretAccessKey:'**********************************'});AWS.config.region='ap-northeast-1';letlambda=newAWS.Lambda();letparams={FunctionName:'sample_lambda',InvocationType:'RequestResponse',Payload:JSON.stringify({"Message":"Hello"})};lambda.invoke(params,(err,data)=>console.log(JSON.parse(data.Payload)));</script>
HTMLファイルに確認したい処理を書いた後、ブラウザで開きます。
Chromeなら、右クリックでブラウザの「検証」を開くことで、ネットワークの通信データを確認できます。
ブラウザ固有のデータも含まれる、CORSが必要になる場合があるなどの違いはありますが、必要なデータはそろっています。
これを参考に、同じリクエストが飛ぶように設定すればOKです。