dbtからSnowflakeのダイナミックデータマスキングを設定できるpackage「dbt_snow_mask」を試してみた #dbt #SnowflakeDB

2022.06.06

さがらです。

dbtにはたくさんのpackageが用意されており、Snowflake専用のpackageもいくつかあります。

その中からdbt_snow_maskを試してみたので、内容をまとめてみたいと思います。

dbt_snow_maskとは

dbt_snow_maskに関する情報は、下記ページにまとまっております。

このページの説明を見ると、

This dbt package contains macros that can be (re)used across dbt projects with snowflake. dbt_snow_mask will help to apply Dynamic Data Masking using dbt meta.

…ということで、Snowflakeのダイナミックデータマスキングの設定に役立つpackageのようです。

ダイナミックデータマスキングとは

ここで、ダイナミックデータマスキングとは何か、説明しておきます。

簡単に言うと、Snowflakeにおける列レベルのセキュリティです。

より具体的には、あるカラムに対して「どのロールを持つ人にはこのカラムを値を見せる」のようなマスキングポリシーを定義することで、ユーザーに付与したロールごとにそのカラムの値を見せる・見せないを動的に切り替えることが出来ます。

以下、具体例を載せます。

下記のマスキングポリシーを適用することを考えてみます。内容としては、現在のロールが「SAGARA_ADMIN_ROLE」であれば値はそのまま見せるけど、このロールでなければ値は見せない、という内容です。

CREATE MASKING POLICY IF NOT EXISTS sagara_dbt_test_db.dbt_ssagara.first_name_mask AS (val string) 
  RETURNS string ->
      CASE WHEN CURRENT_ROLE() IN ('SAGARA_ADMIN_ROLE') THEN val 
      ELSE '**********'
      END

これをFIRST_NAMEというカラムに対して、適用してみます。

alter table  sagara_dbt_test_db.dbt_ssagara.customers modify column  first_name set masking policy sagara_dbt_test_db.dbt_ssagara.first_name_mask;

この上で、各ロールごとにどうなるかを見てみます。

  • 現在のロールが「SAGARA_ADMIN_ROLE」の場合

  • 現在のロールが「SAGARA_ADMIN_ROLE」以外の場合

このように、ロールによって見せる値を動的に変更できるのが、Snowflakeのダイナミックデータマスキングです。

また、ダイナミックデータマスキングの公式Docはこちらになりますので、併せてご覧ください。

dbt_snow_maskを用いたマスキングポリシーの定義と適用

実際に、dbt_snow_maskを試してみます。内容としては、先程ダイナミックデータマスキングの例として見せた内容を、dbt_snow_maskを通じて設定してみます。

dbt_snow_maskのインストール

まず、dbt_snow_maskを対象のdbtプロジェクトに対してインストールします。

dbt_project.ymlと同じ階層に、packages.ymlを定義し、以下の内容を記述します。すでにpackages.ymlがある場合は、下記の内容の- package:以降を追記してください。

※2022年6月6日時点ではv0.2.0となっていますが、随時適用させたいバージョンは確認をお願い致します。

注意点としては、dbt_snow_maskはdbt_utilsがインストールされていないと使用できません。dbt_utilsも忘れずにインストールしましょう。

packages:
  - package: dbt-labs/dbt_utils
    version: 0.8.5
  - package: entechlog/dbt_snow_mask
    version: 0.2.0

後はpackageのお作法に沿って、dbt depsコマンドを実行すればdbt_snow_maskをインストール出来ます。

マスキングポリシーの作成

このpackageを使用することにより、dbtからマスキングポリシーを新しく作成することが出来ます。

ここでは、macroを新しく定義します。この時の注意点としては、「作成するmacroファイルの名称」「定義するmacroの名称」「このmacroにより作成するマスキングポリシーの名称」の3つがルールに沿っていないと、後でマスキングポリシーを適用させるときに上手くいきません。

  • 作成するmacroファイルの名称:
    • create_masking_policy_”マスキングポリシー名”.sql
  • 定義するmacroの名称:
    • create_masking_policy_”マスキングポリシー名”(node_database,node_schema)
    • ※末尾の()はmacroの引数を意味しています。node_databaseとnode_schemaの2つの引数が必要です。
  • このmacroにより作成するマスキングポリシーの名称:
    • {{node_database}}.{{node_schema}}.”マスキングポリシー名”
    • ※先頭のJinjaによる記述は、動的なデータベース・スキーマ指定のために必要です。

