ブログ

【AWS】RDS Proxy × IAM認証で実現する省運用とは!?構築のポイントを解説!

この記事をSNSでシェア!

はじめに

「DBパスワードは定期的に変更すべき」——。

セキュリティの鉄則として誰もが知るこの原則は、AWS Lambda(以下、Lambda)を中心としたサーバーレス環境においては、しばしば運用上の大きな負担となります。

「Secrets Managerでローテーションを設定したはずが、反映のタイムラグでLambdaがエラーを吐いた」

「パスワードを安全に受け渡すためだけのコードが、ビジネスロジックを汚している」

「そもそもパスワードを管理すること自体がリスクではないか?」

そんな悩みを一掃し、データベース接続から「パスワード」という概念そのものを消し去るのが、Amazon RDS Proxy(以下、RDS Proxy)の 「エンドツーエンドIAM認証」 です。本記事では、実案件での検証から得られた知見を元に、従来のシークレット管理を不要にする「究極の省運用」への道筋を、AWS CDKによる具体的な実装例を交えて解説します。

▼ この記事の対象読者

  • Lambda と Amazon RDS(以下、RDS) を組み合わせて利用しているエンジニア
  • RDSのパスワード管理の運用負荷に課題を感じている方
  • 具体的なコード例や実装のポイントを知りたい方

▼ 本記事で取り扱わないこと

  • AWS 基盤自体の基礎的な構築手順
    • RDS インスタンスそのものの作成や、VPC・サブネットなどのネットワーク基礎設計については詳細に解説しません。
  • 従来型のパスワード認証に関する詳細な運用ノウハウ
    • AWS Secrets Manager(以下、Secrets Manager) を使用した「パスワードによる認証」のベストプラクティスについては詳しく触れず、あくまで「IAM認証によるパスワードレス化」にフォーカスします。

なぜ「パスワードの呪縛」から逃れられないのか?従来方式の限界

LambdaとRDSを組み合わせる際、Lambdaの並列実行による「接続数オーバー」を防ぐために RDS Proxy は重要なコンポーネントです。

そのため、RDS Proxyはサーバーレスアーキテクチャにおいてなくてはならないぐらい重要なサービスですが、認証管理という点では課題がありました。

従来のパスワード認証:ローテーションの不安

Secrets Managerで自動化していても、本番環境で「パスワードが書き換わり続ける」ことによるエラーリスクや、キャッシュ考慮の不安は完全には拭えません。具体的な解決策として 「シングルユーザー戦略」あるいは「代替ユーザー戦略」が考えられますが、やや複雑な実装を取り入れる必要があります。それぞれの戦略の詳細や実装例についてはAWS Secrets Manager で実現する RDS パスワードの自動ローテーションで紹介していますのでそちらをご覧ください。

標準的なIAM認証:残されたシークレット

「Lambda〜Proxy間だけIAM認証」にする方式でも、Proxy〜RDS間には依然としてパスワードが必要です。管理者の視点では、シークレット管理の負担が半分残っている状態でした。

ついに来た!「エンドツーエンドIAM認証」の真価

クライアントからDBまで、すべての接続パスをIAM認証のみで完結させます。シークレットの管理対象がなくなるため、ローテーションという概念自体が消滅することになります。

標準IAM認証とエンドツーエンドIAM認証の違いをまとめると以下になります。

項目標準的なIAM認証エンドツーエンドIAM認証
Lambda 〜 Proxy 間の認証IAM認証IAM認証
Proxy 〜 DB 間の認証パスワード認証IAM認証
Secrets Manager の必要性必要 (Proxyがパスワードを取得するため)不要
※管理者操作用のパスワード管理は必要
シークレット管理の状態Proxy〜DB間のパスワード管理が必要であり、管理負担が残るシークレット管理が不要になる

なお、現時点でエンドツーエンドIAM認証をサポートしているのは以下のサービスのみです。

  • Amazon Aurora (MySQL / PostgreSQL)
  • RDS for MySQL
  • RDS for PostgreSQL

エンドツーエンドIAM認証のCDK実装解説

ここからは詰まったところを中心に実装手順やポイントを解説します。なお、掲載しているソースコードは動作を保証するものではありませんので活用の際は必ずご自身で確認をお願いいたします。

今回の検証で利用した各種ライブラリのバージョン情報は以下です。

  • aws-cdk-lib 2.232.1
  • aws-cdk 2.1033.0
  • Python 3.13

なお、データベースは通常のRDS PostgreSQL(単一インスタンス)で検証しています。

