📘

BigQueryのカラムレベルセキュリティを試す

2024/12/22に公開

はじめに

企業・組織がデータを利活用する上で、データを安全に処理することは非常に重要です。データの秘匿化やアクセス制御を行い安全に扱うためには、以下のような方法があります。

  • Sensitive Data Protection の DLP (Data Loss Prevention) 機能を使用して、秘匿情報を検出・分類・匿名化する
  • 承認済みビューにより、アクセス制御する
  • BigQuery のテーブルに対し、行レベルや列レベルのアクセス制御を行う
  • Data Catalog によりアクセス制御する

以前、強い要件を満たすためにデータの秘匿化を行う必要があり、ゴリラ実装を行ったことがあるのですが、分析等の通常のユースケースにおいてデータクリーンルームを構築するためには、上記のように Google が用意したデータ保護手段で十分なはずです。

今回は、列レベルのマスキング及び、Data Catalog のポリシータグを利用した方法を試し、その内容を記します。

ポリシータグによるアクセス制御について

そもそも今回試すサービス・機能について軽くご紹介します。

BigQuery では、Data Catalog に紐づくポリシータグと、そのタグに対してどのような処理を行うのかという BigQuery 側のポリシーによって、列レベルのアクセス制御を行うことができます。

以下の手順でこれを設定します。それぞれの具体的な Terraform コードは後ほど示します。

  1. taxonomy というポリシータグのホルダーのようなものと、それに紐づくポリシータグを1つ以上作成する
  2. ポリシータグに対して、どのようなデータ処理を行うか(ハッシュするなど)を定めるポリシーを作成する。このポリシーはあらかじめ定義されたものに加え、スカラー関数(SQLの関数)として作成されたカスタムルーティンをマスキングポリシーとして付与することができる
  3. カラムに対して、1で作成したポリシータグをアタッチする
  4. IAM で閲覧権限を設定する

詳細は以下の参考ドキュメントをご参照ください。

参考ドキュメント

実装

以下では Terraform による実装と、どのようにアクセス制御・マスキングされるか紹介します。サンプルコードはここにあります。(命名規則や description などはかなりいい加減ですのでご了承ください。)

1. taxonomy と、それに紐づくポリシータグを1つ以上作成する

まず、Data Catalog リソースの taxonomy というものと、それに紐づくポリシータグを作成します。ポリシータグは、日付や名前、住所といったマスキング対象の属性・処理内容に応じて複数作成します。

terraformで書くと以下のようになります。activated_policy_types = ["FINE_GRAINED_ACCESS_CONTROL"] とすることによって、列レベルのきめ細かいアクセス制御が可能になります。

resource "google_data_catalog_taxonomy" "taxonomy" {
  region                 = local.region
  display_name           = "the_taxonomy"
  description            = "the_taxonomy"
  activated_policy_types = ["FINE_GRAINED_ACCESS_CONTROL"]
}

resource "google_data_catalog_policy_tag" "policy_tag" {
  taxonomy     = google_data_catalog_taxonomy.taxonomy.id
  display_name = "the_tag"
  description  = "the_tag"
}

2. ポリシータグに対してポリシーを作成する

次に、各ポリシータグに対して、どのようなデータ処理を行うかを定めるポリシーを作成します。例えばハッシュ化する、数字を X でマスキングするなどです。このポリシーはあらかじめ定義されたものと、スカラー関数として作成されたカスタムルーティンをマスキングポリシーのいずれかを指定することができます。

あらかじめ定義されたルールはこちらを、またカスタムマスキングルールに使用可能な関数はこちらをご参照ください。

あらかじめ定義されたルール(SHA256)を使う場合
resource "google_bigquery_datapolicy_data_policy" "predefined_policy" {
  location         = local.region
  data_policy_id   = "predefined_policy"
  policy_tag       = google_data_catalog_policy_tag.for_predefined.name
  data_policy_type = "DATA_MASKING_POLICY"
  data_masking_policy {
    predefined_expression = "SHA256"
  }
}
カスタムマスキングルーティンを作成して指定する場合
resource "google_bigquery_routine" "custom_masking" {
  dataset_id = google_bigquery_dataset.my_dataset.dataset_id
  routine_id = "custom_masking_fn"

  routine_type         = "SCALAR_FUNCTION"
  language             = "SQL"
  data_governance_type = "DATA_MASKING"

  arguments {
    name      = "ssn"
    data_type = jsonencode({ "typeKind" : "STRING" })
  }

  return_type = jsonencode({ "typeKind" : "STRING" })

  definition_body = "SAFE.REGEXP_REPLACE(ssn, '[0-9]', 'X')"
}