具体的には、下記のようにコードを記述して、マスキングポリシー生成のための新しいmacroを作成します。

-- ファイル名:create_masking_policy_first_name_mask.sql
{% macro create_masking_policy_first_name_mask(node_database,node_schema) %}

CREATE MASKING POLICY IF NOT EXISTS {{node_database}}.{{node_schema}}.first_name_mask AS (val string) 
  RETURNS string ->
      CASE WHEN CURRENT_ROLE() IN ('SAGARA_ADMIN_ROLE') THEN val 
      ELSE '**********'
      END

{% endmacro %}

下図は実際のdbt Cloud上の画像ですが、画像の赤枠内が「マスキングポリシー名」です。

作成するマスキングポリシーをどのカラムに適用するか定義

続いて、作成するマスキングポリシーをどのカラムに適用するか定義します。

具体的には、modelsに関するyamlファイルの中で、下記のように記述します。

meta:欄がポイントで、masking_policyに先程作成したマスキングポリシー名を記述している形となります。

version: 2

models:
  - name: customers
    columns:
      - name: first_name
        meta:
            masking_policy: first_name_mask

もちろん、他のdescriptiontestsと併記することは可能です。下図は実際に私が試した時のyamlファイルの中身です。

macroをmodelに適用する

次に、作成したmacroをmodelに適用します。

私が上手くいった方法は、「modelファイルに対してconfigでpre_hookとpost_hookを設定し、dbt run実行時にマスキングポリシーの作成と適用を行う」方法です。 (コマンドラインで適用する方法もあるのですが、私の場合はapplyが上手く実行されず…原因は不明です。)

具体的には、modelファイルに下記のように記述します。

{{ config(
    pre_hook=[
      "{{ dbt_snow_mask.create_masking_policy('models')}}"
    ],
    post_hook=[
      "{{ dbt_snow_mask.apply_masking_policy('models') }}"
    ]
) }}

-- 以下、modelに記述したselect文が続きます

dbt runを実行してマスキングポリシーを作成&適用

設定としてはここまでで完了です!

ここでdbt runを実行すると、このようなクエリ履歴となります。hookを指定しているため、対象のmodelの実行前後でマスキングポリシーの作成・適用が行われているのがわかると思います。

実際に、マスキングポリシーで指定したロール以外から参照してみると、マスキングが適用されていることがわかります!これで、dbtからSnowflakeへのダイナミックデータマスキングの適用が無事にできました。

上手くいかなかったこと:マスキングポリシーの作成先のデータベースやスキーマを分ける

packageの説明のHow to configure database and schema for the masking policy ?を見ると、共有のデータベースやスキーマを新しく作成し、その中にマスキングポリシーを定義する、ということも出来るようです。

しかし、dbt_project.ymlに下記のように追記した上でdbt runを実行しても、対象のスキーマが自動で作られず、マスキングポリシーを作成するタイミングでエラーとなってしまいました。

vars:
  use_common_masking_policy_schema_only: "True"
  common_masking_policy_schema: "masking_policies_schema"
  create_masking_policy_schema: "True"

これの原因はよくわからなかったです…ちょっと心残りですね。

ちなみにこの設定を行わない場合は、通常の開発プロセスと同じスキーマにマスキングポリシーが定義されます

  • 開発環境:dbt_ssagaraのような開発用スキーマ
  • 本番環境:Environmetで指定したスキーマ

私が試したときも、このスキーマ構成に沿ってマスキングポリシーが作成され、対象のテーブルのカラムに適用されていきました。

最後に

dbtからSnowflakeのダイナミックデータマスキングを適用する、dbt_snow_maskを試してみました。

少し上手くいかない所はあったものの、dbtだけでマスキングポリシーを適用できる、というのは面白いなーと感じました!

Snowflakeは他にもdbt packageがあるので、試していきたいですね。