データエンジニアのための VPC Service Controls 入門

# はじめに
こんにちは。もう少し寝るとお正月という事実に震えている [xavifrog](https://x.com/xavifrog) です。

今回は GCP(Google Cloud Platform) Advent Calendar 2024 の23日目の記事として、Google Cloud のセキュリティサービスの一つである VPC Service Controls の概要と、データエンジニアが設計・運用していく上で必要になりそうな知識や Tips を紹介します。

https://qiita.com/advent-calendar/2024/gcp

VPC Service Controls は組織 (Organization)があることを前提とし、組織レベルの権限が必要なことや、各 Google Cloud リソースの内部動作を考慮しなくてはならない部分があるなど、敷居が高いサービスと感じる部分はありますが、取り組んでいく中で得られる経験によって Google Cloud をより深く理解することができる面もあります。もちろん VPC Service Controls の適用によってもたらされるセキュリティレベルの向上はセンシティブデータを扱う環境に大きな効果をもたらすはずです。

本稿はそんな VPC Service Controls にこれから取り組もうとする方の助けになることを願い、 terraform での実例を交えながら解説していきます。公式資料や先人の資料、実業務での構築・運用経験を元に作成していますが、誤り等を見つけた場合はコメント等でご指摘いただけますと幸いです。

### 想定読者
- 明日から VPC Service Controls を設計/運用しなくてはならなくなったデータエンジニア
- Google Cloud をある程度理解していて、セキュリティ機能に興味がある方
- VPC Service Controls を実例交えて理解したい方

### 資料
公式資料

https://cloud.google.com/security/vpc-service-controls?hl=ja

株式会社G-gen 様の Tech Blog による VPC Service Controls 解説。
こちらを読んで育ちました。本稿を書くにあたっても参考にさせていただいております。

https://blog.g-gen.co.jp/entry/vpc-service-controls-explained

https://blog.g-gen.co.jp/entry/components-of-vpc-service-controls

# VPC Service Controls
VPC Service Controls (以後 VPC-SC) は各 Google Cloud リソースの API 単位 (一部の API は更に細かく permission、method 単位) へのアクセスをアカウントや IP といった様々な条件によって制限するセキュリティサービスです。
具体的なユースケースを見ていきましょう。

#### 具体的なユースケース
- 厳秘情報を扱う環境のため、アクセスを許された特定ユーザーが社用 VPN 経由でアクセスした場合以外は bigquery へのアクセスを遮断する
- 特に取り扱いに注意しなくてはならないデータを定常的に協力会社に共有(i.e. [BigQuery Data Transfer Service](https://cloud.google.com/bigquery/docs/dts-introduction), [AnalyticsHub](https://cloud.google.com/analytics-hub?hl=en)) しなくてはならない際、協力会社の project にしかデータ送信ができないように Google Cloud の API を用いた外向きの通信を制限する

Google Cloud を利用したことがある方は上記の制限の一部は [Identity and Access Management（IAM）](https://cloud.google.com/security/products/iam) によっても実現できることに気付かれることと思います。VPC-SC で実現できる状態と IAM によって実現できる状態は一部似通っていますが、具体の動作は異なります。

#### IAM による制限
- ID (ユーザーアカウント・Google Group・サービスアカウント) ベース
- role 単位の権限付与

#### VPC-SC による制限
- コンテキストベース
    - 内向き通信 (ingress) か・外向き通信 (egress) か
    - IP
    - region
    - アカウント (投稿時点で google group 非対応) 
    - 通信の発出 project 
- API (一部 permission, method) ベースの許可・制限

上記の通り、VPC-SC はネットワーク的なコンテキストベースの制限を行います。IAM であれば必要な権限を付与すれば通信経路によりませんが、VPC-SC では外から内への通信 (ingress) は許可するが、内から外の通信 (egress) は制限するといった、詳細な制限が可能です。これによってセンシティブなデータを限られたユーザー内で利活用しつつ、外への持ち出しは防ぐといった堅牢なデータセキュリティを実現します。
ここで注目すべきは、それぞれが独立したサービスであり、併用が可能であるということです。例えば、誤って不適切なユーザーに BigQuery の権限を与えてしまった場合でも、 VPC-SC が設定されていればデータを閲覧ができないといった多層防御を実現することができます。VPC-SC の公式資料では他にも悪意のある内部者や外部攻撃者からデータを守ることができるなどの利点が挙げられています。

https://cloud.google.com/security/vpc-service-controls?hl=en


## 前提
#### 組織の作成
前提として、対象の Google Cloud project が特定の組織(Organization) に所属している必要があります。Google Cloud における組織については下記が詳しいです。

https://blog.g-gen.co.jp/entry/google-cloud-organization-explained

組織の作成には Google Workspace への登録か、Cloud Identity への登録が必要ですが、どちらもドメインが必要です。新規取得するか、個人ブログなどで所有しているものがあればそちらを転用するのが良いでしょう。私がプライベートの環境を構築する際は Cloud Identity への登録を行いました。

https://cloud.google.com/resource-manager/docs/creating-managing-organization?hl=en


#### IAM
VPC-SC を構成・閲覧するための role として下記が用意されています。

- Access Context Manager Admin (roles/accesscontextmanager.policyAdmin)
    - VPC-SC に関連するリソースの編集権限(Access Policy, Access Level, Perimeter)
    - Access Context Manager に対する権限管理
    - 組織下 project の名前取得 / 一覧表示
- Access Context Manager Editor (roles/accesscontextmanager.policyEditor)
    - VPC-SC に関連するリソースの編集権限(Access Policy, Access Level, Perimeter)
    - 組織下 project の名前取得 / 一覧表示
- Access Context Manager Reader (roles/accesscontextmanager.policyReader)
    - VPC-SC に関連するリソースの閲覧権限(Access Policy, Access Level, Perimeter)
    - 組織下 project の名前取得 / 一覧表示

https://cloud.google.com/vpc-service-controls/docs/access-control

上記は組織レベルの権限付与になります。個人の環境なら問題になりませんが、企業においては自分が関わっている project 以外に対しても影響を及ぼせるため、権限付与には細心の注意が必要です。

## 全体のイメージ図
![VPC-SC_イメージ図.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1748962/1a8af44f-e263-3648-a900-4767c6ac575e.jpeg)



## access policy
access policy は後述する access level や perimeter を格納する箱的存在です。適用範囲 (箱の大きさ) は組織全体とすることもできれば、特定の project や project をまとめたフォルダにすることもできます。制限を行う主体である perimeter の制限範囲は最大で access policy の範囲内になるため、VPC-SC による制限対象としたい project や VPC は access policy の範囲内にある必要があります。

```terraform
# organizations 全体に適用する場合
resource "google_access_context_manager_access_policy" "default_policy" {
  parent = "organizations/XXXXXXXXXXXXXX"
  title  = "Default Access Policy"
}

# folder や project に適用する場合
resource "google_access_context_manager_access_policy" "access_policy_01" {
  parent = "organizations/XXXXXXXXXXXXX"
  title  = "Scoped Access Policy"
  scopes = ["folders/YYYYYYYYYYYY"]
  # scopes = ["projects/ZZZZZZZZZZZZZ"]
}
```

https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/access_context_manager_access_policy

## access level
access level はアクセスを許される通信コンテキストの定義です。具体的には下記が条件として設定可能です。

- IPアドレス
- ユーザーアカウント・サービスアカウント
- 発信リージョン
- デバイス情報 ([Chrome Enterprise Premium[旧BeyondCorp Enterprise]](https://cloud.google.com/beyondcorp-enterprise/docs/overview?hl=en) が必要)

access level 自体は単なる定義であるため単体では効果を持たず、perimeter にアタッチされることで初めて効力を発揮します。

```terraform
resource "google_access_context_manager_access_level" "access_level_01" {
  parent = "accessPolicies/${var.policy_name}"
  name   = "accessPolicies/${var.policy_name}/accessLevels/access_level_01"
  title  = "access_level_01"
  basic {
    combining_function = "AND"
    conditions {
      ip_subnetworks = [
        "X.X.X.X/32"
      ]
      regions = [
        "JP",
      ]
      members = [
        "user:hoge@XXXXX.com",
        "serviceAccount:fuga@YYYYY.iam.gserviceaccount.com"
      ]
    }
  }
}
```

https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/access_context_manager_access_level

## perimeter
VPC-SC による制限を実際に行うリソースです。指定した API 群を囲う"境界"として、ルールを満たさない perimeter 外から内へのアクセスや perimeter 内から外部へのアクセスを制限します。

```terraform
resource "google_access_context_manager_service_perimeter" "perimeter_01" {
  parent = "accessPolicies/${google_access_context_manager_access_policy.access_policy_01.name}"
  name   = "accessPolicies/${google_access_context_manager_access_policy.access_policy_01.name}/servicePerimeters/perimeter_01"
  title  = "perimeter_01"
  perimeter_type = "PERIMETER_TYPE_REGULAR"
  status {
    resources = ["projects/${var.project_A_number}"]
    restricted_services = [
      "bigquery.googleapis.com",
      "bigquerydatapolicy.googleapis.com",
      "bigquerydatatransfer.googleapis.com",
      "bigquerymigration.googleapis.com",
      "analyticshub.googleapis.com",
    ]

    vpc_accessible_services {
      enable_restriction = true
      allowed_services = [
      ]
    }

    access_levels = [
      google_access_context_manager_access_level.access_level_01.name
    ]

    # access level を満たすかつ許可されたユーザーは制限リソースへのアクセスを許可
    ingress_policies {
      ingress_from {
        identities = var.allow_users
      }
      ingress_to {
        resources = ["projects/${var.project_A_number}"]
        operations {
          service_name = "bigquery.googleapis.com"
          method_selectors {
            method = "*"
          }
        }
        operations {
          service_name = "bigquerydatatransfer.googleapis.com"
          method_selectors {
            method = "*"
          }
        }
        operations {
          service_name = "bigquerymigration.googleapis.com"
          method_selectors {
            method = "*"
          }
        }
        operations {
          service_name = "bigquerydatapolicy.googleapis.com"
          method_selectors {
            method = "*"
          }
        }
        operations {
          service_name = "analyticshub.googleapis.com"
          method_selectors {
            method = "*"
          }
        }
      }
    }

    # project_A から project_B への bigquery に関する通信を許可
    egress_policies {
      egress_from {
        identities = var.allow_users
      }
      egress_to {
        resources = ["projects/${var.project_B_number}"]
        operations {
          service_name = "bigquery.googleapis.com"
          method_selectors {
            method = "*"
          }
        }
      }
    }
  }
}
```

https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/access_context_manager_service_perimeter

上記の通り、perimeter の作成に当たっては様々な設定を行う必要があります。具体的な項目を見ていきましょう。

### perimeter に含めるリソースの指定
perimeter による制限対象の指定は project 単位、または VPC単位で行うことができます。1つの perimeter 内に複数のリソースを含めることも可能です。重要な点として、terraform での perimeter の定義は2024年12月時点では project 単位での指定のみ対応しています。

```terraform
    resources = ["projects/${var.project_A_number}"]
```

### perimeter のタイプ
perimeter は regular タイプと bridge タイプが存在します。
regular タイプはその名の通りデフォルトのタイプです。指定した gcp project、または VPC における指定した API へのアクセスを access level 等の定義によって制限する、これまで説明してきた機能を持ったリソースと言えます。

```terraform
  perimeter_type = "PERIMETER_TYPE_REGULAR"
```

これに対し、bridge タイプは複数の perimeter 間の双方向通信を許可するためのタイプです。例えば、それぞれ別の perimeter によって囲まれた project 間で data transfer 等によるリソースの移動通信を行いたい場合にはこのタイプが有用であるとされています。

https://cloud.google.com/vpc-service-controls/docs/share-across-perimeters

注意すべき点として、perimeter 間の通信をするためには必ず bridge タイプの perimeter を指定する必要があるというわけではありません。bridge タイプが行っているのは異なる perimeter に属する project、VPC 同士の双方向通信の担保です。これは regular タイプの perimeter の設定で疎通したい project 間の通信を全ての API で双方向 (ingress・egress) に許可することと同義であり、これは後述する regular タイプでの ingress_policies・egress_policies でも行うことができます。むしろ regular タイプでは API 単位の疎通設定ができる他、bridge タイプの作成には双方向通信を許可したい project 同士が同じ access policy 下に存在する必要があるなど、bridge タイプを使用するのに適したユースケースが少ない印象です。

個人的な考えとしては、カジュアルに双方向性を実現したい場合は bridge タイプの perimeter を作成、細かい設定を行いたい場合は regular タイプの中の設定を調整するという使い方が想定されているのではと推測しています。とはいえ、 bridge タイプの実運用経験があるわけではないため、bridge タイプでしか行うことができないことをご存知の方がいらっしゃればコメント等でご教示いただけますと幸いです。


### 制限を行うAPIの指定
perimeter ではどのAPIを制限に含めるかを設定します。

```terraform
    restricted_services = [
      "bigquery.googleapis.com",
      "bigquerydatapolicy.googleapis.com",
      "bigquerydatatransfer.googleapis.com",
      "bigquerymigration.googleapis.com",
      "analyticshub.googleapis.com",
    ]
```

対応しているサービスの一覧は下記です。

https://cloud.google.com/vpc-service-controls/docs/supported-products




### perimeter 全体への access level のアタッチ
perimeter に含めた API 全体へのアクセスについて、access level での制限適用が可能です。こちらを指定しない場合は後述の ingress_policies、egress_policies によって許可されていない全てのアクセスを拒否する状態になります。

```terraform
    # perimeter 全体に access level を適用する
    access_levels = [
      google_access_context_manager_access_level.access_level_01.name
    ]
```
```terraform
    # perimeter 全体の設定ではアクセスを拒否する
    access_levels = []
```

### 内向き・外向きのホワイトリスト (ingress_policies ・ egress_policies)
上記の全体への access level のアタッチとは別に、より詳しい範囲で通信を許可するルールをホワイトリスト形式で設定できます。こちらは内向き通信に対する許可 (ingress_policies) と 外向き通信に対する許可 (egress_policies) で分かれており、それぞれ複数の許可設定を定義することが可能です。

```terraform
    # access level を満たすかつ許可されたユーザーは制限リソースへのアクセスを許可
    ingress_policies {
      ingress_from {
        identities = var.allow_users
        # access_level の指定や source project / vpc の指定も可能
        # sources {
        #   access_level = "*"
        #   resource = "projects/${var.project_B_number}"
        # }
      }
      ingress_to {
        resources = ["projects/${var.project_A_number}"]
        operations {
          service_name = "bigquery.googleapis.com"
          method_selectors {
            method = "*"
          }
        }
        operations {
          service_name = "bigquerydatatransfer.googleapis.com"
          method_selectors {
            method = "*"
          }
        }
        operations {
          service_name = "bigquerymigration.googleapis.com"
          method_selectors {
            method = "*"
          }
        }
        operations {
          service_name = "bigquerydatapolicy.googleapis.com"
          method_selectors {
            method = "*"
          }
        }
        operations {
          service_name = "analyticshub.googleapis.com"
          method_selectors {
            method = "*"
          }
        }
      }
    }

    # project_A から project_B への bigquery に関する通信を許可
    egress_policies {
      egress_from {
        identities = var.allow_users
      }
      egress_to {
        resources = ["projects/${var.project_B_number}"]
        operations {
          service_name = "bigquery.googleapis.com"
          method_selectors {
            method = "*"
          }
        }
      }
    }
```

こちらはホワイトリスト形式であり、特定の通信を弾くといったことはできません。そのため、制限したい API については全て perimeter の中に含め、必要に応じて ingress_policies・egress_policies を設定する運用になります。

また、基本的にはそれぞれ どこの・誰からの通信で (from)、どこの・どのAPIに対するアクセス (to) を許可するかという構成になっていますが、特定の API については permission, method 単位でのアクセス許可にも対応しています。

https://cloud.google.com/vpc-service-controls/docs/supported-method-restrictions?hl=en

### vpc_accessable_service
perimeter 内からアクセス可能な API を vpc_accessible_services で設定できます。
perimeter の挙動として、workbench instance で設定できるような VPC にアタッチしつつ固定のIPを持たないインスタンス ( [Private Google Access](https://cloud.google.com/vpc/docs/private-google-access) を利用して GCP の API にアクセスするリソース)はデフォルトで perimeter の範囲内とされているようで、そのようなインスタンスからのアクセスを制限することが出来ると理解しています。しかし、上記が正しければ制限されるはずの通信が制限されない事象を確認していたりと、明確な動作を掴めているわけではないため、詳細ご存知の方がいらっしゃいましたらご教示ください．

```terraform

    vpc_accessible_services {
      enable_restriction = true
      allowed_services = [
      ]
    }
```

なお、特定の API のみアクセスできる状態にした場合でも問題になるパターンが存在します。具体的には、google の API であるが VPC-SC の対応外になる drive 系のAPIです。colab enterprise 等の内部 IP しか持たないリソース上で google drive の python client を使用して gss のデータを編集しようとする場合、enable_restriction = false の場合は取得・編集できるが bigquery への制限ができず、enable_restriction = true にした場合は bigquery の制限はできるが VPC-SC が sheets.googleapis.com に対応していないので許可設定ができず、 gss のデータ編集ができないというデットロック状態になります。なお、データの取得だけで良いならば BigQuery の外部テーブルを用いることで上記の問題を回避できます。
colab enterprise からGSSの編集をしたい場合、cloud functions が perimeter 外であれば perimeter 内から cloud functions へのアクセスを許可し、cloud functions から GSS へのアクセスを行うなどの踏み台的なアクセスが可能ですが、それらのAPIが perimeter 内になっている場合の回避策は知る限りありません。おそらく外部IPを持った / 持たせたリソースを介し、許可設定を追加して対応することになるでしょう．
早期のAPI対応が待たれます。

### dry run モード
perimeter には実際の制限を行わないが、もし適用していたら発生する error log を出す dry run モードが用意されています。これによって動作確認を行うことができますし、意図しないアクセスが行われていないかを確認する目的での利用も可能です。

https://cloud.google.com/vpc-service-controls/docs/dry-run-mode
```
  # dry run する場合
  use_explicit_dry_run_spec = true
  spec {
  #...
```


```
  # 本適用する場合
  use_explicit_dry_run_spec = false
  status {
  #...
```



# 実例と tips
ここまでで VPC-SC の概要を述べてきましたが、実際に動かしてみて初めて気が付く要素や公式資料等で注意喚起されている特殊なパターンが存在するため、いくつか紹介します。

前提として理解しなくてはならないのは、VPC-SC は API ベースの制限なのであって、サービスベースの制限ではないということです。例えば analyticsHub は analyticshub.googleapis.com というAPIが用意されていますが、データの共有処理には bigquery.googleapis.com が用いられています。cloud functions は cloudfunctions.googleapis.com (または run.googleapis.com) だけでなく、デプロイ時に artifact registry に保存されたイメージを取得したり、GCS へソースファイルをアップロードしたりと、本来の API 以外にも様々な Google API を使用しています。
VPC-SC ではこれらの抽象化されたサービスの内部動作を検証・推測し、必要十分なアクセス許可を行うことが必要です。もちろんそれら全てを一度に定義するのは困難であるため、dry run の使用や実際の制限を行った上で logging に書き込まれるエラーを確認しながら業務要件に合わせて最適解を模索する作業になるでしょう。幸運を祈ります。


## 実例
基本的な例として project A、B に対する bigquery, analyticsHub の制限と、許可されたユーザーに関しては各環境へのアクセス、 A、B間の bigquery での双方向通信を許可した場合の定義を記載します。[^2]

![VPC-SC_qiita例.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1748962/3289eb36-6378-60e8-938a-b68d35d5ddeb.jpeg)

```terraform
variable "project_A_number" {
  type    = string
  default = "AAAAAAAAAAAAA"
}

variable "project_B_number" {
  type    = string
  default = "BBBBBBBBBBBBB"
}

variable "allow_users" {
  type        = list(string)
  description = "許可するユーザー"
  default = [
    "user:hoge@fuga.com"
  ]
}

resource "google_access_context_manager_access_policy" "access_policy_01" {
  parent = "organizations/XXXXXXXXXXXXX"
  title  = "Scoped Access Policy"
  scopes = ["folders/YYYYYYYYYYYY"]
  # scopes = ["projects/ZZZZZZZZZZZZZ"]
}

resource "google_access_context_manager_access_level" "access_level_01" {
  parent = "accessPolicies/${google_access_context_manager_access_policy.access_policy_01.name}"
  name   = "accessPolicies/${google_access_context_manager_access_policy.access_policy_01.name}/accessLevels/access_level_01"
  title  = "access_level_01"
  basic {
    combining_function = "AND"
    conditions {
      ip_subnetworks = [
      ]
      regions = [
        "JP",
      ]
      members = var.allow_users
    }
  }
}

resource "google_access_context_manager_service_perimeter" "perimeter_01" {
  parent = "accessPolicies/${google_access_context_manager_access_policy.access_policy_01.name}"
  name   = "accessPolicies/${google_access_context_manager_access_policy.access_policy_01.name}/servicePerimeters/perimeter_01"
  title  = "perimeter_01"
  perimeter_type = "PERIMETER_TYPE_REGULAR"
  status {
    resources = ["projects/${var.project_A_number}"]
    restricted_services = [
      "bigquery.googleapis.com",
      "bigquerydatapolicy.googleapis.com",
      "bigquerydatatransfer.googleapis.com",
      "bigquerymigration.googleapis.com",
      "analyticshub.googleapis.com",
    ]

    vpc_accessible_services {
      enable_restriction = true
      allowed_services = [
      ]
    }

    access_levels = [
      google_access_context_manager_access_level.access_level_01.name
    ]

    # access level を満たすかつ許可されたユーザーは制限リソースへのアクセスを許可
    ingress_policies {
      ingress_from {
        identities = var.allow_users
      }
      ingress_to {
        resources = ["projects/${var.project_A_number}"]
        operations {
          service_name = "bigquery.googleapis.com"
          method_selectors {
            method = "*"
          }
        }
        operations {
          service_name = "bigquerydatatransfer.googleapis.com"
          method_selectors {
            method = "*"
          }
        }
        operations {
          service_name = "bigquerymigration.googleapis.com"
          method_selectors {
            method = "*"
          }
        }
        operations {
          service_name = "bigquerydatapolicy.googleapis.com"
          method_selectors {
            method = "*"
          }
        }
        operations {
          service_name = "analyticshub.googleapis.com"
          method_selectors {
            method = "*"
          }
        }
      }
    }

    # project_A から project_B への bigquery に関する通信を許可
    egress_policies {
      egress_from {
        identities = var.allow_users
      }
      egress_to {
        resources = ["projects/${var.project_B_number}"]
        operations {
          service_name = "bigquery.googleapis.com"
          method_selectors {
            method = "*"
          }
        }
      }
    }
  }
}


resource "google_access_context_manager_service_perimeter" "perimeter_02" {
  parent = "accessPolicies/${google_access_context_manager_access_policy.access_policy_01.name}"
  name   = "accessPolicies/${google_access_context_manager_access_policy.access_policy_01.name}/servicePerimeters/perimeter_02"
  title  = "perimeter_02"
  perimeter_type = "PERIMETER_TYPE_REGULAR"
  status {
    resources = ["projects/${var.project_B_number}"]
    restricted_services = [
      "bigquery.googleapis.com",
      "bigquerydatapolicy.googleapis.com",
      "bigquerydatatransfer.googleapis.com",
      "bigquerymigration.googleapis.com",
      "analyticshub.googleapis.com",
    ]

    vpc_accessible_services {
      enable_restriction = true
      allowed_services = [
      ]
    }

    access_levels = []

    ingress_policies {
      # 許可されたユーザーかつ access level を満たせば制限リソースへのアクセスを許可
      ingress_from {
        identities = var.allow_users
        sources {
          access_level = google_access_context_manager_access_level.access_level_01.name
        }
      }
      ingress_to {
        resources = ["projects/${var.project_B_number}"]
        operations {
          service_name = "bigquery.googleapis.com"
          method_selectors {
            method = "*"
          }
        }
        operations {
          service_name = "bigquerydatatransfer.googleapis.com"
          method_selectors {
            method = "*"
          }
        }
        operations {
          service_name = "bigquerymigration.googleapis.com"
          method_selectors {
            method = "*"
          }
        }
        operations {
          service_name = "bigquerydatapolicy.googleapis.com"
          method_selectors {
            method = "*"
          }
        }
        operations {
          service_name = "analyticshub.googleapis.com"
          method_selectors {
            method = "*"
          }
        }
      }
    }

    # project_B から project_A への bigquery に関する通信を許可
    egress_policies {
      egress_from {
        identities = var.allow_users
      }
      egress_to {
        resources = ["projects/${var.project_A_number}"]
        operations {
          service_name = "bigquery.googleapis.com"
          method_selectors {
            method = "*"
          }
        }
      }
    }
  }
}

```

## tips
### GCS に対する制限は terraform state のアクセスを失う可能性があるので注意
terraform state を GCS bucket に保存している場合、GCSに対する自らのアクセスを制限してしまうと state ファイルにアクセスできずに terraform の plan や apply が不能になり、GUI上から設定を変更しなくてはならなくなります。当たり前とも言える内容ですが、うっかりやってしまいがちなので注意しましょう(1敗)。

### analyticshub.googleapis.com
前述の通り analyticsHub には analyticshub.googleapis.com という API が存在しますが、そちらは data exchange 等に対する制限であり、データの共有自体は bigquery.googleapis.com を必要とします。
また、table に実体化して共有した場合、元々の project での制限が適用されず、共有先 project の制限ルールに則るため注意が必要です。(逆に言えば、セキュリティレベル的に問題がないデータを共有する際は table として実体化するのが選択肢の一つです)


### perimeter 外の dataset を analyticsHub によって perimeter 内の project に共有したい
analyticsHub で perimeter 外の dataset の listing を作成し、perimeter 内の project で subscribe する場合、 listing を作成する data exchange がどちらの project に所属するかによって挙動が変わります。
経験的な内容になりますが、最もセキュアな構成になるのは perimeter 内の data exchange に listing を作成するパターンです。こちらの場合では "bigquery.datasets.get" と "bigquery.datasets.update" を egress で許可するのみで perimeter 外の dataset での listing 作成、perimeter 内での subscribe を担保しつつ、perimeter 内のデータセットの analyticsHub を用いた共有を防ぐことができます。

```terraform
    egress_policies {
      egress_from {
        identities = var.allow_users
      }
      egress_to {
        resources = ["projects/XXXXXXXXXXXXX"]
        operations {
          service_name = "bigquery.googleapis.com"
          method_selectors {
            permission = "bigquery.datasets.get"
          }
          method_selectors {
            permission = "bigquery.datasets.update"
          }
          # 下記があると perimeter 内の dataset を perimeter 外の project に共有することができるようになる
          # method_selectors {
          #   permission = "bigquery.datasets.create"
          # }
        }
      }
    }
```


### policy tags
bigquery では [policy tags を用いた列レベルのアクセス制御](https://cloud.google.com/bigquery/docs/column-level-security-intro)を行うことができます。
analyticsHub 等で perimeter 内の policy tag 付きテーブルを perimeter 外の project でクエリする際、perimeter 内に対する bigquery 系 ingress 許可に加え、bigquery.jobs.create の egress 許可が必要です。



```terraform
    egress_policies {
      egress_from {
        identity_type = "ANY_IDENTITY"
      }
      egress_to {
        resources = ["*"]
        operations {
          service_name = "bigquery.googleapis.com"
          method_selectors {
            permission = "bigquery.jobs.create"
          }
        }
      }
    }
```

### connected sheet
perimeter による制限下のデータを connected sheet 機能を用いて google spreadsheet 上で使用したい場合、データ取得に使用するアカウントのアクセス許可の他に google spreadsheet が動作している (Google Cloud が所有している) project に対する egress 許可が必要です。

```terraform
    egress_policies {
      egress_from {
        identity_type = "ANY_USER_ACCOUNT"
      }
      egress_to {
        resources = ["projects/628550087766"]  # Sheets-owned Google Cloud project
        operations {
          service_name = "bigquery.googleapis.com"
          method_selectors {
            permission = "bigquery.vpcsc.importData"
          }
        }
      }
    }
```

https://cloud.google.com/bigquery/docs/connected-sheets#vpc-service-controls

### 既知の問題
下記の資料で既知の問題が述べられています。

https://cloud.google.com/vpc-service-controls/docs/troubleshooting

https://cloud.google.com/vpc-service-controls/docs/troubleshoot-services-within-perimeter


# 最後に
本稿では VPC Service Controls の概要や実例、tips を紹介してきました。
資料や動作確認を元にしたものにはなりますが、手探りで構築・運用していく中で経験的に得たものも多分に含まれるため、より優れた方法や誤りがあるかもしれません。その場合はコメント等でご指摘いただけますと幸いです。
なお、本稿を元にしたことによって生じた問題への責任は負えませんので、本稿を参考に VPC-SC を構築される場合は十分な検証の上で取り組まれますようにお願いいたします。

それでは、ここまでお付き合いいただきありがとうございました。
良い年末をお過ごしください。

[^2]: あくまでデモ構成であるので、転用される場合はご自身で十分に検証されますようお願いいたします