外部 IP を持たない Cloud Build のプライベートプールをインターネットに通信させる方法
クラウドエースの北野です。
外部 IP を割り当てていない Cloud Build のプライベートプールをインターネットにアクセスさせる方法を紹介します。
要約
Cloud Build のプライベートプールから GitHub などインターネットにアクセスする構成は以下の通りです。
- プライベートサービスアクセスによる VPC ネットワークとプライベートプールのネットワーク接続
- カスタムルートのエクスポートの有効
- Compute Engine の NAT 化
- IP 転送の有効化
- OS 内の NAT 設定
- VPC ネットワーク上のプライベートプールの通信を NAT 化した Compute Engine へフォワーディング
- ルートによる通信を Compute Engine へのルーティング
- Firewall によるプライベートサービスアクセスから Compute Engine への通信許可
はじめに
セキュリティの観点から Compute Engine などのコンピュートリソースに外部 IP アドレスを持たせないセキュリティポリシーがあるかと思います。
CI/CD の実行環境にもこのセキュリティポリシーを適用した場合、SaaS の GitHub Actions を使えなくなる可能性があります。
そういったとき、Cloud Build のプライベートプールを使うと簡単にセキュリティ要件を満たせることもあります。
プライベートプールは作成時に外部 IP アドレスの割り当てを選択することができ、割り当てないと外部 IP も持たない環境にすることができます。しかし、外部 IP を持たないと、以下のように GitHub のリポジトリの内容をクローンすることができず、ビルドに失敗します。
本記事では、外部 IP アドレスを持たないプライベートプールをインターネットにアクセスさせる方法を紹介します。
設計
外部 IP アドレスを持たないプライベートプールがインターネットにアクセスさせるために以下のように NAT 化した Compute Engine (以下 GCE と呼ぶ)を構築し、プライベートプールからの通信をインターネットに転送します。
上記の内容を実現するための各 Google Cloud のコンポーネントの設定は以下の通りです。
- Cloud Build プライベートプールの設定
- プライベートネットワークによる VPC ネットワークに接続
- 外部 IP の割り当てをしない
- VPC ネットワークの設定
- プライベートサービスアクセスによる接続サービスプロデューサー Google Cloud Platform とのプライベート接続の作成
- ルートによるすべての通信を NAT 化した GCE の へのフォワーディング
- Firewall によるプライベートプールのから NAT 化した GCE へのアクセス許可
- GCE の設定
- IP 転送の有効化
- OS への NAT 化設定
構築
Terraform を使って、設計内容のシステムを構築していきます。記事の内容を試す場合、紹介する Terraform コードの <>
の部分は、実環境に沿うように書き替えてください。
VPCネットワークの構築
プライベートプールを接続する VPC ネットワークを作成します。
本記事では、以下のアドレス範囲でシステムを構築します。
- NAT 化した GCE を構築するサブネットワークのアドレス範囲: 192.168.0.0/24
- プライベートプールのアドレス範囲: 192.168.1.0/24
resource "google_compute_network" "main" {
name = <VPC Network Name>
project = var.project
auto_create_subnetworks = false
}
resource "google_compute_subnetwork" "main" {
name = <Subnetwork Name>
project = var.project
ip_cidr_range = "192.168.0.0/24"
region = "asia-northeast1"
network = google_compute_network.main.id
}
作成した上記の VPC ネットワークに以下の設定をします。
- プライベートサービスアクセスによる接続サービスプロデューサー Google Cloud Platform とのプライベート接続の作成
- NAT 化した GCE へフォワーディングするルートの作成
- NAT 化した GCE への通信を許可する Firewall の作成
プライベートサービスアクセスの設定
Cloud Build のプライベートプールのネットワークをプライベートネットワークで構築するために、プライベートサービスアクセスを使って接続サービスプロデューサー Google Cloud Platform とのプライベート接続を作成します。このとき、カスタムルートのエクスポートを有効化します。
resource "google_compute_global_address" "main" {
name = <IPAddress Name>
project = var.project
purpose = "VPC_PEERING"
address_type = "INTERNAL"
prefix_length = "24" # プライベートプールの IP アドレス範囲 (192.168.1.0/24)
address = "192.168.1.0" # プライベートプールの IP アドレス範囲 (192.168.1.0/24)
network = google_compute_network.main.id
}
resource "google_service_networking_connection" "main" {
network = google_compute_network.main.id
service = "servicenetworking.googleapis.com"
reserved_peering_ranges = [
google_compute_global_address.main.name
]
}
resource "google_compute_network_peering_routes_config" "main" {
peering = google_service_networking_connection.main.peering
project = var.project
network = google_compute_network.main.name
export_custom_routes = true
import_custom_routes = false
}
ルートの作成
プライベートプールからの通信を NAT 化した GCE にフォワーディングする以下のルートを作成します。
ターゲット | 宛先アドレス | ネクストホップ | プライオリティ |
---|---|---|---|
VPC ネットワーク上のすべてのトラフィック (指定なし) | 0.0.0.0/1 | NAT の GCE インスタンス | 1000 |
VPC ネットワーク上のすべてのトラフィック (指定なし) | 128.0.0.0/1 | NAT の GCE インスタンス | 1000 |
NAT 化した GCE インスタンス (GCE のネットワークタグ) | 0.0.0.0/1 | default-internet-gateway | 10 |
NAT 化した GCE インスタンス (GCE のネットワークタグ) | 128.0.0.0/1 | default-internet-gateway | 10 |
resource "google_compute_route" "all-traffic-to-nat-1" {
name = <NAT Name1>
project = var.project
dest_range = "0.0.0.0/1"
network = google_compute_network.main.name
priority = 1000
next_hop_instance = google_compute_instance.main.id
}
resource "google_compute_route" "all-traffic-to-nat-2" {
name = <NAT Name2>
project = var.project
dest_range = "0.0.0.0/1"
network = google_compute_network.main.name
priority = 1000
next_hop_instance = google_compute_instance.main.id
}
resource "google_compute_route" "nat-to-default-gw-1" {
name = <NAT Name3>
project = var.project
dest_range = "0.0.0.0/1"
network = google_compute_network.main.name
priority = 10
next_hop_gateway = "default-internet-gateway"
tags = ["nat"]
}
resource "google_compute_route" "nat-to-default-gw-2" {
name = <NAT Name4>
project = var.project
dest_range = "128.0.0.0/1"
network = google_compute_network.main.name
priority = 10
next_hop_gateway = "default-internet-gateway"
tags = ["nat"]
}
Firewallの作成
プライベートプール から NAT 化した GCE への通信を許可する Firewall を作成します。
resource "google_compute_network_firewall_policy" "main" {
name = <Firewall Policy Name>
project = var.project
}
resource "google_compute_network_firewall_policy_association" "main" {
name = <Firewall Policy Association Name>
project = var.project
attachment_target = google_compute_network.main.id
firewall_policy = google_compute_network_firewall_policy.main.name
}
resource "google_compute_network_firewall_policy_rule" "main" {
firewall_policy = google_compute_network_firewall_policy.main.name
rule_name = <Firewall Policy Rule Name>
project = var.project
action = "allow"
direction = "INGRESS"
priority = 1000
target_service_accounts = [
google_service_account.gce-nat-gcb.email
]
match {
src_ip_ranges = [
"192.168.1.0/24" # プライベートプールの IP アドレス範囲 (192.168.1.0/24)
]
layer4_configs {
ip_protocol = "all"
ports = []
}
}
}
GCE の作成
NAT 化する GCE インスタンスを作成します。GCE を NAT 化するために以下のように GCE を作成します。
- IP 転送の有効化
- OS 内への NAT 化設定
- IP フォーワードの有効化
- iptables の設定
NAT 化するための GCE の OS 内の設定は、スタートアップスクリプトでおこないます。
resource "google_compute_instance" "main" {
name = <Compute Engine Name>
project = var.project
can_ip_forward = true
machine_type = "e2-micro"
zone = "asia-northeast1-b"
tags = ["nat"]
boot_disk {
initialize_params {
image = "ubuntu-os-cloud/ubuntu-2204-lts"
}
}
network_interface {
subnetwork = google_compute_subnetwork.main.id
access_config {
}
}
service_account {
email = google_service_account.gce-nat-gcb.email
scopes = ["cloud-platform"]
}
metadata = {
startup-script = <<EOT
sysctl -w net.ipv4.ip_forward=1
iptables -t nat -A POSTROUTING -o $(ip addr show scope global | head -1 | awk -F: '{print $2}') -j MASQUERADE
EOT
}
}
resource "google_service_account" "gce-nat-gcb" {
account_id = "gce-nat-gcb"
project = var.project
}
Cloud Build のプライベートプールの作成
プライベートネットワークに上記で作成した VPC ネットワークを指定して、外部 IP アドレスを持たないプライベートプールを作成します。
resource "google_cloudbuild_worker_pool" "main" {
name = <Worker Pool Name>
project = var.project
location = "asia-northeast1"
worker_config {
disk_size_gb = 100
machine_type = "e2-medium"
no_external_ip = true
}
network_config {
peered_network = google_compute_network.main.id
}
depends_on = [
google_service_networking_connection.main
]
}
プライベートプールでのビルド実行
最後に構築したプライベートプールを使って Cloud Build トリガーを実行させてみます。Cloud Build トリガーは以下のコードで作成される terraform plan を実行するトリガーです。
resource "google_cloudbuild_trigger" "main" {
name = <Cloud Build Trigger Name>
project = var.project
location = "asia-northeast1"
service_account = google_service_account.gcb-tf.id
source_to_build {
repository = <Repository ID>
ref = "refs/heads/main"
repo_type = "GITHUB"
}
build {
logs_bucket = <GCS Bucket URL>
step {
name = "hashicorp/terraform"
id = "terraform-init"
entrypoint = "/bin/sh"
args = [
"-c",
"terraform init",
]
}
step {
name = "hashicorp/terraform"
id = "terraform-plan"
entrypoint = "/bin/sh"
env = [
format("TF_VAR_project=%s", var.project)
]
args = [
"-c",
"terraform plan"
]
}
options {
worker_pool = google_cloudbuild_worker_pool.main.id
}
}
}
resource "google_service_account" "gcb-tf" {
account_id = "gcb-tf-pr"
project = var.project
}
resource "google_project_iam_member" "project" {
project = var.project
role = "roles/editor"
member = google_service_account.gcb-tf.member
}
作成したトリガーを実行すると、以下のようにプライベートプールは GitHub にアクセスし、Terraform を実行します。
さいごに
外部 IP アドレスを持たない Cloud Build のプライベートプールを VPC ネットワークに接続してインターネットにアクセスさせる方法を紹介しました。
今回、外部 IP を持つ GCE インスタンスを NAT として構築しましたが、Cloud NAT を使うとこの GCE も外部 IP アドレスを持たせることなく構築できます。また、EGRESS の通信を制御する Firewall を作成すると、プライベートプールからインターネットへの通信も制限できます。
みなさんも Cloud Build のプライベートプールを使ってセキュリティポリシーに沿った CI/CD 環境を構築してみてください。
Discussion