Amazon Aurora PostgreSQL における動的データマスキングの実装と検証 - pg_columnmask を用いたロール別動作・性能評価

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

こんにちは、近藤(りょう)です!

Amazon Aurora PostgreSQL(Aurora PostgreSQL) に pg_columnmask 拡張が追加され、データベース側で「動的データマスキング(Dynamic Data Masking)」を実現できるようになりました。
aws.amazon.com

これにより、アプリケーションを変更せずに、ロールごとにマスクされたデータを返す柔軟なアクセス制御が可能になります。

本記事では、pg_columnmask を用いた動的データマスキングの仕組みと設定方法、ロール別の動作を確認し、1000 万行のデータによる性能検証(フルスキャン / SELECT / ORDER BY / GROUP BY / JOIN)も行います。また、運用上の注意点やベストプラクティスについても解説します。

動的データマスキング(Dynamic Data Masking)とは?

動的データマスキング(DDM)は、ユーザーがデータベースへクエリを実行した際に、返却される値のみをマスクする仕組みです。

データそのものは変更せず、ロールごとにどの程度の詳細な値を表示するかを柔軟に制御できるため、アプリケーションの改修なしで機密情報を安全に扱える点が特徴です。

Aurora PostgreSQL では、pg_columnmask 拡張を有効化することで DDM が利用でき、次のようなマスキングを実現できます。

  • 氏名の一部伏せ字(例:山田太郎 → 山田**)
  • メールアドレスの部分マスク(例:taro@example.com → t*@example.com
  • 数値の丸め や ゼロ化(例:残高、個数などを粗い粒度で返す)

マスキングは列単位で柔軟に設定でき、ロールごとに異なるマスキングポリシーを適用することも可能です。

DDM は、データの閲覧権限をきめ細かく制御したい場合や、アプリケーションの改修コストを抑えながら機密情報を保護したい場面に適しています。 特に、分析環境や複数部門が利用するシステムなど、利用者ごとに異なる可視性が求められるケースで有効に活用できます。

pg_columnmask 拡張とは?

Aurora PostgreSQL の pg_columnmask は、ユーザーやロールに応じて SELECT 結果を動的にマスキングするための独自拡張です。

クエリ実行時にマスク関数を適用するよう自動で書き換える仕組みを持ち、アプリケーション側の修正なしで柔軟なアクセス制御を実現します。

docs.aws.amazon.com


Aurora PostgreSQL 固有の機能であり、OSS PostgreSQL の拡張一覧(Additional Supplied Modules)には含まれていません。

検証環境の準備

pg_columnmask を利用した動的データマスキングの動作検証を行うため、Aurora PostgreSQL Serverless v2 と、接続用の Amazon EC2 インスタンスを用いたシンプルな構成で環境を準備します。

前提条件

■ Aurora PostgreSQL のバージョン
本検証で利用する動的データマスキング(DDM)は、Aurora PostgreSQL の特定バージョン以降でサポートされています。

  • Aurora PostgreSQL のバージョン要件
    • Aurora PostgreSQL 16.10 以上
    • Aurora PostgreSQL 17.6 以上

docs.aws.amazon.com

■ クライアント用インスタンス(EC2)
Aurora PostgreSQL に対して接続し、DDL 実行や性能検証を行うため、以下の CLI が利用可能である必要があります。

  • AWS CLI(SSM 経由で EC2 に接続)
  • SSM Session Manager(SSH は不要)
  • psql(PostgreSQL クライアントツール)

検証構成

Aurora PostgreSQL Serverless v2 とクライアント用の EC2 インスタンスを利用し、Aurora PostgreSQL の動的データマスキング(DDM)の動作を確認します。

CloudFormation(CFn)テンプレートをデプロイすると、Aurora PostgreSQL に接続するためのエンドポイントや接続情報が Outputs に出力されますので、接続時はこちらをご参照ください。

検証構成

■ スペック
動作検証で使用する環境のスペックは以下の通りです。

  • データマスキング検証用DB(Aurora PostgreSQL Serverless v2)
    • エンジン:Aurora PostgreSQL 16.10
    • 構成:Serverless v2
    • スケーリング:2 ACU 固定(MinCapacity = 2 / MaxCapacity = 2)
  • クライアント用インスタンス(EC2)
    • OS:Amazon Linux 2023
    • インスタンスタイプ:t3.small

■ CFn テンプレート
以下の CloudFormation テンプレートを利用することで、本記事と同じ検証環境(Aurora PostgreSQL Serverless v2 + EC2)をそのまま構築できます。 動作を確認したい方は、こちらのテンプレートをご活用ください。


■ サンプルテーブル/ロール/データ
pg_columnmask の基本動作を確認するために、検証用の顧客情報テーブルとロール(ユーザー)ごとに異なるマスキングポリシーを設定します。
DDL の投入は postgres ユーザー(または同等権限のロール)で実行してください。
なお、各ロールのパスワードについてDDLをご確認下さい。

  • テーブル
    • customers(検証用の顧客情報テーブル)
  • ロール 及び マスキングポリシー
    • analyst(分析担当者)
       個人情報は強めにマスク
       (email / ssn 全てマスク、salary は粗粒度)
    • support(サポート担当者)
       必要な範囲のみ部分マスク
       (email のローカル部のみマスク、ssn 末尾4桁表示)
    • admin_user(管理者)
       すべてのデータをフル表示
       (マスキングなし)


以下、データが登録されます。

 id |    name     |       email        |     ssn     |  salary  
----+-------------+--------------------+-------------+----------
  1 | Taro Yamada | taro@example.com   | 123-45-6789 | 75000.00
  2 | Hanako Sato | hanako@example.com | 987-65-4321 | 85000.00
  3 | Jiro Tanaka | jiro@example.com   | 555-12-3456 | 65000.00

pg_columnmask が提供するマスキング関数は、以下の 3 種類のマスキング関数が用意されています。

  • mask_email(メールアドレス用マスク)
  • mask_text(任意の文字列マスク)
  • mask_timestamp(時刻データの粒度を落としてマスク)

pg_columnmask 機能を有効にするには、以下のSQLの実行が必要となります。

CREATE EXTENSION pg_columnmask;

実際に Aurora PostgreSQL 上で確認すると、拡張に含まれる関数は以下の通りです。

> SELECT proname 
FROM pg_proc
WHERE pronamespace = 'pgcolumnmask'::regnamespace 
  AND proname LIKE 'mask_%';
proname
----------------
mask_email
mask_text
mask_timestamp

docs.aws.amazon.com

マスキングポリシーの詳細はこちらをご確認下さい。

docs.aws.amazon.com

ロール別 動作検証

analyst(分析担当者) 動作検証

■ マスキングポリシー
email / ssn 全てマスク、salary は粗粒度

CALL pgcolumnmask.create_masking_policy(
    'analyst_policy',
    'public.customers',
    JSON_BUILD_OBJECT(
        'email',  'pgcolumnmask.mask_email(email)',
        'ssn',    'pgcolumnmask.mask_text(ssn)',
        'salary', 'ROUND(salary, -4)'
    )::JSONB,
    ARRAY['analyst'],
    100
);

■ 結果

testdb=> SELECT * FROM customers;
 id |    name     |       email        |     ssn     |  salary  
----+-------------+--------------------+-------------+----------
  1 | Taro Yamada | XXXX@XXXXXXX.com   | XXXXXXXXXXX | 80000.00
  2 | Hanako Sato | XXXXXX@XXXXXXX.com | XXXXXXXXXXX | 90000.00
  3 | Jiro Tanaka | XXXX@XXXXXXX.com   | XXXXXXXXXXX | 70000.00
(3 rows)

support(サポート担当者)動作検証

■ マスキングポリシー
email のローカル部のみマスク、ssn 末尾4桁表示

CALL pgcolumnmask.create_masking_policy(
    'support_policy',
    'public.customers',
    JSON_BUILD_OBJECT(
        'email', 'pgcolumnmask.mask_email(email, ''*'', true, false)',
        'ssn',   'pgcolumnmask.mask_text(ssn, ''*'', 0, 4)'
    )::JSONB,
    ARRAY['support'],
    200
);

■ 結果

testdb=> SELECT * FROM customers;
 id |    name     |       email        |     ssn     |  salary  
----+-------------+--------------------+-------------+----------
  1 | Taro Yamada | ****@example.com   | *******6789 | 75000.00
  2 | Hanako Sato | ******@example.com | *******4321 | 85000.00
  3 | Jiro Tanaka | ****@example.com   | *******3456 | 65000.00

admin_user(管理者)動作検証

■ マスキングポリシー
ポリシーなし

■ 結果

testdb=> SELECT * FROM customers;
 id |    name     |       email        |     ssn     |  salary  
----+-------------+--------------------+-------------+----------
  1 | Taro Yamada | taro@example.com   | 123-45-6789 | 75000.00
  2 | Hanako Sato | hanako@example.com | 987-65-4321 | 85000.00
  3 | Jiro Tanaka | jiro@example.com   | 555-12-3456 | 65000.00
(3 rows)

パフォーマンス検証

pg_columnmask を有効にした Aurora PostgreSQL に対して、
1000 万件のデータを用いた性能検証の結果をまとめます。

テスト条件

  • DB エンジン:Amazon Aurora PostgreSQL 16.10
  • インスタンス構成:Aurora Serverless v2(2 ACU 固定)
  • テーブルcustomers(1000 万件)
  • テストロール
    • admin_user:マスキングなし(フルアクセス)
    • analyst:強マスク(email / ssn 全マスク + salary 粗粒度化)
    • support:部分マスク(email ローカル部のみマスク、ssn 末尾 4 桁表示)
  • その他
    • 各ロールのテスト前に DISCARD ALL を実行し、セッションキャッシュをクリア
    • 計測は EXPLAIN ANALYZEExecution Time を使用

ケース 1:フルスキャン(COUNT)

■ ユースケース
分析バッチ / ETL などで全件カウント・集計を行うケースを想定

SELECT COUNT(*) FROM customers;
ロール 実測値 3回(ms) 平均(ms) 備考
analyst 2044.8 / 1976.3 / 2404.7 2142 マスク影響ほぼなし
support 2094.3 / 2405.6 / 1991.9 2164 ほぼ analyst と同等
admin_user 2410.7 / 1823.2 / 2403.5 2212

ケース 2:SELECT * LIMIT 10000

■ ユースケース
画面一覧表示や管理画面での「先頭の一定件数だけ表示する」ようなクエリ

SELECT * FROM customers LIMIT 10000;
ロール 実測値 3回(ms) 平均(ms) 備考
analyst 9.480 / 9.559 / 9.765 9.60
support 7.903 / 8.577 / 8.791 8.42
admin_user 1.824 / 1.814 / 1.873 1.84 最速

ケース 3:WHERE + ORDER BY

■ ユースケース
BI 分析で「給与帯」や「金額レンジ」グルーピングを模倣するクエリ

SELECT * FROM customers WHERE salary > 50000 ORDER BY salary DESC LIMIT 1000;
ロール 実測値 3回(ms) 平均(ms) 備考
analyst × / × / × マスク列のため、対象外
support 10384 / 8945 / 9306 9545
admin_user 5502 / 4373 / 5185 5020 最速

※ analyst はマスク制約により実行不可

ケース4:GROUP BY

■ ユースケース
BI 分析で「給与帯」や「金額レンジ」グルーピングを模倣するクエリ

SELECT ROUND(salary, -4) AS salary_range, COUNT(*) \
FROM customers \
GROUP BY ROUND(salary, -4) \
ORDER BY salary_range \
LIMIT 20;
ロール 実測値 3回(ms) 平均(ms) 備考
analyst × / × / × 対象列がマスク対象のため、実行不可
support 10623 / 12298 / 13215 12045
admin_user 10007 / 11715 / 10197 10639 最速

※ analyst はマスク制約により実行不可

ケース5:JOIN(自己結合)

■ ユースケース
「ユーザー情報 JOIN 注文情報」などの多テーブル JOIN を模倣したクエリ

SELECT c1.name, c1.email, c2.salary \
FROM customers c1 \
JOIN customers c2 ON c1.id = c2.id + 1 \
WHERE c1.id <= 100000 \
LIMIT 1000;
ロール 実測値 3回(ms) 平均(ms) 備考
analyst 5114 / 7026 / 6516 6219
support 5276 / 4795 / 5884 5318
admin_user 5256 / 5133 / 4587 4992 最速

パフォーマンス検証 まとめ

クエリ analyst support admin_user 備考
フルスキャン(COUNT) 2142ms 2164ms 2212ms ほぼ差なし
SELECT * LIMIT 10000 9.6ms 8.4ms 1.84ms
WHERE + ORDER BY × 9545ms 5020ms
GROUP BY × 12045ms 10639ms
JOIN 6219ms 5318ms 4992ms

COUNT(*) のような rewrite を必要としないクエリ では、マスク有無による性能差はほとんど見られませんでした。

一方で、マスキング処理の有無で実行時間に影響 していることが確認できました。

今回のテスト規模(1000 万件)でも一定の差分は見られたため、より大規模なテーブル(数億行)では、この差がさらに顕著に現れる可能性が高いと考えられます。

補足:analyst ロール のエラーについて

analyst ロールでは、マスクされたカラム(email, ssn, salary)に対してテスト3(WHERE句)やテスト4(GROUP BY)を使用するクエリが実行できませんでした。これは pg_columnmask の制約によるもので、マスクされたカラムに対する述語(Predicates)の使用が制限されています。

以下が発生したメッセージとなります。

ERROR:  Predicates on masked columns are not allowed
HINT:  Modify your query to exclude masked columns from predicates or joins.

docs.aws.amazon.com

参考:Performance insights の結果

以下、1回目~3回目実施中のPerformance insights の状況です。

Performance insights

参考:パフォーマンス検証用 - サンプルスクリプト

パフォーマンス検証をする時に利用したサンプルスクリプトとなります。

エンドポイント名を変更してご利用ください。(ユーザー名等を変更している場合はも修正お願いします。)

ベストプラクティス

Amazon Aurora PostgreSQL の動的データマスキング(pg_columnmask)を安全に運用するためには、「ロール設計」「マスキング関数の作成」「テーブル/ビューのトリガー扱い」の3つが重要です。

docs.aws.amazon.com

ロール設計:ロールベースのセキュリティアーキテクチャ

権限はユーザー個別ではなく ロール(=グループ) で管理し、必要なテーブルにのみ SELECT を許可するようにします。

スキーマ全体へ広い権限を付与せず、analyst、support のように職務別ロールを作り、ユーザーに割り当てることで最小権限の原則を維持しつつ、運用・監査をシンプルにすることができます。

マスキング関数の作成:セキュアマスキング機能の開発

pg_columnmask の挙動は、マスキング関数の品質に大きく依存します。以下の3点を満たす作り方が推奨されます。

  • BEGIN ATOMIC で早期バインディング
  • すべての関数呼び出しをスキーマ修飾する
  • NULL・空文字・不正フォーマットを必ず検証する

テーブル/ビューのトリガー扱い:DML トリガーの動作

pg_columnmask は SELECT 時のみ マスクを適用します。そのため、DML(INSERT / UPDATE / DELETE)とトリガーを組み合わせた場合、以下のように挙動が変わります。

  • テーブルトリガー
    • マスク前の生データを扱うので RAISE NOTICE 等で生データが漏洩する危険性があり注意が必要
  • ビュートリガー(INSTEAD OF)
    • マスク後のデータを扱うので表示権限に応じたデータが渡り比較的安全

マスクされたユーザーがトリガーを実行できないようにテーブルトリガー禁止またはビュートリガー禁止の制御を行うことが必要となることもありますのでご検討下さい。

まとめ

Aurora PostgreSQL の pg_columnmask 機能は、アプリ改修なしでロールごとに動的データマスキングを実現できることが分かりました。 COUNT(*) のような単純クエリは、ほぼ性能差はありませんが、WHERE / GROUP BY / JOIN など rewrite が必要なクエリの処理では差があることが確認できました。

また、マスク対象列に対する WHERE / GROUP BY / JOIN などの使用が制約されるため(Predicates 制限)、一部クエリを実行できないケースがありますが、個人情報を含む分析基盤、外部委託オペレーション、機密データの閲覧制御が必要な SaaS / マルチテナントの環境で有効そうです。

ロール設計・安全なマスキング関数・トリガー挙動の理解が若干必要ですが、pg_columnmask 機能を利用することで柔軟にマスキングを活用できるので是非導入を検討してはいかがでしょうか?

Amazon Comprehend を使った匿名化もありますが、日本語はまだ未対応(2025/12/09 時点)なのと開発が必要なので、実運用レベルでは pg_columnmask のほうが選択しやすいですね。

近藤 諒都

(記事一覧)

カスタマーサクセス部CS5課

夜行性ではありません。朝活派です。

趣味:お酒、旅行、バスケ、掃除、家庭用パン作り(ピザも)など

2025 Japan AWS All Certifications Engineers