メモ
・リソースポリシーや認証等にて拒否された API コールは料金が発生しない。
・スロットルされた API コールも料金は発生しない。
記事一覧
APIGateway/JavascriptでAPIGatewayを呼び出す
はじめに
JavascriptでAPIGatewayを呼び出すサンプル
こちらで作成したAPIGatewayを利用しています。
下記サンプルコードをhtml形式で保存してブラウザで表示してください。ローカルで利用できます。
事前準備
サンプルコード
Webページのボタンをクリックすると、コンソールログとWeb画面に署名付きURLが表示される。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | $(function(){ $('#btn1').click(function(){ //Ajax通信を行う $.ajax({ //APIGatewayで作成したGETメソッドのURLを記載する。 url:"https://XXXXXXXX.execute-api.ap-northeast-1.amazonaws.com/LambdaTest/lambdatest", type:"GET", dataType:"text" }) .done((data) => { //成功した場合の処理 var json = data.replace(/'/g,"""); //シングルクオートをダブルクオートに変換 console.log(JSON.parse(json).UrlPath); //JSONデータのUrlPathの値を抽出 $("#msg").text(JSON.parse(json).UrlPath); //JSONデータのUrlPathの値を抽出 }); }); });
|
実行結果
Aws/APIGateway/Lambdaオーソライザーを理解してみる
ようやくLambdaオーソライザーが理解できました(多分)
簡単に言うと、Lambdaオーソライザーは、後続のAPIを実行するポリシードキュメントを返す仕組みです。
ポリシードキュメントを返す際、EffectがAllowの場合はContexを必須で付与し、Denyの場合はContexを付与しなくて良いです。
公式ドキュメントには、「principalIdを含む」とありますが、つけてもつけなくても動作します。
Amazon API Gateway Lambda オーソライザーからの出力 - Amazon API Gateway
下記は、認証に成功した場合のフロー図です。
はじめに
APIの統合リクエストは、eventをLambdaに渡したかったので「Lambda プロキシ統合の使用」を有効にしています。
気にしたことが無かったのですが、下図のメソッドリクエストの認可で、オーソライザーに設定した名前を選択しないとオーソライザーが有効にならないんですね。
オーソライザーに設定したLambdaは実行されるので、有効になっているものだと思いました・・・。※CloudWatchlogsにログ出てたら有効と思うじゃん!!!
クライアントからのAPIの呼び出しは、下記の通りcurlコマンドでAuthorizationヘッダーを指定して呼び出しています。
| 1 | curl -v https://XXX.execute-api.ap-northeast-1.amazonaws.com/test -H "Authorization:1" |
設定値
APIGateway
■オーソライザー
・名前:LambdaAuth
・タイプ:Lambda
・Lambda関数:LambdaAuthorization
・Lambda イベントペイロード:リクエスト
・ID ソース:Authorization
・認可のキャッシュ:無効
■API
・タイプ:REST API
・エンドポイントタイプ:パブリック
・メソッド:GET
・メソッドリクエスト(認可):LambdaAuth
・Lambda プロキシ統合の使用:有効
・統合タイプ:Lambda関数(AllowResponse)
・ドメイン:https://XXX.execute-api.ap-northeast-1.amazonaws.com
・ステージ:test
Lambda(オーソライザー用)サンプル
・event情報(headersのIDソースなど)を元に、認証判定
・許可の場合:プリンシンバルID、後続処理のポリシードキュメント(資格情報)、ContexをReturn
・拒否の場合:プリンシンバルID、後続処理のポリシードキュメント(資格情報)をReturn
Contexはkey-valueで定義できて、返却した値はAPIで指定したLambda関数のevent['requestContext']['authorizer']に格納されます。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | def lambda_handler(event, context): token = event['headers']['Authorization'] if token == '1': return { 'principalId' : "LambdaAuthorization", 'policydocument' : { 'Version' : '2012-10-17', 'Statement' : [ { "Action": "execute-api:Invoke", "Effect": "Allow", #下記の形式を許可しますが、汎用的なオーソライザーにすると思うので*を用いています。 #"Resource": "arn:aws:execute-api:ap-northeast-1:{アカウントID}:{api-id}/{{stageNameOrWildcard}}/{{httpVerbOrWildcard}}/{{resourcePathOrWildcard}}" "Resource": "arn:aws:execute-api:ap-northeast-1:*" } ] }, 'context': { "sukina": "KeyValue", "wo": "settei", "dekiru": "YO" } } return { 'principalId' : "LambdaAuthorization", 'policydocument' : { 'Version' : '2012-10-17', 'Statement' : [ { #公式ではexecute-api:Invokeに対してDenyしていますが、全てのアクションDenyで良いかなと思って下記のようにしています。 "Action": "*", "Effect": "Deny", "Resource": "*" } ] } } |
Lambda(API用)サンプル
受取ったeventをbodyに渡すだけの関数です。
| 1 2 3 4 5 6 7 8 9 10 | import json
def lambda_handler(event, context): jsn_str = json.dumps(event, ensure_ascii=False, indent=2) return { 'isbase64Encoded': False, 'statusCode': 200, 'headers': {},
'body': jsn_str } |
豆知識
Lambdaオーソライザのタイムアウトと、後続の統合リクエストで呼び出すLambdaは、タイムアウトの最大値はそれぞれ29秒のようです。(Lambdaオーソライザは動作仕様で確認)
なお、Lambdaオーソライザで28秒、統合リクエストで28秒かかるLambda関数を設定してみましたが、56秒程で応答が返ってきましたのでAPIGateway全体で29秒以内となるわけでは無いようです。
クライアントにエラーが返る場合のフロー図
Lambdaオーソライザーの関数で拒否(Deny)された場合
下記コマンドのように、オーソライザーのLambda関数で拒否(Deny)される値の場合は、クライアント側にはエラー(403 Forbidden)がレスポンスされる。
| 1 | curl -v https://XXX.execute-api.ap-northeast-1.amazonaws.com/test -H "Authorization:2" |
定義されたヘッダーがない場合
下記コマンドのように、ヘッダーにイベントソース(Authorization)が無い場合は、クライアント側にはエラー(401 Unauthorized)がレスポンスされる。
| 1 | curl -v https://XXX.execute-api.ap-northeast-1.amazonaws.com/test -H "Auth:1" |
Lambdaオーソライザーの関数がエラーの場合
クライアント側にはエラー(500 Internal Server Error)がレスポンスされる。
Aws/APIGateway/Lambdaプロキシ統合の使用
以前から気になっていたが、APIGatewayをトリガーにLambda関数を動かしているのにeventが取得できなく・・・調べたところ
リクエストのマッピングテンプレートを使わない場合、[Lambdaプロキシ統合の使用]にチェックしないとeventが空になってしまうんですね。
実験
・APIGatewayはエンドポイントをリージョンとしたシンプルなものAPIGateway/Lambda関数の呼び出しAPIGateway/Lambda関数の呼び出し辺りを参考に作成
・Lambda関数は下記のコードで、APIGatewayからのeventをそのままブラウザに表示しています。
| 1 2 3 4 5 6 7 8 9 10 | import json
def lambda_handler(event, context): jsn_str = json.dumps(event, ensure_ascii=False, indent=2) return { 'isbase64Encoded': False, 'statusCode': 200, 'headers': {}, 'body': jsn_str } |
Lambdaプロキシ統合の使用:チェック無し
※Lambda関数選択時に、[Lambdaプロキシ統合の使用]をチェックしません。
ブラウザへのレスポンス
| 1 | {"isbase64Encoded": false, "statusCode": 200, "headers": {}, "body": "{}"} |
Lambdaプロキシ統合の使用:チェックあり
※Lambda関数選択時に、[Lambdaプロキシ統合の使用]をチェックします。
クエリパラメータを追加してみる。
[https://XXXXX.execute-api.ap-northeast-1.amazonaws.com/test]の末尾に[?id=0001&value=tokyo]をつけてリクエストしてみる。
[queryStringParameters]と[multiValueQueryStringParameters]が追加されていました。
| 1 2 3 4 5 6 7 8 9 10 11 12 | "queryStringParameters": { "id": "0001", "value": "tokyo" }, "multiValueQueryStringParameters": { "id": [ "0001" ], "value": [ "tokyo" ] } |
ついでにDynamoDBに書き込んでみる
Lambdaプロキシ統合の使用:チェックありのレスポンス
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 | { "resource": "/", "path": "/", "httpMethod": "GET", "headers": { "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,**;q=0.8,application/signed-exchange;v=b3;q=0.9" ], "accept-encoding": [ "gzip, deflate, br" ], "accept-language": [ "ja" ], "cache-control": [ "max-age=0" ], "Host": [ "{APIGWのID}.execute-api.ap-northeast-1.amazonaws.com" ], "referer": [ "https://ap-northeast-1.console.aws.amazon.com/" ], "sec-ch-ua": [ "" Not A;Brand";v="99", "Chromium";v="90", "Google Chrome";v="90"" ], "sec-ch-ua-mobile": [ "?0" ], "sec-fetch-dest": [ "document" ], "sec-fetch-mode": [ "navigate" ], "sec-fetch-site": [ "cross-site" ], "sec-fetch-user": [ "?1" ], "upgrade-insecure-requests": [ "1" ], "User-Agent": [ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36" ], "X-Amzn-Trace-Id": [ "Root=1-60aa083e-2598a84640ebc8667f9f08ee" ], "X-Forwarded-For": [ "{接続元IPアドレス}" ], "X-Forwarded-Port": [ "443" ], "X-Forwarded-Proto": [ "https" ] }, "queryStringParameters": null, "multiValueQueryStringParameters": null, "pathParameters": null, "stageVariables": null, "requestContext": { "resourceId": "sk5324ywjb", "resourcePath": "/", "httpMethod": "GET", "extendedRequestId": "fxY5tHljtjMFq3w=", "requestTime": "23/May/2021:07:46:06 +0000", "path": "/test", "accountId": "{アカウントID}", "protocol": "HTTP/1.1", "stage": "test", "domainPrefix": "{APIGWのID}", "requestTimeEpoch": 1621755966012, "requestId": "e00c2936-dca1-4084-a574-a7c8b7fb4b21", "identity": { "cognitoIdentityPoolId": null, "accountId": null, "cognitoIdentityId": null, "caller": null, "sourceIp": "{接続元IPアドレス}", "principalOrgId": null, "accessKey": null, "cognitoAuthenticationType": null, "cognitoAuthenticationProvider": null, "userArn": null, "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", "user": null }, "domainName": "{APIGWのID}.execute-api.ap-northeast-1.amazonaws.com", "apiId": "{APIGWのID}" }, "body": null, "isbase64Encoded": false } |
APIGateway/Lambda関数の呼び出し 事前準備
① Lambda関数を作成する。
② APIを作成する。
1.API タイプを選択
今回は、REST APIで作成します。
2.API 詳細設定
プロトコルをREST API、新しいAPI、エンドポイントをリージョンとして作成
3.リソース、メソッドの作成
GETメソッドのみの作成としています。
統合タイプをLambda関数、リージョン、Lambda関数名を指定します。
4.マッピングテンプレートの設定
「GET - メソッドの実行」画面が表示されたら、統合レスポンスをクリックしマッピングテンプレートの設定をします。
レスポンス200の詳細[▶]を開き、赤枠の通り設定し、保存をクリックします。
[$input.json('$.body')]の設定で、Lambda関数出力結果のbody部分だけレスポンスを返すようになります。マッピングテンプレートの参考リンク
5.CROSの有効化
API呼び出し時のCROSを有効にします。CROS有効化の参考リンク
6.APIのデプロイ
APIをデプロイします、ステージが表示されたらGETメソッドを選択し「URLの呼び出し」にある、URLをクリックします。
マッピングテンプレートなど変更した場合は都度デプロイを実行すること!!!実施しないと反映されません!!!
実行(API呼び出し)結果
下記のように表示されれば完了です。
Aws/APIGateway/VPCエンドポイントとプライベートAPIの関連付け下記の設定がどこに影響するかようやく理解できました。
VPCエンドポイントのプライベートDNSを無効にした場合にのみ効果のある設定でした。
解説
VPCエンドポイント(execute-api)で、プライベートDNSを有効/無効にした場合、Route53に以下のDNSレコードが生成されます.
| プライベートDNS | Route 53 DNSレコード | 備考 |
|---|---|---|
| 有効 | {vpce-id}-{文字列}.execute-api.{region}.vpce.amazonaws.com {vpce-id}-{文字列}-{az-name}.execute-api.{region}.vpce.amazonaws.com execute-api.ap-northeast-1.amazonaws.com *.execute-api.ap-northeast-1.amazonaws.com | |
| 無効 | {vpce-id}-{文字列}.execute-api.{region}.vpce.amazonaws.com {vpce-id}-{文字列}-{az-name}.execute-api.{region}.vpce.amazonaws.com |
VPCエンドポイントとプライベートAPIの関連付けた場合、上記に加え以下の{api-id}-{vpce-id}.execute-api.{region}.amazonaws.com形式のDNSレコードがRoute53に生成されます。
| プライベート DNS | VPCエンドポイント 関連付け | Route 53 DNSレコード | 備考 |
|---|---|---|---|
| 有効 | 無効 | {vpce-id}-{文字列}.execute-api.{region}.vpce.amazonaws.com {vpce-id}-{文字列}-{az-name}.execute-api.{region}.vpce.amazonaws.com execute-api.ap-northeast-1.amazonaws.com *.execute-api.ap-northeast-1.amazonaws.com | 以下の形式でリクエストできる為、VPCエンドポイントの関連付けは無効で良い {api-id}.execute-api.ap-northeast-1.amazonaws.com/{stage} |
| 有効 | 有効 | {api-id}-{vpce-id}.execute-api.{region}.amazonaws.com {vpce-id}-{文字列}.execute-api.{region}.vpce.amazonaws.com {vpce-id}-{文字列}-{az-name}.execute-api.{region}.vpce.amazonaws.com execute-api.ap-northeast-1.amazonaws.com *.execute-api.ap-northeast-1.amazonaws.com | 以下の形式でリクエストできる為、VPCエンドポイントの関連付けは不要 {api-id}.execute-api.ap-northeast-1.amazonaws.com/{stage} |
| 無効 | 無効 | {vpce-id}-{文字列}.execute-api.{region}.vpce.amazonaws.com {vpce-id}-{文字列}-{az-name}.execute-api.{region}.vpce.amazonaws.com | Host ヘッダやx-apigw-api-idヘッダを指定してリクエストすることとなる。 {vpce-id}-{文字列}.execute-api.{region}.vpce.amazonaws.com/{stage} -H “x-apigw-api-id:{api-id}” |
| 無効 | 有効 | {api-id}-{vpce-id}.execute-api.{region}.amazonaws.com {vpce-id}-{文字列}.execute-api.{region}.vpce.amazonaws.com {vpce-id}-{文字列}-{az-name}.execute-api.{region}.vpce.amazonaws.com | Host ヘッダやx-apigw-api-idヘッダを指定せずリクエストできるようになる。 {api-id}-{vpce-id}.execute-api.{region}.amazonaws.com/{stage} |
※内部的に登録されるだけなので、VPCエンドポイントの画面で確認はできません。
Aws/APIGateway/カスタムドメイン設定
APIGatewayでカスタムドメインを利用する際の設定です。(APIマッピングは記載していません)
はじめに
・ACMで発行するSSL証明書はRoute53でCNAMEを登録しています。
設定
①SSL証明書の発行
Route53にパブリックホストゾーンにDNSレコード名が登録された状態になります。
②カスタムドメイン名の登録
APIGatewayにてカスタムドメイン名を作成します。
・ドメイン名:ドメイン名
・ACM証明書:手順①で発行したSSL証明書を選択する
作成が完了すると、[API Gateway ドメイン名]が発行されます。
③Route53へ[API Gateway ドメイン名]を登録する。
下図のようにAレコードにAliasDNSレコードとして[API Gateway ドメイン名]を登録します。
以上で、カスタムドメイン名でAPIGatewayにWebリクエストが可能となります。
余談:カスタムドメインを利用したDR構成
気にしたことなかったんですが、別リージョンでも同じカスタムドメイン名が作成できるんですね。
なので、予め登録(カスタムドメイン名、SSL証明書の発行やAPIマッピング)だけしておいて、Route53のレコード切り替えだけすればDR構成がとれることになります。
サーバーレスを勉強中に、classmethodさんの記事を見て、え?そうなの?と思って試してみた。
イメージは以下の感じで、プライベートAPIGatewayを作成したアカウントAからは勿論、異なるアカウントBからもアクセスさせたい。
ついでにLambdaからのプライベートAPIGateway呼び出しも試してみた
VPCエンドポイントで「*.execute-api.ap-northeast-1.amazonaws.com」がワイルドカードで登録されるので
Route53を登録しなくてもプライベートAPIGatewayのURL形式であればエンドポイントに接続されました。
アカウントAでの環境準備
各リソースの作成手順は割愛します、重要な設定値のみです。
APIGatewayの作成
以下の設定値でAPIGatewayを作成。
・エンドポイントタイプ:プライベート
・ID:xxxx
・メソッド:GET
・タイプ:Lambda関数
・ドメイン:https://xxxx.execute-api.ap-northeast-1.amazonaws.com
・ステージ:test
・リソースポリシー:下記の通りvpcidを指定してアクセス許可を設定(参考:API Gateway でプライベート API 用の VPC エンドポイントポリシーを使用する - Amazon API Gateway)
事前にアカウントBのVPCIDも登録しています。
※プライベートAPI Gatewayの送信先IPアドレス制限は意味がない(0.0.0.0/0をDenyしてもリクエストできます。)→VPCで送信元偽れますもんね、よく考えたらそらそうか・・・。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": "*", "Action": "execute-api:Invoke", "Resource": "arn:aws:execute-api:ap-northeast-1:1234567890:xxxx/*" }, { "Effect": "Deny", "Principal": "*", "Action": "execute-api:Invoke", "Resource": "arn:aws:execute-api:ap-northeast-1:1234567890:xxxx/*", "Condition": { "StringNotEquals": { "aws:SourceVpc": [ "vpc-xxxxa", "vpc-xxxxb" ] } } } ] } |
APIGatewayから呼び出されるLambda関数
| 1 2 3 4 5 6 7 | def lambda_handler(event, context): return { 'isbase64Encoded': False, 'statusCode': 200, 'headers': {}, 'body': {"message": "Hello from AWS Lambda"} } |
APIGatewayを呼び出すLambda関数
エンドポイント経由でアクセスするため、VPC、サブネット、セキュリティグループを指定して作成します。
指定したリソースは下記として説明します。
・VPC:vpc-xxxxa
・サブネット:Lambda-subnet
・セキュリティグループ:Lambda-sg
| 1 2 3 4 5 6 7 8 9 10 11 | import urllib.request import json
def lambda_handler(event, context): url = "https://xxxx.execute-api.ap-northeast-1.amazonaws.com/test/"
response = urllib.request.urlopen(url)
content = json.loads(response.read().decode('utf8'))
return content |
VPCエンドポイントの作成
VPCに設置したLambdaがプライベートAPIGatewayに到達できるようにVPCエンドポイントを作成します。
VPCエンドポイントは、Lambdaからhttpsのインバウンドを許可する必要があります。
指定したリソースは下記になります。
・サービス名:com.amazonaws.ap-northeast-1.execute-api
・VPC:vpc-xxxxa
・サブネット:Lambda-subnet
・セキュリティグループ:Vpcendpoint-sg(Lambda-sgをソースとしたhttpsのインバウンドを許可する)
アカウントAでの実行結果
以上でアカウントAの環境は整ったので、「APIGatewayを呼び出すLambda関数」を実行します。
実行結果は以下の通りで、LambdaからAPIGatewayが呼び出せました。
アカウントBでの環境準備
各リソースの作成手順は割愛します、重要な設定値のみです。
APIGatewayを呼び出すLambda関数
これかアカウントAとソースコードは変わりません。
指定したリソースは下記として説明します。
・VPC:vpc-xxxxb
・サブネット:Lambda-subnet
・セキュリティグループ:Lambda-sg
| 1 2 3 4 5 6 7 8 9 10 11 | import urllib.request import json
def lambda_handler(event, context): url = "https://xxxx.execute-api.ap-northeast-1.amazonaws.com/test/"
response = urllib.request.urlopen(url)
content = json.loads(response.read().decode('utf8'))
return content |
VPCエンドポイントの作成
VPCに設置したLambdaがプライベートAPIGatewayに到達できるようにVPCエンドポイントを作成します。
VPCエンドポイントは、Lambdaからhttpsのインバウンドを許可する必要があります。
指定したリソースは下記になります。
・サービス名:com.amazonaws.ap-northeast-1.execute-api
・VPC:vpc-xxxxb
・サブネット:Lambda-subnet
・セキュリティグループ:Vpcendpoint-sg(Lambda-sgをソースとしたhttpsのインバウンドを許可する)
アカウントBでの実行結果
以上でアカウントBの環境は整ったので、「APIGatewayを呼び出すLambda関数」を実行します。
実行結果は以下の通りで、アカウントAと同様にLambdaからAPIGatewayが呼び出せました。
まとめclassmethodさんの記事は正しかった。
AWS のプライベートネットワーク内においては特に制限をしない限り、誰でもアクセスできる API Gateway です。←まさにこれでした。
その他
LambdaからAPIGatewayの呼出し方は下記のスクリプトでも良いかもメモ
| 1 2 3 4 5 6 7 8 9 10 11 12 13 | import urllib3 import json def lambda_handler(event, context): url = "https://xxxx.execute-api.ap-northeast-1.amazonaws.com/test"
http = urllib3.PoolManager()
response = http.request("GET",url)
content = json.loads(response.data.decode("utf-8"))
return content |
Aws/APIGateway/プライベートもパブリックもアクセス可能とする方法
APIGatewayをプライベートもパブリックも両方呼び出したい場合どうすれば良いか?
・回答:エンドポイント(execute-api)の[プライベート DNS 名を有効にする]をあえて無効にする。
※上記以外でも、[プライベート DNS 名を有効にする]を有効とした場合でも、パブリックAPIGatewayにカスタムドメインを設定したり
CloudFrontのオリジンにパブリックAPIGatewayを登録することで、プライベートもパブリックも両方呼び出すことは可能です。
今回の場合は「https://{rest-api-id}.execute-api.{region}.amazonaws.com/{stage}」形式で呼び出す前提で記載します。
[プライベート DNS 名を有効にする]を無効にする。
[プライベート DNS 名を有効にする]を無効にした場合の接続結果は以下の図のイメージとなる。
・パブリックAPIGateway:ステージで表示されたURLで呼び出し可能
・プライベートAPIGateway:下記のように、[x-apigw-api-id]ヘッダーに[api-id]を指定して呼び出す。
curl https://{VPCエンドポイントのDNS名}/{stage} -H "x-apigw-api-id:{api-id}"
[プライベート DNS 名を有効にする]を有効にする。
[プライベート DNS 名を有効にする]を有効にした場合の接続結果は以下の図のイメージとなる。
・パブリックAPIGateway:VPCエンドポイントに接続してしまう為、ステージで表示されたURLで呼び出せない。[x-apigw-api-id]ヘッダーを指定してもNG
・プライベートAPIGateway:ステージで表示されたURLで呼び出し可能。[x-apigw-api-id]ヘッダーを指定してもOK



