0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

dbt Core × Snowflakeにおける本番データガバナンスとタグベースマスキングの実装設計

0
Posted at

dbt Core × Snowflakeにおける本番データガバナンスとタグベースマスキングの実装設計

Feature(228).png

はじめに

こんにちは、NKKTech Global 技術チームです。

Snowflakeとdbt Coreを組み合わせたデータパイプライン運用において、多くのセキュリティ担当者が直面する「落とし穴」があります。

  • 「Snowflake側でせっかくカラムにマスキングポリシーを付与したのに、dbt runが走ったらテーブルが再作成されてポリシーが剥がれてしまった」
  • 「新しいテーブルやカラムが追加されるたびに、手動でSQLを叩いてマスキングを再設定しており、ヒューマンエラーによる情報漏洩(PII漏洩)のリスクが放置されている」

これを根本から解決するのが、Snowflakeの**オブジェクトタグ(Object Tagging)とタグベースマスキングポリシー(Tag-based Masking Policy)**です。
カラム自体ではなく「タグ」に対してマスキングルールを紐付け、dbt側からはYAMLのメタデータ定義を通じてタグを宣言することで、テーブルが何度再作成されても100%自動でマスキングが再適用される仕組みを構築できます。


1. タグベースマスキングの全体アーキテクチャ

dbtのYAMLファイルで宣言されたセキュリティ分類(meta)が、dbtマクロを通じてSnowflakeのタグに変換され、Snowflakeエンジンが動的にマスキングを発動します。


2. 【ステップ1】Snowflake側の事前準備(タグとポリシー作成)

まず、Snowflakeのセキュリティ管理者ロール(ACCOUNTADMIN または SECURITYADMIN)で、中央集権型のガバナンスオブジェクトを作成します。

-- 1. ガバナンス専用データベース・スキーマの作成
CREATE DATABASE IF NOT EXISTS GOVERNANCE_DB;
CREATE SCHEMA IF NOT EXISTS GOVERNANCE_DB.POLICIES;

USE SCHEMA GOVERNANCE_DB.POLICIES;

-- 2. セキュリティタグの定義
CREATE OR REPLACE TAG pii_type
    ALLOWED_VALUES 'email', 'name', 'phone', 'financial'
    COMMENT = '個人情報および機密データの分類タグ';

-- 3. メールアドレス用マスキングポリシーの作成
CREATE OR REPLACE MASKING POLICY mask_email AS (val string) 
RETURNS string ->
    CASE
        -- PII閲覧許可ロールの場合は平文表示
        WHEN CURRENT_ROLE() IN ('ACCOUNTADMIN', 'SECURITY_ADMIN', 'HR_RESTRICTED_ROLE') THEN val
        -- それ以外のロールは先頭とドメインのみ残してマスク
        WHEN val IS NOT NULL THEN REGEXP_REPLACE(val, '(^.).*(@.*$)', '\\1*****\\2')
        ELSE NULL
    END;

-- 4. 汎用文字列(氏名・電話等)用マスキングポリシー
CREATE OR REPLACE MASKING POLICY mask_generic_string AS (val string) 
RETURNS string ->
    CASE
        WHEN CURRENT_ROLE() IN ('ACCOUNTADMIN', 'SECURITY_ADMIN') THEN val
        WHEN val IS NOT NULL THEN '*** MASKED ***'
        ELSE NULL
    END;

-- 5. 【核心】タグに対してマスキングポリシーを紐付ける
ALTER TAG pii_type SET 
    MASKING POLICY mask_email = 'email',
    MASKING POLICY mask_generic_string = 'name',
    MASKING POLICY mask_generic_string = 'phone';

ここがポイント: カラムに対して ALTER TABLE ... MODIFY COLUMN ... SET MASKING POLICY を実行するのではなく、ALTER TAG ... SET MASKING POLICY を実行します。これにより、今後該当タグが付与されたカラムには、テーブルが再作成されようが即座にポリシーが強制適用されます。


3. 【ステップ2】dbt Core側でのタグ自動付与マクロ実装

dbt標準の tags はdbt内部のセレクタ用(dbt run --select tag:foo)として使われることが多いため、Snowflakeの実オブジェクトタグには meta プロパティ を利用して連携します。

① dbtマクロの作成 (macros/apply_snowflake_tags.sql)

モデル実行後の post-hook で、schema.yml に記載されたメタデータを読み取り、Snowflakeの ALTER ... SET TAG を発行するマクロです。

{% macro apply_snowflake_tags() %}
  {% if execute %}
    {% for column_name, column_dict in model.columns.items() %}
      {% if column_dict.meta and column_dict.meta.pii_type %}
        {% set pii_val = column_dict.meta.pii_type %}
        {% set alter_query %}
          ALTER {{ model.materialized | upper }} {{ this }} 
          MODIFY COLUMN {{ adapter.quote(column_name) }} 
          SET TAG GOVERNANCE_DB.POLICIES.pii_type = '{{ pii_val }}';
        {% endset %}
        {% do run_query(alter_query) %}
        {{ log("Applied Snowflake Tag [" ~ pii_val ~ "] to " ~ this ~ "." ~ column_name, info=True) }}
      {% endif %}
    {% endfor %}
  {% endif %}
{% endmacro %}

② 全モデル共通の post-hook 設定 (dbt_project.yml)

すべてのモデル実行完了後に、自動的に上記マクロが走るように登録します。

# dbt_project.yml
name: 'enterprise_data_pipeline'
version: '1.0.0'

