テックカリキュラム

マスキングビュー生成をCI/CDパイプラインに組み込む方法

マスキングビュー生成をCI/CDパイプラインに組み込む方法

マスキングビューは「作って終わり」ではなく、スキーマ変更分類辞書の更新に追随して継続的に再生成・検証できる状態にしておくのが理想です。そこで本記事では、マスキングビュー生成をCI/CDに組み込み、安全に自動適用する実践パターンを紹介します(PostgreSQL中心)。


全体像:CI/CDに入れるべきステップ

  1. 分類辞書の反映(data_classification_dictionaryの更新)
  2. 分類の再計算(classified_columnsの更新)
  3. マスキングビューの生成(CREATE OR REPLACE VIEW …)
  4. 検証(ビューが作れている/PIIが漏れていない)
  5. 本番適用(マイグレーションとして実行)

ポイントは「生成 → 検証 → 適用」をパイプラインに固定化することです。


推奨ディレクトリ構成(例)

repo/ sql/ 00_create_tables.sql 10_update_dictionary.sql 20_refresh_classification.sql 30_generate_masked_views.sql 40_validate_masking.sql scripts/ run_sql.sh migrations/ (Flyway/Liquibase/DBMateなど使う場合) .github/workflows/db.yml (GitHub Actions例) 

1) 「生成SQL」をDB内関数として持つ(運用が安定)

CI/CDからは「関数を呼ぶだけ」にすると壊れにくいです。まず、ビュー生成を行う管理用関数を用意します。

ビュー生成関数(PostgreSQL)

CREATE OR REPLACE FUNCTION admin_generate_masked_views( target_schema text DEFAULT 'public', view_suffix text DEFAULT '_masked_l2' ) RETURNS void LANGUAGE plpgsql AS $$ DECLARE r record; col_sql text; ddl text; BEGIN FOR r IN SELECT DISTINCT table_schema, table_name FROM classified_columns WHERE table_schema = target_schema LOOP SELECT string_agg( CASE WHEN classification = 'PII' THEN format('mask_text(%1$I, 2) AS %1$I', column_name) WHEN classification = 'Confidential' THEN format('NULL AS %1$I', column_name) ELSE format('%1$I', column_name) END, ', ' ORDER BY column_name ) INTO col_sql FROM classified_columns WHERE table_schema = r.table_schema AND table_name = r.table_name; ddl := format( 'CREATE OR REPLACE VIEW %1$I.%2$I%3$s AS SELECT %4$s FROM %1$I.%2$I;', r.table_schema, r.table_name, view_suffix, col_sql ); EXECUTE ddl; END LOOP; END; $$; 

※上の例では mask_text() を呼んでいます。メール・電話・氏名は列ごとに関数を分けてもOKです(前回のUDF方式)。


2) CIで「変更検知+レビュー可能」にする

CIでは、いきなり本番に適用せず、まずは生成結果(DDL)をアーティファクトとして出すのが安全です。

CIでやること(例)

  • DB(テスト用コンテナ)を立ち上げる
  • 分類辞書・分類更新SQLを適用
  • ビュー生成関数を実行
  • 検証SQL(PII漏れチェック)を実行
  • 成功したらPRを通す(CDへ)

3) 検証SQL:PIIが「生で出ていない」ことをチェック

例えば「maskedビューのemailに@が残っていたらNG」「電話番号が生の形式ならNG」など、事故を防ぐガードレールをSQLで持ちます。

検証例(PostgreSQL)

-- 例:users_masked_l2.email に @ が含まれていたら失敗扱い DO $$ DECLARE cnt int; BEGIN SELECT COUNT(*) INTO cnt FROM public.users_masked_l2 WHERE email LIKE '%@%'; IF cnt > 0 THEN RAISE EXCEPTION 'Masking validation failed: raw email detected (% rows)', cnt; END IF; END $$; 

同様に電話番号・氏名なども検証を用意します。


4) GitHub Actionsでの実装例(PostgreSQLコンテナ+psql)

最小構成の例です(WordPress貼り付け用にそのまま載せられる形)。

name: db-masking-views on: pull_request: push: branches: [ "main" ] jobs: test-generate-views: runs-on: ubuntu-latest services: postgres: image: postgres:16 env: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres POSTGRES_DB: appdb ports: - 5432:5432 options: >- --health-cmd="pg_isready -U postgres -d appdb" --health-interval=10s --health-timeout=5s --health-retries=10 steps: - uses: actions/checkout@v4 - name: Install psql client run: | sudo apt-get update sudo apt-get install -y postgresql-client - name: Apply schema & classification & generate views env: PGPASSWORD: postgres run: | psql -h localhost -U postgres -d appdb -f sql/00_create_tables.sql psql -h localhost -U postgres -d appdb -f sql/10_update_dictionary.sql psql -h localhost -U postgres -d appdb -f sql/20_refresh_classification.sql psql -h localhost -U postgres -d appdb -f sql/30_generate_masked_views.sql psql -h localhost -U postgres -d appdb -f sql/40_validate_masking.sql 

運用Tip: 30_generate_masked_views.sql の中身は「SELECT admin_generate_masked_views(...)」だけにしておくと保守が楽です。


5) CD(本番適用)の考え方:マイグレーションに落とし込む

本番には、次のいずれかで適用するのが安全です。

  • Flyway / Liquibase / DBMate などのDBマイグレーションツールに載せる
  • リリースジョブで「生成関数実行+検証SQL」を必ず通す

特にビューは CREATE OR REPLACE VIEW が使えるため、差分管理がしやすいです。


6) 事故防止のチェックリスト(重要)

  • 元テーブルへのSELECT権限を剥奪して、ビュー経由以外で見えないようにする
  • ビュー生成は専用ロール(admin系)だけが実行可能にする
  • CIで検証SQLを必須化(漏れがあればビルドを落とす)
  • ステージングで本番同等データ量のリフレッシュをテストする

まとめ

マスキングビュー生成をCI/CDに組み込むと、次の効果が得られます。

  • スキーマ変更・分類辞書更新に自動追随
  • レビュー前に漏洩を検知して止められる
  • ビュー定義が再現可能になり、運用が属人化しない

次の発展として、「分類辞書の変更をPRでレビュー」→「CIで自動分類」→「マスキングビュー自動生成」まで一気通貫にすると、ガバナンスがかなり強固になります。