GoogleCloudの共有VPC通信をネットワークタグで制御してみた

 # やりたいこと
- サービスプロジェクトで外部IPが制限している制約下で、特定のサブネットのみインターネット通信を許可したい
- プロジェクトのサブネット内でのみ通信を許可し、プロジェクト外のサブネット間通信は拒否したい
- VPC内のインスタンスに対してIAP経由でのみsshを許可する

![sharedvpc1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3550845/b796b170-14ca-ad80-894b-49a28df7e67a.png)

 ## VPC
 - awsではVPCでIP範囲を持つが、gcpではvpcにIP範囲を持たない
     そのため、サブネット単位でIP範囲を指定するため、VPCのIP制約なく追加可能
 - awsのVPCは特定のリージョンに存在するが、gcpのvpcはグローバルリソースである
    gcpの場合はリージョン間通信に追加のリソースが必要ない
 

 ## 通信制御
 - awsではセキュリティグループやACLがあるが、GCPではファイアウォールとなる
 - ネットワークタグやIP範囲等を使用してフィルタできるステートレスなルールを設定できる

 # 実装
## 構築するリソース
- Cloud RouterおよびNATの構成（外部IPを持ち、外向きインターネット通信を可能にする）
- 共有VPC（ネットワーク管理をプロジェクトで分離することができる）
- 権限付与（IAP経由でssh可能、共有VPC利用のためサービスプロジェクトでComputeネットワークユーザロール付与） 

## terraform 

※countを使用するのでなく、set, each_forのほうがよさそう（stateファイルに保存される際、配列のキーがインデックスかリソース名の違い。インデックスだと何らかの変更を加えた際に別物だと認識し破壊と再作成の挙動が生じる）
※google_project_iam_bindingの使用は推奨されていない。複数のgoogle_project_iam_bindingを使用するとどれか一つしかプロビジョニングされない

```tf
ホストプロジェクトとして有効化するプロジェクトを指定する
resource "google_compute_shared_vpc_host_project" "hostpj" {
  project = google_project.network_host_project.id
}

#サービスプロジェクトとして有効化するプロジェクトをループして指定する
resource "google_compute_shared_vpc_service_project" "servicepj" {
  count = "var.number_of_project"
  host_project    = google_compute_shared_vpc_host_project.hostpj.project
  service_project = "element(google_project.project, count.index).id}"
}

#共有VPCとするVPCを作成する
resource "google_compute_network" "shared_vpc" {
  project                 = google_project.network_host_project.id 
  name                    = "shnw-prd-asne1-sharedvpc" 
  auto_create_subnetworks = false
  mtu                     = 1460
}

#サービスプロジェクトごとに共有するサブネットをホストプロジェクト側で作成する
resource "google_compute_subnetwork" "shared_subnets" {
  project       = google_project.network_host_project.id
  count         = "var.number_of_project"
  name          = "shnw-prd-asne1-sbn${count.index}" 
  ip_cidr_range = "10.0.${count.index+1}.0/24"
  region        = var.gcp_region
  network       = google_compute_network.shared_vpc.id
}

# サービスプロジェクト側で共有VPCが利用可能なようにComputeネットワークユーザロールを付与する
resource "google_compute_subnetwork_iam_binding" "shared_subnet_iam_binding" {
  count         = "var.number_of_project" 
  project       = google_project.project, count.index).id
  region        = element(google_compute_subnetwork.shared_subnets, count.index).region
  subnetwork    = element(google_compute_subnetwork.shared_subnets, count.index).id
  role   = "roles/compute.networkUser"
  members = [
      "user:XXXXXXXXX",
  ]
}


# CloudNATをホストプロジェクトで定義
resource "google_compute_router_nat" "nat" {
  name                               = "shnw-prd-asne1-cnat"
  project                            = replace(google_project.network_host_project.id 
  region                             = var.gcp_region
  router                             = google_compute_router.router.name
  source_subnetwork_ip_ranges_to_nat = "LIST_OF_SUBNETWORKS"
  nat_ip_allocate_option             = "AUTO_ONLY"

  # 共有VPC用のサブネット分だけループする
  dynamic "subnetwork" {
    for_each = google_compute_subnetwork.shared_subnets
    content {
      name                     = subnetwork.value.id
      source_ip_ranges_to_nat  = ["ALL_IP_RANGES"]
    }
  }

  log_config {
    enable = true
    filter = "ALL"
  }
  depends_on = [google_compute_subnetwork.shared_subnets]
}

# NATを構成するルータの設定
resource "google_compute_router" "router" {
  name    = "router"
  project = google_project.network_host_project.id
  region  = var.gcp_region
  network = google_compute_network.shared_vpc.id
}

# 共有VPCで80,8080ポート通信を許可するようにFirewallを設定する。
resource "google_compute_firewall" "allow_internet" {
  description = "共有VPCから外向けにhttp,https通信を許可します"
  project     = google_project.network_host_project.id
  name        = "shnw-prd-asne1-fwr01"
  network     = google_compute_network.shared_vpc.name
  direction   = "EGRESS"
  priority    = 1000
  allow {
    protocol  = "icmp"
  }
  allow {
    protocol  = "tcp"
    ports     = ["80", "8080"]
  }
}

resource "google_compute_firewall" "allow_ssh" {
  description    = "共有VPCに対するSSH接続を許可します"
  project        = google_project.network_host_project.id 
  name           = "shnw-prd-asne1-fwr02" 
  network        = google_compute_network.shared_vpc.name
  priority       = 1000
  allow {
    protocol     = "tcp"
    ports        = ["22","3389"]
  }
  direction      = "INGRESS"
  source_ranges  = ["35.235.240.0/20"]
}

# ターゲットタグを用いてインスタンスに特定のネットワークタグが付いた通信のみ許可する
resource "google_compute_firewall" "allow_between_only-my-subnets" {
  count          = "var.number_of_project" 
  description    = "共有VPCの自分のプロジェクトのsubnet内で通信を許可します"
  project        = google_project.network_host_project.id 
  name           = "shnw-prd-asne1-fwr-${element(google_project.project, count.index).number}}" 
  network        = google_compute_network.shared_vpc.name
  priority       = 1000
  allow {
    protocol     = "all"
  }
  direction      = "INGRESS"
  source_ranges  = ["10.0.${count.index+1}.0/24"]
  target_tags    = ["${element(google_project.project, count.index).number}"]
}



#サービスプロジェクト側で共有VPC利用のためのIAMロールの付与
resource "google_project_iam_member" "project_iam_binding_networkuser" {
  count      = "var.number_of_project"
  project    = "${element(google_project.project, count.index).id}" 
  role       = "roles/compute.networkUser"
  member     = "user:XXXXXXXX"
  depends_on = [google_compute_subnetwork_iam_binding.shared_subnet_iam_binding]
}
#IAP経由でsshするためのロール付与
resource "google_project_iam_member" "project_iam_binding_iapsshuser" {
  count   　 = "var.number_of_project"
  project    = "${element(google_project.project, count.index).id}" 
  role       = "roles/iap.tunnelResourceAccessor"
  member     = "user:XXXXXXXXXX"
  depends_on = [google_compute_subnetwork_iam_binding.shared_subnet_iam_binding]
}
```