Terraform で GCP の組織設定・共有 VPC・VPN 作成

## 内容 
これまで GCP を手動構築していた範囲を、Terraform で IaC 化したので、その内容を記載する

#### これまで手動(Webコンソール, gcloud, etc.)でやっていた内容 (Terraform コード化対象)

https://qiita.com/suzuyui/items/947867f52897417ee31b

https://qiita.com/suzuyui/items/562a0a05ee6d424478c1

https://qiita.com/suzuyui/items/c38a7c694ce0e7a6133c

https://qiita.com/suzuyui/items/b69be2a50df9cbedf402

## 構築内容

前述の「これまでやっていた内容 (Terraform コード化対象)」のリンク先内容を実施する
内容は下記で、記載済み内容については説明を省略し、追加するものは補足を記載している箇所がある

- 組織の作成と共有 VPC 構築
    - ホストプロジェクト作成
    - サービスプロジェクト作成
    - 共有 VPC 
    - Subnet の設定
    - [追加] 組織のポリシー追加
    - [追加] 権限の追加
- HA VPN
    - 自宅ラボ向けに HA VPN を構築する
    - 自宅ラボ向けに設定Config例を出力する (EdgeRouer向け)
- VPC Service Controls
    - サービス境界の作成
- 限定公開 Google アクセス
    - googleapis.com 向けの Private 向け DNS 設定

