Quantcast
Channel: Bashタグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 2822

AWS-SDKをbashで自作してみる

$
0
0

概要

AWS-SDKをbashで作りました。
AWS-SDKやプログラミング環境の用意できないPCでも、AWSが扱えるようになります。

作るにあたって、以下の条件を守るようにしています。

  • プレーンなbash(今回はgitbash)を利用、標準のgitbashにないものは使わないこと
  • プログラミング言語は使わない。awk, sed, bashで解決すること
  • プロファイル情報はAWSと同じものを利用すること(~/.aws/credentials)

サービスごとに記事を開閉できるようにしていますので、必要なサービスだけ読んでいただければ。

構成図

draw_1.png

できあがったもの

SDKの全機能の動作確認はできないため、記事では以下の動作確認にとどめます。

  • DynamoDBのデータを操作
  • Lambdaを直接実行
  • SQSのデータを操作
  • STSから自身のプロファイル情報を取得

※なお、S3は違いが大きいため、今回のスクリプトでは対応しません。
 簡単な動作確認以上の試験はしていないので、利用は自己責任の範囲でお願いします。

利用の前に:プロファイルの用意

利用の際は、~/.aws/credentialsにプロファイル情報を作成しておいてください。
形式はテキスト、拡張子は不要です。

~/.aws/credentials
[default]
aws_access_key_id = AKIA**************************
aws_secret_access_key = *****************************

動作確認:DynamoDBのデータを操作

DynamoDBを操作する手順(クリックで開きます)
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"}}}

同じリクエストをAWS-SDK(boto3)で書いた場合

boto3
fromboto3importSessionSession(profile_name="default",region_name="ap-northeast-1").client(service_name="dynamodb").get_item(TableName="target_table",Key={"id":{"S":"key"}})

引数を変えて、データの登録、スキャン操作もできることを確認

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.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)…etc
POSTペイロード{"TableName":"target_table", "Key": {"id" : "S" : "key"}}

動作確認:Lambdaを直接実行

Lambdaを操作する手順(クリックで開きます)
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\""}

同じリクエストをAWS-SDK(boto3)で書いた場合

boto3
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のデータを操作

SQSを操作する手順(クリックで開きます)
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>{&quot;id&quot;:&quot;NewMessage&quot;}</Body></Message></ReceiveMessageResult><ResponseMetadata><RequestId>9e05f85b-4a27-5d6f-97c4-af723bb68a70</RequestId></ResponseMetadata></ReceiveMessageResponse>

メッセージの送信をboto3で書いた場合

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-05SQSはフォームデータ形式で送信される。
送信、受信などの操作の切り替えはActionで行う。

動作確認:STSから自身のプロファイル情報を取得

STSを操作する手順(クリックで開きます)
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>

同じ処理をboto3で書いた場合

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-15STSはフォームデータ形式で送信される。
操作の切り替えはActionで行う。

ソースコード

動かす場合は、aws-sdk-bash.shのファイル名で任意の場所に保存してください。
実行するコマンドは「動作確認」を参照してください。

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-sdk-bash.sh_署名情報の取得
# .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}
aws-sdk-bash.sh_呼出側
# アクセスキー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}
aws-sdk-bash.sh_UTC日時の取得
# 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}
aws-sdk-bash.sh_一時ファイルの作成
# 一時ファイルを作成、一時ファイルは終了時に削除する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}
aws-sdk-bash.sh_ヘッダを設定
# 送信する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では以下のように書きます。

HMAC-SHA256を取得
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}
aws-sdk-bash.sh_ハッシュ化
# 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が投げるペイロードとヘッダの形式は、サービスによってバラバラです。
公式にドキュメントはありませんので、自分で調べるしかありません。

サービスメソッドの指定方法ペイロード
LambdaURLパスJSON
DynamoDBヘッダJSON
SQSペイロードForm形式
STSペイロードForm形式

boto3+Wiresharkで調べる方法

そのまま通信すると暗号化されるので、ちょっと小細工を入れます。

暗号化を切って、HTTP通信にする
importboto3client=boto3.client("dynamodb",use_ssl=False)print(client.get_item(TableName="target_table",Key={"id":{"S":"key"}}))

use_sslをFalseにすると80ポートへの送信になります。
平文で通信するので、Wiresharkで読めるようになります。

capture_1.png

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を使います。

test.html
<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なら、右クリックでブラウザの「検証」を開くことで、ネットワークの通信データを確認できます。

lambda.png

ブラウザ固有のデータも含まれる、CORSが必要になる場合があるなどの違いはありますが、必要なデータはそろっています。
これを参考に、同じリクエストが飛ぶように設定すればOKです。


Viewing all articles
Browse latest Browse all 2822

Latest Images

Trending Articles