「Amazon API Gateway」から「Lambda」を通さずに「S3」へアクセスできるようにしてみる( `aws-cli` で構築)
はじめに
AWSを利用したサーバレスな事例が増えてきました。
例えば以下のような構成の事例がWeb上で掲載されています。
「API Gateway」 から 「Lambda」 を経由して 「DynamoDB」あるいは「S3」 にアクセスする例は、様々な方がWeb上に掲載しています。
「API Gateway」 から 「S3」 へ直接アクセスするための例はあまり見つからず、またあったとしても AWS-CLI を使って設定している例が見つからなかったため、整理してみました。
ちなみに設定した内容はAmazonが提供している以下のドキュメントを参考にしています。こちらのドキュメントは「AWS マネージメントコンソール」からポチポチ操作していく内容となっています。
作成する「API」概要
- S3にアクセスできる。(今回は読み取りのみ)
- アクセス用URLは
https://<host>/<S3-BUCKET-NAME>/<S3-OBJECT-KEY>
の形式とする。
準備
aws-cli
コマンドが利用できるjq
コマンドが利用できる
ja
はなくてもよいのですが、今回各AWS構成要素を作成した際のJSONレスポンスをパースしてできる限りコマンドラインのみの操作としたかったので利用してみました。
検証環境
$ bash -version
GNU bash, バージョン 5.0.2(1)-release (x86_64-apple-darwin18.2.0)
$ aws --version
aws-cli/1.16.120 Python/3.7.2 Darwin/18.2.0 botocore/1.12.110
$ jq --version
jq-1.6
手順
デプロイ直前までの流れは以下の通り。
(デプロイは次回実施してみる。)
- IAMの作成
- S3バケットの作成
- 「REST API」の作成
- 「REST API リソース」の作成
- 「REST API リソース」に対するメソッドの作成
- リソースに対してリクエスト、レスポンス設定を行う
- テスト
セットアップ時に事前に決めておかないといけない内容がいくつかあったため表にまとめておきました。
セットアップ手順は非常に長いですが 事前に決めておかないといけない情報は意外と少ないです。
設定項目(当エントリで登場する環境変数名) | 目的 | 設定値 |
---|---|---|
IAM_ROLE_NAME | 「API Gateway」に設定するロール名 | g-test-role-s3-full-access |
S3_BUCKET_NAME | アクセス対象の「S3バケット名」 | g-test-bucket |
APIGATEWAY_RESTAPI_NAME | 「REST API」名 | g-test-restapi |
AWS_DEFAULT_REGION | 各種設定を行う「リージョン」名 | ap-northeast-1 |
IAMの作成
まずは 「API Gateway」 に割り当てるIAMロールを作成します。
「API Gateway」かS3にアクセスできるようにします。
# IAMロール名は以下の名前で作成します。
$ IAM_ROLE_NAME='g-test-role-s3-full-access'
# まずは「API Gateway」の初期ポリシーファイルを作成
$ cat <<'EOF' >${IAM_ROLE_NAME}-policy.json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "apigateway.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
EOF
# 作成したポリシーファイルを元に、ロールを作成。
# 同時にIAM ROLE IDを保存しておきます。
$ IAM_ROLE_ID=$(
aws iam create-role \
--role-name "${IAM_ROLE_NAME}" \
--assume-role-policy-document file://${IAM_ROLE_NAME}-policy.json | jq -r '.Role.RoleId'
)
# 動作確認
$ echo ${IAM_ROLE_ID}
加えて、「S3」へのフルアクセスを付与しておきます。 (実サービスに適用する際には注意してください!)
$ aws iam attach-role-policy \
--role-name "${IAM_ROLE_NAME}" \
--policy-arn arn:aws:iam::aws:policy/AmazonS3FullAccess
S3バケットの作成
アクセスする対象の「S3バケット」を作成します。
$ S3_BUCKET_NAME=g-test-bucket
# S3バケット作成
$ aws s3 mb s3://${S3_BUCKET_NAME}
「REST API」の作成
いよいよ「REST API」を作成していきます。
$ APIGATEWAY_RESTAPI_NAME=g-test-restapi
# REST API IDを保存しておきます
$ APIGATEWAY_REST_API_ID=$(
aws apigateway create-rest-api \
--name "${APIGATEWAY_RESTAPI_NAME}" | jq -r '.id'
)
# 動作確認
$ echo "${APIGATEWAY_REST_API_ID}"
「REST API リソース」の作成
次に、作成した「REST API」に 「リソース」 を割り当てます
「リソース」 とはURLのパスのことです。(僕はそのように解釈しました。)
URLのパスですので、階層構造があります。
予めルートリソース( = "/"
) は作成されています。
自分でリソースを作成する際には親となるリソースを指定しなければなりません。
はじめに記載していたとおり、今回作成したいパスは
- アクセス用URLは
https://<host>/<S3-BUCKET-NAME>/<S3-OBJECT-KEY>
の形式とする。
としていたので、ルートリソースの他に2つ登録が必要です。
# 「ルート リソース ID」を保存しておきます
$ APIGATEWAY_RESOURCE_ID_ROOT=$(
aws apigateway get-resources \
--rest-api-id "$APIGATEWAY_REST_API_ID" | jq -r '.items[0].id'
)
# 「リソース ID」を保存しておきます
$ APIGATEWAY_RESOURCE_ID_S3_BUCKET_NAME=$(
aws apigateway create-resource \
--rest-api-id "${APIGATEWAY_REST_API_ID}" \
--parent-id "${APIGATEWAY_RESOURCE_ID_ROOT}" \
--path-part '{s3_bucket_name}' | jq -r '.id'
)
# 「リソース ID」を保存しておきます
$ APIGATEWAY_RESOURCE_ID_S3_OBJECT_KEY=$(
aws apigateway create-resource \
--rest-api-id "${APIGATEWAY_REST_API_ID}" \
--parent-id "${APIGATEWAY_RESOURCE_ID_S3_BUCKET_NAME}" \
--path-part '{s3_object_key}' | jq -r '.id'
)
# 動作確認
$ echo "${APIGATEWAY_RESOURCE_ID_S3_BUCKET_NAME}"
$ echo "${APIGATEWAY_RESOURCE_ID_S3_OBJECT_KEY}"
「REST API リソース」に対するメソッドの作成
作成した「リソース」がどんな 「HTTPリクエストメソッド」 を受け付けられるかを設定していきます。
今回は読み取りのみを設定したいので、 GET
メソッドだけ作成します。
GET
メソッドの設定対象のリソース(パス)は /{s3_bucket_name}/{s3_object_key}
です。
メソッド作成時には --request-parameters
を設定しないとパスを分解したときのパラメータ値が正しく取得できないことに注意。僕はこれでまる一日近くハマりました。
( AWS-CLI上はエラーが出ないのですが、後々「統合リクエスト」設定でCUI/GUIいずれでも操作できなくなってしまいます。仕様? )
# REST API リソース に対してGET可能とする。Gateway外からCallする場合にはAPIキーが必須とする。
aws apigateway put-method \
--rest-api-id "${APIGATEWAY_REST_API_ID}" \
--resource-id "${APIGATEWAY_RESOURCE_ID_S3_OBJECT_KEY}" \
--http-method GET \
--authorization-type NONE \
--request-parameters "method.request.path.s3_bucket_name=true,method.request.path.s3_object_key=true" \
--api-key-required \
;
リソースに対してリクエスト、レスポンス設定を行う
もう一息。
「GET
メソッド」が実行されたときに、「API Gateway」がS3と通信する方法について設定を行います。
パスとして受け取った s3_bucket_name
/ s3_object_key
の値を S3 に対して引き渡すための設定も行っています。
aws apigateway put-method-response \
--rest-api-id "${APIGATEWAY_REST_API_ID}" \
--resource-id "${APIGATEWAY_RESOURCE_ID_S3_OBJECT_KEY}" \
--http-method GET \
--status-code 200 \
--response-models '{"application/json": "Empty"}'
aws apigateway update-method-response \
--rest-api-id "${APIGATEWAY_REST_API_ID}" \
--resource-id "${APIGATEWAY_RESOURCE_ID_S3_OBJECT_KEY}" \
--http-method GET \
--status-code 200 \
--patch-operations op="add",path="/responseParameters/method.response.header.Content-Type",value="false"
aws apigateway put-method-response \
--rest-api-id "${APIGATEWAY_REST_API_ID}" \
--resource-id "${APIGATEWAY_RESOURCE_ID_S3_OBJECT_KEY}" \
--http-method GET \
--status-code 400 \
--response-models '{"application/json": "Empty"}'
aws apigateway put-method-response \
--rest-api-id "${APIGATEWAY_REST_API_ID}" \
--resource-id "${APIGATEWAY_RESOURCE_ID_S3_OBJECT_KEY}" \
--http-method GET \
--status-code 500 \
--response-models '{"application/json": "Empty"}'
aws apigateway put-integration \
--rest-api-id "${APIGATEWAY_REST_API_ID}" \
--resource-id "${APIGATEWAY_RESOURCE_ID_S3_OBJECT_KEY}" \
--http-method GET \
--type AWS \
--integration-http-method GET \
--uri "arn:aws:apigateway:${AWS_DEFAULT_REGION}:s3:path/{s3_bucket_name}/{s3_object_key}" \
--credentials $(
aws iam get-role \
--role-name "${IAM_ROLE_NAME}" | jq -r '.Role.Arn'
) \
--request-parameters 'integration.request.path.s3_bucket_name=method.request.path.s3_bucket_name,integration.request.path.s3_object_key=method.request.path.s3_object_key'
aws apigateway put-integration-response \
--rest-api-id "${APIGATEWAY_REST_API_ID}" \
--resource-id "${APIGATEWAY_RESOURCE_ID_S3_OBJECT_KEY}" \
--http-method GET \
--status-code 200 \
--response-templates '{"application/json": ""}'
テスト
テストデータの作成
対象のバケットに、 test.json
というテストファイルを作成しておいておきます。
$ echo '{ "msg": "hello" }' > test.json
$ aws s3 cp test.json s3://${S3_BUCKET_NAME}
upload: ./test.json to s3://g-test-bucket/test.json
REST API コール
APIのコールが正しく行えるかを「AWS マネージメントコンソール」からテストしてみます。
↓
「クエリ文字列」の部分の表示が気持ち悪い。何かミスっていそうな気がするが先に進む。
↓
正しく動作していることがわかりました。
AWS-CLIを使ってコールしてみる
「AWS マネージメントコンソール」を使わずに、「AWS-CLI」を使ってコールすることもできます。
$ aws apigateway test-invoke-method \
--rest-api-id "${APIGATEWAY_REST_API_ID}" \
--resource-id "${APIGATEWAY_RESOURCE_ID_S3_OBJECT_KEY}" \
--http-method GET \
--path-with-query-string "${S3_BUCKET_NAME}/test.json" \
;
{
"status": 200,
"body": "{ \"msg\": \"hello\" }\n",
"headers": {
"Content-Type": "application/json",
"X-Amzn-Trace-Id": "Root=1-5c8c8f47-bd5e7301b636a01d36c505ce"
},
"multiValueHeaders": {
"Content-Type": [
"application/json"
],
"X-Amzn-Trace-Id": [
"Root=1-5c8c8f47-bd5e7301b636a01d36c505ce"
]
},
...(省略)...
HTTPレスポンスコードが 200 、 "body" にもJSONが正しく出力されています。
【2019-03-16 追記】
一連の処理をシェルスクリプト化して Github で公開しました。
ひとこと
一部表示が怪しい部分がありましたがなんとか設定できました。
次回やり残しは以下の通り。
- デプロイ
POST
メソッドの設定
ディスカッション
コメント一覧
まだ、コメントがありません