AWS

Aurora MySQL × CDK によるDB運用ユーザー管理と監査ログ設計

chippy

はじめに

こんにちは、ちっぴーです。早いものでDWSに入社して半年が経ちました。

ここ最近は、AWS上で稼働する業務Webアプリケーションの開発に携わっており、現在は本番運用フェーズへの移行を進めています。

これまでは開発者だけがDBに直接アクセスしていましたが、運用フェーズへ移行するにあたり、「誰が、何ができるか」を明確に定義し、適切な権限を持つユーザーを整備する必要がありました。

また、DBを操作するユーザーが増えることで、操作履歴を追跡できる監査ログの整備も必要になります。

この記事では、Aurora MySQL でのロール設計から、CDK を使った監査ログの設定まで、実際に取り組んだ内容をまとめます。

システム構成

構成はおおまかに次のようなものです。

  • データベース: Amazon Aurora MySQL 3 系
  • IaC: AWS CDK
  • アプリケーション接続: RDS Proxy 経由
  • 運用者接続: 踏み台サーバーなどを経由して接続

DBロール設計

MySQL ロールで権限を整理する

MySQL にはロール機能があり、権限をロールとしてまとめてユーザーに付与できます。ユーザーに直接権限を付与すると管理が難しくなるため、ロールを介する設計にしました。

今回は用途ごとに次のようなロールを定義しました。

ロール用途主な権限
role_dbadminDB管理ユーザーCRUD + DDL + CREATE USER + ロール管理
role_readonly運用ユーザー(参照)SELECT, SHOW VIEW
role_write運用ユーザー(更新)SELECT, INSERT, UPDATE, DELETE, SHOW VIEW

運用ユーザーには、通常は role_readonly のみを付与し、更新作業が必要な場合だけ一時的に role_write を追加付与する運用にしました。

「権限は広めに付けておく」より、「普段は狭く、必要なときだけ広げる」という方針で設計しました。

ロール管理をDB管理ユーザーに委譲する

運用フェーズでは、開発チームがDB運用から離れ、運用チームのDB管理ユーザーがユーザー管理を行う想定です。

そのため role_dbadmin に対して WITH ADMIN OPTION を付与することで、DB管理ユーザーが他ユーザーへのロール付与・剥奪を行えるようにしました。

GRANT 'role_readonly' TO 'role_dbadmin' WITH ADMIN OPTION;
GRANT 'role_write' TO 'role_dbadmin' WITH ADMIN OPTION;

たとえば、DB管理ユーザーがrole_readonlyユーザーにrole_writeを一時付与する時は、次のように実行します。

GRANT 'role_write' TO 'target_user'@'%';
SET DEFAULT ROLE ALL TO 'target_user'@'%';

SET DEFAULT ROLE を忘れない

MySQL では、GRANT でロールを付与しただけでは、ログイン時にそのロールが有効にならない場合があります。

そのため、ロール付与とあわせて SET DEFAULT ROLE を実行します。これにより、付与済みのロールがログイン時に自動で有効になります。

GRANT 'role_readonly' TO 'user01'@'%';
SET DEFAULT ROLE 'role_readonly' TO 'user01'@'%';

これを忘れると、ロールは付与されているのに権限が有効にならず、Access denied が発生します。

ロール作成スクリプトによる自動化

ロール定義は develop / staging / 本番の複数環境で同じ内容を適用する必要があったので、ロール作成と権限付与はスクリプト化しました。

一方、ユーザー作成はパスワードの平文をコードに書きたくなかったので、別の運用手順として扱うことにしました。

// ロール作成(IF NOT EXISTS で冪等に実行可能)
for (const role of ['role_dbadmin', 'role_readonly', 'role_write']) {
  await db.execute(sql.raw(`CREATE ROLE IF NOT EXISTS '${role}'`));
}

// role_dbadminへの権限付与
await db.execute(sql.raw(
  `GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, ALTER, DROP, INDEX,
   CREATE VIEW, SHOW VIEW ON \`${dbname}\`.* TO 'role_dbadmin'`
));

