MySQL Enterprise Masking and De-identification とは?
MySQL Enterprise Masking and De-identification(マスキングと匿名化) に製品紹介ページが有るのですが、商用版の MySQL Edition のみで提供されている、セキュリティ関連の機能となっております
そこで、本記事はデータマスキング機能のうち、比較的利用シーンの多い機能である、選択的マスキングの使用方法と、単純な速度検証の結果をご紹介したいと思います
環境
項目 | 内容 |
---|---|
MySQL | MySQL 8.0.15-commercial |
OS | Oracle Linux Server release 7.6 |
CPU | 2vCPU |
Memory | 15GB |
InnoDB Buffer Pool | 11GB |
利用準備
商用版のバイナリにはプラグインは同梱されていますので、マニュアル の通りにコマンドを実行すれば、利用可能となります
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
mysql> INSTALL PLUGIN data_masking SONAME 'data_masking.so'; Query OK, 0 rows affected (0.00 sec) mysql> CREATE FUNCTION gen_blacklist RETURNS STRING SONAME 'data_masking.so'; Query OK, 0 rows affected (0.00 sec) mysql> CREATE FUNCTION gen_dictionary RETURNS STRING SONAME 'data_masking.so'; Query OK, 0 rows affected (0.00 sec) mysql> CREATE FUNCTION gen_dictionary_drop RETURNS STRING SONAME 'data_masking.so'; Query OK, 0 rows affected (0.00 sec) mysql> CREATE FUNCTION gen_dictionary_load RETURNS STRING SONAME 'data_masking.so'; Query OK, 0 rows affected (0.01 sec) mysql> CREATE FUNCTION gen_range RETURNS INTEGER SONAME 'data_masking.so'; Query OK, 0 rows affected (0.00 sec) mysql> CREATE FUNCTION gen_rnd_email RETURNS STRING SONAME 'data_masking.so'; Query OK, 0 rows affected (0.00 sec) mysql> CREATE FUNCTION gen_rnd_pan RETURNS STRING SONAME 'data_masking.so'; Query OK, 0 rows affected (0.00 sec) mysql> CREATE FUNCTION gen_rnd_ssn RETURNS STRING SONAME 'data_masking.so'; Query OK, 0 rows affected (0.01 sec) mysql> CREATE FUNCTION gen_rnd_us_phone RETURNS STRING SONAME 'data_masking.so'; Query OK, 0 rows affected (0.00 sec) mysql> CREATE FUNCTION mask_inner RETURNS STRING SONAME 'data_masking.so'; Query OK, 0 rows affected (0.00 sec) mysql> CREATE FUNCTION mask_outer RETURNS STRING SONAME 'data_masking.so'; Query OK, 0 rows affected (0.00 sec) mysql> CREATE FUNCTION mask_pan RETURNS STRING SONAME 'data_masking.so'; Query OK, 0 rows affected (0.00 sec) mysql> CREATE FUNCTION mask_pan_relaxed RETURNS STRING SONAME 'data_masking.so'; Query OK, 0 rows affected (0.00 sec) mysql> CREATE FUNCTION mask_ssn RETURNS STRING SONAME 'data_masking.so'; Query OK, 0 rows affected (0.01 sec) |
データの準備
sysbench のカスタマイズスクリプトを用意する
今回は、後で行う速度検証のためにも、sysbench のカスタマイズスクリプトを用意しました
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
#!/usr/bin/env sysbench sysbench.cmdline.options = { table_size = {"Number of rows per table", 10000}, column = {"'Bare' for bare columns, 'mask' for masking columns", "bare"} } function get_con() drv = sysbench.sql.driver() return drv:connect() end function prepare() con = get_con() con:query([[ CREATE TABLE masking ( id INT NOT NULL AUTO_INCREMENT, data VARCHAR(16) NOT NULL, PRIMARY KEY (id) );]]) con:query([[CREATE OR REPLACE VIEW masked_data AS SELECT id, MASK_INNER(data, 0, 4) AS data FROM masking;]]) for n=1, sysbench.opt.table_size do con:query(string.format("INSERT INTO masking (data) VALUES ('%04d%04d%04d%04d')", math.random(0, 9999), math.random(0, 9999), math.random(0, 9999), math.random(0, 9999))) end end function cleanup() con = get_con() con:query("DROP VIEW IF EXISTS masked_data") con:query("DROP TABLE IF EXISTS masking") end function thread_init() con = get_con() end function event() local sql = "" if sysbench.opt.column == "bare" then sql = string.format("SELECT data FROM masking WHERE id = %d ", math.random(1, sysbench.opt.table_size)) else sql = string.format("SELECT data FROM masked_data WHERE id = %d ", math.random(1, sysbench.opt.table_size)) end con:query(sql) end |
こちらのスクリプトを下記コマンドにて実行し、データを 200,000,000 件登録しています
データサイズは約 8GB となり、全てのデータが InnoDB Buffer Pool に乗るデータ量です
1 2 3 |
$ ./bench.lua --db-driver=mysql --mysql-host=localhost --mysql-user=bench --mysql-password=xxxxx --mysql-db=bench --table-size=200000000 --threads=2 prepare $ ./bench.lua --db-driver=mysql --mysql-host=localhost --mysql-user=bench --mysql-password=xxxxx --mysql-db=bench --table-size=200000000 --time=600 --threads=2 --column=bare run $ ./bench.lua --db-driver=mysql --mysql-host=localhost --mysql-user=bench --mysql-password=xxxxx --mysql-db=bench --table-size=200000000 --time=600 --threads=2 --column=mask run |
実際のアプリケーションでは、 mask_inner() ファンクションを使用した View を定義し、実体の表には Select 権限を付与しないことで、マスクされていないデータの情報漏えいの対策を行うのが一般的だと思われます
そこで、本スクリプトでも、マスクされたデータは View 経由で取得するようにし、実際に利用されるアプリケーションの利用シーンに近い状態での確認を行っております
DEFINER には実体の表へ Select 権限を付与しているユーザを指定し、View にアクセスするユーザと分離する必要がありますが、本検証では同一ユーザで検証しております
選択的マスキングの確認
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
mysql> SELECT data FROM masking ORDER BY id LIMIT 10; +------------------+ | data | +------------------+ | 4060144950650982 | | 9711561289287805 | | 2241778922257757 | | 2113729101188717 | | 3233001403701809 | | 7932423973964264 | | 9131286669366083 | | 9607978701151213 | | 5718495276083185 | | 3102595404684474 | +------------------+ 10 rows in set (0.01 sec) mysql> SELECT MASK_INNER(data, 0, 4) FROM masking ORDER BY id LIMIT 10; +------------------------+ | MASK_INNER(data, 0, 4) | +------------------------+ | XXXXXXXXXXXX0982 | | XXXXXXXXXXXX7805 | | XXXXXXXXXXXX7757 | | XXXXXXXXXXXX8717 | | XXXXXXXXXXXX1809 | | XXXXXXXXXXXX4264 | | XXXXXXXXXXXX6083 | | XXXXXXXXXXXX1213 | | XXXXXXXXXXXX3185 | | XXXXXXXXXXXX4474 | +------------------------+ 10 rows in set (0.00 sec) mysql> SELECT data FROM masked_data ORDER BY id LIMIT 10; +------------------+ | data | +------------------+ | XXXXXXXXXXXX0982 | | XXXXXXXXXXXX7805 | | XXXXXXXXXXXX7757 | | XXXXXXXXXXXX8717 | | XXXXXXXXXXXX1809 | | XXXXXXXXXXXX4264 | | XXXXXXXXXXXX6083 | | XXXXXXXXXXXX1213 | | XXXXXXXXXXXX3185 | | XXXXXXXXXXXX4474 | +------------------+ 10 rows in set (0.00 sec) |
mask_inner()
ファンクションを使用することで、簡単にマスキングされたデータが取得できました
速度検証
計測対象は実行クエリ数とし、マスキング処理の有り・無しで、それぞれ 10 回計測しております
sysbench の実施オプションは下記を指定しております
オプション | 値 |
---|---|
実行時間 | 10 分 |
スレッド | 2 |
データの準備の箇所にも記載しておりますが、マスクされたデータは、 View 経由で取得しております
まとめ
マスキング+Viewでのアクセスを行うことでおおよそ、10〜20%ほど、実行クエリ数が下がっています
しかし、Web アプリケーションの脆弱性や GDPR への対応を考えると、このようなマスキング機能を活用し、データベースレベルでの対応も一考する必要があるのでは無いでしょうか?