### GCP リソース構成
組織の中にフォルダ(部署に当たる)を作成して、プロジェクトを所属構成にしている (記載方法[参照](https://cloud.google.com/iam/docs/overview?hl=ja#resource-hierarchy))
terraform 用に管理プロジェクトを組織直に構築している (参考：https://cloud.google.com/community/tutorials/managing-gcp-projects-with-terraform)

![スクリーンショット 2021-03-27 12.36.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/b6431907-fc45-bd56-32a0-8a9a2b67a9ee.png)



プロジェクト名については、[エンタープライズ向けガイドのプロジェクト名称](https://cloud.google.com/docs/enterprise/best-practices-for-enterprise-organizations#project-structure)を参照に、
プラスして自分のIDを追加して他と被らないようにした (IDはユニークである必要がある)
- プロジェクト名称(ID): [組織名]-[サービス名]-[環境名]


### Terraform ディレクトリ構成
正解がわからなかったため、単純にファイル分割とした。(module 化などは後ほど検討したい.デファクトスタンダードはなさそう)
事前作業・Terraform 用プロジェクト作成については、別フォルダ (prestage) にした
すべてを Qiita に書くと長くなったので、省略記載して、全体は GitHub に公開した (URL:https://github.com/suzuyu/terraform-public)
※`backend.tf` は prestage/ 実行での出力に沿って作成する(GitHubにはない)

```
.
├── README.md
├── backend.tf
├── cloudnat.tf
├── dns.tf
├── firewall.tf
├── host.tf
├── main.tf
├── organization.tf
├── prestage
│   ├── main.tf
│   ├── output.tf
│   ├── variables.tf
├── service1.tf
├── subnet.tf
├── terraform_pj
├── variables.tf
├── vpc_service_controls.tf
└── vpn.tf

```

リソース構成に合わせるとファイルは下記のように分割している
![スクリーンショット 2021-03-27 12.36.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/6cdaceb0-4b31-ddb0-7583-2aca27dd7fa9.png)


以降、各ファイルごとに構築・記載内容を説明する

## 構築
下記順番で実施している 

1. 前準備 / ドメイン取得・CloudIdentity有効化(手動)
2. terraform 管理プロジェクトの作成 (prestage/)
3. 組織の設定 (organaization.tf)
4. ホストプロジェクトの作成 (host.tf)
5. サービスプロジェクトの作成 (service1.tf)
6. サブネットの作成 (subnet.tf)
7. ファイアウォールの設定 (firewall.tf)
8. DNS の設定(限定公開 Google アクセス) (dns.tf)
9. HA VPN の作成 (vpn.tf)
10. インターネット向け NAT の設定 (cloudnat.tf)
11. VPC Service Controls の作成 (vpc_service_controls.tf)

### 1. 前準備 / ドメイン取得・CloudIdentity有効化(手動)
ドメイン取得と組織の有効化までは、手動で実施

- ドメイン取得
    - https://qiita.com/suzuyui/items/947867f52897417ee31b#1-%E3%83%89%E3%83%A1%E3%82%A4%E3%83%B3%E5%8F%96%E5%BE%97
- 組織の作成 (Cloud Identity, 組織の作成までを完了させる)
    - https://qiita.com/suzuyui/items/947867f52897417ee31b#2-%E7%B5%84%E7%B9%94%E3%81%AE%E4%BD%9C%E6%88%90

下記のようにIDと組織の登録画面まで終わらせる
![スクリーンショット 2021-03-21 21.19.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/69b4cf35-be53-ae21-8fd6-cb4aff191440.png)


作業環境に、terraform のインストールと gcloud のインストールを実施しておく

インストール方法：https://qiita.com/suzuyui/items/231b39a60ff545032bc6#terraform-%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB

```
% terraform version
Terraform v0.14.6
```

```
% gcloud --version
Google Cloud SDK 327.0.0
alpha 2021.02.05
beta 2021.02.05
bq 2.0.64
core 2021.02.05
gsutil 4.58
```

以降、terraform での構築を進める

### 2. terraform 管理プロジェクトの作成 (prestage/)

Google Cloud の Community にグーグル社員が書いた[チュートリアル](https://cloud.google.com/community/tutorials/managing-gcp-projects-with-terraform)を参考に、Terraform 管理プロジェクトを作成する
チュートリアルでは手動で実施している箇所を `prestage/` でterraformのコードにして実施する (手動でも良かったかもしれない)
Terraform 用のサービスアカウントは未作成のため、gcloud の認証情報で gcp へアクセスすることになる

Create the Terraform Admin Project
：https://cloud.google.com/community/tutorials/managing-gcp-projects-with-terraform

下記、上記URL の中で参照しコード化した内容

- Create the Terraform Admin Project
    - terraform admin project の作成
    - 組織配下の指定をする
- Create the Terraform service account
    - terraform を動かすための service account を作成する
    - prestage は gcloud の認証アカウントで動かすが、今後サーバ等で動かすことが可能になる
    - service account の認証キー (json) はファイルとして出力し、その後の必要コマンドは output で出力する
    - terraform で必要な API を有効化する
- Add organization/folder-level permissions
    - terraform service account にプロジェクト作成権限と課金ユーザ権限などを付与する
- terraform state の保存先ストレージの作成 / Set up remote state in Cloud Storage
    - terraform の backend にする Cloud Storage を作成する
    - backend 設定の記載方法は output で出力する

```tree
├── prestage
│   ├── main.tf
│   ├── output.tf
│   ├── variables.tf
```

https://github.com/suzuyu/terraform-public/tree/main/prestage

変数として、下記を準備する
コメントに書いてあるが、`xxx`でマスクしてあるところを変更する

```variables.tf
# org_id (下記で出力される "ID" を "org_id" の値にする)
## gcloud organizations list
# billing_account (下記で出力される "ACCOUNT_ID" を "billing_account" の値にする)
## gcloud beta billing accounts list
# gcp-terraform-admin@[xxx.xxx]
## 組織で使用するドメイン(xxx.xxx)の Cloud Identity で事前に Terraform 管理ユーザグループのアカウントグループを作成しておく(同じ権限にして切り分けよう)
# org_name
## 組織の識別子、プロジェクトの命名に必要なだけで何でも良い

variable "gcp_common" {
  type = object({
    org_name        = string
    org_id          = string
    billing_account = string
  })
  default = {
    org_name        = "xxxxxx"
    org_id          = "xxxxxxxxxxxx"
    billing_account = "xxxxxx-xxxxxx-xxxxxx"
  }
}

variable "admin_user_group" {
  type = object({
    email = string
  })
  default = {
    email = "gcp-terraform-admin@[xxx.xxx]"
  }
}

variable "terraform_pj" {
  type = object({
    identity_name = string
  })
  default = {
    identity_name = "terraformadmin"
  }
}
```

terraform での実行内容は下記`main.tf`の通り
コメントに書いてあるが、事前作業として書きを実施する(実施コマンドはコメント参照)

- 課金アカウントへ権限を付与する
    - 課金アカウント(`variables.tf`で`billing_account`)へ今回 gcloud を実施する組織管理者アカウントへ権限を付与する
- 組織管理者アカウントで gcloud 設定をする
- 組織ポリシーを操作できるように権限を付与する
- workspace の名前で環境名としてプロジェクトに名称付与するので、terraform workspace を切り替える
    - workspace　を使ったほうがいいかは悩みどころだが試しに使用してみている

```main.tf
## 内容
# Terraform 用のプロジェクトを作成する
# 参照 https://cloud.google.com/community/tutorials/managing-gcp-projects-with-terraform
## 前提
# 組織を作成済み
# 課金アカウント作成済み
# 管理ユーザグループを admin.google で作成済み
## 組織管理者が課金アカウントへ権限付与できるようにする
# gcloud config set account [課金アカウントのアドミンアカウント]
# gcloud beta billing accounts list
# gcloud beta billing accounts add-iam-policy-binding [var.gcp_common.billing_account] --member=user:[org admin user account] --role roles/billing.admin
## 組織管理者のアカウントで gcloud コマンドを利用できるようにする
# gcloud auth login [org admin user account]
# gcloud config set account [org admin user account]
# gcloud auth application-default login
## 組織管理者が組織ポリシーを編集できるようにする
# gcloud organizations list
# gcloud organizations add-iam-policy-binding [var.gcp_common.org_id] --member=user:[org admin user account] --role=roles/orgpolicy.policyAdmin
## workspace を "dev", "prd" などにする
# terraform workspace new dev

provider "google" {}

# Terraform Project 作成
resource "google_project" "terraform" {
  name                = join("-", [var.gcp_common.org_name, var.terraform_pj.identity_name, terraform.workspace])
  project_id          = join("-", [var.gcp_common.org_name, var.terraform_pj.identity_name, terraform.workspace])
  org_id              = var.gcp_common.org_id
  billing_account     = var.gcp_common.billing_account
  auto_create_network = false
}

# Sevice API 有効化 (google_project と同じ terraform で実施が必須)
resource "google_project_service" "terraform" {
  project                    = google_project.terraform.id
  disable_dependent_services = true

  for_each = toset([
    "cloudresourcemanager.googleapis.com",
    "serviceusage.googleapis.com",
    "cloudidentity.googleapis.com",
    "cloudbilling.googleapis.com",
    "iam.googleapis.com",
    "compute.googleapis.com",
    "container.googleapis.com",
    "accesscontextmanager.googleapis.com", # VPC Service Controls に必要
  ])
  service = each.value

  depends_on = [
    google_project.terraform,
  ]
}

# Terraform サービスアカウントの作成
resource "google_service_account" "terraform" {
  account_id   = "terraform"
  display_name = "Terraform IaC Account"
  project      = google_project.terraform.project_id

  depends_on = [
    google_project.terraform,
  ]
}

# Terraform へホストプロジェクトの閲覧ロールを付与
resource "google_project_iam_binding" "storage_serviceusage" {
  project = google_project.terraform.project_id
  for_each = toset([
    "roles/storage.admin",
    "roles/serviceusage.serviceUsageAdmin",
  ])

  role = each.value

  members = [
    join(":", ["serviceAccount", google_service_account.terraform.email]),
    join(":", ["group", var.admin_user_group.email]),
  ]

  depends_on = [
    google_service_account.terraform,
  ]
}

# Terraform へホストプロジェクトの閲覧ロールを付与
resource "google_project_iam_binding" "viewer" {
  project = google_project.terraform.project_id
  for_each = toset([
    "roles/viewer",
  ])

  role = each.value

  members = [
    join(":", ["serviceAccount", google_service_account.terraform.email]),
  ]

  depends_on = [
    google_service_account.terraform,
  ]
}

# Terraform へホストプロジェクトの編集ロールを付与
resource "google_project_iam_binding" "editor" {
  project = google_project.terraform.project_id
  for_each = toset([
    "roles/editor",
  ])

  role = each.value

  members = [
    join(":", ["group", var.admin_user_group.email]),
  ]

  depends_on = [
    google_service_account.terraform,
  ]
}

# Terraform へ組織内のプロジェクト作成権限を付与
resource "google_organization_iam_binding" "terraform" {
  org_id = google_project.terraform.org_id
  for_each = toset([
    "roles/resourcemanager.projectCreator",
    #    "roles/billing.projectManager",
    "roles/billing.user",
    "roles/compute.xpnAdmin",
    "roles/resourcemanager.projectIamAdmin",
    "roles/resourcemanager.organizationAdmin",
    "roles/orgpolicy.policyAdmin",
    "roles/resourcemanager.folderAdmin",
    "roles/accesscontextmanager.policyAdmin", # VPC SC 時に必要
  ])
  role = each.value

  members = [
    join(":", ["serviceAccount", google_service_account.terraform.email]),
    #    join(":", ["user", var.admin_user.email]),
    join(":", ["group", var.admin_user_group.email])
  ]

  depends_on = [
    google_project.terraform,
    google_service_account.terraform,
  ]
}

# Terraform へ課金アカウントの利用権限を付与
resource "google_billing_account_iam_binding" "user" {
  billing_account_id = google_project.terraform.billing_account
  role               = "roles/billing.user"
  members = [
    join(":", ["serviceAccount", google_service_account.terraform.email]),
    #    join(":", ["user", var.admin_user.email]),
    join(":", ["group", var.admin_user_group.email])
  ]

  depends_on = [
    google_project.terraform,
  ]
}

# Terraform のステートファイル置き場の作成
resource "google_storage_bucket" "terraform" {
  name          = join("-", [google_project.terraform.project_id, "terraform-backet"])
  project       = google_project.terraform.project_id
  location      = "US"
  force_destroy = true
  storage_class = "STANDARD"

  lifecycle_rule {
    condition {
      num_newer_versions = 5
    }
    action {
      type = "Delete"
    }
  }

  depends_on = [
    google_project.terraform,
  ]
}

```

下記を出力するようにしている

- 作成したterraform サービスアカウント名
- backend.tf の作成コマンド
- terraform 用のサービスアカウントのキーファイル作成コマンド、移動コマンド

```output.tf
output "A001_Terraform_Service_Account" {
  value       = google_service_account.terraform.email
  description = "Terraform Account"
}

output "A002_GCP_BACKEND" {
  value = join("\n", [
    "cat > backend.tf << EOF",
    "terraform {",
    "  backend \"gcs\" {",
    "    bucket = \"${google_storage_bucket.terraform.name}\"",
    "    prefix = \"terraform/state\"",
    "  }",
    "}",
    "EOF",
    "mv backend.tf ../",
  ])
}

output "A003_Next_Commands" {
  value       = join("", ["gcloud iam service-accounts keys create terraform_serviceacoount_credential.json --iam-account ", google_service_account.terraform.email, ";cp terraform_serviceacoount_credential.json ../;cd ../"])
  description = "Next"
}
```


#### 実行
下記コマンドで prestage 内の terraform を実行する

```
cd prestage
terraform workspace new dev
terraform apply
```

上記実行後`Outputs:`で出力される情報を参考に、`backend.tf`の作成と、`terraformのクレデンシャルjsonファイル`の作成を実施する

以上で、terraform を実行する前の事前準備が完了 ([参照先](https://cloud.google.com/community/tutorials/managing-gcp-projects-with-terraform)のterraterm実行までの手動設定箇所)



### 3. 組織の設定 (organaization.tf)

ここでは、組織全般の設定やユーザグループへの権限付与、１階層目のフォルダを作成する

![スクリーンショット 2021-03-21 23.42.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/5fe080cd-631b-19a1-8582-744be6e2d5ac.png)

[以前の組織・共有 VPC 手動作成](https://qiita.com/suzuyui/items/947867f52897417ee31b)では下記の管理ユーザグループ・組織ポリシー・フォルダ管理を実施してなかったので、ここに内容を記載する

#### 管理ユーザグループ
管理するグループは[エンタープライズ向けガイドを参照](https://cloud.google.com/docs/enterprise/best-practices-for-enterprise-organizations#groups-and-service-accounts)している
個人的に作成してて試せればいいので今回は抜粋して２つのグループで設定する(サービスプロジェクトは別でグループを作成する)
CloudIdentity の設定は、今回 Terraform の対象にしていないので、**事前にグループを作成しておく必要がある**

| グループ | 内容 |
|:-:|:-:|
| gcp-organization-admins | 組織の設定を管理する |
| gcp-network-admins | ネットワークを管理する。共有 VPC など管理する |

terraform サービスアカウントも同じ操作が必要なため、同じ権限を与える

また、ドメイン全体に組織とフォルダの閲覧権限を与えている (フォルダ構成などはドメインユーザはみんな見えるようにしている)

#### 組織ポリシー
以前作成時は設定しなかったが、サービスプロジェクトを作成するときなどに、共有 VPC 以外のネットワークをデフォルトで作成させないように、
プロジェクト作成時にデフォルトでネットワークを作らないようなポリシーを設定する

`compute.skipDefaultNetworkCreation`を有効化する (下記は Web コンソールで見た対象ポリシー)

![スクリーンショット 2021-03-21 23.36.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/9927c871-0f08-323c-bd67-d5041c82f92b.png)


#### フォルダ
ホストプロジェクトを担当する部署を`infrastructure`、
サービスプロジェクトを担当する部署を`service`という名前でフォルダを作成する




#### orgnaization.tf

変数は下記を利用する
※今後追加してもまとめられやすいようにtype を obuject にしているが行数増えてしまった…
`xxx`でマスクしているところは、**2. terraform 管理プロジェクトの作成 (prestage/)**同様に実施環境にあった内容を埋める
`terraform-service-accounts`は**2. terraform 管理プロジェクトの作成 (prestage/)**で最後に`Output: `で出力されたアカウントを入れる
region / zone はリソースを作成する際にデフォルトで使用するものを記載する (ここでは無料枠が適用されやすいように`us-west1`にしている)

```tf:使用する変数[organaization利用抜粋]
variable "gcp_common" {
  type = object({
    org_name        = string
    org_id          = string
    billing_account = string
    region          = string
    zone            = string
  })
  default = {
    org_name        = "xxxxxx"
    org_id          = "xxxxxxxxxxxx"
    billing_account = "xxxxxx-xxxxxx-xxxxxx"
    region          = "us-west1"
    zone            = "us-west1-b"
  }

  validation {
    condition     = (length(regexall(var.gcp_common.region, var.gcp_common.zone)) > 0)
    error_message = "Zone must be in region."
  }
}

variable "terraform-service-accounts" {
  type    = string
  default = "terraform@[terraform管理プロジェクト名].iam.gserviceaccount.com"
}

variable "organization_admin_group" {
  type = object({
    email = string
  })
  default = {
    email = "gcp-organization-admin@[xxx.xxx:ドメイン名]"
  }
}

variable "network_admin_group" {
  type = object({
    email = string
  })
  default = {
    email = "gcp-network-admin@[xxx.xxx:ドメイン名]"
  }
}

variable "domain" {
  type    = string
  default = "[xxx.xxx:ドメイン名]"
}
```

メインコードは下記の通り

```organaization.tf
## 組織ポリシー
### デフォルトネットワーク作成の無効化
resource "google_organization_policy" "skipDefaultNetworkCreation" {
  org_id     = var.gcp_common.org_id
  constraint = "compute.skipDefaultNetworkCreation"

  boolean_policy {
    enforced = true
  }
}

# ドメインユーザに組織・フォルダ構成の閲覧権限付与
resource "google_organization_iam_binding" "organization_domain_viewer" {
  org_id = var.gcp_common.org_id
  for_each = toset([
    "roles/resourcemanager.organizationViewer",
    "roles/resourcemanager.folderViewer",
  ])
  role = each.value

  members = [
    join(":", ["domain", var.domain]),
  ]
}

# 組織管理者への管理権限付与
resource "google_organization_iam_binding" "organization_org_admin" {
  org_id = var.gcp_common.org_id
  for_each = toset([
    "roles/resourcemanager.organizationAdmin",
    "roles/billing.admin",
    "roles/resourcemanager.folderAdmin",
    "roles/resourcemanager.projectCreator",
    "roles/iam.organizationRoleAdmin",
    "roles/orgpolicy.policyAdmin",            # 組織ポリシー管理者
    "roles/accesscontextmanager.policyAdmin", # VPC SC 時に必要
  ])
  role = each.value

  members = [
    join(":", ["group", var.organization_admin_group.email]),
    join(":", ["serviceAccount", var.terraform-service-accounts]),
  ]

  # 削除すると管理者が削除されてしまうので偶発的な破壊を防ぐ
  # 全体を削除する場合は、管理系を手動で逃してあげる必要がある
  lifecycle {
    prevent_destroy = true
    # ignore_changes = all
  }
}

# ネットワーク管理者への共有VPC等の権限付与
resource "google_organization_iam_binding" "organization_network_admin" {
  org_id = var.gcp_common.org_id
  for_each = toset([
    "roles/compute.networkAdmin",
    "roles/compute.xpnAdmin",
    "roles/compute.securityAdmin",
  ])
  role = each.value

  members = [
    join(":", ["group", var.network_admin_group.email]),
    join(":", ["serviceAccount", var.terraform-service-accounts]),
  ]
}

# インフラ向けフォルダ
resource "google_folder" "organization_infrastructure_folder" {
  display_name = "infrastructure"
  parent       = join("/", ["organizations", var.gcp_common.org_id])

  depends_on = [
    google_organization_policy.skipDefaultNetworkCreation,
  ]
}

# サービス向けフォルダ
resource "google_folder" "organization_service_folder" {
  display_name = "service"
  parent       = join("/", ["organizations", var.gcp_common.org_id])

  depends_on = [
    google_organization_policy.skipDefaultNetworkCreation,
  ]
}

```

以降、すべてコードを記載していると長くなってきたので、特記箇所のみ記載して、特記以外は GitHub 参照とする (各章にコード参照リンクを載せている)

### 4. ホストプロジェクトの作成 (host.tf)

https://github.com/suzuyu/terraform-public/blob/main/host.tf

ホストプロジェクの作成や API, IAM 設定を実施している
`variables.tf` の `host_project_admin_group`の`email`にホストプロジェクトの管理グループのアドレスを入れる
共有 VPC のホスト有効化には`google_compute_shared_vpc_host_project`を使用する

```tf:共有VPC箇所抜粋
resource "google_compute_shared_vpc_host_project" "host" {
  project = google_project.host_project.name

  depends_on = [
    google_project.host_project,
    google_project_service.host_api_enable,
  ]
}
```

### 5. サービスプロジェクトの作成 (service1.tf)

https://github.com/suzuyu/terraform-public/blob/main/service1.tf

サービスプロジェクトの作成や API, IAM 設定を実施している
`variables.tf` の `service1_project_admin_group`の`email`にサービスプロジェクトの管理グループのアドレスを入れる
サービスプロジェクトは`google_compute_shared_vpc_service_project`でhost_projectを指定して接続する
また、GCE などのためにサービスアカウントも２つ、Public 用と、Private 用に作成した
これは、Firewall で区別をできるようにするためにしている

```tf:サービスプロジェクト抜粋
# サービスプロジェクト設定
resource "google_compute_shared_vpc_service_project" "service1" {
  host_project    = google_project.host_project.name
  service_project = google_project.service1.name

  depends_on = [
    google_project_service.service1_api_enable,
    google_compute_shared_vpc_host_project.host,
  ]
}

# サービスアカウント
## Public 向け
resource "google_service_account" "service1_public_account" {
  account_id   = "service1-public-account-id"
  display_name = "Service1 Public Account"
  project      = google_project.service1.name
}
## Private 向け
resource "google_service_account" "service1_private_account" {
  account_id   = "service1-private-account-id"
  display_name = "Service1 Private Account"
  project      = google_project.service1.name
}
```

### 6. サブネットの作成 (subnet.tf)

https://github.com/suzuyu/terraform-public/blob/main/subnet.tf

サブネットの作成を`google_compute_subnetwork `で実施する
アドレスは`172.18.0.0/24`を例として設定している (運用によって `variables.tf`や `tfvars` にするのもいいと思われる)

サービスプロジェクトで共有 VPC のサブネットを利用できるように、
`google_compute_subnetwork_iam_binding `でサービスプロジェクトユーザグループにネットワークユーザ権限を付与している
※ホストプロジェクトでサブネット全体に付与も可能だが、ここではサブネットごとの権限管理にしている

```tf:ネットワークユーザ権限付与抜粋
resource "google_compute_subnetwork_iam_binding" "service1-gce-subnets" {
  project    = google_compute_subnetwork.service1-gce-subnets.project
  region     = google_compute_subnetwork.service1-gce-subnets.region
  subnetwork = google_compute_subnetwork.service1-gce-subnets.name
  role       = "roles/compute.networkUser"
  members = [
    join(":", ["group", var.service1_project_admin_group.email]),
  ]
}
```

### 7. ファイアウォールの設定 (firewall.tf)

https://github.com/suzuyu/terraform-public/blob/main/firewall.tf

ファイアウォール設定を実施する
ファイアウォールルールとしては下記2つを今回のコードでは記載している

| name | 内容 |送信元| 送信先|
|:-:|:-:|:-:|:-:|
| private-private-001 | Privateアドレス内のアクセス許可.ゆるい設定なので環境によって要修正 | 192.168.0.0/16, 172.16.0.0/12, 10.0.0.0/8 | サービス1プロジェクト向け Private/Public サービスアカウント|
| internet-public-001 | インターネットから INGRESS で許可する IP 設定<br>ここでは例として、外部テザリング環境などでログインする際にソフトバンク携帯のアドレスとして公開されている[サイト] (https://www.support.softbankmobile.co.jp/partner/home_tech1/index.cfm) [iphone/ipadはこちらでした(追記)](https://www.support.softbankmobile.co.jp/partner_st/home_tech1/ios/index.cfm) から抜粋したアドレスを許可している | 許可するグローバルアドレスリスト([参照](https://www.support.softbankmobile.co.jp/partner/home_tech1/index.cfm)) [iphone/ipadはこちらでした(追記)](https://www.support.softbankmobile.co.jp/partner_st/home_tech1/ios/index.cfm) | サービス1プロジェクト向け Public サービスアカウント |

![スクリーンショット 2021-03-22 0.15.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/20bcdd42-7622-3a39-e1d0-f578ba34f13d.png)


```tf:firewall抜粋
resource "google_compute_firewall" "private-permit" {
  name        = "private-private-001"
  description = "Private Subnet Permit"
  network     = google_compute_network.host_sharedvpc.name
  priority    = 1000
  direction   = "INGRESS"
  project     = google_project.host_project.name

  source_ranges = ["192.168.0.0/16", "172.16.0.0/12", "10.0.0.0/8"]
  target_service_accounts = [
    google_service_account.service1_public_account.email,
    google_service_account.service1_private_account.email,
  ]


  allow {
    protocol = "icmp"
  }

  allow {
    protocol = "tcp"
    ports    = ["22"]
  }

  depends_on = [
    google_compute_network.host_sharedvpc,
    google_project_service.host_api_enable,
    google_service_account.service1_public_account,
    google_service_account.service1_private_account,
  ]

}

resource "google_compute_firewall" "softbank-mobile" {
  name        = "internet-public-001"
  description = "Softbank Moible Global Address Permit"
  network     = google_compute_network.host_sharedvpc.name
  priority    = 1000
  direction   = "INGRESS"
  project     = google_project.host_project.name
  # https://www.support.softbankmobile.co.jp/partner/home_tech1/index.cfm
  # 2021.03.20 時点
  source_ranges = [
    "123.108.237.128/28",
  #...(省略)...
  ]
  target_service_accounts = [
    google_service_account.service1_public_account.email,
  ]

  allow {
    protocol = "tcp"
    ports    = ["22"]
  }

  depends_on = [
    google_compute_network.host_sharedvpc,
    google_project_service.host_api_enable,
    google_service_account.service1_public_account,
    google_service_account.service1_private_account,
  ]
}
```

### 8. DNS の設定(限定公開 Google アクセス) (dns.tf)

https://github.com/suzuyu/terraform-public/blob/main/dns.tf

限定公開 Google アクセス用に DNS を設定する
設定内容は基本的には[ドキュメント](https://cloud.google.com/vpc/docs/configure-private-google-access?hl=ja)通り
追加として(ドキュメントにも補足説明がある)、gcr.ioへの設定も合わせて実施している
また、`cloudbilling`APIは restricted でのアクセスに対応していないため、private でのアクセスを利用できるように設定をしている

```tf:cloudbilling箇所抜粋
# cloudbilling へのアクセスがrestricted に未対応のため
resource "google_dns_record_set" "googleapis_cname2" {
  name         = "cloudbilling.googleapis.com."
  managed_zone = google_dns_managed_zone.googleapis.name
  type         = "CNAME"
  ttl          = 300
  rrdatas      = ["private.googleapis.com."]
  project      = google_project.host_project.name
  depends_on = [
    google_dns_managed_zone.googleapis,
    google_dns_record_set.googleapis_cname,
  ]
}
```

### 9. HA VPN の作成 (vpn.tf)

https://github.com/suzuyu/terraform-public/blob/main/vpn.tf

下記で記載した内容とほぼ同じ構成・パラメータで作成している (詳細は下記参照)

https://qiita.com/suzuyui/items/562a0a05ee6d424478c1#%E6%A7%8B%E7%AF%89

`Output: `で上記qiitaで書いたような EdgeRouter Config 例を、HA VPN のグローバルIPなどを反映した形で出力するようにしている

変数は下記を利用するので、環境に合わせて更新する

```tf:HA_VPN_変数(variables.tf抜粋)
variable "vpn" {
  type = object({
    peer_global_ip_address  = string
    peer_private_ip_address = string
    shared_secret           = string
    peer_asn                = number
    asn                     = number
  })
  default = {
    peer_global_ip_address  = "自宅ラボ側グローバルIP"
    peer_private_ip_address = "自宅ラボ側ルータ プライベートIP"
    shared_secret           = "シークレット(右コメントで生成)" # openssl rand -base64 24
    peer_asn                = 65001
    asn                     = 65101
  }
}
```

### 10. インターネット向け NAT の設定 (cloudnat.tf)

https://github.com/suzuyu/terraform-public/blob/main/cloudnat.tf

プライベートIPのみのインスタンスがインターネットと通信可能な用に、CloudNATを設定している

### 11. VPC Service Controls の作成 (vpc_service_controls.tf)

https://github.com/suzuyu/terraform-public/blob/main/vpc_service_controls.tf

VPC Service Controls について下記にまとめている

https://qiita.com/suzuyui/items/c38a7c694ce0e7a6133c

今回は試しとして、`storage.googleapis.com`をホスト・サーピスプロジェクトを境界線の内側に保護するようにしている

```tf:VPC_SC_設定例
resource "google_access_context_manager_access_policy" "access-policy" {
  parent = "organizations/${var.gcp_common.org_id}"
  title  = "my policy"

  depends_on = [
    google_organization_iam_binding.organization_org_admin,
  ]
}

resource "google_access_context_manager_service_perimeters" "service-perimeter" {
  parent = "accessPolicies/${google_access_context_manager_access_policy.access-policy.name}"

  service_perimeters {
    name           = "accessPolicies/${google_access_context_manager_access_policy.access-policy.name}/servicePerimeters/Perimeter1"
    title          = "Service Production Perimeter1"
    perimeter_type = "PERIMETER_TYPE_REGULAR"
    status {
      restricted_services = ["storage.googleapis.com"]
      resources = [
        "projects/${google_project.host_project.number}",
        "projects/${google_project.service1.number}",
      ]
    }
  }
}
```

以上で今回準備したコードの説明は終わり

実行は下記コマンドで実施する(環境を`dev`とする場合)

```
terraform workspace new dev
terraform apply
```

## おわりに
いままで手動で実施していた GCP 構築を Terraform で IaC 化することができた
※途中でまとめる時間が足りなくなって雑な記載になっているので、後ほど清書したい…

Terraform はドキュメントが整理されて例もわかりやすいので、基本はドキュメントを読めば大体使い方がわかった
(ドキュメント(provider google 箇所)：https://registry.terraform.io/providers/hashicorp/google/latest/docs)

Terraform 初心者のため、良い記載方法や良いディレクトリ構成がわかれば修正をしていきたい

今後は、Google Cloud 向けの module が公開されているのでそちらの利用も検討したい
(Terraform modules for Google Cloud: https://github.com/terraform-google-modules)
(Terroform サイトにも説明あり： https://registry.terraform.io/providers/hashicorp/google/latest)

下記実施してて個人的に詰まったポイントを共有する

### 実施して困った Tips
#### IAMの状態反映
組織の IAM はデフォルトで、ドメインに`請求先アカウント作成者`と`プロジェクト作成者`が設定されている(下記デフォルト状態例)
![スクリーンショット 2021-02-22 22.27.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/ffe37754-ee8f-59bb-0592-2caf35a2ed81.png)

Terraform での Role 設定は、設定する Role の権限を持っているユーザを記載状態に維持するように設定するため、
記載していない既存のユーザ設定がある場合、消してしまう
よって、例えば`組織の管理者`を terraform で設定してから、`terraform destroy`をすると設定がデフォルトに戻るわけではなくロール所属ユーザ設定が消えるので注意が必要
既存のロールをいじる際は要注意だが、新規なものもデフォルトで作成されるものを、２回目には記載のもののみに修正するのでやはり注意が必要

#### プロジェクト API
プロジェクト作成`google_project `と API の設定`google_project_service `を別の Terraform で実施しようとしたところ、
`google_project`のみだとプロジェクトの API がゼロの状態でプロジェクトが構築される状態のため、`google_project_service`を使用するために必要な API を terraform で有効化できない状態になった
`google_project `と `google_project_service `は同じ terraform で実施する必要がある


## 参考
Creating Google Cloud projects with Terraform
https://cloud.google.com/community/tutorials/managing-gcp-projects-with-terraform

Identity and Access Management > ドキュメント > 概要 > リソース階層
https://cloud.google.com/iam/docs/overview?hl=ja#resource-hierarchy

Best practices for enterprise organizations
https://cloud.google.com/docs/enterprise/best-practices-for-enterprise-organizations