AWS lambdaの拡張機能を使ってパラメータストアを取得するときに注意すること

## はじめに

クラウドコンピューティングが主流となる現代において、セキュアな設定情報や機密データの管理はますます重要になっています。特に、AWS（Amazon Web Services）を利用する企業や個人はその安全性と効率性を最大限に活用するためのツールやサービスを常に模索しています。この記事では、そんなAWSの中でも特に注目されている「AWS Parameters and Secrets Lambda Extension」を使ったときにハマったポイントがあったので紹介します。

## AWS Parameters and Secrets Lambda Extensionってなんぞ

AWS Parameters and Secrets Lambda Extensionは、AWS Lambda関数で利用する設定情報や機密データの取得と管理を簡素化するための拡張機能です。AWS Lambdaはサーバーレスコンピューティングを実現するためのサービスで、コードの実行に特化しており、従来のサーバー管理の煩わしさから解放されます。しかし、Lambda関数で利用する設定情報やシークレットの管理は依然として課題となっていました。

この拡張機能を導入することで、AWS Systems Manager Parameter StoreやAWS Secrets Managerから必要なデータを効率良く取得できるようになります。これにより、セキュアな方法で機密データを管理しつつ、Lambda関数のパフォーマンスも向上させることができます。具体的には、各Lambda関数の実行時に必要なパラメータやシークレットをキャッシュし、反復アクセスのコストを削減する仕組みが提供されます。

また、AWS Parameters and Secrets Lambda Extensionは設定が容易で、わずかなステップで導入できるのも特徴です。これにより、開発者はコアビジネスに集中しつつ、安全かつ効率的にデータを管理できるのです。

## AWS Lambda 関数での Parameter Store パラメーターの使用

堅苦しい話を書いたんですが、要は下記のようにしてパラメータストアの値を呼び出すことができます。いままでは、aws-sdkを使っていたのがhttp経由で取得が可能になっています。

```main.py
import urllib.request
import os
import json

aws_session_token = os.environ.get('AWS_SESSION_TOKEN')

def lambda_handler(event, context):
    # Retrieve /my/parameter from Parameter Store using extension cache
    req = urllib.request.Request('http://localhost:2773/systemsmanager/parameters/get?name=%2Fmy%2Fparameter')
    req.add_header('X-Aws-Parameters-Secrets-Token', aws_session_token)
    config = urllib.request.urlopen(req).read()

    return json.loads(config)
```

https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/ps-integration-lambda-extensions.html

## はまりポイントである、リクエストをどこで呼び出すのか

さて、サンプルコードも特に難しくなく実際にあの通りにやれば動きます（権限周りや、拡張機能をレイヤーに追加するとかは必要だけども、、、）。

ここまでは全然いいのですが、私がはまったのはこのリクエストを呼び出す場所がどこにあるのかということでした。例えば、こんな感じでハンドラー外で呼び出すと`400 Bad Request`となるのです。

__原因がわからないので有識者のかたがいれば教えてもらいたいです__


lambdaのinitフェーズで拡張機能が実行されるとエラーになるのかなぁと勝手に考えている次第です。


```sample1.py
# Retrieve /my/parameter from Parameter Store using extension cache
req = urllib.request.Request('http://localhost:2773/systemsmanager/parameters/get?name=%2Fmy%2Fparameter')
req.add_header('X-Aws-Parameters-Secrets-Token', aws_session_token)
config = urllib.request.urlopen(req).read()

def lambda_handler(event, context):
    return json.loads(config)
```

ただ、原因はなんであれ知っていれば上記のような書き方は回避できるんですが、これが外部モジュールに呼び出し部分を書いて置いていると途端に発見が困難になるんですよね。。。

例えば、こんな感じで別ファイルで`config`にあらかじめパラメータストアから取得した値をいれておいて、それを呼び出す形にしようとします。設定ファイルはあらかじめ用意しておきたい気持ちがありますよね。

ですが、これは先ほどの例と同じようにエラーになってしまいます。Lambdaの初期化フェーズ時にトップレベルに書かれているコードはすべて読み込まれるのですが、そのときにimportの部分ももれなく読み込まれます。したがって、先ほどのsample1のようにこちらもエラーになってしまいます。

```sample2.py
import config

def lambda_handler(event, context):
    return json.loads(config)
```

```config.py
req = urllib.request.Request('http://localhost:2773/systemsmanager/parameters/get?name=%2Fmy%2Fparameter')
req.add_header('X-Aws-Parameters-Secrets-Token', aws_session_token)
config = urllib.request.urlopen(req).read()
```

なので、こうやってimportをハンドラーの中にいれて解決するのも手段のひとつとしては存在します。

```sample3.py
def lambda_handler(event, context):
    import config
    return json.loads(config)
```

ただ、importは関数内に書くのは気持ち悪いので、大人しくファクトリ関数を作るなり、クラスを作るなりしてトップレベルでリクエストが起きないような工夫をするのがよいと思います。

```sample4.py
import config

def lambda_handler(event, context):
    config = create_config()
    return json.loads(config)
```

```config.py
def create_config():
    req = urllib.request.Request('http://localhost:2773/systemsmanager/parameters/get?name=%2Fmy%2Fparameter')
    req.add_header('X-Aws-Parameters-Secrets-Token', aws_session_token)
    config = urllib.request.urlopen(req).read()
    return config
```


# 終わりに

最初は権限周りのエラーかな？とも思ったのですが、そこらへんは問題なく。コンテナを使っていたのでコンテナバージョンだけそうなるのかと思ったがそうではなく。はたまた、外部モジュールの設置場所がおかしいのかと、、、

結構いろいろ試したのですが、結局ハンドラー外からの呼び出しが原因という。調べても原因がわからなかったので、どなたか教えてください。

追伸.

記事を書くにあたって同じような記事を見つけてしまった。が、書いてしまったので上げてしまうことにした。


https://qiita.com/hayate_h/items/00c6cf92b0dd7886c1f6