terraformを実行する強い権限をSAにもたせ、権限の借用を用いて最小限の権限のユーザーがterraformを実行できるようにする

#やりたいこと

- terraformは リソースの作成/消去を伴うので 強い権限をもたさないといけない
- そのような強い権限は、リソース操作を定常的に行わければ不要なので、個別ユーザーに直接権限をもたせたくない

そのような要件を実現するための方法になります

.. というのを実現するために 以下の 手順を見つけたのですが、そのままではうまくいかなかったため、
工夫して実用できるようにした、という記事になります

#### 参考
https://medium.com/google-cloud/a-hitchhikers-guide-to-gcp-service-account-impersonation-in-terraform-af98853ebd37


また、 このような方法によらず、本来terraformの実行を人の手を介さないで CI/CDを利用するアプローチのほうが理にかなっているかもしれません


# 用途

VPCServiceControlを設定する場合は SAを通じてteerraform実行が必要なので、
ユーザーからVPC ServiceControlのリソースを操作したい場合は、 このような実行方法を要することになると思います (※未検証)


# 準備

準備作業及び設定までは 強い権限を持ったユーザーで行う必要があります

また、プロジェクトにクォータを追加しないとエラーになるので、予め設定しておきます

```
myuser@mypc  gcloud auth application-default set-quota-project myproject
API [cloudresourcemanager.googleapis.com] not enabled on project 
[548159814557]. Would you like to enable and retry (this will take a 
few minutes)? (y/N)?  y

Enabling service [cloudresourcemanager.googleapis.com] on project [548159814557]...
Operation "operations/acf.p2-511111-111111-111-111-11111111" finished successfully.

Credentials saved to file: [/Users/myuser/.config/gcloud/application_default_credentials.json]

These credentials will be used by any library that requests Application Default Credentials (ADC).

Quota project "myproject" was added to ADC which can be used by Google client libraries for billing and quota. Note that some services may still bill the project owning the resource.
```

ちゃんと設定できていないと以下のように怒られます

```
WARNING: 
Cannot add the project "my-project" to ADC as the quota project because the account in ADC does not have the "serviceusage.services.use" permission on this project. You might receive a "quota_exceeded" or "API not enabled" error. Run $ gcloud auth application-default set-quota-project to add a quota project.
```

ちなみに serviceusage.services.use は **roles/serviceusage.serviceUsageConsumer** に含まれています


### tfstateの用意

backendとして gcs を 簡易的に用意しておきまます
SAではなく ユーザーに backendにアクセスするための権限が必要になります


```
project_id=myproject_id
locale=us-west1
gsutil mb -p ${project_id} -c standard -l ${locale} -b on gs://tfstate00000


gsutil iam ch user:myaccount@mydomain.com:roles/storage.admin gs://${project_id}-tfstate

cat << EOF > life-cycle.json 
{
    "lifecycle": {
        "rule": [
            {
                "action": {
                    "type": "Delete"
                },
                "condition": {
                    "numNewerVersions": 5
                }
            }
        ]
    }
}
EOF

gsutil lifecycle set life-cycle.json gs://tfstate00000
rm life-cycle.json

```

# 設定

端的に terraformでの設定を示します

```
# set backend
terraform {
  backend "gcs" {
    bucket = "tfstate00000"
    prefix = "terraform/state"
  }
}

# prepare provider for token

provider "google" {
  alias = "tokengen"
}

data "google_client_config" "default" {
  provider = google.tokengen
}


# sa settings
resource "google_service_account" "sa-tfexc" {
  provider   = google.tokengen
  account_id = "sa-tfexec"
  project    = var.basic.project
}

# give strong role for SA 
resource "google_project_iam_member" "sa-tcexec-role" {
  provider = google.tokengen
  project  = var.basic.project

  for_each = toset([
    "roles/editor",
  ])
  role   = each.value
  member = "serviceAccount:${google_service_account.sa-tfexc.email}"

  depends_on = [
    google_service_account.sa-tfexc
  ]
}

# my user account
# gives minimum role for initial access to gcs backend
resource "google_project_iam_member" "my-minimum-role" {
  project = var.basic.project

  for_each = toset([
    "roles/serviceusage.serviceUsageConsumer",
    "roles/iam.serviceAccountUser",
    "roles/viewer",
  ])

  role   = each.value
  member = "user:myaccount@mydomain.com"
}

# roles impersonate
resource "google_service_account_iam_member" "sa-tfexec-member" {
  provider = google.tokengen

  service_account_id = google_service_account.sa-tfexc.name
  role = "roles/iam.serviceAccountTokenCreator"
  member = "user:myuser@mydomain.com"
  
  depends_on = [
    google_service_account.sa-tfexc
  ]
}

# myuser@mydomain.com の利用時にコメントを外す
## create token with lifetime for inpersonate user
#data "google_service_account_access_token" "sa" {
#  provider               = google.tokengen
#  target_service_account = google_service_account.sa-tfexc.email
#  lifetime               = "600s"
#  scopes                 = ["cloud-platform"]
#}
#
## set defauilt provider with token
#provider "google" {
#  access_token = data.google_service_account_access_token.sa.access_token
#  project      = var.basic.project
#  region       = var.basic.region
#}

variable "basic" {
  type = map(any)
  default = {
    project = "my_project",
    region  = "us-west1"
    zone    = "us-west1-b"
  }
}

```

最初のリソース作成時は tokenを利用しないのでコメントアウトしていますが、tokenを利用する際にコメントを外します


リソースを作り終えたら、 権限の借用を行うユーザーに切り替えてlogin

```
gcloud auth application-default login
```
### 実行例
```
mypc@myuser gcloud auth application-default login
Your browser has been opened to visit:

    https://accounts.google.com/o/oauth2/auth?response_type=code&client_id=xxx

Credentials saved to file: [/Users/myuser/.config/gcloud/application_default_credentials.json]

These credentials will be used by any library that requests Application Default Credentials (ADC).

Quota project "myproject" was added to ADC which can be used by Google client libraries for billing and quota. Note that some services may still bill the project owning the resource.
```


## 構成検証

provider を２段で構成しており、最初の provider は SA を含む token を発行するために用意する
２つ目の providerは通常のもの (こちららで リソースを作っていく)


検証としてバケットを作ってみます

```
resource "google_storage_bucket" "buket0000" {
  name     = "test-bucket"
  location = "us-west1"
}
```
-> 成功するはずです

tokenは有効期限が切れれば使えなくなるので、terraformを実行するときだけ強い権限を持つということが実現できました。

設定は以上ですが、ここまで気を配ったのであれば 誰が権限の借用を利用したのか、Logを取得し、意図しないアクセスに対してはalrtを上げるなどの設定もしたほうがよいと思います