Amazon Aurora + Amazon RDS Proxy でパスワード管理が不要な DB 運用構成を作ってみた

記事タイトルとURLをコピーする

こんにちは。 アプリケーションサービス本部ディベロップメントサービス1課の八鍬です。

Amazon Aurora PostgreSQL + Amazon RDS Proxy の構成で、DB 接続周りのパスワード管理を完全になくし、よりセキュアで運用負荷の低い構成に移行しました。

具体的には以下の 2 つを導入しています。

  • End-to-End IAM 認証: アプリケーション(AWS Lambda)から Aurora までの全経路を IAM 認証にし、接続用パスワードの管理を不要にする
  • Managed Master User Password: マスターユーザーのパスワードを RDS サービスに自動管理させ、ローテーション用の Lambda や AWS CloudFormation スタックを不要にする

この記事では、それぞれの仕組みと AWS CDK での設定手順を紹介します。なお、本記事の内容は 2026 年 5 月時点の情報に基づいています。AWS サービスや CDK の仕様は変更される可能性があるため、最新のドキュメントも併せてご確認ください。

背景: パスワード管理の運用課題

従来の構成では、以下のようなパスワード管理の課題がありました。

  • Lambda が DB 接続するたびに AWS Secrets Manager からパスワードを取得する必要がある
  • DB ユーザーごとにシークレットを作成・管理し、RDS Proxy に登録する必要がある
  • マスターパスワードのローテーションに専用の Lambda と AWS CloudFormation スタックが必要
  • ローテーション用 Lambda のランタイムがサポート切れになるリスクがある

これらを解消するため、「アプリケーション接続はパスワードレス」「マスターパスワードは RDS に自動管理させる」構成を目指しました。

対象読者

  • Amazon Aurora + Amazon RDS Proxy を使っていて、パスワード管理の運用負荷を下げたい方
  • AWS CDK で IAM 認証や Managed Master User Password を設定しようとしている方
  • Lambda から RDS Proxy 経由で DB 接続している方

検証環境

  • Amazon Aurora PostgreSQL 17.6(Aurora Serverless v2)
  • Amazon RDS Proxy
  • AWS Lambda(Node.js 22.x)
  • AWS CDK v2.236.0
  • TypeScript

1. End-to-End IAM 認証でアプリケーション接続をパスワードレスに

Amazon RDS Proxy の IAM 認証には 2 種類ある

RDS Proxy の IAM 認証には Standard と End-to-End の 2 種類があります(RDS Proxy concepts and terminology)。

Standard IAM authentication: Enforce IAM authentication for connections to your proxy while the proxy connects to the database using credentials stored in Secrets Manager.

End-to-end IAM authentication: Enforces IAM authentication for connections directly from your applications to your database through the proxy. End-to-end IAM authentication simplifies your security configuration and avoids database credential management in Secrets Manager.

Standard IAM 認証

Lambda --[IAMトークン]--> RDS Proxy --[パスワード認証]--> Aurora
  • クライアント → Proxy 間のみ IAM 認証
  • Proxy → Aurora 間は Secrets Manager のパスワードで接続
  • 各 DB ユーザーのシークレットを Proxy に登録する必要がある

End-to-End IAM 認証

Lambda --[IAMトークン]--> RDS Proxy --[IAM認証]--> Aurora
  • 全経路が IAM 認証
  • Proxy は自身の IAM ロール(rds-db:connect 権限)で Aurora に接続
  • アプリケーション接続用のパスワード管理が不要

今回はアプリケーション接続にパスワードを使わない構成にしたかったので、End-to-End IAM 認証を選択しました。

End-to-End IAM 認証の設定手順(AWS CDK)

1. Aurora クラスタで IAM 認証を有効化

const cluster = new rds.DatabaseCluster(this, "Cluster", {
  // ...
  iamAuthentication: true,
});

2. RDS Proxy で End-to-End IAM 認証を有効化

CDK で iamAuth: true を設定するだけでは Standard IAM 認証にしかなりません。End-to-End にするには defaultAuthScheme の指定が必要です。

