サードパーティDBのシークレット情報をAWS SecretsManagerで管理する

AWS

AWS Secrets Managerとは

AWS Secrets Managerは、AWSのマネージドサービスで、機密情報やAPIキーなどの秘密情報を安全に管理します。シークレット情報の安全な保管と自動ローテーション、AWSリソースとのシームレスな統合、アクセス管理と監査、そしてAPIやSDKを提供しています。これにより、セキュリティを強化し、アプリケーションの機密情報を効果的に管理できます。

今回すること

サードパーティのDBであるTiDBをAWS Lambdaで使用するため、TiDBのシークレット情報を管理し、実際にLambdaでどう動かすかの例を紹介したいと思います。

記事の構成

1. CDK実行環境にシークレット情報の環境変数を登録

2. CloudFormation(CDK)を実行してAWSリソースを作成

3. Lambda実行時、SDKでSecretsManagerからシークレット情報を取得、TiDBへアクセスする

1. CDK実行環境にシークレット情報の環境変数を登録

以下のようなRDBMSのシークレット情報を環境変数に登録します

RDBMS_HOST_NAME
RDBMS_USER_NAME
RDBMS_PASSWARD
RDBMS_REGION
RDBMS_PORT
RDBMS_DATABASE_NAME

2.CloudFormation(CDK)を実行してAWSリソースを作成

CDKでSecretsManagerリソースを定義

シークレットの識別子となる値としてsecret_nameと登録したいシークレット情報をここで定義します

import os

from aws_cdk import SecretValue
from aws_cdk import aws_secretsmanager as secretsmanager
from constructs import Construct

rdbms_host_name = os.getenv("RDBMS_HOST_NAME", "rdbms_host_name")
rdbms_user_name = os.getenv("RDBMS_USER_NAME", "rdbms_user_name")
rdbms_passward = os.getenv("RDBMS_PASSWARD", "rdbms_passward")
rdbms_region = os.getenv("RDBMS_REGION", "rdbms_region")
rdbms_port = os.getenv("RDBMS_PORT", "rdbms_port")
rdbms_database_name = os.getenv("RDBMS_DATABASE_NAME", "rdbms_database_name")


class RdbmsSecretsmanagerResource(Construct):
    def __init__(self, scope: Construct, id: str, secret_name):
        super().__init__(scope, id)

        self.secret: secretsmanager.Secret = secretsmanager.Secret(
            self,
            "RdbmsTokenSecret",
            secret_object_value={
                "RDBMS_HOST_NAME": SecretValue.unsafe_plain_text(rdbms_host_name),
                "RDBMS_USER_NAME": SecretValue.unsafe_plain_text(rdbms_user_name),
                "RDBMS_PASSWARD": SecretValue.unsafe_plain_text(rdbms_passward),
                "RDBMS_REGION": SecretValue.unsafe_plain_text(rdbms_region),
                "RDBMS_PORT": SecretValue.unsafe_plain_text(rdbms_port),
                "RDBMS_DATABASE_NAME": SecretValue.unsafe_plain_text(
                    rdbms_database_name
                ),
            },
            secret_name=secret_name,
        )

CDKでLambdaを定義(SecretsManagerを連携)

  • SecretsManagerのsecret_full_arnをLambdaの環境変数へ登録

注)secret.secret_nameだと余計なsuffixがついてしまい、後続のSecretIdに使えないようでしたのでsecret_full_arnを使用しました。secretから取らずに直接上記で設定したsecret_nameを使用しても動きますが、secretから取る方がコードがスッキリするのでsecret_full_arnを使用しています。

lambda_function = lambda_.Function(
    self,
    "TestFunction",
    environment={
      "RDBMS_SECRET_ARN": secret.secret_full_arn
    },
    ...
)
  • SecretsManagerを操作する権限をLambdaのロールへ付与

grant_readメソッドを使うと読み込み権限を簡単に付与できます

secret.grant_read(lambda_function)

3.Lambda実行時、SDKでSecretsManagerからシークレット情報を取得、TiDBへアクセスする

ここのSecretIdではsecret_arnかsecret_nameを使用できます(suffix付きのsecret_nameは使用できませんでした)

class SecretsmanagerModule:
    def __init__(self, secretsmanager_client, secret_id) -> None:
        self.secretsmanager_client = secretsmanager_client
        self.secret_id = secret_id

    def get_secret_value(self):
        response = self.secretsmanager_client.get_secret_value(SecretId=self.secret_id)
        return response["SecretString"]

取得したSecretStringはjson文字列になっているのでjson.loadsでパースする必要があります。

今回はsqlalchemyを使用してDBへアクセスします

import json
import os

import boto3
from secretsmanager_module import SecretsmanagerModule
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker


RDBMS_DATABASE_NAME = os.environ.get("RDBMS_DATABASE_NAME")

secretsmanager_client = boto3.client(
    "secretsmanager", region_name=os.environ["AWS_REGION"]
)

secret = json.loads(
    SecretsmanagerModule(
        secretsmanager_client=secretsmanager_client,
        secret_id=os.environ["RDBMS_SECRET_ARN"],
    ).get_secret_value()
)


class RdbmsModule:
    def __init__(self):
        engine = create_engine(
            f"mysql+mysqlconnector://{secret['RDBMS_USER_NAME']}:"
            + f"{secret['RDBMS_PASSWARD']}@{secret['RDBMS_HOST_NAME']}:"
            + f"{secret['RDBMS_PORT']}/{RDBMS_DATABASE_NAME}"
        )
        self.Session = sessionmaker(bind=engine)

    def create_session(self):
        return self.Session()

上記で生成したsessionでusersテーブルからuserリストを取得します

from rdbms.rdbms_module import RdbmsModule

from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

Base = declarative_base()

class User(Base):
    __tablename__ = 'users'

    id = Column(Integer, primary_key=True)
    name = Column(String)
    age = Column(Integer)

class GetUserProcessor:
    def __init__(self) -> None:
        self.rdbms_module = RdbmsModule()

    def get_user_list(self, parameter):
        session = self.rdbms_module.create_session()
        try:
            users = session.query(User).all()
            return users
        except Exception as e:
            session.rollback()
            raise e
        finally:
            session.close()

まとめ

今回はCDKでSecretsManagerにシークレットを登録する方法、Lambdaで登録したシークレットを取得しDBへアクセスした例を紹介しました

SecretsManagerではパスワードの自動ローテーションの設定なども出来るので今後も上手く付き合っていきたいサービスの一つですね

無料相談実施中
AWSを使用したサーバーレスアプリケーションの構築
サーバーレス開発内製化、AWSへの移行等
様々な課題について無料でご相談お受けいたします。
最新情報をチェックしよう!