🛡️

VPC Service Controlsを用いてデータ制限を行う

2023/12/12に公開

こんにちは D2C エンジニアの羽原です。
この記事は、D2C m-tech Advent Calendar Day8の記事です

はじめに

Google Cloudの機能としてVPC Service Controlsがあります。
こちらの機能を用いることで、Google Cloudのリソースに対して仮想的な境界を作りデータの制御を行うことが可能となります。
しかし、設定項目が多くどのような操作がコントロール配下になり、システムに影響がでるかわからないことも多くあります。
本記事では、実際の設定事例を交えながら、どのような点に気をつけて設定を行ったかをお伝えできればと思います。

VPC Service Controlsとは

VPC Service Controls(VPC SC)は、Google Cloudのセキュリティ機能の一部であり、境界と呼ばれる論理的な囲いを作ることで、データを内部から外部への流出を防ぐことが可能となります。

できること・できないこと

VPC Service Controlsを使用して、できること・できないことには以下のような内容が挙げられます。
できること:Google CloudのAPIコールを制限できる

  • BigQueryに対するクエリのアクセス元制限
  • Cloud Storageのオブジェクトの読み取りとアクセス元の制限

できないこと:VPCのネットワークに対してのアクセス制限を行うこと

  • VMのSSHアクセスの制限
  • webアプリへの攻撃を防御(WAFのような用途)

以上の項目が、VPC SCの機能についてです。
詳細な内容は公式ページもしくは、G gen社の記事が大変参考となりますので御覧ください

今回の要件

今回のVPC Service Controlsの要件については以下の通りでした。

  • Google Cloudのコンソールにアクセスできるユーザーのうち、開発者ユーザー以外は特定のIP環境からアクセスされた場合のみコンソール・CLIからBigQueryとCloud Storageが使用できる
  • 制限された境界から外にでるAPIアクセスについては、制限を行わない
  • CI/CDやBigQueryのAnalytics Hubに対してVPC Service Controlsに影響が出ないようにする。

上記の要件を満たす環境をVPC SCにより構築しました。
要件について、簡単に示した図は以下の通りです。
概要図

構築手順

ポリシーの作成

デフォルトの状態では、VPC SCは各プロジェクト単位には作成されていないため、
Google Cloud組織のVPC SCからポリシーの新規作成を行う必要があります。

  1. 組織レベルのGoogle Cloudのコンソール(ドメイン名プロジェクト)に入り、VPC Service Controlsを選択
    VPC SCコンソール
  2. 「ポリシーを管理する」を選択し、「作成」を選択
    ポリシーを管理
  3. 作成したいアクセスポリシーの対象範囲(プロジェクト・フォルダ)を指定し、プリンシパルを指定する。
    アクセスポリシーの新規作成

Access Context Managerの定義

IP制限を行う際に使われる方法として、Access Context Managerがあります。
こちらは、リソースに対してアクセスを行うユーザーやSAに対して属性ベースのアクセス制御を行うことができます。アクセス制御できる属性としては以下の内容が可能です。

  • デバイスの種類とOS
  • IPアドレス
  • ユーザーID
    今回の要件では、特定のIPからのアクセスを許可するためIPアドレスベースのポリシーを作成します。
  1. 各プロジェクトのAccess Context Managerへアクセスと「アクセスレベルの作成」を選択
    ACMとアクセスレベルの作成
  2. アクセスレベルの作成
    • アクセスレベルのタイトル
    • 条件のIPサブネットワークをCIDR形式で入力
      アクセスレベルの作成

VPC SCの境界作成

Access Context Managerの作成を行った後、VPC SCの新たな境界を作成します。
境界の作成方法には2種類あり

  • 自動適用モード:作成した境界を本番実行するモード
  • ドライランモード:作成した境界をドライランで実行するモード

