マスキングビューは「作って終わり」ではなく、スキーマ変更や分類辞書の更新に追随して継続的に再生成・検証できる状態にしておくのが理想です。そこで本記事では、マスキングビュー生成をCI/CDに組み込み、安全に自動適用する実践パターンを紹介します(PostgreSQL中心)。
全体像:CI/CDに入れるべきステップ
- 分類辞書の反映(data_classification_dictionaryの更新)
- 分類の再計算(classified_columnsの更新)
- マスキングビューの生成(CREATE OR REPLACE VIEW …)
- 検証(ビューが作れている/PIIが漏れていない)
- 本番適用(マイグレーションとして実行)
ポイントは「生成 → 検証 → 適用」をパイプラインに固定化することです。
推奨ディレクトリ構成(例)
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で自動分類」→「マスキングビュー自動生成」まで一気通貫にすると、ガバナンスがかなり強固になります。