Slide 1

Slide 1 text

第19回 PostgreSQLアンカンファレンス@オンライン ~ データマスキングについて少々 ~ DBREJP 1

Slide 2

Slide 2 text

自己紹介 mysql > SELECT * FROM me \G ************** 1. row ************* name: 粟田 啓介 nickname: あわっち twitter: @_awache like: SlowQuery, Explain 1 rows in set (0.00 sec) mysql> SELECT * FROM readme; +------------------------------------------------------------------+ | readme | +------------------------------------------------------------------+ | この資料は @_awache という個人の独断と偏見により作成されたものです。 | | 所属する組織等の意見を代表するものではありません。 | | 合法性や安全性、情報の正確性についても保証できません。 | +------------------------------------------------------------------+ 3 rows in set (0.00 sec) DBREJP 2

Slide 3

Slide 3 text

RDB の データマスキング DBREJP 3

Slide 4

Slide 4 text

データマスキング DBREJP 4 ● 動的マスキング ○ クエリの結果やクエリ自体がマスクされる手法 ● 静的マスキング ○ データベースの定点を取得してその中身をマスクする手法 ● 代表的な製品 ○ MySQL Enterprise and De-identification ○ Data Sunrize ○ MariaDB MaxScale 中身についてはあまり詳しくないので今回は流させてください 大きく2種類のマスキング手法

Slide 5

Slide 5 text

実際データマスキングってどうですか? DBREJP 5 ● 僕がやるときはたいてい静的マスキングのパターン ○ 実際にやろうと思ったら・・・ ■ どうやって実現したらいいか分からない ● How を調べようとしても自分たちに合った方法を見つけることが困難 ■ カラム毎の定義を決めることに疲弊する ● 特にDBA と アプリケーションが組織的に分断されているとどこに個人情報が入っているか分からない ● 間違っていたり漏れていたりするとマスキングそのものが無意味になってしまう ■ リードタイムが長い ● データサイズにもよるがマスキング処理自体に時間がかかる ● 定点を取得してマスキングしている間にもデータは順調に成長していく 個人的にはとても厄介なイメージ (個人的な主観です)

Slide 6

Slide 6 text

● 検証環境にリストアしてパフォーマンス検証をしたい ○ 検証する時間が長くなればなるほどデータの鮮度は落ちていくので作り直しが発生するケース も多い ● インシデント/トラブル時の調査に使用したい ○ 時間をかけてマスキングデータを作成したとしても一度のオペレーションで不要になってしまう ので費用対効果が悪い ● マーケティングに利用する ○ マスクしたデータを BigQuery に入れてマーケティング用途で継続的利用を行うなど 実際データマスキングってどうですか? データマスキングが必要なユースケース DBREJP 6

Slide 7

Slide 7 text

● 処理概要 ○ 本番データベースのSnapshot を取得 ○ Restore して直接 UPDATE ○ TSV に吐き出す ■ BigQuery に取り込む ○ Snapshot を作成して検証用 Snapshot として保存 ■ 必要に応じていつでも Restore できる * AWS 環境で実装しています マスキング処理を自動化 Daily で Snapshot から復元して Daily でマスキング DBREJP 7 参照: Database Reliability Engineering はじめました https://speakerdeck.com/_awache/no-ops-more-code?slide=21

Slide 8

Slide 8 text

今日の本題 実際マスキングをした時の勘所について DBREJP 8 この部分について少し触 れたいと思います

Slide 9

Slide 9 text

● クエリの実行計画がなるべく変わらないようにする ○ 復元不可能だが、カーディナリティを維持する方法の検討 ■ 同じ文字列はマスクされたとしても同じ結果が得られる ○ テーブル定義との不整合を減らす ■ UK や Join への考慮 ○ 日付などはランダム性を持たせつつ、レンジを設定 ■ 登録されているデータから過去/未来/前後1年のうちでランダムな日付を取得 実装したマスキング方式 Platform 化する際の考慮点 DBREJP 9

Slide 10

Slide 10 text