Step 1: RDS インスタンスのIAM認証の有効化

iamAuthentication プロパティをtrue にすることでRDSのIAM認証設定は有効化されます。

    // RDSインスタンス (PostgreSQL 15, IAM認証有効)
    const dbInstance = new rds.DatabaseInstance(this, 'PostgresInstance', {
      engine: rds.DatabaseInstanceEngine.postgres({
        version: rds.PostgresEngineVersion.VER_15,
      }),
      instanceType: ec2.InstanceType.of(ec2.InstanceClass.T3, ec2.InstanceSize.MICRO),
      vpc,
      vpcSubnets: {
        subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
      },
      securityGroups: [rdsSecurityGroup],
      multiAz: false,
      allocatedStorage: 20,
      maxAllocatedStorage: 100,
      databaseName: 'verificationdb',
      credentials: rds.Credentials.fromGeneratedSecret('postgres'), // ユーザー作成時に使用
      iamAuthentication: true, // IAM認証を有効化
      removalPolicy: cdk.RemovalPolicy.DESTROY,
    });

後述するようにIAM認証にはデータベースのユーザー作成および権限付与が必要です。その際にデータベースへのアクセスが必要ですので credentialsを指定してパスワードを生成しています。

Step 2: RDS Proxyの有効化

ポイントは以下3点です。

  • 認証スキーム(defaultAuthScheme)にIAM_AUTHを指定する。
  • Secrets Managerとの紐付け(auth)は設定不要。
  • TLS(クライアント – Proxy間)を有効化する。※ 標準的なIAM認証においても必要な設定
    // RDS Proxy (エンドツーエンドIAM認証) - L1コンストラクトを使用
    const cfnProxy = new rds.CfnDBProxy(this, 'RdsProxy', {
      dbProxyName: 'verification-rds-proxy',
      engineFamily: 'POSTGRESQL',
      defaultAuthScheme: 'IAM_AUTH', // エンドツーエンドIAM認証を有効化
      roleArn: proxyRole.roleArn,
      vpcSubnetIds: vpc.selectSubnets({
        subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
      }).subnetIds,
      vpcSecurityGroupIds: [proxySecurityGroup.securityGroupId],
      requireTls: true, // IAM認証ではTLSの有効化が必須
    });

L2コンストラクト(DatabaseProxy)で実装できないか調査しましたが、defaultAuthSchemeの設定が調査バージョンでは対応していないことを確認しました。iamAuthというプロパティはありますが、標準的なIAM認証で使用されるプロパティですのでこちらを使ってもダメなようです。

そもそもですが、secretsプロパティが必須ですのでその点からもうまくいかないだろうなと判断しましたので今回は最初からL1コンストラクトを使っています。

Step 3: RDS Proxyへの権限付与

RDS ProxyにRDSへの接続権限(rds-db:connect)を付与します。

    // RDS Proxy用のIAMロール
    const proxyRole = new iam.Role(this, 'RdsProxyRole', {
      assumedBy: new iam.ServicePrincipal('rds.amazonaws.com'),
      description: 'Role for RDS Proxy to access RDS with end-to-end IAM auth',
    });

    // RDS ProxyにRDSへのIAM認証権限を付与 (エンドツーエンドIAM認証に必要)
    proxyRole.addToPolicy(new iam.PolicyStatement({
      actions: ['rds-db:connect'],
      resources: [
        // エンドツーエンドIAM認証では、利用するDBユーザーごとに rds-db:connect が必要。
        // 追加ユーザー(hogeiam 等)を想定して、対象DB(=resourceId)配下をワイルドカード許可。
        `arn:aws:rds-db:${this.region}:${this.account}:dbuser:${cdk.Token.asString(dbInstance.instanceResourceId)}/*`
      ],
    }));

ポイントはリソース指定の後半部分です。

arn:aws:rds-db:AWSリージョン:AWSアカウント:dbuser:リソースID/DBユーザー名

リソースID部分は「データベースのインスタンスID」を指定します。インスタンスIDはAWSが生成する物理IDで「db-XXXXXXXXXXXXX」の形式で発行されます。なお、もしAuroraを利用する場合は「cluster-XXXXXXXXXXXXX」の形式です。

Step 4: Lambdaへの権限付与

