【ITニュース解説】InvalidSignature in Node with AWS SDK
2025年09月29日に「Dev.to」が公開したITニュース「InvalidSignature in Node with AWS SDK」について初心者にもわかりやすく解説しています。
ITニュース概要
Lambda関数がAWSサービスと連携する際、AWS SDKクライアントを関数外で初期化すると、署名タイムスタンプが古くなり`InvalidSignatureException`が発生する問題があった。解決策はクライアント初期化をハンドラ関数内に移すこと。都度初期化のコストはキャッシュで対策できる。
ITニュース解説
AWS Lambdaで開発された機能が、AWS Secrets Managerから秘密情報を取得する際に、時折「InvalidSignatureException」というエラーを発生させるという問題が起きた。このエラーは本番環境で発生し、約99%のリクエストは成功するにもかかわらず、残りの1%がランダムに失敗するという、非常に原因特定が難しいケースであった。
この「InvalidSignatureException」というエラーは、AWSサービスへのリクエストが正しく認証されていないことを示すものである。通常、AWS SDKを使ってAWSサービスにリクエストを送る際、SDKはリクエストの内容、日時、アクセスキーなどの情報を用いて「署名」というものを生成し、これをリクエストに添付して送る。AWSサービス側はこの署名を検証し、リクエストが正規のものであるか、改ざんされていないかなどを確認する。この署名の検証には、リクエストが送信された日時を示す「タイムスタンプ」が重要な要素となる。もしリクエストのタイムスタンプが大きくずれていると、AWSサービス側は不正なリクエストと判断し、「InvalidSignatureException」を返すことがある。
今回の根本原因は、AWS Lambda関数の実行環境と、AWS SDKクライアントの初期化方法の組み合わせにあった。AWS Lambda関数は、一度起動すると、その実行環境が一定時間保持され、同じ関数が再度呼び出された際に再利用されることがある。これを「ウォームスタート」と呼ぶ。初めて呼び出されたり、長期間使われなかったりすると、新たに実行環境が作られる「コールドスタート」となる。このとき、アプリケーションのコードが実行環境にロードされ、モジュールが初期化される。
問題のシステムでは、コードに「リポジトリパターン」という設計を採用しており、このリポジトリのコンストラクタ(初期化時に実行される部分)内でAWS SDKクライアントを初期化し、AWS Secrets Managerから秘密情報を取得するようにしていた。そして、このリポジトリのインスタンスがLambdaハンドラ関数(Lambdaが実際に処理を行うメインの関数)の「外側」で初期化されていたのである。
これにより、Lambda関数がコールドスタートされる際に一度だけリポジトリが初期化され、その中のAWS SDKクライアントも一度だけ作られることになった。その後、Lambda関数がウォームスタートで複数回呼び出されると、同じリポジトリのインスタンスが再利用され、それに伴い同じAWS SDKクライアントのインスタンスも繰り返し使われることになった。その結果、この再利用されるAWS SDKクライアントが、最初に初期化された時点の古いタイムスタンプを保持し続けてしまい、AWSサービスへのリクエスト署名にその古いタイムスタンプを使ってしまうという問題が発生したのだ。AWSサービス側は、現在の時刻と大きく異なる古いタイムスタンプで署名されたリクエストを受け取ると、認証に失敗したと判断し、「InvalidSignatureException」を返していたのである。
この問題の解決策は、AWS SDKクライアントの初期化、あるいはリポジトリの初期化をLambdaハンドラ関数の「内側」に移動することだった。具体的には、Lambda関数が呼び出されるたびに新しいリポジトリのインスタンスを作成し、その中でAWS SDKクライアントが初期化されるように変更した。これにより、Lambdaの呼び出しごとに、常に最新のタイムスタンプを持つ新しいAWS SDKクライアントが生成されることになり、正しい署名がAWSサービスに送られるようになった。例えば、これまでハンドラの外でconst repository = new MyRepository();としていたのを、ハンドラの中でconst repository = new MyRepository();と記述するように変更したのである。
しかし、この解決策には新たな課題も伴う。リポジトリを毎回初期化するということは、Secrets Managerから秘密情報を取得する処理も毎回実行されることを意味する。Lambda関数が頻繁に呼び出されるような場合、毎回Secrets Managerにアクセスすることは、処理の遅延(レイテンシの増加)や、AWSの利用料金の増加につながる可能性がある。
このデメリットを緩和するために、取得した秘密情報をキャッシュする仕組みを導入することが推奨される。秘密情報を一度取得したら、それをメモリ上に一時的に保存しておき、次の呼び出しで同じ秘密情報が必要になった際には、Secrets Managerへの実際のアクセスを避け、キャッシュされた値を利用するという方法だ。これにより、パフォーマンスの低下やコストの増加を抑えることができる。
キャッシュの実装は、AWSが提供する「AWS Powertools Parameters」というユーティリティライブラリを使うと容易に行える。このライブラリは、秘密情報などのパラメータをメモリにキャッシュする機能を持っており、追加の設定なしでもデフォルトで5秒間キャッシュを保持し、自動的に更新するようになっている。これを利用することで、署名エラーの解決と同時に、アプリケーションのパフォーマンス最適化も実現できるのである。このアプローチは、AWS SDKリクエストの有効な署名を保証することと、Secrets Managerへの不要な呼び出しを減らしてパフォーマンスを最適化することのバランスを取るものだ。