API Gateway・Lambda・AuroraでAPIを作成してみた[Secrets Manager編]

# はじめに

みなさんこんにちは！「API Gateway・Lambda・AuroraでAPIを作成してみた」というテーマのもと、作成方法を複数回に分けてご紹介しております。前回[こちら](https://qiita.com/kazuki_horiuchi/private/1663571a754ca6e36b2f)の記事では、データベースの認証情報はLambda関数のコード上にハードコーディングされていましたので、今回はSecrets Manager経由で認証情報を取得する方法についてご紹介します。

# 目次

* [全体編](https://qiita.com/kazuki_horiuchi/private/cb80dbfd51e77255c653)
* [VPC・サブネット・セキュリティグループ編](https://qiita.com/kazuki_horiuchi/private/a9fef24b77a52e0109b1)
* [Aurora編](https://qiita.com/kazuki_horiuchi/private/a3dc6dec1dc5f1ec9445)
* [CloudShell編](https://qiita.com/kazuki_horiuchi/private/b2804424e44ad4c4790e)
* [Lambda編](https://qiita.com/kazuki_horiuchi/private/1663571a754ca6e36b2f)
* Secrets Manager編 << 今回はこちら
* [API Gateway編](https://qiita.com/kazuki_horiuchi/private/9cb5352c45211b3573f9)

# 本記事の対象

全体の構成図に対して、本記事では赤字で記載しているLambda関数からVPCエンドポイントを経由してSecrets Managerの認証情報を取得する部分を作成していきます。

![image.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3773682/e5c0eae9-a99b-8bb8-1ac5-8a9936899332.png)

# 作成の流れ

以下の流れで実施していきます。

* VPCエンドポイント用セキュリティグループの作成
* VPCエンドポイントの作成
* VPCエンドポイントポリシーの更新
* Lambda用セキュリティグループの更新
* Lambda実行ロールの更新
* Lambda関数のコード更新

:::note info
前提として以下が必要となります。詳細は[目次](#目次)のリンクよりご確認ください。

* VPC・サブネット・セキュリティグループの作成
* Auroraの作成
* Auroraへデータの格納
* Lambda関数の作成
:::

# 実際に作成してみた

## VPCエンドポイント用セキュリティグループの作成

新しくVPCエンドポイント用のセキュリティグループを作成し、インバウンド/アウトバウンドルールの設定を行います。

インバウンド

|設定項目 |ルール |
|---|---|
|タイプ|HTTPS|
|プロトコル|TCP|
|ポート範囲|443|
|リソースタイプ|カスタム|
|ソース|Lambda用セキュリティグループ|

アウトバウンド

* ルールなし

## VPCエンドポイントの作成

VPCエンドポイントを作成します。詳細は以下スクリーンショットよりご確認ください。

<details><summary>詳細な設定項目と設定値</summary>

![secretsmanager2.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3773682/054eb760-598c-04ca-e00a-876e9bac6645.png)

![secretsmanager3.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3773682/b156f032-4a5c-771b-1bbc-0fa58bdbd1f9.png)

![secretsmanager1.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3773682/00cfee0d-3e2b-6289-8574-19cdccd7f8d7.png)

</details>

## VPCエンドポイントポリシーの更新

VPCエンドポイントの作成ではフルアクセスのポリシーで作成しておりましたので、ポリシーを以下のように設定します。アカウントIDやVPCエンドポイントのリソースIDは適宜修正ください。

``` yaml
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "EnableSecretsManagerpermissions",
            "Effect": "Allow",
            "Principal": {
                "AWS": [
                    "アカウントID"
                ]
            },
            "Action": [
                "secretsmanager:GetSecretValue"
            ],
            "Resource": "*"
        },
        {
            "Sid": "RestrictGetSecretValueoperation",
            "Effect": "Deny",
            "Principal": "*",
            "Action": [
                "secretsmanager:GetSecretValue"
            ],
            "Resource": "*",
            "Condition": {
                "StringNotEquals": {
                    "aws:sourceVpce": "VPCエンドポイントのリソースID"
                }
            }
        }
    ]
}
```

## Lambda用セキュリティグループの更新

LambdaがVPCエンドポイントと疎通ができるように、Lambda用セキュリティグループのアウトバウンドルールの設定を行います。

アウトバウンド

|設定項目 |ルール |
|---|---|
|タイプ|HTTPS|
|プロトコル|TCP|
|ポート|443|
|送信先タイプ|カスタム|
|送信先|VPCエンドポイント用セキュリティグループ|

## Lambda実行ロールの更新

Lambda関数がSecrets Managerでシークレットを取得できるようにLambda実行ロールを以下順序で更新します。詳細はスクリーンショットを確認ください。

* [設定]->[アクセス権限]より実行ロールを選択
* 実行ロールの[許可]->[ポリシーをアタッチ]を選択
* 許可ポリシーに「SecretsManagerReadWrite」を追加

![secretsmanager4.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3773682/113bd46e-6925-7984-0d75-0fc55def8d25.png)

## Lambda関数のコード更新

更新後のコードは以下になります。「RDS Proxyのエンドポイント」「Secrets Managerの名前」「リージョン名」は適宜修正ください。更新点としては以下になります。

* Secrets Managerからシークレットを取得する「get_secrets()」関数の追加
* 「get_secrets()」関数で使用する変数の追加
* 「get_secrets()」関数の呼び出し

```diff_python
  import pymysql
  import json
+ import boto3

  HOST = "RDS Proxyのエンドポイント"
  DB_NAME = "dev"
- DB_USER_NAME = "データベースのユーザ名"
- DB_PASSWORD = "パスワード"
+ SECRET_NAME="Secrets Managerの名前"
+ REGION_NAME="リージョン名"

  def lambda_handler(event, context):
+     # データベースの認証情報を取得
+     secrets = get_secrets()
+     DB_USER_NAME = secrets["username"]
+     DB_PASSWORD = secrets["password"]
      # データベースへ接続
      conn = pymysql.connect(
          host=HOST,
          user=DB_USER_NAME,
          password=DB_PASSWORD,
          database=DB_NAME,
          cursorclass=pymysql.cursors.DictCursor
      )
      try:
          # SQLクエリ実行・結果取得
          with conn.cursor() as cur:
              cur.execute('SELECT * FROM book')
              results = cur.fetchall()
          return {
              'statusCode': 200,
              'body': results
          }
      except Exception as e:
          # エラーハンドリング
          return{
              'statusCode': 500,
              'body': json.dumps({'error': str(e)})
          }
      finally:
          # 接続を閉じる
          conn.close()

+ def get_secrets():
+     # Secrets Managerクライアントの作成
+     session = boto3.session.Session()
+     client = session.client(
+         service_name='secretsmanager',
+         region_name=REGION_NAME
+     )
+     try:
+         secrets = client.get_secret_value(SecretId=SECRET_NAME)
+     except Exception as e:
+         raise e
+     return json.loads(secrets["SecretString"])
```

# 終わりに

いかがだったでしょうか。今回はLambda関数からVPCエンドポイントを経由してSecrets Managerの認証情報を取得する方法についてご紹介しました。次回はついに最終回である[API Gateway編](https://qiita.com/kazuki_horiuchi/private/9cb5352c45211b3573f9)についてご紹介します。是非、次回以降もご覧いただけますと幸いです。もしこの記事がお役に立ちましたら、「いいね」「記事のストック」をお願いします！

---

- AWS は、米国その他の諸国における Amazon.com, Inc. またはその関連会社の商標です。
- その他、記載されている会社名および商品・製品・サービス名は、各社の商標または登録商標です。