皆さんこんにちは。どうも。
今回は、AWS LambdaやAWS CognitoといったAWSのサービスを使って、「AWS Lambdaで作ったWeb APIを画面から叩く」という簡易的な動作検証をしてみました。
全体的な構成としては、以下のようになります。
・フロントエンド : HTML / JavaScript (jQuery)
・バックエンド : API Gateway + AWS Lambda
・認証 : Amazon Cognito ➔ JWT (ID トークン) による認可
初め、バックエンド側のAPIを作成した際は、AWS_IAM認証を設定しました。この時点で、きちんと認証情報(アクセスキーやシークレットキー)の設定を行い、aws curlコマンドから叩けました。
※AWSを使う際は、各種設定変更や認証情報の設定などのためにaws cli(awsコマンド)やweb apiの簡易テスト用にaws curlコマンドを使えるように(ローカルPCに)インストールしておくと便利です。
ただ、画面からLambdaで作ったAPI Gatewayを叩くのが以下の理由から難しいので、Cognito ユーザープール認証(JWT)に変更しました。
【AWS_IAM認証のAPIを画面から叩くのが難しい理由】
・画面に固定の Access Key / Secret Key を埋め込む ➔ セキュリティ的に絶対不可(キーが丸見えになり、AWS アカウントを乗っ取られる危険があるため)
・Amazon Cognito を使って「一時的な AWS 認証情報(STS)」をログインユーザーごとに発行する ➔ 設定・実装のハードルが非常に高い
で、色々と試行錯誤し、画面から作成したAPIにリクエスト(GET、POST)を送ることができました。
苦労した部分(401 UnauthorizedのエラーでAPI Gateway へリクエストが失敗する・・)
ログインはできて、IDトークン(JWT)は取得できましたが、その後のAPI Gateway へのリクエストが401 Unauthorizedのエラーで失敗しました(以下画像の「保護されたAPIを叩く」ボタンで失敗)。
なので、claude先生に聞いてみました。
以下、質問内容です。
「画面側のJavaScript内に記載されたAPI Gatewayのエンドポイントへリクエストを送りたい。ただ、IDトークン(JWT)取得は上手くいくが、その後のAPI Gateway へのリクエストで以下のような401エラーが出てしまう。
【エラー】ステータスコード: 0
undefined
API GatewayのCORSの設定も正しく行ったつもりである(スクショも添付した)。
原因を教えて・・」
で、これに対するclaude先生の回答は、
「CORSが通ったあとに 401 が出る場合は、CORSではなく認可(JWTオーソライザー)側の問題です。
その場合は、Issuer(Cognitoのユーザープール)、Audience(App Client ID)、IDトークンを使っているか、を確認してください。
その際はエラー内容を貼っていただければ続けて見ます。」
とのことでした。
なので、回答の通りに確認し(問題なし)、デベロッパーツールとかで色々と調べた結果、プリフライト(OPTIONS)リクエストが401 Unauthorizedになっていました。
なので、こちらの原因もclaude先生に質問しました。
その回答は以下の通りでした。
「なぜOPTIONSが401になるのか?
API Gatewayの「CORS設定」機能は、本来はOPTIONSを自動処理して認証をスキップしてくれるはずです。しかし、これが機能しないケースがあります:
ルートが ANY /myFirstApi(今回のAPI名) として作られていて、そこにJWTオーソライザーがアタッチされている場合です。ANY はOPTIONSも含むすべてのHTTPメソッドにマッチするため、OPTIONSリクエストもオーソライザーを通ろうとしてしまいます。ブラウザが送るプリフライトOPTIONSにはAuthorizationヘッダーが付いていないため、オーソライザーが「トークンがない」として401を返す、という流れです。これはAWSの公式ドキュメントやユーザー事例でも報告されている既知の挙動です。」
らしいです。API Gatewayのルーティングの仕様の問題らしいですね。
なので、API Gatewayのメソッドで、(今回リクエストで使いたい)GETとPOSTのみにオーソライザーをアタッチし、OPTIONSにはアタッチせず(認可なし)で設定しました。
その結果、無事に(プリフライト(OPTIONS)リクエストも200で成功し)API Gateway へのリクエストが成功しました。
AWS(やGCP)のセキュリティについての考察
・AWS_IAM認証でアクセスキーを取得することのリスク
AWS_IAM認証でのアクセスキーとは、AWS アカウントの全リソースを操作できる永続的な秘密鍵のことです。
基本的に、(当たり前ですが)アクセスキーを取得しないとAWSの外部からのサービスへのアクセスやAPIへのリクエストはできません(ローカル開発環境やCLI ツールからのAPIリクエストの実行や外部のサーバーからAWS の S3 へファイルをバックアップしたりする場合など)。
なので、アクセスキーがあればそういう諸々の操作などができてしまうため、漏洩して悪用されるとかなりヤバいです。
例えば、漏洩したキーを使って新しい API リクエスト(リソースの作成・削除、データの閲覧など)をされる、S3 や RDS から機密情報を持ち出される、IAM ユーザーを作成される、仮想通貨マイニング用の EC2 インスタンスを起動されることなどがあり得ます。
なので、漏洩するとセキュリティ的にはかなりヤバい状況になります。
なので、漏洩した場合は、ただちに漏洩したアクセスキーを 「無効化 (Deactivate)」 または 「削除 (Delete)」しないといけません。
それと同時に、アクティブなセッションの強制終了や不正アクセスの形跡調査(CloudTrail の確認)や不正リソースの削除・クリーンアップなども行わないといけません(それでも漏洩した情報は戻ってこない)。
※AWSのアクセスキーは、GCPでのサービスアカウントキーに該当する。
AWSのアクセスキーと同様に、こちらも漏洩するとサービスアカウントが不正利用される恐れがある(サービスアカウントとしてリソースにアクセスできてしまうため)。
→リスクを下げるために、IAM権限を絞っておく。
AWSでもGCPでも必要最小限の権限だけを付与しておくのがいいです。
AWSでは、AdministratorAccessなどを付与してしまうと、全ての操作ができてしまうので危険です(使うサービスだけに限定しておくのが良い)。
GCPでは、私がGoogleのVeoの動画生成APIを呼び出すアプリを作った際、サービスアカウントの権限を「Vertex AI ユーザーだけ(Vertex AI関連の操作に限られる権限)」に設定しました。
編集者(Editor)などの広い権限にしてしまうと、プロジェクト内のほとんどのサービスの作成・変更・削除ができてしまい、(キー漏洩時の)リスクが高いです。
・代替手段として、Cognito ユーザープール認証やIAMロールの付与
・Cognito ユーザープール(JWT)認証
今回使用した認証方式で、ユーザーごとのトークン(ワンタイムパスワードのようなもの)で認証するため、固定のシークレットキーを管理する必要がなくて安全です。
・IAM ロールの付与
処理を実行する場所が AWS の内部サービス環境(EC2, Lambda, ECSなど)である場合は、IAM ロールによる一時的な認証情報の付与で済みます(AWS サービス同士が連携・通信する場合など)。
※AWS 側が自動的に数時間で失効する「一時的なアクセスキーとセッショントークン」を内部的に発行・更新してくれるため。
・以下、本番運用時の注意点
・API Gateway のルート(パス)ごとに、Cognito などの適切なオーソライザーが正しく設定されているか?を確認する(認証・認可の徹底)。
・S3バケットの公開設定では、パブリックアクセスをすべてブロックする(インターネットから直接アクセスさせない)。
・ログイン時のMFAやCAPTCHA(人間確認)の他に、API Gateway 側でスロットリング(レート制限)を設定し、1秒あたりのリクエスト上限を超える異常なアクセスを遮断する(AIによる不正ログインの防止)。
・アプリケーション側で、サポートが終了した(EOL)古いフレームワークやライブラリを使わない(依存ライブラリの脆弱性に気をつける)。
・ログの監視を必ず行う。
AWSを使う上で、意図しない課金を防ぐための予防策
AWSは色んなサービスがあるので、意図しない課金が発生する可能性がありますよね。
以前EC2で、インスタンスは停止されていても、EBSボリューム(ストレージ)がアタッチされていたり、ElasticIPアドレスを保持している場合も課金され続けるということを分かっておらず、課金対策もしていなかったため、2ヶ月分ぐらい余分にかかってしまいました。
なので、AWSを使う際は、課金対策をきちんとしておくと安全ですよね(AzureやGCP等も同様)。
以下は、私が設定してあるAWSの課金対策です。
・AWS Budgets(予算アラート)を設定
「月額0.1ドルを超えたらメール通知する」アラートをAWSコンソールから設定しておくと、設定ミスによる課金を未然に防げます。
・CloudWatch Logsのログデータの保持期間を設定
Lambdaを実行すると自動的にログ(CloudWatch Logs)が蓄積されます。不要になったロググループは削除するか、保持期間(例: 7日間など)を設定しておくとストレージ課金を防止できます。
※GCPでの設定
AWSと同様に、所定の金額に達したら予算アラート通知メールが飛ぶ設定にできます。
後、上記の通知だけでなく、クォータ(割り当て上限)の引き下げ設定で、APIの使いすぎを制限しておくこともできます(APIやモデルの種類によっては、ユーザーが自由に設定できない場合もありますが、システム側で上限値が固定されているので制限自体はかかっています)。
【おまけ】対話型AIに質問する際の考察(メリット)
・コマンドの確認作業に使える
APIへのリクエストURLが正しいか?を確認するために、めちゃくちゃ長い文字列のトークンを付与してcurlコマンドを送る時とか・・Geminiに聞けば1文字のミスもなくコマンドの誤り(タイポなど)を修正して作り直してくれたり、トークンが有効期限内かどうかを瞬時に教えてくれたり・・するんですよね(スクショ又はコピペして対話型AIに「確認して」とお願いするだけで正確に確認してくれる)。
人だと、(疲れてると)絶対にミスりますしね・・
こういう部分はAIの恩恵だと思うんです。
(無駄に長い文字列を読み込ませて、編集してもらうとか?、コマンドが正しいかレビューしてもらうとか。
→人はAIが作ってくれた文字列をコピペするだけ。コピペミスだけ気をつけてればいい)
・私の対話型AI活用法(GeminiとClaudeを併用)
GeminiとClaudeを併用していると、(メインをGeminiとして使っており)ちょっと踏み込んだエラー内容とかになると、Geminiだけだとなかなか的確な回答が得られないことがあります。
その際にClaudeに聞くと、一発で解決するか、グッと解決に近づきます。
なので、私の対話型AI活用法はこんな感じで、メインをGeminiとして、踏み込んだ内容をClaudeに聞くようにしています。
もちろん、何でもかんでも聞くわけではなく、可能な限り自分でもデベロッパーツールなどで調べたりして、その上で必要な情報(エラーメッセージやスクショや調査内容など)を提供した上でClaudeに聞くと、より早く解決します(今までの経験上、Claudeに聞く際に、質問文に必要な情報をできる限り詰め込んで聞くと1回か2回でサクッと解決します)。
後、GeminiやClaudeが教えてくれたソースコードをそのままコピペするのは、個人的な動作確認が目的だったらいいかもしれませんが、できるだけ内容を理解して自分でもリファクタリング等ができるようにしておくと、スキルアップにもなりますし、保守性という意味でも有用です。
後、(特にGeminiの場合)教えてくれるソースコードがたまに間違えていることがあるので、その際は自分で(理解した上で)修正する方が早かったりするんですよね。
はい。以上、ここまでお付き合いいただきありがとうございました。