● 割り切りは大切 ○ 割と難しかったのは数値型 ■ smallint で 5 桁の数字があった場合 ● 32767 を超えない範囲でランダムとかちょっと骨折り ● 割り切って 1 で埋めることに ○ hash 化した結果文字列長オーバーする可能性 ■ 諦めてそれっぽいところで落とし所をつける ● 前方から30文字を取得するとした ● 今のところ弊社の使い方としてはまだこれで問題になっていない 実装したマスキング方式 Platform 化する際の考慮点 DBREJP 10

Slide 11

Slide 11 text

● 文字列 ○ 特定の文字列で埋める ■ ex) アスタリスク(*) でマスキング ● CONCAT(LEFT(colmun_name, 0), REPEAT('*', CHAR_LENGTH(colmun_name)-0)) ○ hash 化 ■ salt を用いて md5 で hash 化した上で 前から 30 文字を切り取る ● CONCAT( LEFT ( md5 ( CONCAT ('salt', md5 ( CONCAT (colmun_name, 'salt') ) ,colmun_name) ) ○ email ■ salt を用いて md5 で hash 化した上で 前から 30 文字を切り取って任意のドメインを付与 ● CONCAT( LEFT ( md5 ( CONCAT ('salt', md5 ( CONCAT (colmun_name, 'salt') ) ,colmun_name) ), 30), {'@example.com'}) 実装したマスキング方式 少しだけ実装に突っ込んでみる DBREJP 11

Slide 12

Slide 12 text

● 日付 ○ 挿入されている日付をキーにして ■ 過去1年の間でランダム日を取得 ● colmun_name + CAST(trunc(random() * -366 + 1) || 'days' AS interval ) ■ 未来1年の間でランダム日を取得 ● colmun_name + CAST(trunc(random() * 366 + 1) || 'days' AS interval ) ■ 前後1年間の間でランダム日を取得 ● colmun_name + CAST(trunc ((random() - random()) * trunc(random() * 366 + 1)) || 'days' AS interval ) ● 数値型 ○ 注意が必要 ■ CONCAT(LEFT((colmun_name), 0),REPEAT( '1', CHAR_LENGTH((colmun_name))-0))::integer 実装したマスキング方式 少しだけ実装に突っ込んでみる DBREJP 12

Slide 13

Slide 13 text

実装したマスキング方式 DBREJP 13 全容

Slide 14

Slide 14 text

● 責任分界点の見極め ○ マスキング Platform としての提供を行う DBRE ■ ユーザーによって指定されたマスキングが正しくなされることに責任を持つ ○ データマスキングの定義そのものはプロダクトの要件によって決めてもらう ■ マスキングのカラムや手法は DBRE からは指定しない ■ カラム追加などでテーブル定義が変わった場合にもプロダクト側で対応してもらう ● マスク定義は git に Commit してもらう ● 全部書いてもらうのはさすがにイニシャルコストがかかりすぎる ○ 最初は information_schema からテーブル定義を抜き出してデフォルトで自動生成 ○ プロダクトはマスキングが必要な箇所のみ気にすることができる状態 実装したマスキング方式 Platform 化する際の考慮点 DBREJP 14

Slide 15

Slide 15 text

Database におけるモノゴトを Reliability Engineering という側面から解決 ● Database に対する専門知識 ● Database Engineer としての判断 を用いて再帰性のあるプロセスや戦略決定に落とし込む ● 基本的なアプローチはこれまでの Database との関わり方ときっと変わらない ● 変わっているのは Database を取り巻く環境 DBRE Database Reliability Engineering とは DBREJP 15

Slide 16

Slide 16 text

(僕が) 本当に欲しいデータマスキング DBREJP 16

Slide 17

Slide 17 text

テーブル型 + 動的マスキング 妄想イメージ DBREJP 17 テーブルの型の一つとしてのマスキング ● CREATE ステートメントで指定できるカラムタイプ ● Role と紐付けて結果を自動的に出し分ける ○ Admin と Application からは生データが見れる ○ ヒトからのアクセスの場合に Role と紐づいてマスキングされる ■ ex) 開発者 Role ● マスキングで指定された型の場合は必ずマスキングされる ● 例え dump をしてもマスキングされる a. dump したデータを検証環境に入れることも SELECT ● Admin Role/Application dvdrental=# SELECT first_name FROM actor LIMIT 2; first_name ------------ Penelope Nick (2 rows) ● ヒトからのアクセス(開発者用とか) dvdrental=# SELECT first_name FROM actor LIMIT 2; first_name ------------ ******** **** (2 rows)

Slide 18

Slide 18 text

テーブル型 + 動的マスキング 妄想イメージ DBREJP 18 Role によってクエリが失敗する ● mask 型のカラムを WHERE 句で指定されたら エラーを返す SELECT ● Admin Role/Application dvdrental=# SELECT first_name FROM actor WHERE first_name = ‘_awache’; first_name ------------ _awache (1 row) ● ヒトからのアクセス(開発者用とか) dvdrental=# SELECT first_name FROM actor WHERE first_name = ‘_awache’; ERROR: XXXX dvdrental=# SELECT first_name FROM actor WHERE first_name LIKE ‘%_awache%’; ERROR: XXXX

Slide 19

Slide 19 text

テーブル型 + 動的マスキング 妄想イメージ DBREJP 19 Role によってクエリが失敗する ● mask 型のカラムを JOIN 句で指定ならOK ○ explain とかそのまま見たい ○ SELECT の結果はマスクされて戻ってくる SELECT ● Admin Role/Application dvdrental=# SELECT actor.first_name FROM actor, hoge WHERE actor.first_name = hoge.first_name LIMIT 1; first_name ------------ _awache (1 row) ● ヒトからのアクセス(開発者用とか) dvdrental=# SELECT actor.first_name FROM actor, hoge WHERE actor.first_name = hoge.first_name LIMIT 1; first_name ------------ ******* (1 row)

Slide 20

Slide 20 text

マスキングカラム型 DBREJP 20 ● mask_char ○ mask_char(n, ‘*’) ■ 裏側は varchar(n) と同じ ■ 第二引数でマスクする (1文字限定) ● 裏側では下記と同じような動き ○ CONCAT(LEFT(colmun_name, 0), REPEAT('*', CHAR_LENGTH(colmun_name)-0)) ● mask_md5 ○ mask_md5(p, s, [salt]) ■ 裏側は varchar(n) と同じ ■ salt と組み合わせて md5 ■ 頭から第二引数の文字数までを区切って出力 ● 裏側では下記と同じような動き ○ CONCAT( LEFT ( md5 ( CONCAT ('salt', md5 ( CONCAT (colmun_name, 'salt') ) ,colmun_name) ) 妄想の世界のマスキング文字列型

Slide 21

Slide 21 text

マスキングカラム型 DBREJP 21 ● mask_email ○ mask_md5(p, s, [salt], [domain]) ■ 裏側は varchar(n) と同じ ■ salt と組み合わせて md5 ■ 頭から第二引数の文字数までを区切って出力 ■ 後ろに第4引数で指定されたドメインをつける ● 裏側では下記と同じような動き ○ CONCAT( LEFT ( md5 ( CONCAT ('salt', md5 ( CONCAT (colmun_name, 'salt') ) ,colmun_name) ), 30), '@example.com') 妄想の世界のマスキング文字列型

Slide 22

Slide 22 text

DBREJP 22 ここまでいろいろ考えてみましたが、Proxy 的な何かで も実現できるかもしれないし、無責任に色々なやり方を 考えるの楽しいですね! (個人的にはこれから動的マスキングの需要は高まると感じています) いろいろと皆さんの妄想を聞いてみるの面白い気がしま す! 機会があればぜひ発表してみてください♪

Slide 23

Slide 23 text

今後ともどうぞよろしくお願いします DBREJP 23

Slide 24

Slide 24 text

Thank you! DBREJP 24