Snowflakeが提供するガバナンス機能のまとめ

Snowflakeのガバナンス機能を整理しました。
2021.11.17

DA事業部コンサルティングチームのnkhrです。

センシティブなデータ(一部の人のみ参照可能)を扱う場合に利用できるSnowflakeのガバナンス機能を整理しました。本ブログでは、以下の内容を記載しています。

Snowflakガバナンス機能の概要

多くの人がデータを利用できる環境においては、データプライバシーや重要情報などの観点から、誰がどのデータまで参照してよいかを考える必要があります。本ブログでは、データ参照権限の制御や追跡をサポートするSnowflake機能をまとめました。

  • 2021/11時点の機能や制約のため、最新の制約については公式サイトを参照してください)
  • 本ブログではSnowflakeが提供している機能にのみフォーカスしています。広い意味でのデータガバナンス(体制や戦略など)については記載していません。

機能を利用する場合の注意点は、テーブルサイズが大きい場合、Maskingやアクセスポリシー機能を利用すると、クエリ実行時のパフォーマンス(Credits消費)に影響する場合があります。また、機能自体の制約事項も考慮する必要があります。機能の利用は、要件や制約および、性能のトレードオフを踏まえて検討する必要があります。

Dynamic Data Masking

カラム(列)ベースのセキュリティ機能で、masking policyによりテーブルまたはビューの列値を動的にマスキングします。(権限のある場合のみ値が見え、それ以外は指定した値でマスキングされます)

以下に特徴を記載します。

  • マスキングの元値と、マスク後の値は同じdata typeとする必要がある(Timetamp型の列をマスキングする場合は、マスキング後の値もTimestamp型:00:00:00T00:00:00とする)
  • 参照可能なRoleでMaskingされた列値を取得(SELECT)し、Maskingの設定のないテーブルや列に書きだした場合、Masking設定は引き継がれない
  • MaskingされたTableを対象にしたビューには、テーブルのMaking policyが適用される
  • クエリ実行時に各カラムを評価する(Makingの対象カラムが多いと処理に影響)
  • Enterprise plan以上が必要

External Tokenization

カラム(列)ベースのセキュリティ機能で、対象カラムの値をtokenizationします。(センシティブな情報を含むデータ<トークン>をランダムな値に変換する)。こちらの機能を利用する場合は、Third-party製品の関数が必要です。

以下に特徴を記載します。

  • Tokenizationは、Third-party Tokenization providerのREST APIを利用するため、外部関数(External function)の実行により実現(TokenizationはSnowflake外で行われる)
  • データシェアリングでは利用できない
  • 特定の文字列ごとに一意の値を生成するため、値はマスクしたいが、件数(COUNT)や別列のMAX値を集計したい場合の利用に適している(Dynamic Maskingでこの要件を実現するためには、Group別でCASE文を書く必要がある。マスク値のハッシュ化は可能)
  • クエリ実行時に各カラムを評価(Makingの対象カラムが多いと処理に影響)
  • 自分たちの利用したいTokenization providerを適用する場合はEnterprise plan以上が必要

Row Access Policy

Row Access Policyは、行レベルで表示・非表示を制御する機能です。Mappingテーブルを使ったPolicy定義と、単純なCASE文によるPolicy定義ができます。

以下に特徴を記載します。

  • Policyは、オブジェクトとして独立しているため、同じPolicyが必要な複数のTableやViewに設定できる(例えば3つのテーブルすべてにregionカラムが存在し、特定Roleは特定Regionのみ参照可の場合、3つのテーブルで同じPolicyを利用できる)
  • Snowflakeが内部でSecure viewを作成するため、Secure Viewと同様に実行時のパフォーマンスに影響あり
  • 大きいテーブルの場合は、Policyで利用する属性をClustering Keyに設定しておくことでパフォーマンス向上が見込める
  • Snowflakeはmetadataでテーブルの件数や大小を管理しているため、通常はCOUNTやMAXの値取得が高速に行える。しかしながら、Row Access policyを設定している場合、metadataで管理しているCOUNTやMAX値が使えないため高速な取得ができない
  • Row access policyとColumn-level Masking policyは、テーブル・ビューに同時に設定できるが、Row access policyが先に評価される
  • Row access policyが設定されているテーブルやビューからマテリアライズドビューは作成できない。設定されてないテーブル、ビューから作成されているマテリアライズドビューには、Row access policyを設定できる
  • データシェアリングで利用できる
  • Enterprise plan以上が必要

Secure Views

Row Access Policyで内部的に利用されている機能で、独立して利用する場合は、CREATE VIEWまたはCREATE MATERIALIZED VIEWコマンドのキーワードでsecureを設定します。権限のないユーザに対してDDL設定などを隠すことができます。

