【AWS Lambda】Secrets Manager 経由で DB に接続してみた

【AWS Lambda】Secrets Manager 経由で DB に接続してみた

VPC 内の Lambda から RDS/Aurora へ、Secrets Manager を経由してセキュアに接続する方法から、VPC 内の Lambda におけるネットワークの課題を、VPC Endpoint で解決する方法をご紹介します。
2026.07.13

はじめに

こんにちは、フニです。
本記事では、VPC に配置した Lambda から DB (RDS/Aurora) への接続を、Secrets Manager 経由で実施する方法についてご紹介します。

Lambda から DB への接続イメージ

Lambda から DB (RDS/Aurora) への接続が必要な場合、VPC に Lambda を配置するソリューションがあります。
例えば、以下画像のように Lambda を VPC 内のサブネットに配置すると、DB への接続が可能になります。

CleanShot 2026-07-10 at 17.31.35@2x.png

しかし、Lambda のみで DB 接続を行う場合は、認証情報を環境変数等に保存する必要があり、Lambda 関数の中身を確認できる人は誰でもパスワードを把握できてしまうため、セキュリティ上の懸念があります。

CleanShot 2026-07-13 at 16.13.19@2x.png

よりセキュアに DB の認証情報を管理したい場合、Secrets Manager にパスワードを保存するソリューションがあります。
しかし、VPC に Lambda を配置しているため、Secrets Manager には届かなくなってしまいます。

CleanShot 2026-07-13 at 16.15.34@2x.png

ネットワーク経路として、Endpoint もしくは NAT Gateway のような通信経路を確保することで、
VPC 内の Lambda からも Secrets Manager 経由で DB 接続が可能になります。
本記事では、VPC Endpoint での方法をご紹介します。

CleanShot 2026-07-10 at 17.51.46@2x.png

設定内容

前提条件

  • VPC 構築済み
  • DB 構築済み(RDS/Aurora)
  • Secrets Manager 構築済み
  • Lambda 関数構築済み (Python3.14)

AWS Lambda

  1. VPC 設定から、DB が配置されているサブネットと同じサブネットに設定します。

CleanShot 2026-07-13 at 14.33.19@2x.png

  1. 実行ロール側に、secretsmanager:GetSecretValue 権限を付与します。
    以下のサンプルポリシーを参考に、GetSecretValue 権限を付与してください。
    ※ 本番環境では、特定のシークレット ARN に限定することを推奨します。
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "secretsmanager:GetSecretValue",
            "Resource": "arn:aws:secretsmanager:ap-northeast-1:<AWSAccountID>:secret:*"
        }
    ]
}

CleanShot 2026-07-13 at 14.44.58@2x.png

  1. 接続テストまでに3秒以上かかる場合はタイムアウトとなる場合があります。
    画像のように十分な時間に変更してください。

CleanShot 2026-07-10 at 18.14.52@2x.png

  1. MySQL 操作のために、PyMySQL モジュールを zip としてアップロードします。

以下はサンプルコマンドです。

mkdir mysql && cd mysql
pip install PyMySQL -t .
zip -r pymysql-layer.zip .

CleanShot 2026-07-13 at 15.20.08@2x.png

CleanShot 2026-07-13 at 15.20.08@2x.png

画像のように、アップロードされたことを確認し、lambda_function.py ファイルの作成ボタンを押下します。

CleanShot 2026-07-13 at 15.43.43@2x.png

  1. 環境変数には、Secrets Manager ARN と DB のエンドポイントを入力し、以下のサンプルコードをデプロイします。

CleanShot 2026-07-13 at 14.58.11@2x.png

import json
import os

import boto3
import pymysql

SECRET_ARN = os.environ.get(
    "SECRET_ARN",
)
SECRET_USERNAME_KEY = os.environ.get("SECRET_USERNAME_KEY", "username")
SECRET_PASSWORD_KEY = os.environ.get("SECRET_PASSWORD_KEY", "password")
DB_HOST = os.environ.get(
    "DB_HOST",
)
DB_PORT = int(os.environ.get("DB_PORT", "3306"))

# Cache the secret across warm invocations to avoid repeated API calls
_cached_credentials = None

def get_db_credentials():
    global _cached_credentials
    if _cached_credentials is None:
        client = boto3.client("secretsmanager")
        response = client.get_secret_value(SecretId=SECRET_ARN)
        secret = json.loads(response["SecretString"])
        try:
            _cached_credentials = {
                "username": secret[SECRET_USERNAME_KEY],
                "password": secret[SECRET_PASSWORD_KEY],
            }
        except KeyError as error:
            # Expose key names only (never values) to help diagnose mismatches
            raise RuntimeError(
                f"Secret is missing key {error}. "
                f"Available keys: {sorted(secret.keys())}"
            ) from error
    return _cached_credentials

def lambda_handler(event, context):
    try:
        credentials = get_db_credentials()
    except Exception as error:
        message = f"Failed to fetch secret: {type(error).__name__}: {error}"
        print(message)
        return {"statusCode": 500, "body": json.dumps({"result": "failure", "error": message})}

    connection = None
    try:
        connection = pymysql.connect(
            host=DB_HOST,
            port=DB_PORT,
            user=credentials["username"],
            password=credentials["password"],
            connect_timeout=5,
        )
        with connection.cursor() as cursor:
            cursor.execute("SHOW DATABASES")
            databases = [row[0] for row in cursor.fetchall()]

        print(f"Connection succeeded. Databases: {databases}")
        return {
            "statusCode": 200,
            "body": json.dumps({"result": "success", "databases": databases}),
        }
    except Exception as error:
        message = f"Connection failed: {type(error).__name__}: {error}"
        print(message)
        return {
            "statusCode": 500,
            "body": json.dumps({"result": "failure", "error": message}),
        }
    finally:
        if connection:
            connection.close()

VPC Endpoint

画像のように、VPC Endpoint を作成します。
サブネットは Lambda・DB が配置されているプライベートサブネットにしています。

CleanShot 2026-07-13 at 14.36.06@2x.png

CleanShot 2026-07-13 at 14.38.20@2x.png

CleanShot 2026-07-13 at 14.39.44@2x.png

CleanShot 2026-07-13 at 14.40.03@2x.png

CleanShot 2026-07-13 at 14.41.04@2x.png

Security Group

  1. DB のセキュリティグループのインバウンドルール側に、Lambda のセキュリティグループを追加します。

CleanShot 2026-07-13 at 15.53.00@2x.png

  1. VPC Endpoint のセキュリティグループのインバウンドルール側に、HTTPS(443)通信の許可を追加します。

※ 本記事では、配置されている VPC の CIDR を許可しています。環境に合わせてご変更ください。

CleanShot 2026-07-13 at 15.54.57@2x.png

結果

Lambda 側でテストを実施すると、以下のように SHOW DATABASES のクエリ結果が表示されていました。
これで、Secrets Manager 経由で DB に接続が成功していることがわかります。

CleanShot 2026-07-10 at 17.51.46@2x.png

CleanShot 2026-07-13 at 15.56.54@2x.png

さいごに

今回は Lambda から DB (RDS/Aurora) への接続を、Secrets Manager 経由で実施してみました。
VPC に Lambda を配置すると、別途ネットワーク経路が必要となる点はハマりポイントでもあると思います。
この記事が、誰かの助けになれば幸いです。

参考

https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/configuration-vpc.html
https://docs.aws.amazon.com/ja_jp/secretsmanager/latest/userguide/vpc-endpoint-overview.html
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/with-secrets-manager.html

この記事をシェアする

AWSのお困り事はクラスメソッドへ

関連記事