の2種類ありますが、実環境にどのような影響があるか不明であるためドライランでの実行をおすすめします。(ドライラン実行時のエラーキャッチについても後述します。)

  1. VPC SCのドライランモードより「新しい境界」を選択
    VPC SCドライラン
  2. 境界のタイトルをつける
    • 境界タイプは標準境界で問題ありません
      VPCSC1
  3. 保護するリソースを選択
    • 保護するリソースを選択する。今回はポリシーの所属するプロジェクトを選択
      VPC SC2
  4. 制限付きサービスを選択
    • 制限するサービスを選択、今回はBigQueryとCloud Storageを選択
    • VPC SCで保護できるリソースをすべて保護する場合は、「すべてのサービスを追加」を選択
      VPC SC3
  5. VPCのアクセス可能なサービス
    • サービス境界内からアクセスできるサービスを定義する箇所になります。今回は、サービス境界内から外部へのアクセスはすべて許可するためすべてのサービスを選択
      VPC SC4
  6. アクセスレベル
    • サービス境界内に入って来るアクセス(上りポリシー)について、アクセスレベルを設定する箇所
    • 今回は、すべてのユーザー・SAに対してIP制限を行うことはしないため、ここではアクセスレベルを設定しない(境界内サービスを特定のIPからのみ接続可とする場合設定)
      VPC SC5
  7. 上り(内向き)のポリシー
    • サービス境界の中の入って来るアクセスに対してのポリシーを設定
    • 今回は3つのポリシーを作成
      • ルール1:開発者ユーザー以外の動作
        • ID:ANY_USER_ACCOUNT(すべてのユーザーアカウント)
        • アクセスレベル:{Access Context Managerで設定してポリシー名}
        • 宛先ープロジェクト:すべてのプロジェクト
        • 宛先ーサービス:すべてのサービス
      • ルール2:開発者ユーザー
        • ID:開発者ユーザーのgoogleアドレス(googleグループのアドレスは追加できないため注意)
        • ソース:すべてのソースを許可
        • 宛先ープロジェクト:すべてのプロジェクト
        • 宛先ーサービス:すべてのサービス
      • ルール3:SAからの操作はすべて許可
        • ID:ANY_SERVICE_ACCOUNT(すべてのサービスアカウント)
        • アクセスレベル:すべてのソースを許可
        • 宛先ープロジェクト:すべてのプロジェクト
        • 宛先ーサービス:すべてのサービス
      • ルール4:一部gserviceaccountの追加(CI/CDにてGCSにアクセスを行えるように追加)
        • ID: {プロジェクト番号}@cloudbuild.gserviceaccount.com
          service-{プロジェクト番号}@gcf-admin-robot.iam.gserviceaccount.com
        • アクセスレベル:すべてのソースを許可
        • 宛先ープロジェクト:すべてのプロジェクト
        • 宛先ーサービス:すべてのサービス
          VPC SC6
  1. 下り(外向き)ポリシー
    • VPC SCから外向きに行うAPIアクセスを定義
    • Analytics Hubの共有データセットがVPC SCのサービス境界内にある場合は外向きの指定が必要となる詳細リンク
    • 今回はすべての外向きポリシーを許可する。
    • ルール
      • ID:ANY_IDENTITY
      • 宛先ープロジェクト:すべてのプロジェクト
      • 宛先ーサービス:すべてのサービス
        VPC SC7

上記の設定で、本要件については満たすことができました。

Terraformによる記述

今回の設定について、Access Context ManagerとVPC service Controlsを合わせてコード化すると以下のようになります。

main.tf
resource "google_access_context_manager_access_level" "access_level" {
  parent = var.access_policy_id
  name   = "${var.access_policy_id}/accessLevels/{ACMポリシー名}"
  title  = "workspace-ip"
  basic {
    conditions {
      ip_subnetworks = ["{IPアドレスCIDR}"]
    }
  }
}