以下に特徴を記載します。

  • Viewが一部のデータをフィルター(WHERE句で絞り込み)を行っている場合、Secureを付与しているViewのDDL定義は表示されない(通常のViewはできる)
    • select get_ddl('schema', 'test_schema');コマンドは、Schema配下のテーブルやViewのDDL定義一覧を表示する。WHERE句で絞り込みを行っているViewの場合、通常のViewはDDLが表示されるが、Secure ViewはDDLが表示されない
  • オプティマイザの評価時に、先にユーザの認可の記述を評価(ユーザのフィルター記述を先に評価することによるエラー発生を防ぐ)
  • データシェアリングで利用できる
  • Clustering Keyの利用を推奨

Secure UDF

UDFに対してsecureキーワードを設定することで実現します。

  • 権限のあるRoleまたはOwner以外に対して、UDF詳細表示(GET_DDLやSHOW FUNCTIONS、Query Profile結果など)を抑制する
  • Secure UDFの場合、通常のSQL UDFで行われるオプティマイザによる最適化をバイパスする(Secure UDFにすることでパフォーマンス影響が出る可能性がある)

Access History

ユーザやクエリが、どのデータをReadしたか記録したViewが提供されます。以前はRead Opeartionのみ記録されていましたが、2021/10から提供されているPreview機能を使うと、Write Operationも参照することができます。

Preview機能の適用状況は下記のクエリで確認できます。

-- 2021/10のPublic Previewが適用されているか確認(Enabledで提供済み)
USE ROLE ACCOUNTADMIN;
SELECT SYSTEM$BEHAVIOR_CHANGE_BUNDLE_STATUS('2021_10');
        
-- 2021/10のPublic Preview有効化
SELECT SYSTEM$ENABLE_BEHAVIOR_CHANGE_BUNDLE('2021_10');

特徴は以下の通りです。

  • 監査やユーザとクエリ傾向のインサイト、利用していないデータ特定などのために利用できる
  • 1年間保持される。1年以上の保持が必要な場合は、Clone等で別テーブルへ書き出しが必要
  • Enterprise plan以上が必要

Object Tagging

オブジェクトに対してタグを付与する機能が提供されています。タグ自体もオブジェクトです。

