この記事は PostgreSQL Advent Calendaer 2019 の 16 日目です。昨日の記事は @masayuki038@github さんの「PostgreSQL の JIT が生成するコードを眺めてみる」でした。この記事では PostgreSQL でデータの匿名化・マスキングを行う拡張 PostgreSQL Anonymizer について紹介します。
匿名化・マスキングとは
データベースには個人情報や企業の機密情報など、取り扱いに注意が必要なデータが含まれています。そのようなデータを開発やテスト、分析でそのまま使っては情報漏洩のリスクがあります。そのような場合にデータを安全に取り扱う方法の 1 つに匿名化があります。
匿名化 (anonymization) はデータから個人を特定できる情報を取り除くことです。マスキング (masking) は匿名化の一種で、情報を隠して匿名化することです。ただ、匿名化とマスキングはほぼ同じ意味で使われることが多いようです。
PostgreSQL Anonymizer とは
PostgreSQL Anonymizer は PostgreSQL でデータの匿名化・マスキングを行う拡張です。開発元はフランスの Dalibo 社です。以前は Ora2Pg の開発者がいたことでも有名でした。ライセンスは PostgreSQL と同じく PostgreSQL ライセンス です。
PostgreSQL Anonymizer では、PostgreSQL のデータ定義言語 (DDL) を拡張し、データをどのようにマスキングするかのルールを定義します。それにより、データをマスキングして更新したり、SQL 形式でダンプしたり、ユーザにマスキングしたデータをアクセスさせたりできます。
PostgreSQL Anonymizer は PostgreSQL 9.6 以降に対応しています。バージョン 9.5 でも使えなくはないですが、パッケージが提供されておらず、いくつか問題 もあります。また、まだ開発途上にあるプロジェクトなので、それを分かった上で使うのがいいでしょう。
PostgreSQL Anonymizer のインストール
PostgreSQL Anonymizer は PostgreSQL 開発元の Yum リポジトリに含まれているので、CentOS などの Red Hat 系 OS を使っていて、Yum リポジトリを登録済みであれば、簡単にインストールできます。PostgreSQL のインストールについては こちらの記事 を参考にしてください。
ここでは、CentOS 8 に PostgreSQL 12 をインストールした状態を前提にします。
まず、PostgreSQL Anonymizer のパッケージ postgresql_anonymizer12
をインストールします。パッケージ名の最後の数字 12
は PostgreSQL のバージョンを表します。お使いの PostgreSQL のバージョンに合わせて変更してください。
# yum -y install postgresql_anonymizer12
(省略)
Installed:
postgresql_anonymizer12-0.5.0-1.rhel8.x86_64
perl-Carp-1.42-396.el8.noarch
perl-Exporter-5.72-396.el8.noarch
perl-libs-4:5.26.3-416.el8.x86_64
ddlx_12-0.15-1.rhel8.noarch
postgresql12-contrib-12.1-2PGDG.rhel8.x86_64
Complete!
postgresql_anonymizer12
パッケージをインストールすると、依存関係で perl
関連、ddlx
、postgresql12-contrib
パッケージがインストールされます。ddlx
パッケージはデータベースオブジェクトから DDL を抽出する拡張です。postgresql12-contrib
パッケージは PostgreSQL の追加モジュールで、必要なのはそれに含まれる拡張 tsm_system_rows
です。tsm_system_rows
拡張は最大行数を指定してテーブルからサンプリングする拡張です。perl
関連パッケージは追加モジュールの一部が必要としています。
次に、PostgreSQL の設定ファイル postgresql.conf
を編集し、PostgreSQL サーバの起動時に読み込む共有ライブラリを指定するパラメータ shared_preload_libraries
に PostgreSQL Anonymizer を表す anon
をカンマ区切りで追加します。
# - Shared Library Preloading -
shared_preload_libraries = 'anon' # (change requires restart)
#local_preload_libraries = ''
#session_preload_libraries = ''
#jit_provider = 'llvmjit' # JIT library to use
パラメータの設定を反映させるため、PostgreSQL のサービスを再起動します。
# systemctl restart postgresql-12.service
最後に、PostgreSQL Anonymizer のテスト用にデータベース anondb
を作成して接続し、PostgreSQL Anonymizer の拡張 anon
をインストールし、anon.load
関数でマスキングに使う偽装データをロードします。
# su - postgres
Last login: Tue Dec 3 21:10:31 UTC 2019 on pts/0
$ createdb anondb
$ psql anondb
=# CREATE EXTENSION anon CASCADE;
NOTICE: installing required extension "tsm_system_rows"
NOTICE: installing required extension "ddlx"
CREATE EXTENSION
=# SELECT anon.load();
SELECT
load
------
t
(1 行)
=# \q
CREATE EXTENSION
コマンドの実行時に CASCADE
を指定するのは、依存関係のある拡張をいっしょにインストールするためです。
これで、PostgreSQL Anonymizer のインストールは完了です。
マスキングルールの定義
テストデータを生成し、マスキングルールを定義します。
まず、テストデータを生成します。部署と社員情報を格納するテーブル departments
、employees
を作成し、データを登録する SQL ファイル init.sql
を用意します。
CREATE TABLE departments (
id serial PRIMARY KEY,
name text NOT NULL
);
CREATE TABLE employees (
id serial PRIMARY KEY,
last_name text NOT NULL,
first_name text NOT NULL,
email text NOT NULL,
salary int NOT NULL,
department_id int NOT NULL REFERENCES departments (id),
birth date NOT NULL
);
INSERT INTO departments (name) VALUES
('営業部'),
('技術部');
INSERT INTO employees (last_name, first_name, email, salary, department_id, birth) VALUES
('佐藤', '太郎', 'sato@example.com', 300000, 1, '1987-01-02'),
('鈴木', '次郎', 'suzuki@example.com', 300000, 2, '1988-03-04'),
('高橋', '三郎', 'takahashi@example.com', 350000, 1, '1989-05-06'),
('田中', '花子', 'tanaka@example.com', 350000, 2, '1990-07-08'),
('伊藤', '雪子', 'ito@example.com', 400000, 1, '1991-09-10'),
('渡辺', '月子', 'watanabe@example.com', 400000, 2, '1992-11-12');
次に、init.sql
ファイルを読み込んで実行します。
$ psql -f init.sql anondb
CREATE TABLE
CREATE TABLE
INSERT 0 2
INSERT 0 6
$ psql anondb
=# SELECT * FROM departments;
id | name
----+--------
1 | 営業部
2 | 技術部
(2 行)
=# SELECT * FROM employees;
id | last_name | first_name | email | salary | department_id | birth
----+-----------+------------+-----------------------+--------+---------------+------------
1 | 佐藤 | 太郎 | sato@example.com | 300000 | 1 | 1987-01-02
2 | 鈴木 | 次郎 | suzuki@example.com | 300000 | 2 | 1988-03-04
3 | 高橋 | 三郎 | takahashi@example.com | 350000 | 1 | 1989-05-06
4 | 田中 | 花子 | tanaka@example.com | 350000 | 2 | 1990-07-08
5 | 伊藤 | 雪子 | ito@example.com | 400000 | 1 | 1991-09-10
6 | 渡辺 | 月子 | watanabe@example.com | 400000 | 2 | 1992-11-12
(6 行)
次に、マスキングルールを定義します。社員の E メールアドレスを格納した列 email
を anon.partial_email
関数でマスキングすようにルールを定義します。
=# SECURITY LABEL FOR anon ON COLUMN employees.email
-# IS 'MASKED WITH FUNCTION anon.partial_email(email)';
SECURITY LABEL
マスキングルールの定義には SECURITY LABEL
コマンドを使います。SECURITY LABEL
コマンドはセキュリティラベルを定義するコマンドです。私は SELinux 専用のコマンドだと勘違いしていましたが、ラベルプロバイダを指定することにより、任意のアクセス制御を適用できるそうです。FOR
の後にはラベルプロバイダとして anon
、ON COLUMN
の後にはマスキング対象の列として employees.email
、IS
の後にはラベルとしてマスキングルールを指定します。
マスキングルールは anon.partial_email
関数を使うというものです。anon.partial_email
関数は PostgreSQL Anonymizer が提供するマスキング関数の 1 つで、E メールアドレスのローカル部、ドメインの最初の 2 文字と最後のサブドメインを残して ******
に置き換える関数です。例えば、E メールアドレス sato@example.com
は sa******@ex******.com
にマスキングされます。
定義したマスキングルールはシステムビュー pg_seclabels
で確認できます。また、マスキングルールを削除するには、ラベルに NULL
を指定して SECURITY LABEL
コマンドを実行します。
=# SELECT * FROM pg_seclabels;
objoid | classoid | objsubid | objtype | objnamespace | objname | provider | label
--------+----------+----------+---------+--------------+-----------------+----------+------------------------------------------------
16645 | 1259 | 4 | column | 2200 | employees.email | anon | MASKED WITH FUNCTION anon.partial_email(email)
(1 行)
インプレース匿名化
インプレース匿名化 (in-place anonymization) はデータをマスキングして更新する機能です。
インプレース匿名化を行うには anonymize_database
または anonymize_table
、anonymize_column
関数を使います。関数名を見れば分かると思いますが、anonymize_database
関数はデータベース内のすべてのテーブル、anonymize_table
関数は指定したテーブル、anonymize_column
は指定したテーブルの列がマスキング対象になります。
=# BEGIN;
BEGIN
=# SELECT anon.anonymize_database();
anonymize_database
--------------------
t
(1 行)
=# SELECT * FROM employees;
id | last_name | first_name | email | salary | department_id | birth
----+-----------+------------+-----------------------+--------+---------------+------------
1 | 佐藤 | 太郎 | sa******@ex******.com | 300000 | 1 | 1987-01-02
2 | 鈴木 | 次郎 | su******@ex******.com | 300000 | 2 | 1988-03-04
3 | 高橋 | 三郎 | ta******@ex******.com | 350000 | 1 | 1989-05-06
4 | 田中 | 花子 | ta******@ex******.com | 350000 | 2 | 1990-07-08
5 | 伊藤 | 雪子 | it******@ex******.com | 400000 | 1 | 1991-09-10
6 | 渡辺 | 月子 | wa******@ex******.com | 400000 | 2 | 1992-11-12
(6 行)
=# ROLLBACK;
ROLLBACK
=# \q
なお、インプレース匿名化はデータ自体を更新するので、元のデータが失われてしまいます。また、テーブル内のすべてのデータを更新するので、実行に時間がかかります。後述の 匿名ダンプ でデータをマスキングしてダンプし、リストアしなおしたほうが速い場合があります。
匿名ダンプ
匿名ダンプ (anonymous dump) はデータをマスキングして SQL 形式でダンプする機能です。
匿名ダンプを行うには anon.dump
関数を実行します。ファイルにダンプする場合には、コマンドラインで関数を実行し、その出力をリダイレクトします。その際、余計なメッセージや位置揃え、行以外の出力を抑止するため、-qAt
オプションを指定します。
$ psql -qAt -c "SELECT anon.dump()" anondb
(省略)
COPY departments
FROM STDIN
CSV QUOTE AS '"'
DELIMITER ',';
1,営業部
2,技術部
\.
COPY employees
FROM STDIN
CSV QUOTE AS '"'
DELIMITER ',';
1,佐藤,太郎,sa******@ex******.com,300000,1,1987-01-02
2,鈴木,次郎,su******@ex******.com,300000,2,1988-03-04
3,高橋,三郎,ta******@ex******.com,350000,1,1989-05-06
4,田中,花子,ta******@ex******.com,350000,2,1990-07-08
5,伊藤,雪子,it******@ex******.com,400000,1,1991-09-10
6,渡辺,月子,wa******@ex******.com,400000,2,1992-11-12
\.
動的マスキング
動的マスキング (dynamic masking) はデータを動的にマスキングし、ユーザにアクセスさせる機能です。元のテーブルと同じ名前のビューを別のスキーマにマスキング関数をかまして定義し、スキーマを切り換えてユーザにアクセスさせることで実現しています。
まず、動的マスキングを有効にするため、anon.start_dynamic_masking
関数を実行します。
=# SELECT anon.start_dynamic_masking();
start_dynamic_masking
-----------------------
t
(1 行)
次に、マスキングしたデータにアクセスさせるユーザ masked_user
を作成します。
=# CREATE ROLE masked_user LOGIN;
CREATE ROLE
=# SECURITY LABEL FOR anon ON ROLE masked_user IS 'MASKED';
SECURITY LABEL
=# GRANT SELECT ON ALL TABLES IN SCHEMA mask TO masked_user;
GRANT
=# \q
CREATE ROLE
コマンドで LOGIN
権限をもったユーザを作成し、SECURITY LABEL
コマンドで MASKED
ラベルを与えます。MASKED
ラベルつきユーザのアクセスはマスキングしたテーブルが格納されたスキーマ mask
に自動的に切り換えられます。アクセス権限は手動で設定する必要があります。GRANT
コマンドで mask
スキーマ内のすべてのテーブルに SELECT
権限を与えます。
最後に、masked_user
ユーザとして anondb
データベースに接続し、データがマスキングされていることを確認します。
$ psql -U masked_user anondb
=> SELECT * FROM employees;
id | last_name | first_name | email | salary | department_id | birth
----+-----------+------------+-----------------------+--------+---------------+------------
1 | 佐藤 | 太郎 | sa******@ex******.com | 300000 | 1 | 1987-01-02
2 | 鈴木 | 次郎 | su******@ex******.com | 300000 | 2 | 1988-03-04
3 | 高橋 | 三郎 | ta******@ex******.com | 350000 | 1 | 1989-05-06
4 | 田中 | 花子 | ta******@ex******.com | 350000 | 2 | 1990-07-08
5 | 伊藤 | 雪子 | it******@ex******.com | 400000 | 1 | 1991-09-10
6 | 渡辺 | 月子 | wa******@ex******.com | 400000 | 2 | 1992-11-12
(6 行)
=> \q
なお、動的マスキングはスキーマの切り換えで実現しているため、マスキング対象のスキーマは 1 つのみに限られます。デフォルトは public
スキーマで、anon.start_dynamic_masking
関数の第 1 引数で指定できます。また、第 2 引数でマスキングしたテーブルを格納するスキーマも指定できます。
PostgreSQL Anonymizer の紹介はこれで完了です。今回は紹介しませんでしたが、マスキング関数はほかにも、ノイズ追加やシャッフル化、ランダム化、偽装、部分スクランブル化、一般化、k-匿名化など、いろいろな関数 があります。なお、人名や都市名などの偽装データは英語しか用意されていませんが、日本語の偽装データを CSV 形式で作成してロードすることもできます。