resource "google_bigquery_datapolicy_data_policy" "data_policy" {
  location         = local.region
  data_policy_id   = "data_policy"
  policy_tag       = google_data_catalog_policy_tag.policy_tag.name
  data_policy_type = "DATA_MASKING_POLICY"
  data_masking_policy {
    routine = google_bigquery_routine.custom_masking.id
  }
}

3. カラムに対して、ポリシータグをアタッチする

次に、ポリシータグをアタッチします。Terraform で書くと以下のようになります。

resource "google_bigquery_table" "test_table" {
  project    = data.google_project.current.project_id
  dataset_id = google_bigquery_dataset.my_dataset.dataset_id
  table_id   = "test_ssn_masking"

  schema = <<EOF
[
  {
    "name": "id",
    "type": "STRING",
    "mode": "NULLABLE"
  },
  {
    "name": "ssn",
    "type": "STRING",
    "mode": "NULLABLE",
    "policyTags": {
      "names": [
        "${google_data_catalog_policy_tag.policy_tag.name}"
      ]
    }
  }
]
EOF
}

これによって、カラムに対して誰がアクセスできるか、またアクセスできる人に応じてマスキング処理をするかといった設定が完了しました。

4. IAM で閲覧権限を設定する

ここまでの設定に対して、Data Catalog のポリシータグに対してきめ細かいアクセスを可能にするロールと、BigQuery 側でマスクされた値を閲覧できるロールという2つのレイヤでアクセス制御を行う例を挙げます。

なお、1-3で記載した Terraform コードで作成したリソースが存在することを前提とします。

4-1. 何の権限も付与されていないプリンシパルでのアクセス

まず、何も権限を付与されていないプリンシパルでデータを見てみます。

この時、BigQuery Studio のプレビューや Data Catalog からは、ポリシータグが付いたカラムは省略されて見えます。

また、クエリでポリシータグが付いたカラムを要求しようとするとエラーになります。以下は BigQuery Studio でクエリを実行した時のキャプチャです。

4-2. roles/bigquerydatapolicy.maskedReader を付与

次に、以下のようにマスキングされた値の閲覧ロールを付与してみてみます。

resource "google_bigquery_datapolicy_data_policy_iam_binding" "parent_tag_masked_readers" {
  location       = local.region
  data_policy_id = google_bigquery_datapolicy_data_policy.data_policy.id
  role           = "roles/bigquerydatapolicy.maskedReader"
  members        = var.the_data_viewers
}

この時、プレビューは4-1と同様に、ポリシータグが付いたカラムは省略されて見えます。

一方で、ポリシータグが付いたカラムを要求するクエリは正常終了し、結果はマスキングされて見えます。

4-3. roles/datacatalog.categoryFineGrainedReader を付与

以下のように、ポリシータグに対してきめ細かい読み込みを許可するロールを付与してみます。

resource "google_data_catalog_policy_tag_iam_binding" "viewers" {
  policy_tag = google_data_catalog_policy_tag.policy_tag.name
  role       = "roles/datacatalog.categoryFineGrainedReader"
  members    = var.the_data_viewers
}

この時、プレビューはポリシータグを付与したものも含め全て見ることができます。

クエリも正常終了し、特にマスキングされることなく見ることができます。

なお、roles/bigquerydatapolicy.maskedReaderroles/datacatalog.categoryFineGrainedReader の両方を付与した場合、後者が優先されてこのように生のデータを閲覧できます。

他に試したこと

リモート関数をカスタムマスキングルーティンに使用できるか

結論、できませんでした。

発想としては、マスキングルーティンをスカラー関数として定義できればリモート関数を使用したトリッキーなマスキングが可能かと思っての試みです。どうもカスタムマスキングルーティンで使用可能な関数に制限があり、ここに記載がないものについては執筆時点(2024年12月現在)で一切使用できないように見えます。CASE句など条件分岐も使えませんでした。

Discussion