G-gen の武井です。当記事では VPC Service Controls のドライランモードについて解説します。
はじめに
VPC Service Controls とは
VPC Service Controls とは Google Cloud (旧称 GCP) 上のセキュリティ機能です。
境界 (perimeter) と呼ばれる論理的な囲いを設け、中から外、外から中の双方向で Google Cloud の各種サービス API への意図しないアクセスを防ぐことでサービスやデータへのセキュリティを強化します。
詳細は以下で解説しています。こちらもあわせてご確認ください。
ドライランモード
ドライランモード (ドライラン構成) とは、設定した境界を仮想的に適用して Google Cloud 上のリソースやサービスに与える影響を評価するためのモードです。
潜在的な不備に気づかぬまま境界を実際に適用してしまうと、予期せぬアクセス拒否など、与える影響は少なくありません。
そのため、VPC Service Controls を利用する際はドライランモードで境界設定を適切に評価し、その上で実環境に適用する流れが適切と言えます。
ドライランモードの仕組み
ドライランモードで設定された境界は仮想的に適用されるため、アクセスが実際にブロックされることはありません。
一方 Cloud Logging にはログとして記録されます。そのため、管理者は記録されたログから潜在的な問題を特定して潰し込み、境界設定をあるべき形に導くことができます。
- 参考:ドライラン モードの利点
ドライランモード
概要
構成
このセクションでは以下のケースをもとに、ドライランモードによる境界設定の評価プロセスについて解説します。
検証プロジェクトにログを格納する BigQuery が存在するという前提で、境界が特定 IP アドレスからのアクセスを許可するように設定されているかを評価します。
設定項目
上記構成は以下のサービスを利用しています。
# | サービス | 設定するもの | 目的 |
---|---|---|---|
1 | VPC Service Controls | 境界 (Perimeter) | アクセス条件に基づき BigQuery API を保護する |
2 | Access Context Manager | アクセスレベル (アクセス条件) | コンテキスト (今回で言えばアクセス元 IP アドレス) を定義する |
Access Context Manager
Access Context Manager はゼロトラストセキュリティを実現する BeyondCorp Enterprise のコンポーネントの一つです。
デバイス情報、アカウント情報、接続状況などの 背景情報 (コンテキスト) を定義することが可能で、VPC Service Controls に関連付けることができます。
設定
権限
以降の設定操作を行うには、公式ドキュメントに従いプリンシパルに IAM ロールを付与する必要があります。
アクセスレベル
特定の IP アドレスを送信元としたアクセスが境界内の BigQuery にアクセスできるよう、Access Context Manager で IP アドレスに基づくアクセスレベルを設定します。
設定方法は以下の公式ドキュメントを参考としています。
境界 (ドライランモード)
次に VPC Service Controls からドライランモードで境界を設定します。
以下の画像のように、ドライランモード
タブを選択した状態で 新しい境界
をクリックします。
設定方法は以下の公式ドキュメントを参考としています。
- 参考:サービス境界を作成する
実例解説
評価の流れ
ドライランモードによる境界設定が完了したら、以下の流れで設定を評価していきます。
- エラーログの有無を確認する
- ログから
metadata.violationReason
フィールドを確認してエラーの原因を特定したうえで詳細を確認する - ログ、境界設定、アクセスレベルを再確認して被疑箇所を特定して修正する
実例1
ログ確認
まずはエラーの有無をログからを確認します。
BigQuery にアクセスしたのち、Cloud Logging のログエクスプローラから以下のクエリを実行してエラーログを確認します。
図のようにエラーログが記録されていた場合は原因を特定していきます。
原因の特定
以下は実際のエラーログです。(プロジェクト ID 等の値を一部加工しています)
46行目の metadata.violationReason
(エラー原因) には NO_MATCHING_ACCESS_LEVEL
と記されていることがわかります。
{ "protoPayload": { "@type": "type.googleapis.com/google.cloud.audit.AuditLog", "status": { "code": 7, "message": "(Dry Run Mode) Request is prohibited by organization's policy. vpcServiceControlsUniqueIdentifier: 9eFMI2pfYddKGBxQtFGub4I66MZpMSBV0li3aN0EkyYmGOiL6T0-Sg", "details": [ { "@type": "type.googleapis.com/google.rpc.PreconditionFailure", "violations": [ { "type": "VPC_SERVICE_CONTROLS", "description": "9eFMI2pfYddKGBxQtFGub4I66MZpMSBV0li3aN0EkyYmGOiL6T0-Sg" } ] } ] }, "authenticationInfo": { "principalEmail": "yu...i@g-...p" }, "requestMetadata": { "callerIp": "2.3.4.5", "requestAttributes": {}, "destinationAttributes": {} }, "serviceName": "bigquery.googleapis.com", "methodName": "bigquery.datasets.get", "resourceName": "projects/111111111111", "metadata": { "securityPolicyInfo": { "organizationId": "333333333333", "servicePerimeterName": "accessPolicies/596827900003/servicePerimeters/test" }, "@type": "type.googleapis.com/google.cloud.audit.VpcServiceControlAuditMetadata", "vpcServiceControlsUniqueId": "9eFMI2pfYddKGBxQtFGub4I66MZpMSBV0li3aN0EkyYmGOiL6T0-Sg", "ingressViolations": [ { "targetResource": "projects/111111111111", "servicePerimeter": "accessPolicies/596827900003/servicePerimeters/test", "targetResourcePermissions": [ "bigquery.datasets.get" ] } ], "violationReason": "NO_MATCHING_ACCESS_LEVEL", "dryRun": true, "intermediateServices": [ "bigquery.googleapis.com" ], "deviceState": "Unknown", "resourceNames": [ "projects/111111111111/datasets/logdestination" ] } }, "insertId": "4fwkn7d1pqp", "resource": { "type": "audited_resource", "labels": { "project_id": "yutakei-test-prj", "method": "bigquery.datasets.get", "service": "bigquery.googleapis.com" } }, "timestamp": "2024-01-30T13:05:14.094591814Z", "severity": "ERROR", "logName": "projects/yutakei-test-prj/logs/cloudaudit.googleapis.com%2Fpolicy", "receiveTimestamp": "2024-01-30T13:05:14.915645252Z" }
詳細確認
以下の公式ガイドを確認すると、NO_MATCHING_ACCESS_LEVEL
は以下のように記されています。
この問題は、IP アドレス、デバイス要件、またはユーザー ID が境界に割り当てられた上り(内向き)ルールまたはアクセスレベルと一致していない場合に発生します。
たとえば、監査レコードの callerIp フィールドに対応する IP アドレスが、サービス境界のアクセスレベルで定義された CIDR 範囲と一致しません。
考察と対応
再度ログを確認すると、23行目の callerIp
(呼び出し元の IP アドレス) が 2.3.4.5
となっています。
つまりこのエラーは、アクセスレベルで定義した IP と呼び出し元 IP の不一致 が原因 (アクセスレベル設定のミス) で発生したものと考えられるため、当該設定を修正してエラーを解消します。
実例2
ログ確認
再度エラーの有無を確認します。すると先程とは異なるエラーが記録されています。
原因の特定
以下は実際のエラーログです。(プロジェクト ID 等の値を一部加工しています)
metadata.violationReason
については先程同様 NO_MATCHING_ACCESS_LEVEL
(アクセスレベルの不一致) とありますが、その他のフィールドで異なる特徴が見受けられます。
{ "protoPayload": { "@type": "type.googleapis.com/google.cloud.audit.AuditLog", "status": { "code": 7, "message": "(Dry Run Mode) Request is prohibited by organization's policy. vpcServiceControlsUniqueIdentifier: RmvvqAujYKJhXMxPzN-bkSzBmHGRHuIXUXvLuvR8Gbh_ZZYCtEFOlQ", "details": [ { "@type": "type.googleapis.com/google.rpc.PreconditionFailure", "violations": [ { "type": "VPC_SERVICE_CONTROLS", "description": "RmvvqAujYKJhXMxPzN-bkSzBmHGRHuIXUXvLuvR8Gbh_ZZYCtEFOlQ" } ] } ] }, "authenticationInfo": { "principalEmail": "service-222222222222@gcp-sa-logging.iam.gserviceaccount.com" }, "requestMetadata": { "callerIp": "private", "requestAttributes": {}, "destinationAttributes": {} }, "serviceName": "bigquery.googleapis.com", "methodName": "google.cloud.bigquery.v2.TableDataService.InsertAll", "resourceName": "projects/111111111111", "metadata": { "@type": "type.googleapis.com/google.cloud.audit.VpcServiceControlAuditMetadata", "ingressViolations": [ { "servicePerimeter": "accessPolicies/596827900003/servicePerimeters/test", "targetResource": "projects/111111111111" } ], "dryRun": true, "resourceNames": [ "yutakei-test-prj", "projects/222222222222" ], "securityPolicyInfo": { "servicePerimeterName": "accessPolicies/596827900003/servicePerimeters/test", "organizationId": "333333333333" }, "vpcServiceControlsUniqueId": "RmvvqAujYKJhXMxPzN-bkSzBmHGRHuIXUXvLuvR8Gbh_ZZYCtEFOlQ", "violationReason": "NO_MATCHING_ACCESS_LEVEL", "deviceState": "Unknown" } }, "insertId": "3vwwvwd2hpq", "resource": { "type": "audited_resource", "labels": { "service": "bigquery.googleapis.com", "method": "google.cloud.bigquery.v2.TableDataService.InsertAll", "project_id": "yutakei-test-prj" } }, "timestamp": "2024-02-20T13:09:17.367737498Z", "severity": "ERROR", "logName": "projects/yutakei-test-prj/logs/cloudaudit.googleapis.com%2Fpolicy", "receiveTimestamp": "2024-02-20T13:09:18.644856847Z" }
詳細確認 その1
20行目の principalEmail
(呼び出し元アカウントの Email) を確認すると、Google アカウントではなく サービスアカウント であることがわかります。
サービスアカウントのメールアドレスは、「@以前にプロジェクト ID、@以後にサービス名」 が含まれています。
このことから、「projects/2222222222222
の何らかのログシンクが、projects/111111111111
の BigQuery API にアクセスしている」 可能性が考えられます。
詳細確認 その2
VPC Service Controls にはトラブルシューティングツールがあります。
47行目の vpcServiceControlsUniqueId
(各違反に付与される一意の識別子) に記載の ID を VPC Service Controls のトラブルシューティングツールに入力することで、エラー原因を可視化して分析できます。
以下はログに記録されていたユニーク ID RmvvqAujYKJhXMxPzN-bkSzBmHGRHuIXUXvLuvR8Gbh_ZZYCtEFOlQ
を入力した際の画面です。
前述同様、他プロジェクトで設定されたログシンクが当プロジェクトの BigQuery API に対してログをエクスポートしていることが推測できます。
考察と対応
これまでの結果から、当初の構成では不十分であることが評価の過程で判明しましたので実態に合わせて境界設定を修正します。
上り (内向き) ポリシー で以下のようにルールを追加して設定を保存します。
これにより、Inbound 方向で他プロジェクトのサービスアカウントが当プロジェクトのサービス境界内にある BigQuery API にアクセスできるようになります。
# | 属性 | 項目 | 設定値 |
---|---|---|---|
1 | FROM | ID | 他プロジェクトのログシンクサービスアカウント |
2 | FROM | ソース | すべてのソース |
3 | TO | プロジェクト | 当プロジェクト |
4 | TO | サービス | BigQuery API (All methods) |
サービス境界修正後、エラーログはすべて潰し込むことができました。
最後に
VPC Service Controls はセキュリティを強固にしてくれる反面、潜在的な不備に気づかぬまま境界を適用してしまうとサービスに影響を与えてしまいます。
適用前にはドライランモードで設定を評価し、実際のサービスにどのような影響があるかを事前に確認することが重要です。
当記事が VPC Service Controls の適切な運用の一助になれば幸いです。
関連記事
その他 VPC Service Controls 関連記事については以下を参照ください。
武井 祐介 (記事一覧)
クラウドソリューション部所属。G-gen唯一の山梨県在住エンジニア
Google Cloud Partner Top Engineer 2025 選出。IaC や CI/CD 周りのサービスやプロダクトが興味分野です。
趣味はロードバイク、ロードレースやサッカー観戦です。
Follow @ggenyutakei