本番データは、日々の運用、サポート、トラブルシューティング、分析、開発において非常に重要です。
しかし、そのデータに SSN(社会保障番号)、メールアドレス、電話番号、その他の識別子のような機微(センシティブ)情報が含まれている場合、広範な読み取り権限はすぐに不要な情報の露出につながりかねません。
同様に重要なのは、多くの組織が、機微情報へのアクセスに対して強力な統制を求める規制上・契約上の要件の下で運用されていることです。そこでは、プライバシーやセキュリティの義務を満たすための手段の一つとして、データマスキングが求められることがよくあります(たとえば GDPR、HIPAA、PCI DSS、SOX、あるいは同様の社内セキュリティ基準に沿った取り組みなど)。
動的データマスキング (Dynamic Data Masking / DDM)はMySQL 9.7 LTS で GA(一般提供)となっており、MySQL Enterprise Edition および OCI MySQL HeatWave で利用できます。
動的データマスキングを使うと、ベーステーブルの列に対して直接マスキングポリシーを設定でき、MySQL はクエリ実行時に、実行ユーザーまたはアクティブなロールに応じて、元の値またはマスクされた値のいずれかを返します。
しかも、アプリケーションの変更や、別途マスク済みデータのコピーを維持する必要はありません。
なぜ動的データマスキングが必要なのか
これまで、MySQL におけるマスキングは、SQL 関数や UDF をビューと組み合わせて実装されることがよくありました。この方法は有効ではあるものの、通常は次のような対応が必要になります。
- 追加のデータベースのオブジェクト(ビュー)を作成し維持すること
- ユーザーがベーステーブルを直接問い合わせてマスキングを回避できないようにすること
- スキーマの変化に応じた継続的な運用作業
動的データマスキングはこの運用を単純化します。ベーステーブルの列レベルでマスキングを強制することで、最小権限アクセスを実装し、機微情報の露出を減らすのに役立ちます。これは、セキュリティ態勢やコンプライアンス施策の重要な構成要素となります。(もちろん、実際のコンプライアンス達成は、より広範な統制、プロセス、設定全体に依存します。)
仕組み:マスキングポリシーとゲートキーパー関数
マスキングポリシーは、次の要素を含む CASE 式として定義されます。
- ゲートキーパー関数:
CURRENT_USER_IN('...')ユーザーベースで判定する場合CURRENT_ROLE_IN('...')ロールベースで判定する場合(アクティブロール)
- マスキングが適用される場合に値を変換する式 (SSNの場合は
mask_ssn()を使用)
クエリがマスク対象列を参照すると、MySQL はゲートキーパーを評価し、次のいずれかを返します。:
- 元の列値(マスクなし)
- マスクされた値(ポリシーに基づいて計算された値)
この制御は MySQL サーバー内部で行われるため、アプリケーションやクエリ経路が異なっても、一貫した動作になります。
例 (コマンドのコピーアンドペーストで利用可): 認可されたユーザー/ロール以外には SSN をマスクする
以下は最小構成でそのまま実行できる例です。ユーザーベースの判定とロールベースの判定の両方を示しており、運用モデルに応じてどちらか一方、または両方を使用できます。
以下の一部の文は、ユーザー/ロール管理およびマスキングポリシー管理に必要な権限を前提とします。
0) セットアップ:スキーマ、テーブル、サンプルデータ
CREATE SCHEMA IF NOT EXISTS protected;
USE protected;
DROP TABLE IF EXISTS user_profiles;
CREATE TABLE user_profiles (
id INT PRIMARY KEY,
first_name VARCHAR(32),
last_name VARCHAR(32),
ssn VARCHAR(16),
zip_code VARCHAR(10)
);
INSERT INTO user_profiles VALUES
(1,'James','Smith','900-01-0001','10001'),
(2,'Mary','Johnson','900-01-0002','90210'),
(3,'Robert','Williams','900-01-0003','60601'),
(4,'Patricia','Brown','900-01-0004','30301');
1) ID のセットアップ:ユーザーとロール
CREATE USER IF NOT EXISTS 'seeall'@'%' IDENTIFIED BY 'Welcome_123!';
CREATE USER IF NOT EXISTS 'noseeall'@'%' IDENTIFIED BY 'Welcome_123!';
GRANT SELECT ON protected.user_profiles TO 'seeall'@'%';
GRANT SELECT ON protected.user_profiles TO 'noseeall'@'%';
CREATE ROLE IF NOT EXISTS 'pii_read'@'%';
GRANT SELECT ON protected.user_profiles TO 'pii_read'@'%';
GRANT 'pii_read'@'%' TO 'seeall'@'%';
2) マスキングポリシーの作成(ユーザーベース)
CREATE MASKING POLICY mask_ssn_policy(ssn_col)
CASE WHEN CURRENT_USER_IN('seeall')
THEN ssn_col
ELSE mask_ssn(ssn_col)
END;
3) SSN 列へのポリシー適用
ALTER TABLE protected.user_profiles
ALTER COLUMN ssn
SET MASKING POLICY mask_ssn_policy;
4) 動作確認
SELECT id, first_name, last_name, ssn, zip_code
FROM protected.user_profiles
ORDER BY id;
noseeallユーザにはマスクされた値を返す (例XXX-XX-0001)seeallユーザ(または該当するアクティブなロール)にはSSNが見える
ユーザー “noseeall” – データはマスクされている
mysql> select * from user_profiles;
+----+------------+-----------+-------------+----------+
| id | first_name | last_name | ssn | zip_code |
+----+------------+-----------+-------------+----------+
| 1 | James | Smith | XXX-XX-0001 | 10001 |
| 2 | Mary | Johnson | XXX-XX-0002 | 90210 |
| 3 | Robert | Williams | XXX-XX-0003 | 60601 |
| 4 | Patricia | Brown | XXX-XX-0004 | 30301 |
| 5 | John | Jones | XXX-XX-0005 | 75201 |
| 6 | Jennifer | Garcia | XXX-XX-0006 | 85001 |
| 7 | Michael | Miller | XXX-XX-0007 | 33101 |
| 8 | Linda | Davis | XXX-XX-0008 | 19101 |
| 9 | William | Rodriguez | XXX-XX-0009 | 94101 |
| 10 | Elizabeth | Martinez | XXX-XX-0010 | 98101 |
+----+------------+-----------+-------------+----------+
10 rows in set (0.000 sec)
ユーザー “seeall” – データはマスクされていない
mysql> select * from user_profiles;
+----+------------+-----------+-------------+----------+
| id | first_name | last_name | ssn | zip_code |
+----+------------+-----------+-------------+----------+
| 1 | James | Smith | 900-01-0001 | 10001 |
| 2 | Mary | Johnson | 900-01-0002 | 90210 |
| 3 | Robert | Williams | 900-01-0003 | 60601 |
| 4 | Patricia | Brown | 900-01-0004 | 30301 |
| 5 | John | Jones | 900-01-0005 | 75201 |
| 6 | Jennifer | Garcia | 900-01-0006 | 85001 |
| 7 | Michael | Miller | 900-01-0007 | 33101 |
| 8 | Linda | Davis | 900-01-0008 | 19101 |
| 9 | William | Rodriguez | 900-01-0009 | 94101 |
| 10 | Elizabeth | Martinez | 900-01-0010 | 98101 |
+----+------------+-----------+-------------+----------+
10 rows in set (0.000 sec)
サーバー側での強制が重要な理由
プロキシ層でのマスキングは有用な場合がありますが、通常それはデータベースの前段に位置します。そのため、ユーザーが MySQL に直接接続したり、何らかの方法でプロキシを迂回したりすると、機微なデータが露出する可能性があります。
動的データマスキングでは、マスキングは MySQL の内部、すなわちクエリ実行時に列値が解決される地点で強制されます。これにより、対話型 SQL、アプリケーション、データベースオブジェクト経由など、クエリがどのように発行されても一貫した強制適用が可能になります(データベースオブジェクトの場合は定義されている invoker / definer に従います)。
分析およびAIワークロードへの適合性
マスキングはクエリ実行時に適用され、サーバーによって強制されるため、動的データマスキングは分析用のデータ抽出、レポーティング、AI 支援クエリやエージェントといったワークフローにおいても、機微なデータの露出を減らす助けになります。しかも、利用する各ツール側で個別にマスキングのロジックを実装・維持する必要はありません。同じデータセットやパイプラインを利用しつつ、MySQL 側が実行主体のユーザーとアクティブなロールに応じた適切な値を返すことができます。
詳細 / 利用してみる
- MySQL 9.7 リリースノート: https://dev.mysql.com/doc/relnotes/mysql/9.7/en/
- OCI MySQL HeatWaveを試す(無料トライアル): https://www.oracle.com/heatwave/free/

