VPC Service Controls導入時のポリシー違反と対策

[mediba Adventカレンダー](https://qiita.com/advent-calendar/2024/mediba) 13日目の記事です。
ソリューション事業本部の苅部からはVPC-SC対応について書いていきます。

## 概要
Google CloudにてVPC-SCを導入すると既存のデータパイプラインに思わぬ影響を及ぼすことがあります。

私の場合、すでに稼働しているプロジェクトへの導入でポリシー違反の詳細を確認しながら試行錯誤していましたが、かなりの時間を要してしまいました。

そのため、この記事では事前に想定されるポリシー違反（エラーメッセージ）と対策を共有します。これから導入を予定されている方の一助となれば幸いでございます。

## VPC-SCとは？
Google Cloudのセキュリティを強化するための機能で、データの不正アクセスや不正なデータ転送を防ぐことを目的としています。具体的には、Google Cloudのサービスやプロジェクト間でのデータアクセスを制限し、リソースを特定の仮想的なセキュリティ境界（ペリメーター）内に閉じ込めることができます。

そして事前に定義した内容に沿わない通信は遮断され「ポリシー違反」としてログに記録されます。


## ポリシー違反の種別

エラーの種別は主に以下の3種類になります。

1. `NO_MATCHING_ACCESS_LEVEL`
    - （事前に許可した）IPアドレスとの不一致による違反
2. `NETWORK_NOT_IN_SAME_SERVICE_PERIMETER`
    - サービス境界を超えた 外向きあるいは内向きのアクセスによる違反 
3. `RESOURCES_NOT_IN_SAME_SERVICE_PERIMETER`
    - 1つのリクエストが複数のリソースにアクセスしていて、それぞれのリソースが同じサービス境界内にない場合の違反

エラーログとして `violationReason` の項目にこれらの文字列が入ります。


では、実際に発生したポリシー違反の内容を見ていきます。


## 1) Google AnalyticsのBigQueryエクスポート

Google AnalyticsではBigQueryエクスポートができますが、このエクスポートがポリシー違反となりviolationReasonとして `NO_MATCHING_ACCESS_LEVEL`が記載されます。

```
"ingressViolations": [
  {
    "targetResourcePermissions": [
      "bigquery.jobs.create"
    ],
    "targetResource": "projects/XXX"
  }
],
"violationReason": "NO_MATCHING_ACCESS_LEVEL"
```
### 対策
`firebase-measurement@system.gserviceaccount.com`のサービスアカウントをアクセスレベルで許可設定をします。

## 2) Google AnalyticsのBigQueryエクスポート（新規接続の場合）

新規でGoogle AnalyticsとBigQueryを接続する場合、自身の認証情報かつGoogleのIPアドレスでAPIコールが発生します。
この操作時にIPアドレス起因でポリシー違反となりviolationReasonが `NO_MATCHING_ACCESS_LEVEL`となります。


```
"ingressViolations": [
  {
    "targetResourcePermissions": [
      "bigquery.datasets.get"
    ],
    "targetResource": "projects/XXX",
  }
],
"violationReason": "NO_MATCHING_ACCESS_LEVEL"
```

### 対策
接続設定を完了させるため、一時的に自身のプリンシパルをサービス境界で許可します。


## 3) Google Search ConsoleのBigQueryエクスポート

Google Search ConsoleはBigQueryにエクスポートする機能がありますが、これもポリシー違反となります。
```
"ingressViolations": [
  {
    "targetResourcePermissions": [
      "bigquery.datasets.get"
    ],
    "targetResource": "projects/XXX"
  }
],
"violationReason": "NO_MATCHING_ACCESS_LEVEL"
```

### 対策

`search-console-data-export@system.gserviceaccount.com`のサービスアカウントをアクセスレベルで許可します。

## 4) BillingのBigQueryエクスポート

Billingでの費用データのBigQueryエクスポートも同様です。

```
"ingressViolations": [
  {
    "targetResource": "projects/XXX"
    "targetResourcePermissions": [
      "vpcsc.permissions.unavailable"
    ],
  }
],
"violationReason": "NO_MATCHING_ACCESS_LEVEL"
```

### 対策

`cloud-account-pricing@cloud-account-pricing.iam.gserviceaccount.com`のサービスアカウントをアクセスレベルで許可します。


## 5) Cloud Run関数
Cloud Run関数（Cloud Run Functions）（旧Cloud Functions）をバックグラウンドで動かす場合にはIPアドレス起因でポリシー違反となります。

```
"ingressViolations": [
  {
    "targetResource": "projects/XXX",
    "targetResourcePermissions": [
      "bigquery.jobs.create"
    ]
  }
],
"violationReason": "NO_MATCHING_ACCESS_LEVEL",
```

※`848655640797`はCloud Runを実行しているプロジェクト番号と考えられます。

### 対策
Cloud Run関数作成時に自動的に払い出されるサービスアカウント`<project number>-compute@developer.gserviceaccount.com`をアクセスレベルで許可します。


## 6) Firebase関連

私が観測した範囲では以下のサービスアカウントでポリシー違反となりましたので、それぞれアクセスレベルで許可しました。

`service-<project number>@gcp-sa-firebasestorage.iam.gserviceaccount.com`
`service-<project number>@gcp-sa-firebase.iam.gserviceaccount.com`
`firebase-service-account@firebase-sa-management.iam.gserviceaccount.com`


## 7) Google Apps Scriptの定期実行でのBigQuery接続
GASを定期実行する場合は実行元がGoogleのIPアドレスとなるためポリシー違反となります。

```
"ingressViolations": [
  {
    "targetResourcePermissions": [
      "bigquery.jobs.create"
    ],
    "targetResource": "projects/XXX"
  }
],
"violationReason": "NO_MATCHING_ACCESS_LEVEL"
```
### 対策
サービスアカウントを作成し、GASの認証情報をサービスアカウントに切り替えます。そしてそのサービスアカウントをアクセスレベルで許可します。

https://qiita.com/satoru-karibe/items/7d4304dcd79e6ed4f67d


## 8) Google ColabからBigQueryへの接続

ColabからのBigQuery接続でサービス境界の上り(内向き)ポリシー違反となります。
violationReasonとして`NETWORK_NOT_IN_SAME_SERVICE_PERIMETER`が記載されます。

```
"ingressViolations": [
  {
    "targetResource": "projects/XXX",
    "targetResourcePermissions": [
      "vpcsc.permissions.unavailable"
    ],
    "source": "projects/495559152420"
  }
],
"violationReason": "NETWORK_NOT_IN_SAME_SERVICE_PERIMETER"
```


### 対策
GAS同様にサービスアカウントを作成し、認証情報をサービスアカウントに切り替えます。そしてそのサービスアカウントをアクセスレベルで許可します。

## 9) Connected SheetsからBigQueryへの接続
Google スプレッドシートにはBigQueryに接続するインターフェイスとしてConnected Sheetsが用意されていますが、サービス境界の下り(外向き)ポリシー違反となります。

```
"egressViolations": [
  {
    "targetResourcePermissions": [
      "bigquery.vpcsc.importData"
    ],
    "source": "projects/XXX",
    "targetResource": "projects/628550087766"
  }
],
"violationReason": "RESOURCES_NOT_IN_SAME_SERVICE_PERIMETER"

```

※`628550087766`はGoogleSpreadSheetのプロジェクト番号のようです。


### 対策

Googleのドキュメントを参考にして、Egressの境界設定をします。

https://cloud.google.com/bigquery/docs/connected-sheets?hl=ja

### 設定例
```
- egressTo:
    operations:
    - serviceName: 'bigquery.googleapis.com'
      methodSelectors:
      - permission: 'bigquery.vpcsc.importData'
    resources:
    - projects/628550087766
  egressFrom:
    identityType: ANY_USER_ACCOUNT
```

## 10) Connected SheetからBigQueryへの接続（定期更新）

前述のEgress設定をしたとしても、自動更新で接続した場合にはIPアドレス起因でポリシー違反となります。

```
"ingressViolations": [
  {
    "targetResourcePermissions": [
      "bigquery.tables.getData"
    ],
    "targetResource": "projects/XXX"
  }
],
"violationReason": "NO_MATCHING_ACCESS_LEVEL"
```

Googleのドキュメントにもあるように定期更新の場合、IPアドレスが問題となります。

> コネクテッド シートの定期更新では、IP アドレスやデバイス情報などのエンドユーザー コンテキストは反映されません。エンドユーザー コンテキストを使用してアクセスを制限する VPC Service Controls の境界では、定期更新が失敗します。
>
> https://cloud.google.com/bigquery/docs/connected-sheets?hl=ja


### 対策
Connected Sheetは認証情報のサービスアカウントへの切り替えができず、対策しようが無いので諦めます。

私の場合、この影響でデータパイプラインの見直しをせざるを得なくなり、工数が大幅に膨らんでしまいました。
※SpreadSheetへの依存度を減らし、BigQueryのスケジュールクエリへ移行する形になりました。

## 11) Looker Studioでの「データ抽出」経由でのBigQueryへの接続（自動更新）

Looker Studioにはデータソース利用としての[「データ抽出」](https://support.google.com/looker-studio/answer/9019969)機能があり自動更新もできますが、Connected Sheets同様にIPアドレス起因でポリシー違反となります。

```
"ingressViolations": [
  {
    "targetResourcePermissions": [
      "bigquery.jobs.create"
    ],
    "targetResource": "projects/XXX"
  }
],
"violationReason": "NO_MATCHING_ACCESS_LEVEL",
```

### 対策
Google Workspace または Cloud Identity の管理対象組織であれば、認証情報をサービスアカウントへ切り替えできるようです。

> [サービス アカウント認証情報] オプションは、Google Workspace または Cloud Identity の管理対象組織でのみ使用できます。
>
> サービス アカウントは、Google Workspace または Cloud Identity の管理者が設定する必要があります。管理者である場合は、サービス アカウントの設定方法をご確認ください。
>
>https://support.google.com/looker-studio/answer/6371135?hl=ja#zippy=%2C%E3%81%93%E3%81%AE%E8%A8%98%E4%BA%8B%E3%81%AE%E5%86%85%E5%AE%B9%2C%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9-%E3%82%A2%E3%82%AB%E3%82%A6%E3%83%B3%E3%83%88%E8%AA%8D%E8%A8%BC%E6%83%85%E5%A0%B1

つまり、当該サービスアカウントをアクセスポリシーで許可することでサービス境界を通過することができます。

> **Looker Studio でサービス アカウントを使用するメリット**
>
> スケジュール設定されたメールやスケジュール設定されたデータ抽出などの自動機能では、VPC Service Controls の境界の背後にあるデータソースが使用されます。
> 
> https://support.google.com/looker-studio/answer/10835295?sjid=3315609380737885839-AP#zippy=%2C%E3%81%93%E3%81%AE%E8%A8%98%E4%BA%8B%E3%81%AE%E5%86%85%E5%AE%B9

## 12) Cloud Shellでのデータ操作

例えばCloud Shellで`bq ls`コマンドを実行するとサービス境界の上り(内向き)ポリシー違反となります。

```
"ingressViolations": [
  {
    "targetResource": "projects/XXXX",
    "source": "projects/751522334863",
    "targetResourcePermissions": [
      "bigquery.datasets.get"
    ]
  }
],
"violationReason": "RESOURCES_NOT_IN_SAME_SERVICE_PERIMETER",
```

Cloud Shellの実行元は別プロジェクト（`751522334863`）となり、プロジェクトを超えた通信となるため境界ポリシーに抵触しているようです。

### 対策
Cloud Shellは諦めて、VPC-SCで保護された領域でVMを立ち上げるか、或いは許可されたNWに接続したローカル環境からgcloud CLIで操作します。


## 13) プロジェクトを超えたBigQueryへの接続
VPC-SCで保護されたプロジェクトから別プロジェクトのテーブルへクエリ発行する場合は外向き（Egress）ポリシーに抵触します。

例えばVPC-SCで保護された2つのプロジェクト間（プロジェクトAにおいてプロジェクトBのテーブルへのクエリ）でのエラーを見てみます。

### プロジェクトA側のエラー

sourceとtargetResourceの関係から A→BのEgress違反ということがわかります。
外向き（Egress）の境界設定をしていないため、外（B）に出ることができない状態です。

```
"egressViolations": [
  {
    "source": "projects/projectA",
    "targetResource": "projects/projectB",
    "sourceType": "Resource",
    "targetResourcePermissions": [
      "bigquery.tables.getData"
    ]
  }
],
"violationReason": "RESOURCES_NOT_IN_SAME_SERVICE_PERIMETER",
```


### プロジェクトB側のエラー

B→AのEgress違反になっています。
こちらも外向き（Egress）の境界設定をしていないため、外（A）に出ることができない状態です。

```
"egressViolations": [
  {
    "targetResource": "projects/projectA",
    "targetResourcePermissions": [
      "bigquery.jobs.create"
    ],
    "sourceType": "Resource",
    "source": "projects/projectB"
  }
],
"violationReason": "RESOURCES_NOT_IN_SAME_SERVICE_PERIMETER",
```
※プロジェクトA側でEgress設定した上で（外に出ることができる状態になった上で）、初めてプロジェクトB側でVPC-SCのエラーが表示されます。

### 対策
プロジェクトA、BそれぞれにEgressポリシーを設定します。

**プロジェクトAでの設定例**
```
- egressTo:
    operations:
    - serviceName: 'bigquery.googleapis.com'
      methodSelectors:
      - method: '*'
    resources:
    - projects/projectB
  egressFrom:
    identityType: ANY_USER_ACCOUNT
```

**プロジェクトBでの設定例**
```
- egressTo:
    operations:
    - serviceName: 'bigquery.googleapis.com'
      methodSelectors:
      - method: '*'
    resources:
    - projects/projectA
  egressFrom:
    identityType: ANY_USER_ACCOUNT
```

上記の例は保護されたプロジェクト同士の例ですが、保護されていないプロジェクトのテーブルに対してクエリ発行する場合も都度設定が必要です。

**[Googleのオープンデータセット](https://cloud.google.com/bigquery/public-data?hl=ja)との疎通設定例**

```
- egressTo:
    operations:
    - serviceName: 'bigquery.googleapis.com'
      methodSelectors:
      - method: 'bigquery.tables.getData'
    resources:
    - projects/20398604589
  egressFrom:
    identityType: ANY_USER_ACCOUNT
```

## その他 ポリシー違反以外のエラー

### BigQuery Editions (Standard)
Editions StandardはVPC-SCに対応していません。

```
"status": {
  "code": 9,
  "message": "Accessing resources in VPC-SC perimeters is disallowed in STANDARD edition. See https://cloud.google.com/bigquery/docs/editions-intro for more information."
}
```

#### 対策
Enterprise以上に切り替える必要があります。
なおEnterpriseはStandard比でスロット時間単価が1.5倍になりますので、相応の費用の増加を見込んでおく必要があります。

### BigQueryのクエリ結果のGoogleドライブへの保存

クエリ結果をGoogleドライブへ保存できなくなります。
```
"status": {
  "code": 7,
  "message": "Export to Google Drive is blocked for projects with VPC-SC enabled"
}
```

#### 対策
境界設定で解決することができないため、CSVとしてローカル環境にダウンロードするか、GoogleSpreadSheetにエクスポートします。

## まとめ

VPC-SC対応では以下のような整理で進めました。

### NO_MATCHING_ACCESS_LEVEL

実行元のIPアドレス起因となるため、

「認証情報がユーザー」の場合は認証情報をサービスアカウントへの切り替えて、そのサービスアカウントをアクセスポリシーで許可する。
「認証情報がサービスアカウント」の場合はそのサービスアカウントをアクセスポリシーで許可する。

「認証情報がユーザー」で、かつサービスアカウントへの切り替えができない場合には諦める。

### NETWORK_NOT_IN_SAME_SERVICE_PERIMETER

境界内リソースへの内向き or 外向きのアクセスに起因するため、サービスアカウントをアクセスポリシーで許可、あるいは境界ポリシーを設定する

### RESOURCES_NOT_IN_SAME_SERVICE_PERIMETER

複数のリソースへのアクセスに起因するため境界ポリシーを設定する



## おわりに
すでに稼働しているプロジェクトへの適用は難しく、私自身の知識もなかったため試行錯誤が続き苦労しましたが、結果的にはGoogleCloudの新たな領域への理解が深まり、たいへん勉強になりました。

## 参考ページ

https://cloud.google.com/vpc-service-controls/docs/audit-logging?hl=ja

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

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

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

https://firebase.google.com/docs/storage/gcp-integration?hl=ja

https://support.google.com/looker-studio/answer/10835295?sjid=3315609380737885839-AP#zippy=%2C%E3%81%93%E3%81%AE%E8%A8%98%E4%BA%8B%E3%81%AE%E5%86%85%E5%AE%B9