models:
  enterprise_data_pipeline:
    +post-hook:
      - "{{ apply_snowflake_tags() }}"

4. 【ステップ3】データモデルの定義とメタデータ記述

データエンジニアやアナリストは、カラム定義の meta 辞書にタグ種別を書くだけで完了します。

カラム定義 (models/marts/schema.yml)

version: 2

models:
  - name: dim_customers
    description: "顧客マスタ(PII保護対象モデル)"
    columns:
      - name: customer_id
        description: "顧客一意識別子 (非機密)"
        tests:
          - unique
          - not_null

      - name: email_address
        description: "顧客連絡先メール"
        meta:
          pii_type: 'email' # 自動で mask_email ポリシーが適用される

      - name: full_name
        description: "顧客氏名"
        meta:
          pii_type: 'name'  # 自動で mask_generic_string ポリシーが適用される

      - name: total_lifetime_value
        description: "累計購買金額 (非機密数値)"

モデルSQLの実装 (models/marts/dim_customers.sql)

{{
    config(
        materialized='table'
    )
}}

SELECT
    user_id AS customer_id,
    email AS email_address,
    CONCAT(first_name, ' ', last_name) AS full_name,
    total_spent AS total_lifetime_value,
    created_at
FROM {{ ref('stg_users') }}

dbt run -s dim_customers を実行すると、テーブルが物理的に新規作成された直後に post-hook が稼働し、email_address には pii_type = 'email'、full_name には pii_type = 'name' が自動でアタッチされます。


5. ロール別クエリ検証(マスキング動作確認)

設定が意図通りに動作しているか、異なる権限のロールでSELECTを実行して確認します。

-- 1. 一般アナリストロールでクエリ
USE ROLE ANALYST_ROLE;
SELECT customer_id, email_address, full_name, total_lifetime_value 
FROM PROD_DWH.ANALYTICS.dim_customers 
LIMIT 2;

-- [結果] 個人情報が完全に隠蔽される
-- customer_id | email_address        | full_name         | total_lifetime_value
-- CUST_001    | y*****@example.com   | *** MASKED ***    | 45000.00
-- CUST_002    | s*****@sample.co.jp  | *** MASKED ***    | 12800.50

-- 2. セキュリティ特権ロールでクエリ
USE ROLE SECURITY_ADMIN;
SELECT customer_id, email_address, full_name, total_lifetime_value 
FROM PROD_DWH.ANALYTICS.dim_customers 
LIMIT 2;

-- [結果] 業務上正当な権限を持つロールのみ平文で閲覧可能
-- customer_id | email_address            | full_name   | total_lifetime_value
-- CUST_001    | yamada.taro@example.com  | 山田 太郎   | 45000.00
-- CUST_002    | sato.hanako@sample.co.jp | 佐藤 花子   | 12800.50

6. 本番運用のためのガバナンス・ガードレール

手作業によるヒューマンエラーをゼロにするため、CI/CDパイプラインに以下の2つの安全柵(Guardrails)を組み込みます。

① dbt-checkpointによるコミット前チェック

PR作成時、Gitの pre-commit または GitHub Actions上で、カラム名に email, phone, ssn などの文字列を含んでいるにもかかわらず、meta.pii_type が定義されていないPRを機械的にブロックします。

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/dbt-checkpoint/dbt-checkpoint
    rev: v2.0.6
    hooks:
      - id: check-column-desc-are-same
      - id: check-model-has-all-columns

② Snowflake タグ未付与カラムの定期監査クエリ

Snowflakeの ACCOUNT_USAGE ビューを利用し、個人情報の可能性がある名称を持ちながらタグが付いていない「野良カラム」を日次で検知し、Slackへアラート通知します。

-- PII疑いがあるがタグが付いていない列を検知する監査SQL
SELECT 
    table_catalog,
    table_schema,
    table_name,
    column_name
FROM snowflake.account_usage.columns c
WHERE 
    (column_name ILIKE '%EMAIL%' OR column_name ILIKE '%PHONE%' OR column_name ILIKE '%NAME%')
    AND deleted IS NULL
    AND NOT EXISTS (
        SELECT 1 
        FROM snowflake.account_usage.tag_references tr
        WHERE tr.object_database = c.table_catalog
          AND tr.object_schema = c.table_schema
          AND tr.object_name = c.table_name
          AND tr.column_name = c.column_name
          AND tr.tag_name = 'PII_TYPE'
    );

まとめ:データガバナンスは「コード」で自動化する

データ活用を加速させることと、厳格なデータコンプライアンス(GDPR / 個人情報保護法 / SOC 2)を遵守することは、両立可能です。

  1. Snowflakeのタグベースマスキング を中核に据え、オブジェクト再作成に耐えうる堅牢な基盤を作る。
  2. dbtの meta プロパティとマクロ により、データモデルのコード変更とセキュリティ設定を1つのPRで同期させる。
  3. CI/CDでの自動検証と監査クエリ により、タグの指定漏れを機械的に遮断する。

この「Governance as Code」のアプローチにより、運用工数を増やすことなく、データ漏洩リスクを最小限に抑えた安全な分析環境を実現できます。


お問い合わせ先

Snowflakeにおけるデータガバナンス基盤の構築、dbt CoreのCI/CD導入、セキュリティ要件に準拠したデータパイプライン設計に関するご相談は、下記よりお気軽にお問い合わせください。

著者:NKKTech Global 技術チーム
私たちは、高度なクラウドセキュリティと実践的なデータエンジニアリングで、企業の信頼あるデータ活用をグローバルに支援します。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?