LambdaからProxyへの接続権限(rds-db:connect)を付与します。

    // Proxyエンドポイントを取得
    const proxyEndpoint = cfnProxy.attrEndpoint;

    // ProxyのARNからリソースIDを抽出 (arn:aws:rds:region:account:db-proxy:prx-XXXXX)
    const proxyResourceId = cdk.Fn.select(6, cdk.Fn.split(':', cfnProxy.attrDbProxyArn));

    // Lambda実行ロール
    const lambdaRole = new iam.Role(this, 'LambdaExecutionRole', {
      assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'),
      managedPolicies: [
        iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaVPCAccessExecutionRole'),
      ],
    });

    // LambdaにRDS Proxy経由でRDSへのIAM認証権限を付与
    lambdaRole.addToPolicy(new iam.PolicyStatement({
      actions: ['rds-db:connect'],
      resources: [
        // RDS Proxy経由の接続では、ProxyのリソースID (prx-XXXXX) を指定
        `arn:aws:rds-db:${this.region}:${this.account}:dbuser:${proxyResourceId}/*`
      ],
    }));

ポイントはリソース指定の後半部分です。

arn:aws:rds-db:AWSリージョン:AWSアカウント:dbuser:リソースID/DBユーザー名

LambdaはProxyに直接接続するのでリソースID部分は「ProxyのリソースID」を指定します。こちらは「prx-XXXXXXXXXXXXX」という形式で発行されます。DBユーザー名部分は先ほどと同様です。

Step 5: データベース内部のユーザー・ロール設定

リソースデプロイ後にStep1で作成したパスワードを使用してデータベースへアクセスし、IAM認証ユーザーの作成と権限付与を行います。

-- IAM認証用ユーザーを作成

CREATE USER iam_user;

-- rds_iamロールを付与(IAM認証に必須)

GRANT rds_iam TO iam_user;

-- データベース接続権限を付与

GRANT CONNECT ON DATABASE verificationdb TO iam_user;

Step 6: クライアント実装(Lambda)

boto3で一時的な認証トークンを生成し、psycopg2でDBへ接続します。

import os
import json
import boto3
import psycopg2

# 環境変数から接続情報を取得
proxy_endpoint = os.environ['DB_PROXY_ENDPOINT']
port = os.environ['DB_PORT']
db_name = os.environ['DB_NAME']
db_user = os.environ['DB_USER']
region = os.environ['AWS_REGION']
rds_client = boto3.client('rds', region_name=region)

def handler(event, context):
    
    try:
        # RDS IAM認証トークンを生成
        auth_token = rds_client.generate_db_auth_token(
            DBHostname=proxy_endpoint,
            Port=port,
            DBUsername=db_user,
            Region=region
        )
                
        # RDS Proxy経由でPostgreSQLに接続
        connection = psycopg2.connect(
            host=proxy_endpoint,
            port=port,
            database=db_name,
            user=db_user,
            password=auth_token,
            sslmode='require',
            connect_timeout=10
        )

...

まとめ

本記事では、RDS Proxy の「エンドツーエンド IAM 認証」を活用し、Lambda から DB 接続に至るまでのパスワード管理を完全に撤廃する構成について解説しました。

サーバーレスアーキテクチャにおいて、DB 接続の管理は常に悩みの種となります。特に「パスワードの定期ローテーション」と「アプリケーションの可用性」を両立させる運用は、Secrets Manager を導入してもなお、キャッシュの考慮や書き換え時のエラーリスクといった「動的な状態」による複雑さが残ります。

完成後のアーキテクチャはシンプルですが、実装には多くの試行錯誤を要しました。 特に CDK の L2 コンストラクト未対応への対処や、IAM ポリシーのリソース ID 指定の複雑さなど、仕様の正確な把握に多くの時間を費やしたのが実情です。 しかし、CDKで一度実装してしまえば、再利用できるため、運用の省力化につながります。

本記事の検証結果が、皆様の構築時のショートカットとなり、スムーズな導入の一助となれば幸いです。

参考文献

RDS Proxy の IAM 認証の設定 – Amazon Relational Database Service 

RDS Proxy を介したデータベースへの接続 – Amazon Relational Database Service 

投稿者プロフィール

西村 僚介
西村 僚介
2021年入社、BS事業部所属。
現在はAWS(Amazon Web Services)を活用したクラウドインフラの設計・構築から、バックエンド開発までを一貫して担当しています。日々進化するクラウド技術をキャッチアップし、常に「その時点でのベストプラクティス」をお客様に提案できるよう心掛けています。単に動くものを作るだけでなく、運用開始後の保守性やコストパフォーマンスまで見据えた、長く愛されるシステム作りを大切にしています。
この記事をSNSでシェア!