// role_dbadminがCREATE USERできるようにする
await db.execute(sql.raw(`GRANT CREATE USER ON *.* TO 'role_dbadmin'`));

// role_dbadminが他ユーザーへrole_readonlyとrole_writeを付与できるようにする
await db.execute(sql.raw(`GRANT 'role_readonly' TO 'role_dbadmin' WITH ADMIN OPTION`));
await db.execute(sql.raw(`GRANT 'role_write' TO 'role_dbadmin' WITH ADMIN OPTION`));

GRANT は冪等に実行できます。一方で、権限を減らしたい場合は REVOKE が必要です。

つまり、スクリプトを修正して再実行するだけでは「不要になった権限を削る」ことはできないので、そこは注意が必要です。

監査ログの設定

運用ユーザーが増えたことで「誰がいつ何をしたか」を記録する必要が生じました。Aurora MySQL の Advanced Auditing を CDK で設定します。

まず、DBクラスター側で CloudWatch Logs へのエクスポートを有効にします。

const rdsCluster = new rds.DatabaseCluster(stack, 'RdsCluster', {
  cloudwatchLogsExports: ['audit'],
  // その他の設定
});

また、クラスターパラメータグループに監査ログ用のパラメータを設定します。

const rdsClusterParameterGroup = new rds.ParameterGroup(stack, 'RdsClusterParameterGroup', {
  engine: rds.DatabaseClusterEngine.auroraMysql({
    version: rds.AuroraMysqlEngineVersion.VER_3_08_1,
  }),
  parameters: {
    server_audit_logging: '1',                                      // 監査ログを有効化
    server_audit_events: 'CONNECT,QUERY_DCL,QUERY_DDL,QUERY_DML',  // 記録するイベント
    server_audit_excl_users: '<アプリユーザー名>,rdsadmin,rdsproxyadmin',  // 除外するユーザー
  },
});

これで、監査ログが CloudWatch Logs に出力されます。

記録するイベントを選ぶ

監査ログを全部取るとログ量とコストが増えるので、「運用ユーザーの接続・権限変更・スキーマ変更・データ操作」が追えれば十分、という基準でイベントを選びました。

今回は、主に次のイベントを対象にしました。

  • CONNECT: ログイン・ログアウト
  • QUERY_DCL: ユーザー作成や GRANT など
  • QUERY_DDL: CREATE / ALTER / DROP など
  • QUERY_DML: SELECT / INSERT / UPDATE / DELETE など

TABLE イベントもありますが、DML と重複する情報が多く、ログ量も増えやすいため今回は対象外にしました。

除外ユーザーを設定する

今回は運用ユーザーの操作のみを記録したかったため、アプリユーザーは除外対象としました。

しかしデプロイ後、Aurora の内部ユーザー(rdsadminrdsproxyadmin)による大量のログが出力されました。

そのため、server_audit_excl_usersでアプリユーザーと合わせて内部ユーザーも除外しました。

server_audit_excl_users: '<アプリユーザー名>,rdsadmin,rdsproxyadmin',

ただし、server_audit_excl_users はすべてのイベントに効くわけではありません。特に CONNECT / DISCONNECT は除外できないため、内部ユーザーの接続ログは出力され続けてしまいます。

その場合は、CloudWatch Logs Insights でユーザー名を条件に絞り込むと、運用上は扱いやすくなります。

まとめ

Aurora MySQL の運用ユーザー管理を整備するにあたり、単にユーザーを追加するだけではなく、

  • 「誰にどこまでの権限を持たせるか」
  • 「権限変更を誰に委譲するか」
  • 「その操作をどう監査するか」

まで含めて設計する必要がありました。

実際に設定してみると、server_audit_excl_users の対象範囲など、ドキュメントを読むだけでは見落としやすい点がありました。

Aurora MySQL を運用フェーズへ移行する際の、ロール設計・監査設計の実例として参考になれば幸いです。

AUTHOR
chippy
chippy
記事URLをコピーしました