const proxy = new rds.DatabaseProxy(this, "Proxy", {
  proxyTarget: rds.ProxyTarget.fromCluster(cluster),
  vpc,
  iamAuth: true,
  defaultAuthScheme: rds.DefaultAuthScheme.IAM_AUTH,
});

defaultAuthScheme は CDK v2.236.0 時点で L2 プロパティとしてサポートされています。

なお、defaultAuthScheme: DefaultAuthScheme.IAM_AUTH を指定する場合、secrets プロパティは不要です。CDK の型定義でも「One or more secrets are required when defaultAuthScheme is DefaultAuthScheme.NONE」と記載されており、End-to-End IAM 認証では Secrets Manager を経由しないため省略できます。

ハマりポイント: iamAuth: true だけで End-to-End になると思い込んでいました。ドキュメントを読み込むまで Standard IAM と End-to-End IAM の違いに気づけず、検証の途中まで CfnDBProxy への Override で対応していたのですが、L2 で対応済みでした。

3. Proxy の IAM ロールに rds-db:connect 権限を追加

End-to-End IAM 認証では、Proxy 自身が IAM で Aurora に接続します。CDK はこの権限を自動付与してくれないため、Proxy に渡すロールを自前で作成し、クラスタリソース ID を指定して rds-db:connect 権限を付与します。

const proxyRole = new iam.Role(this, "ProxyRole", {
  assumedBy: new iam.ServicePrincipal("rds.amazonaws.com"),
});

const proxy = new rds.DatabaseProxy(this, "Proxy", {
  // ...
  role: proxyRole,
  defaultAuthScheme: rds.DefaultAuthScheme.IAM_AUTH,
});

proxyRole.addToPolicy(
  new iam.PolicyStatement({
    actions: ["rds-db:connect"],
    resources: [
      `arn:aws:rds-db:${this.region}:${this.account}:dbuser:${cluster.clusterResourceIdentifier}/app_user`,
    ],
  }),
);

ハマりポイント 1: これがないと Proxy → Aurora 間の接続で PAM authentication failed エラーになります。エラーメッセージからは原因が分かりにくく、特定に時間がかかりました。

ハマりポイント 2: CDK の proxy.grantConnect(proxyRole, "app_user") を使いたくなりますが、これは Proxy リソース ID ベースの ARN(dbuser:{proxyResourceId}/app_user)を生成します。End-to-End IAM 認証で Proxy が Aurora に接続する際に必要なのは クラスタリソース ID ベースの ARN(dbuser:{clusterResourceId}/app_user)です。そのため、Proxy ロールへの権限付与には addToPolicy でクラスタリソース ID を明示する必要があります。grantConnect はクライアント(Lambda 等)→ Proxy 間の権限付与にのみ使用してください。

4. Lambda に rds-db:connect 権限を付与

proxy.grantConnect(lambda, "app_user");

5. DB ユーザーに rds_iam ロールを付与

Aurora 側でも、IAM 認証で接続するユーザーに rds_iam ロールを付与する必要があります。CDK デプロイだけでは DB 内部のユーザー設定は変わらないので、踏み台経由などで手動実行します。

GRANT rds_iam TO app_user;

ハマりポイント: これがないと同じく PAM authentication failed になります。手順 3 と同じエラーが出るため、どちらが原因か切り分けが必要です。

6. Lambda から IAM トークンで接続

@aws-sdk/rds-signer で IAM トークンを生成し、pgpassword フィールドに渡します。

公式には完全な接続サンプルコードは提供されておらず、@aws-sdk/rds-signer の READMEgetAuthToken() の使用例と「Use this token as the password for connecting to your RDS instance」という注記があるのみです。以下のコードはこの注記に従って実装しました。

import { Signer } from "@aws-sdk/rds-signer";
import { Client } from "pg";

const signer = new Signer({
  hostname: process.env.PROXY_ENDPOINT,
  port: 5432,
  username: "app_user",
});

const token = await signer.getAuthToken();

