LoginSignup
0
0

【ナレッジ】AWS Parameters and Secrets Lambda Extension は、ハンドラー関数内から呼び出さないと400Errorになる

Last updated at Posted at 2023-07-26

0. 要約

タイトルの通りですが、
AWS Parameters and Secrets Lambda Extension は、ハンドラー関数"外"から呼び出しをすると HTTP 400 Error が出てしまいます。
そのため、ハンドラー関数""から呼び出しましょう!

1. はじめに...

2022年10月に、AWS Parameters and Secrets Lambda Extension が発表されました。
これまで、Lambda 関数内からパラメーターストアシークレットマネージャーに保存した値(APIキーなど)を取得するには、Lambda 関数内からSDK を使って取得するのが一般的でした。  
しかし、この Lambda Extension を活用することで、 パラメータ値を Lambda にキャッシュできるようになり、レイテンシーとコスト効率の点でメリットがあります。

2. 使ってみる...

詳しい使い方は、公式ドキュメントや、他のブログ記事に譲ります。(例えばこちら
要点をまとめると、以下の作業をしています。

  1. SecretsManager にAPIキーなどを保存
  2. Lambda 関数に、今回の Lambda Layer を追加
  3. Lambda の IAMロールに権限を追加

今回は、私がハマった「コードを書く場所」に焦点を当てて記事を書きます。
※ この記事を読む方は、既に使っている方が過半数だと思いますし...

2-1. 【これはNG】Handler 関数"外"から呼び出す

以下は、ハンドラー関数""から、Lambda Extension 経由で、 Secrets Manager に保存した API キーを取得するコードです。  
実行結果も貼り付けていますが、HTTP Error 400: Bad Request のエラーが起き、API キーを取得できていません。

Lambda 関数のコード

lambda_function.py
import json
import os
import urllib.request

# Handler 関数"外"から、AWS Parameters and Secrets Lambda Extension 経由でシークレットの値を取得する
endpoint = 'http://localhost:2773/secretsmanager/get?secretId=SecretsManagerName'
request = urllib.request.Request(endpoint)
request.add_header('X-Aws-Parameters-Secrets-Token', os.environ['AWS_SESSION_TOKEN'])
with urllib.request.urlopen(request) as response:
    body = response.read()
secret_strings = json.loads(body.decode("utf-8"))["SecretString"]
APIKEY = json.loads(secret_strings)["APIKEY"]
print(f'GET APIKEY = {APIKEY}')

# Handler 関数
def lambda_handler(event, context):
    return {
        'statusCode': 200,
        'headers': {
            'Content-Type': 'text/plain'
        },
        'body': f'APIKEY is :{APIKEY}'
    }

実行結果

=== Function Logs [一部のみ] ===
[ERROR] HTTPError: HTTP Error 400: Bad Request

=== Response ===
{
  "errorMessage": "HTTP Error 400: Bad Request",
  "errorType": "HTTPError",
  "requestId": "",
  "stackTrace": [
    "  File \"/var/lang/lib/python3.10/importlib/__init__.py\", line 126, in import_module\n    return _bootstrap._gcd_import(name[level:], package, level)\n",
    "  File \"<frozen importlib._bootstrap>\", line 1050, in _gcd_import\n",
    "  File \"<frozen importlib._bootstrap>\", line 1027, in _find_and_load\n",
    "  File \"<frozen importlib._bootstrap>\", line 1006, in _find_and_load_unlocked\n",
    "  File \"<frozen importlib._bootstrap>\", line 688, in _load_unlocked\n",
    "  File \"<frozen importlib._bootstrap_external>\", line 883, in exec_module\n",
    "  File \"<frozen importlib._bootstrap>\", line 241, in _call_with_frames_removed\n",
    "  File \"/var/task/lambda_function.py\", line 9, in <module>\n    with urllib.request.urlopen(request) as response:\n",
    "  File \"/var/lang/lib/python3.10/urllib/request.py\", line 216, in urlopen\n    return opener.open(url, data, timeout)\n",
    "  File \"/var/lang/lib/python3.10/urllib/request.py\", line 525, in open\n    response = meth(req, response)\n",
    "  File \"/var/lang/lib/python3.10/urllib/request.py\", line 634, in http_response\n    response = self.parent.error(\n",
    "  File \"/var/lang/lib/python3.10/urllib/request.py\", line 563, in error\n    return self._call_chain(*args)\n",
    "  File \"/var/lang/lib/python3.10/urllib/request.py\", line 496, in _call_chain\n    result = func(*args)\n",
    "  File \"/var/lang/lib/python3.10/urllib/request.py\", line 643, in http_error_default\n    raise HTTPError(req.full_url, code, msg, hdrs, fp)\n"
  ]
}

↓ 実際のスクリーンショット
image.png

2-2. 【こっちだとOK】Handler 関数"内"から呼び出す

Lambda のハンドラー関数""からであれば、Secrets Manager に保存したAPIキーを読み込めます。

Lambda 関数のコード

lambda_function.py
import json
import os
import urllib.request

# Handler 関数
def lambda_handler(event, context):
    
    # Handler 関数"内"から、AWS Parameters and Secrets Lambda Extension 経由でシークレットの値を取得する
    endpoint = 'http://localhost:2773/secretsmanager/get?secretId=SecretsManagerName'
    request = urllib.request.Request(endpoint)
    request.add_header('X-Aws-Parameters-Secrets-Token', os.environ['AWS_SESSION_TOKEN'])
    with urllib.request.urlopen(request) as response:
	    body = response.read()
    secret_strings = json.loads(body.decode("utf-8"))["SecretString"]
    APIKEY = json.loads(secret_strings)["APIKEY"]
    print(APIKEY)
    
    return {
        'statusCode': 200,
        'headers': {
            'Content-Type': 'text/plain'
        },
        'body': f'APIKEY:{APIKEY}'
    }

実行結果

=== Function Logs [一部のみ] ===
GET APIKEY = hogehoge

=== Response ===
{
  "statusCode": 200,
  "headers": {
    "Content-Type": "text/plain"
  },
  "body": "APIKEY is :hogehoge"
}

↓ 実際のスクリーンショット
image.png

3. あれ? Handler 関数の "内" と "外" って何が違うの?

ここまで書くと、「そもそも、なぜ Handler 関数"外"に処理を書いたのか? 俺は常に Handler 関数"内"に処理を書いてるぞ!」という意見も出てくるかと思います。

実は、Lambda の Handler 関数 "外" と "内" では、実行時の挙動が異なります。

Handler 関数 "外" に書いたコード Handler 関数 "内" に書いたコード
動作 コールドスタート時の1回だけ実行される。 ウォームスタート時に毎回実行される。
用途 全ユーザー共通の処理や、時間のかかるライブラリの読み込み、初期化などを記載する ユーザーごとに異なるデータがあるようなコードを記載する(DBアクセスなど)

3.1 自分の開発を振り返ってみると...

私は開発前半、APIキーを環境変数に設定し、Handler 関数 "外" から取得していました。
これは、APIキーは長期間再利用できるため、Handler関数""に記載/定義するのが良いと考えたためです。(実際、Lambda関連のソースコードを見ると同じような実装をよく見かけます。)
開発後半になってから、Lambda Extension を利用する構成に作り直した際、同じく Handler 関数 "" に Secrets Manager へのアクセスコードを書いてしまったため、うまくいかないエラーに巻き込まれました。

3.2 未解決の疑問点

Handler 関数の "内" と "外" で違いがあることはご理解いただけたと思います。
しかし、なぜ、「AWS Parameters and Secrets Lambda Extension を Handler 関数外からだと呼び出せないのか?」については、すみません。私には分かりませんでした。
もし、ご存知の方がいらっしゃっいましたら、教えていただけますと幸いです。

X. 終わりに

個人開発をしていた時に、このトラブルに遭遇しました。
私は 「400 Error」 が権限不足に起因するもの(具体的には 401 Error)だと考え、間違った調査や修正対応をしてしまい、休日を丸1日溶かしました:scream:
こちらのナレッジが、悩める皆様の休日を救えることを祈っています。

おまけ

このナレッジは、以下のアプリ(ゲーム)を開発していた際の副産物です。
技術的な工夫点も多いアプリですので、ぜひこちらもご覧ください。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0