特徴は以下の通りです。

  • タグはSchemaレベルのオブジェクトとして存在する。タグのアタッチ先は以下の通り。
    • Account Level Object(User, Role, Database, Warehouseのみ)
    • Database Level Object(Schema)
    • Schema Level Object(Table, External Table, View, Materialized View, Stageのみ)
    • Columns

    • タグはSnowflakeのオブジェクト階層(上図)と同様に継承される(Tableにタグを付与した場合は、Tableオブジェクトの子オブジェクトColumnにも継承される)
    • Key/Value形式でValueは最大256文字で、1つのオブジェクトに紐づけられるタグ数は最大20
    • タグはオブジェクトとして存在するため、同じタグオブジェクトを複数オブジェクトにアタッチできる
    • タグはReplicationにおいても反映されるため、以下の場合はエラーとなる
    • Enterprise plan以下の環境にタグ付きのオブジェクトをReplicationしようとする
    • Replication対象のデータベースが、Replication対象外のデータベースのタグを参照している
    • クローンによる複製においてもタグは維持される
    • Snowflake.ACCOUNT_USAGEビューで作成タグを確認する場合は、更新まで1-3時間程度の遅延がある(ACCOUNT_USAGEビューは、Snowflake内部管理のmetadata storeからデータを抽出しているため、最新の情報が反映されるまでに通常1-3時間のタイムラグが発生する)
    • TagはSchema-Levelのオブジェクトのため、利用にはTagの親となるDatabaseとSchemaに対するUsage権限が必要(Snowflakeのアクセスコントロ- Enterprise plan以上が必要

タグは、以下のような用途に利用することができます。

  • タグは独立したオブジェクトのため、異なるテーブルのカラムや、複数のテーブルに同じタグを付与でき、異なるオブジェクトに同じ意味をもたせられる
  • Credit集計において、Credit Viewテーブル(*_METERING_HISTORY)と結合することでタグベースのCredit集計ができる
  • タグから、タグが付与されたオブジェクトをたどれるため、タグベースでオブジェクトのグルーピングができる

Dynamic Maskingサンプル

Dynamic Maskingはテーブルのカラムに設定します。Dynamic Maskingのサンプルコードは以下の設定を事前に行っています(コードに書いてない部分)

【実行の前提となるオブジェクトと権限設定】

  • テーブル「test_db」とスキーマ「gov」の作成
  • スキーマのowner role「shema_ower」の作成
  • ウェアハウス「test_wh」の作成
  • Maskingを管理するRole「masking_admin」の作成
    • test_dbとgov、test_whへのUSAGE権限付与
  • Maskingを検証するためのRole「test_role_sensitive_ok;」と「test_role_sensitive_ng」の作成
    • test_dbとgov、test_whへのUSAGE権限付与
    • 作成したテーブル「masking_test」へのSELECT権限付与

【補足事項】

  • 「—」はコメントを表します
  • ROLEオブジェクト名はcase-insensitiveだが、CURRENT_ROLE()はROLE名を大文字の文字列で返すため、Mapping Policyでは大文字のROLE名を指定する
-- MASKING POLICYを作成・アタッチするための権限付与
USE ROLE shema_ower;
GRANT CREATE MASKING POLICY ON SCHEMA gov TO ROLE masking_admin;
USE ROLE ACCOUNTADMIN;
GRANT APPLY MASKING POLICY ON ACCOUNT TO ROLE masking_admin;

-- Maskingのテスト用テーブル作成
USE ROLE shema_ower;
CREATE OR REPLACE TABLE test_db.gov.masking_test (
    id INTEGER, 
    sensitive_data VARCHAR, 
    sensitive_time TIMESTAMP, 
    src VARIANT,
    encrypted_data BINARY) 
AS SELECT column1, column1, 
					column3::timestamp_ntz, 
					parse_json(column4), 
					encrypt(column5, 'passphrase111') -- デフォルトはAES-256で暗号化される
FROM VALUES 
(1, 'deny KAG group', '2021-10-01', '{"data": "sensitive"}', 'important data1'), 
(2, 'allow ABA group, CC group', '2021-10-01', '{"data":"important"}', 'important data2'), 
(3, 'deny MMMMM group', '2021-10-02', '{"data":"daiji"}', 'important data3')
;
SELECT * FROM test_db.gov.masking_test;

-- Masking policyの作成
USE ROLE masking_admin;
CREATE OR REPLACE MASKING POLICY test_db.gov.sensitive_comment_mask_gen 
AS (val string) RETURNS string -> 
    CASE 
				-- ROLEオブジェクトは通常case-insensitiveだが、CURRENT_ROLE()はROLE名を大文字で返す
        WHEN CURRENT_ROLE() IN ('TEST_ROLE_SENSITIVE_OK') THEN val
        ELSE '*********'
    END
;

-- テーブルにMasking Policyをアタッチ
ALTER TABLE IF EXISTS test_db.gov.masking_test MODIFY COLUMN sensitive_data 
SET MASKING POLICY test_db.gov.sensitive_comment_mask_gen;

-- 動作確認
USE ROLE test_role_sensitive_ok; 
SELECT * FROM TATENO_TEST_DB.gov.masking_test;
-- >> Maskingされずに表示される

USE ROLE test_role_sensitive_ng; 
SELECT * FROM TATENO_TEST_DB.gov.masking_test;
-- >> Maskingされる

おまけ:Masking Policyの色々なパターン

-- 権限のないユーザにはHASH化した値を返す
CREATE OR REPLACE MASKING POLICY test_db.gov.sensitive_comment_mask_sha2
AS (val string) RETURNS string -> 
    CASE 
        WHEN CURRENT_ROLE() IN ('TEST_ROLE_SENSITIVE_OK') THEN val
        ELSE sha2(val)
    END
;

-- Timestamp型のカラムに対してマスキングを設定する
CREATE OR REPLACE MASKING POLICY test_db.gov.sensitive_comment_mask_timesamp
AS (val TIMESTAMP) RETURNS TIMESTAMP -> 
    CASE 
        WHEN CURRENT_ROLE() IN ('TEST_ROLE_SENSITIVE_OK') THEN val
        ELSE date_from_parts(0001, 01, 01)::timestamp_ntz
    END
;

-- VARIANT型(JSON格納)の1つの属性をマスキングする
-- object_insert(<VARIANT型のカラム名>, <属性名>, <変換する属性値>, <updateFlag>)
-- updateFlagがTRUEの場合は、既存のキーを上書き/更新。デフォルトはFALSEで新しく挿入
CREATE OR REPLACE MASKING POLICY test_db.gov.sensitive_comment_mask_variant
AS (val VARIANT) RETURNS VARIANT -> 
    CASE 
        WHEN CURRENT_ROLE() IN ('TEST_ROLE_SENSITIVE_OK') THEN val
        ELSE object_insert(val, 'data', '****', true)
    END
;

-- 暗号化済みのカラムに対して権限のあるユーザのみ復号化した値を取得
CREATE OR REPLACE MASKING POLICY test_db.gov.sensitive_comment_mask_decrypt
AS (val BINARY) RETURNS BINARY -> 
    CASE 
        WHEN CURRENT_ROLE() IN ('TEST_ROLE_SENSITIVE_OK') THEN decrypt(val, 'passphrase111')
        ELSE val
    END
;

-- マスキングの有無を別テーブル(entitlement)に保持してマスキング時に動的に判定
-- マスキングポリシー変更なしで、マスキング/アンマスキング設定変更が可能だがパフォーマンスtrade-off
CREATE OR REPLACE TABLE test_db.gov.entitlement (role_name VARCHAR, can_read BOOLEAN) AS 
SELECT column1, column2 FROM 
VALUES ('TEST_ROLE_SENSITIVE_OK', true), ('TEST_ROLE_SENSITIVE_NG', false)
;

CREATE OR REPLACE MASKING POLICY test_db.gov.sensitive_comment_mask_usetable
AS (val string) RETURNS string -> 
    CASE 
        WHEN EXISTS (
					SELECT 1 FROM test_db.gov.entitlement 
					WHERE can_read and role_name = CURRENT_ROLE()
				) THEN val
        ELSE '*********'
    END
;

Access Historyサンプル

Access Historyは以下のクエリで取得できます。サイズが大きいのでキーで絞るかLIMITを使うとよいと思います。

USE WAREHOUSE <warehouse name>;

-- ABC_Userの履歴(最新履歴10件取得)
SELECT * 
FROM SNOWFLAKE.ACCOUNT_USAGE.access_history
WHERE USER_NAME = 'ABC_User'
ORDER BY query_start_time DESC
LIMIT 10;

-- WriteOperationのAccount History取得(最新履歴10件取得)
SELECT * 
FROM SNOWFLAKE.ACCOUNT_USAGE.access_history
WHERE ARRAY_SIZE(OBJECTS_MODIFIED) != 0
ORDER BY query_start_time DESC
LIMIT 10;

ObjectTaggingサンプル

テーブルやカラムへのタグの付与サンプルです。

【実行の前提となるオブジェクトと権限設定】

  • テーブル「test_db」とスキーマ「gov」の作成
  • スキーマのowner role「shema_ower」の作成
  • ウェアハウス「test_wh」の作成
  • タグを管理するRole「tag_admin」の作成
    • test_dbとtest_whへのUSAGE権限付与
    • スキーマgovのUSAGEとCREATE TABLE権限付与
-- タグの作成とアタッチ権限を付与
USE ROLE shema_ower;
GRANT CREATE TAG ON SCHEMA gov TO ROLE tag_admin;
USE ROLE ACCOUNTADMIN;
GRANT APPLY TAG ON ACCOUNT TO ROLE tag_admin;

-- タグを作成
USE ROLE tag_admin;
CREATE TAG test_db.gov.usage_type;
CREATE TAG test_db.gov.data_type;
CREATE TAG test_db.gov.env;
-- スキーマ内のタグ一覧を表示
SHOW TAGS IN SCHEMA test_db.gov;

-- テーブルとカラムにタグを付与
USE ROLE tag_admin;
CREATE OR REPLACE TABLE test_db.gov.test (
    id Integer, 
    v1 VARCHAR WITH TAG data_type = 'sensitive'
) 
WITH TAG (usage_type = 'personal', env = 'test')
AS SELECT column1, column2 FROM VALUES (1, 'aaa'), (2, 'bbb'), (3, 'ccc');

-- 削除済みのタグも含めてアカウントすべてのタグを表示する
SELECT * FROM snowflake.account_usage.tags
ORDER BY tag_name;

-- タグとタグをアタッチしたオブジェクトの一覧を表示(どのタグがどのオブジェクトにアタッチされているか)
-- domainはオブジェクトのタイプを表す('TABLE', 'COLUMN', 'WAREHOUSE'など)
SELECT * FROM snowflake.account_usage.tag_references
ORDER BY tag_name, domain, object_id

最後に

データガバナンスやセキュリティは、後回しにされる場合もありますが、扱うデータ量が増えれば増えるほど、後付けでの対応が大変になる部分かなと思います。機能の利用有無にかかわらず、データ分析基盤を検討していく中で、以下のようなことも意識するとよいかもしれません。

  • 構築予定のデータ分析基盤は、どのようなレベルのデータを保持する環境か
  • どのようなユーザがアクセスできる(する)環境か
  • データポリシーの確認(個人情報、機密情報などレベルごとのデータ分類に従い、だれがどのレベルのデータまでアクセスしてよいか、どのような対応をすればアクセスできるかなどの方針の確認)
  • 機密性の高いデータを取り込む場合に必要な対応の確認(マスキング済みデータのみ取り込むか、取り込んだ後にマスキングするか、その場合はどのようなアクセス管理が必要かなど)
  • データ品質や信頼性、セキュリティを維持するための体制やプロセスの設計