const client = new Client({
  host: process.env.PROXY_ENDPOINT,
  port: 5432,
  user: "app_user",
  password: token, // IAM トークンを password に渡す
  database: "mydb",
  ssl: { rejectUnauthorized: false },
});

await client.connect();

password フィールドに渡していますが、中身は固定パスワードではなく有効期限 15 分の一時トークンです。PostgreSQL のプロトコル上、認証情報は password フィールドで送る仕組みしかないため、このような形になります。

AWS 公式ドキュメントには Python の接続サンプルはありますが、TypeScript/Node.js で RDS Proxy 経由の End-to-End IAM 認証を実装したサンプルは見当たりませんでした。

2. Managed Master User Password でマスターパスワードの管理も自動化

アプリケーション接続をパスワードレスにしても、マスターユーザーのパスワードは依然として存在します。従来はこのローテーションにも運用コストがかかっていたため、Managed Master User Password を導入して自動化しました。

従来の方式の課題

  • Secrets Manager のローテーションに Lambda が必要
  • ローテーション用の AWS CloudFormation スタックが自動作成される(サポート切れの Python ランタイムの Lambda が含まれる場合がある)

Managed Master User Password の設定

RDS サービスがシークレット(rds!cluster-xxx)を自動作成・管理し、デフォルト 7 日間隔で自動ローテーションします。ローテーション用の Lambda / AWS CloudFormation スタックは作成されません。

CDK での設定は、L2 未対応のため CfnDBCluster への Override が必要です(L2 対応の PR がレビュー中: https://github.com/aws/aws-cdk/pull/35734 )。

const cfnCluster = cluster.node.defaultChild as rds.CfnDBCluster;
cfnCluster.addPropertyOverride("ManageMasterUserPassword", true);
cfnCluster.addPropertyOverride("MasterUserPassword", undefined);

注意: ManageMasterUserPassword を有効にすると、CDK が自動生成する DatabaseClusterSecret と RDS が管理する rds!cluster-xxx の 2 つのシークレットが作られます。実際に有効なのは rds!cluster-xxx の方で、パスワードの値が異なります。検証中にこれに気づかず混乱しました。

ローテーション検証

実際にローテーションを実行して、旧パスワードで接続できなくなることを確認しました。

aws rds modify-db-cluster \
  --db-cluster-identifier <cluster-id> \
  --rotate-master-user-password \
  --apply-immediately
確認項目 結果
ローテーション前: 旧パスワードで接続 ✅ 成功
ローテーション後: 旧パスワードで接続 password authentication failed
ローテーション用 Lambda が作成されていない ✅ 確認
ローテーション用 AWS CloudFormation スタックが作成されていない ✅ 確認

CDK コード全体

上記の設定をまとめた CDK スタックの全体像です。

import * as cdk from "aws-cdk-lib";
import * as ec2 from "aws-cdk-lib/aws-ec2";
import * as iam from "aws-cdk-lib/aws-iam";
import * as lambda from "aws-cdk-lib/aws-lambda";
import * as rds from "aws-cdk-lib/aws-rds";
import { Construct } from "constructs";

export class IamAuthStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // VPC
    const vpc = new ec2.Vpc(this, "Vpc", {
      maxAzs: 2,
      natGateways: 1,
    });

    // セキュリティグループ
    const dbSg = new ec2.SecurityGroup(this, "DbSg", { vpc });
    const proxySg = new ec2.SecurityGroup(this, "ProxySg", { vpc });
    dbSg.addIngressRule(proxySg, ec2.Port.tcp(5432));

    // Aurora クラスタ(IAM 認証有効 + Managed Master User Password)
    const cluster = new rds.DatabaseCluster(this, "Cluster", {
      engine: rds.DatabaseClusterEngine.auroraPostgres({
        version: rds.AuroraPostgresEngineVersion.VER_17_6,
      }),
      serverlessV2MinCapacity: 0,
      serverlessV2MaxCapacity: 2,
      writer: rds.ClusterInstance.serverlessV2("Writer"),
      defaultDatabaseName: "mydb",
      vpc,
      vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_ISOLATED },
      securityGroups: [dbSg],
      iamAuthentication: true,
      storageEncrypted: true,
    });

    // Managed Master User Password
    const cfnCluster = cluster.node.defaultChild as rds.CfnDBCluster;
    cfnCluster.addPropertyOverride("ManageMasterUserPassword", true);
    cfnCluster.addPropertyOverride("MasterUserPassword", undefined);

    // Proxy 用 IAM ロール
    const proxyRole = new iam.Role(this, "ProxyRole", {
      assumedBy: new iam.ServicePrincipal("rds.amazonaws.com"),
    });

    // RDS Proxy(End-to-End IAM 認証)
    const proxy = new rds.DatabaseProxy(this, "Proxy", {
      proxyTarget: rds.ProxyTarget.fromCluster(cluster),
      vpc,
      role: proxyRole,
      securityGroups: [proxySg],
      vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_ISOLATED },
      iamAuth: true,
      defaultAuthScheme: rds.DefaultAuthScheme.IAM_AUTH,
    });

    // Proxy ロールに rds-db:connect 権限を付与(End-to-End IAM 認証に必要)
    // grantConnect は Proxy リソース ID ベースの ARN を生成するため使用不可。
    // End-to-End ではクラスタリソース ID を指定する必要がある。
    proxyRole.addToPolicy(
      new iam.PolicyStatement({
        actions: ["rds-db:connect"],
        resources: [
          `arn:aws:rds-db:${this.region}:${this.account}:dbuser:${cluster.clusterResourceIdentifier}/app_user`,
        ],
      }),
    );

    // Lambda
    const fn = new lambda.Function(this, "AppFn", {
      runtime: lambda.Runtime.NODEJS_22_X,
      handler: "index.handler",
      code: lambda.Code.fromAsset("lambda"),
      environment: {
        PROXY_ENDPOINT: proxy.endpoint,
      },
      vpc,
      vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS },
      timeout: cdk.Duration.seconds(30),
    });

    // Lambda に rds-db:connect 権限を付与
    proxy.grantConnect(fn, "app_user");

    // Lambda → Proxy のセキュリティグループ許可
    proxySg.addIngressRule(
      fn.connections.securityGroups[0],
      ec2.Port.tcp(5432),
    );
  }
}

