Aurora MySQL × CDK によるDB運用ユーザー管理と監査ログ設計
はじめに
こんにちは、ちっぴーです。早いものでDWSに入社して半年が経ちました。
ここ最近は、AWS上で稼働する業務Webアプリケーションの開発に携わっており、現在は本番運用フェーズへの移行を進めています。
これまでは開発者だけがDBに直接アクセスしていましたが、運用フェーズへ移行するにあたり、「誰が、何ができるか」を明確に定義し、適切な権限を持つユーザーを整備する必要がありました。
また、DBを操作するユーザーが増えることで、操作履歴を追跡できる監査ログの整備も必要になります。
この記事では、Aurora MySQL でのロール設計から、CDK を使った監査ログの設定まで、実際に取り組んだ内容をまとめます。
システム構成
構成はおおまかに次のようなものです。
- データベース: Amazon Aurora MySQL 3 系
- IaC: AWS CDK
- アプリケーション接続: RDS Proxy 経由
- 運用者接続: 踏み台サーバーなどを経由して接続
DBロール設計
MySQL ロールで権限を整理する
MySQL にはロール機能があり、権限をロールとしてまとめてユーザーに付与できます。ユーザーに直接権限を付与すると管理が難しくなるため、ロールを介する設計にしました。
今回は用途ごとに次のようなロールを定義しました。
| ロール | 用途 | 主な権限 |
|---|---|---|
role_dbadmin | DB管理ユーザー | 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 の内部ユーザー(rdsadmin・rdsproxyadmin)による大量のログが出力されました。
そのため、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 を運用フェーズへ移行する際の、ロール設計・監査設計の実例として参考になれば幸いです。
