Upgrade to Pro — share decks privately, control downloads, hide ads and more …

PostgreSQLUnconference#19

_awache
December 01, 2020

 PostgreSQLUnconference#19

2020-12-07 第19回 PostgreSQLアンカンファレンス@オンライン
での発表資料

_awache

December 01, 2020
Tweet

More Decks by _awache

Other Decks in Technology

Transcript

  1. 自己紹介 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
  2. データマスキング DBREJP 4 • 動的マスキング ◦ クエリの結果やクエリ自体がマスクされる手法 • 静的マスキング ◦

    データベースの定点を取得してその中身をマスクする手法 • 代表的な製品 ◦ MySQL Enterprise and De-identification ◦ Data Sunrize ◦ MariaDB MaxScale 中身についてはあまり詳しくないので今回は流させてください 大きく2種類のマスキング手法
  3. 実際データマスキングってどうですか? DBREJP 5 • 僕がやるときはたいてい静的マスキングのパターン ◦ 実際にやろうと思ったら・・・ ▪ どうやって実現したらいいか分からない •

    How を調べようとしても自分たちに合った方法を見つけることが困難 ▪ カラム毎の定義を決めることに疲弊する • 特にDBA と アプリケーションが組織的に分断されているとどこに個人情報が入っているか分からない • 間違っていたり漏れていたりするとマスキングそのものが無意味になってしまう ▪ リードタイムが長い • データサイズにもよるがマスキング処理自体に時間がかかる • 定点を取得してマスキングしている間にもデータは順調に成長していく 個人的にはとても厄介なイメージ (個人的な主観です)
  4. • 処理概要 ◦ 本番データベースの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
  5. • クエリの実行計画がなるべく変わらないようにする ◦ 復元不可能だが、カーディナリティを維持する方法の検討 ▪ 同じ文字列はマスクされたとしても同じ結果が得られる ◦ テーブル定義との不整合を減らす ▪ UK

    や Join への考慮 ◦ 日付などはランダム性を持たせつつ、レンジを設定 ▪ 登録されているデータから過去/未来/前後1年のうちでランダムな日付を取得 実装したマスキング方式 Platform 化する際の考慮点 DBREJP 9
  6. • 割り切りは大切 ◦ 割と難しかったのは数値型 ▪ smallint で 5 桁の数字があった場合 •

    32767 を超えない範囲でランダムとかちょっと骨折り • 割り切って 1 で埋めることに ◦ hash 化した結果文字列長オーバーする可能性 ▪ 諦めてそれっぽいところで落とし所をつける • 前方から30文字を取得するとした • 今のところ弊社の使い方としてはまだこれで問題になっていない 実装したマスキング方式 Platform 化する際の考慮点 DBREJP 10
  7. • 文字列 ◦ 特定の文字列で埋める ▪ 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
  8. • 日付 ◦ 挿入されている日付をキーにして ▪ 過去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
  9. • 責任分界点の見極め ◦ マスキング Platform としての提供を行う DBRE ▪ ユーザーによって指定されたマスキングが正しくなされることに責任を持つ ◦

    データマスキングの定義そのものはプロダクトの要件によって決めてもらう ▪ マスキングのカラムや手法は DBRE からは指定しない ▪ カラム追加などでテーブル定義が変わった場合にもプロダクト側で対応してもらう • マスク定義は git に Commit してもらう • 全部書いてもらうのはさすがにイニシャルコストがかかりすぎる ◦ 最初は information_schema からテーブル定義を抜き出してデフォルトで自動生成 ◦ プロダクトはマスキングが必要な箇所のみ気にすることができる状態 実装したマスキング方式 Platform 化する際の考慮点 DBREJP 14
  10. Database におけるモノゴトを Reliability Engineering という側面から解決 • Database に対する専門知識 • Database

    Engineer としての判断 を用いて再帰性のあるプロセスや戦略決定に落とし込む • 基本的なアプローチはこれまでの Database との関わり方ときっと変わらない • 変わっているのは Database を取り巻く環境 DBRE Database Reliability Engineering とは DBREJP 15
  11. テーブル型 + 動的マスキング 妄想イメージ 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)
  12. テーブル型 + 動的マスキング 妄想イメージ 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
  13. テーブル型 + 動的マスキング 妄想イメージ 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)
  14. マスキングカラム型 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) ) 妄想の世界のマスキング文字列型
  15. マスキングカラム型 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') 妄想の世界のマスキング文字列型