注意: 上記に加えて、Aurora 側で GRANT rds_iam TO app_user; の実行が必要です(踏み台経由等で手動実行)。

まとめ

今回の構成変更により、以下のパスワード管理が不要になりました。

項目 従来 今回
アプリケーション接続 Secrets Manager でパスワード管理 End-to-End IAM 認証(パスワード不要)
マスターパスワード Lambda + CloudFormation でローテーション Managed Master User Password(RDS が自動管理)

End-to-End IAM 認証の設定一覧です。

# 設定 方法
1 Aurora クラスタで IAM 認証を有効化 iamAuthentication: true
2 RDS Proxy で End-to-End IAM を有効化 defaultAuthScheme: rds.DefaultAuthScheme.IAM_AUTH
3 Proxy IAM ロールに rds-db:connect 権限を追加 proxyRole.addToPolicy(...) でクラスタリソース ID を指定
4 Lambda に rds-db:connect 権限を付与 proxy.grantConnect(lambda, "app_user")
5 DB ユーザーに rds_iam を付与 GRANT rds_iam TO app_user;
6 Lambda から IAM トークンで接続 @aws-sdk/rds-signerSigner.getAuthToken()

#3 と #4 で権限付与の方法が異なる点に注意してください。proxy.grantConnect() は Proxy リソース ID ベースの ARN を生成するため、クライアント → Proxy 間(#4)には使えますが、Proxy → Aurora 間(#3)にはクラスタリソース ID が必要なため addToPolicy で明示する必要があります。

設定箇所が複数のレイヤー(AWS CDK / IAM / DB / Lambda コード)に分散しているため、全体像を把握した上で進めるのがポイントです。同じ構成でパスワード管理の運用負荷を下げたい方の参考になれば幸いです。

参考資料