resource "google_access_context_manager_service_perimeter" "service_perimeter_restrict_user" {
  parent                    = var.access_policy_id
  name                      = "${var.access_policy_id}/servicePerimeters/{VPC SCポリシー名}"
  title                     = "{ポリシータイトル}"
  use_explicit_dry_run_spec = true

  spec {
    // 境界を作るサービスを列挙する
    restricted_services = [
      "storage.googleapis.com",
      "bigquery.googleapis.com"
    ]
    // 境界を作るプロジェクト
    resources = [
      "projects/${var.project_no}" // Project IDで指定する
    ]
    vpc_accessible_services {
      enable_restriction = false
    }
    // 内向きポリシー
    // query User
    ingress_policies {
      ingress_from {
        identity_type = "ANY_USER_ACCOUNT"
        sources {
          access_level = google_access_context_manager_access_level.access_level.id
        }
      }
      ingress_to {
        resources = ["*"]
        operations {
          service_name = "*"
        }
      }
    }
    //Developer
    ingress_policies {
      ingress_from {
        identity_type = "IDENTITY_TYPE_UNSPECIFIED"
        identities = [
	//MLは登録できないため注意
          "user:hoge@example.com",
        ]
        sources {
          access_level = "*"
        }
      }
      ingress_to {
        // 境界内のプロジェクトの内、アクセスするプロジェクト
        resources = ["*"]
        operations {
          service_name = "*"
        }
      }
    }
    //SAからのアクセス許可
    ingress_policies {
      ingress_from {
        identity_type = "ANY_SERVICE_ACCOUNT"
        sources {
          access_level = "*"
        }
      }
      ingress_to {
        // 境界内のプロジェクトの内、アクセスするプロジェクト
        resources = ["*"]
        // 許可する操作
        operations {
          service_name = "*"
        }
      }
    }
    //cloud build向けの許可するSA
    ingress_policies {
      ingress_from {
        identity_type = "IDENTITY_TYPE_UNSPECIFIED"
        identities = [
          "user:service-${var.project_no}@gcf-admin-robot.iam.gserviceaccount.com",
          "user:${var.project_no}@cloudbuild.gserviceaccount.com"
        ]
        sources {
          access_level = "*"
        }
      }
      ingress_to {
        // 境界内のプロジェクトの内、アクセスするプロジェクト
        resources = ["*"]
        // 許可する操作
        operations {
          service_name = "*"
        }
      }
    }
    egress_policies {
      egress_from {
        identity_type = "ANY_IDENTITY"
      }
      egress_to {
        resources = ["*"]
        operations {
          service_name = "*"
        }
      }
    }
  }
}
  • google_access_context_manager_service_perimeterのブロックにおいて、use_explicit_dry_run_spec = trueを指定しspec{}の部分にルールを記述することでドライランになります。

ドライランでのエラーキャッチ

ドライランにて、VPC SCを定義した後ブロックされたリクエストをログからキャッチすることができます。
方法は以下の公式ドキュメントの通りとなっており
https://cloud.google.com/vpc-service-controls/docs/manage-dry-run-configurations?hl=ja#identifying_blocked_requests

  1. Google Cloudのコンソールよりロギング→ログエクスプローラーを選択
  2. クエリフィールドに以下のクエリを記述、検索時刻範囲を指定しクエリを実行
log_id("cloudaudit.googleapis.com/policy") AND severity="error" AND protoPayload.metadata.dryRun="true"

上記のクエリの結果を確認し、チャッチすべきでない操作が含まれていたら再度VPC SCのルール設定を見直すといった流れになります。

まとめ

  • VPC SCを用いたサービス境界の作り方とIPによるアクセス制限を行いました。
  • 一回で思いどおりの設定は行えないため、ドライランを用いてログを見つけてはキャッチしてほしくない操作を除外するルールを書くといった開発になるため、地道な検証作業が必要です

参考ページ

https://cloud.google.com/vpc-service-controls/docs/overview?hl=ja
https://blog.g-gen.co.jp/entry/vpc-service-controls-explained
https://cloud.google.com/access-context-manager/docs/overview?hl=ja
https://cloud.google.com/vpc-service-controls/docs/ingress-egress-rules?hl=ja#unsupported-features

D2C m-tech

Discussion