【AWS】LambdaからSSM Parameter Storeの値を取得する (Lambda Extensions)

## 概要
Lambda Extensionsを使用することで、LambdaからParameter Storeの値を取得できるので、備忘録として残します。

## 権限
LambdaからParameter Storeの値を取得するためには、Lambdaに適切な実行ロールを設定する必要があります。以下は必要な権限です。

- ssm:GetParameter

- kms:Decrypt

kms:Decryptは、KMS (Key Management Service) でSecureStringとして暗号化されたパラメータを復号化するために必要です。

KMSを使用して暗号化していない場合（String）、ssm:GetParameterだけで十分です。

### ポリシー設定の例
``` json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "getParameterSSM",
            "Effect": "Allow",
            "Action": [
                "kms:Decrypt",
                "ssm:GetParameter"
            ],
            "Resource": [
                "arn:aws:kms:ap-northeast-1:AWS_ACCOUNT_ID:key/aws/ssm のキー ID",
                "arn:aws:ssm:ap-northeast-1:AWS_ACCOUNT_ID:parameter/*"
            ]
        }
    ]
}
```
## Lambda Extensions
次に、Lambda Extensionsレイヤーを追加します。

AWSレイヤーを選択
![スクリーンショット 2024-01-12 23.01.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2851087/b645520d-8f80-e901-356a-3989a561f18e.png)

AWS-Parameters-and-Secrets-Lambda-Extensionを選択
バージョンも選択しないとエラーになります。
![スクリーンショット 2024-01-12 23.02.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2851087/db07839e-69d7-ba85-d076-8207301b44f4.png)

### 環境変数
AWS-Parameters-and-Secrets-Lambda-Extensionには環境変数があり、設定することでキャッシュのTTLやポートなど細かい値を制御することができます。

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

| 環境変数 | 詳細 | 必須 | 有効な値 | デフォルト値 |
| ---- | ---- | --- | --- | --- |
| SSM_PARAMETER_STORE_TIMEOUT_MILLIS | Parameter Store へのリクエストのタイムアウト (ミリ秒単位)。0 (ゼロ) 値は、タイムアウトは発生しません。 | いいえ | すべての整数 | 0 (ゼロ) |
| SECRETS_MANAGER_TIMEOUT_MILLIS | Secrets Manager へのリクエストのタイムアウト (ミリ秒単位)。0 (ゼロ) 値は、タイムアウトは発生しません。 | いいえ | すべての整数 | 0 (ゼロ) |
| SSM_PARAMETER_STORE_TTL | キャッシュ内のパラメータが無効になるまでの最大有効期間 (秒単位)。0 (ゼロ) 値は、キャッシュをバイパスする必要があることを示します。PARAMETERS_SECRETS_EXTENSION_CACHE_SIZE の値が 0 (ゼロ) の場合、この変数は無視されます。 | いいえ | 0 (ゼロ) から 300 秒 (5 分) | 300 秒 (5 分) |
| SECRETS_MANAGER_TTL | キャッシュ内のシークレットが無効になるまでの最大有効期間 (秒単位)。0 (ゼロ) 値は、キャッシュがバイパスされたことを示します。PARAMETERS_SECRETS_EXTENSION_CACHE_SIZE の値が 0 (ゼロ) の場合、この変数は無視されます。 | いいえ | 0 (ゼロ) から 300 秒 (5 分) | 300 秒 (5 分) |
| PARAMETERS_SECRETS_EXTENSION_CACHE_ENABLED | 拡張機能のキャッシュが有効になっているかどうかを決定します。重要な値: TRUE FALSE | いいえ | TRUE、FALSE | TRUE |
| PARAMETERS_SECRETS_EXTENSION_CACHE_SIZE | アイテム数に関するキャッシュの最大サイズ。0 (ゼロ) 値は、キャッシュがバイパスされたことを示します。両方のキャッシュ TTL 値が 0 (ゼロ) の場合、この変数は無視されます。 | いいえ | 0 (ゼロ) から 1000 | 1000 |
| PARAMETERS_SECRETS_EXTENSION_HTTP_PORT | ローカル HTTP サーバーのポート。 | いいえ | 1024-65535 | 2773 |
| PARAMETERS_SECRETS_EXTENSION_MAX_CONNECTIONS | 拡張機能が Parameter Store または Secrets Manager へのリクエストに使用する HTTP クライアントの最大接続数。これは、Secrets Manager クライアントと Parameter Store クライアントの両方がバックエンドサービスに対して行う接続数のクライアントごとの設定です。 | いいえ | 最小 1、上限なし。 | 3 |
| PARAMETERS_SECRETS_EXTENSION_LOG_LEVEL | 拡張機能のログに報告された詳細レベル。拡張機能をセットアップしてテストする際は、キャッシュ構成に関する最も詳細に知るには DEBUG を使用することをお勧めします。Lambda 操作のログは、関連する CloudWatch Logs ロググループに自動的にプッシュ配信されます。 | いいえ | DEBUG WARN ERROR NONE INFO | INFO |

## 値を取得する
ここまで設定できたらあとは、ローカルホストに対して以下のようなHTTPリクエストを行うことで値を取得することができます。

``` python
parameter_url = ('http://localhost:' + port + '/systemsmanager/parameters/get/?name=' + ssm_parameter_path)
```
KMSで暗号化された値を復号化して取得したい場合は、ssm_parameter_pathの後ろに```&withDecryption={true}```を設定するだけでOKです。
(false に設定することで、パラメータが復号化されないように指定できます。)

## 標準ライブラリrequestsを使用
### Pythonを使用したサンプルコード

今回は、requestsライブラリを使用して値を取得しました。
わかりやすいようにCloudWatch Logsへログを出力しています。

``` python
import os
import requests

def get_parameter(key_name, label):
    # デフォルトポートは2773
    end_point = 'http://localhost:2773'
    path = '/systemsmanager/parameters/get/?name={}'.format(key_name)
    url = end_point + path
    
    # GETリクエストに必要なトークン
    headers = {
        'X-Aws-Parameters-Secrets-Token': os.environ['AWS_SESSION_TOKEN']
    }

    res = requests.get(url, headers=headers)

    # レスポンスの中から値のみを取得
    value = res.json()['Parameter']['Value']

    # ログを出力
    print(f"環境変数 {label}: {value}")
    return value

```

### 実行
例としてParameter Storeに/sample/app/nameという階層構造で保存されている12345という値を取得してみます。

``` python
sample = get_parameter('%2Fsample%2Fapp%2Fname', 'sample')
```

ログを確認すると取得できています。
```
2024-01-12T22:32:44.478+09:00	環境変数 sample: 12345
```

## 注意点
- パスはHTTP用に```エンコード```する必要があります。
    - /sample/app/nameの場合は、%2Fsample%2Fapp%2Fnameのように。
- headerに```AWS_SESSION_TOKEN```を設定する必要があります。
- ポートは環境変数で変更することができますが、デフォルトでは```2773```になっています。

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