【Workload Identity】 GKE で Google Service Account を利用する正しいやり方

# TL;DR

- GKE から Google Service Account (GSA) を利用するのに GSA のアカウントキーを発行して Secret を作成してはいけない

- Kubernetes Service Account (KSA) と GSA を紐づける Workload Identity を利用して認証を行うべき

# Why?

以下は[公式ドキュメント](https://cloud.google.com/kubernetes-engine/docs/tutorials/authenticating-to-cloud-platform?hl=ja)からの抜粋。
アカウントキーを発行してGCPのプロダクトを利用するのには、

1. キーの漏洩リスク
2. キーの更新（デフォルトの有効期限は10年）

という **Secret を手動で管理、保護する**手間がありました。

>注: Workload Identity は、GKE 内から Google Cloud サービスにアクセスする場合におすすめの方法です。Workload Identity を使用すると、Kubernetes サービス アカウントを Google サービス アカウントとして機能するよう構成でき、Secret を手動で管理、保護する必要がなくなります。

# Workload Identity とは
一言で言うと、「kubernetes の Service Account GCP の Serivce Account を紐づけ(Binding)られるようにする仕組み」です。アカウントキーでの認証方法とWorkload Identity での認証方法を図で比較します。


**アカウントキーでの認証方法**

![Workload Identity.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/351392/7bb5de34-3985-1e0b-0463-ddcc64876eb0.png)

**Workload Identity での認証方法**

![Workload Identity (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/351392/4764840f-b690-5afc-ea6e-1b0ccd0e55b9.png)

# 利用手順
大別すると以下のようになります。
詳細は[公式ドキュメント](https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity?hl=ja)を参照してください。

1. クラスタで Workload Identity を有効化
2. GSA を作成し、KSA との IAM policy Binding を作成。
3. KSA を作成し、GSA へのアノテーションを追加。
4. Pod で KSA を指定

### 1. クラスタで Workload Identity を有効化

- 新しいクラスタで Workload Identity を有効にする

```
$ gcloud container clusters create cluster-name \
  --release-channel regular \
  --workload-pool=project-id.svc.id.goog
```

- 既存のクラスタで Workload Identity を有効にする

```
$ gcloud container clusters update cluster-name \
  --workload-pool=project-id.svc.id.goog
```


### 2. GSA を作成し、KSA との IAM policy Binding を作成

```
$ gcloud iam service-accounts create gsa-name
$ gcloud iam service-accounts add-iam-policy-binding \
  --role roles/iam.workloadIdentityUser \
  --member "serviceAccount:project-id.svc.id.goog[k8s-namespace/ksa-name]" \
  gsa-name@project-id.iam.gserviceaccount.com
```

<details><summary>Terraform</summary><div>

```main.tf

data "google_project" "project" {
}

locals {
  annotations = formatlist(
    "serviceAccount:${data.google_project.project.project_id}.svc.id.goog[%s]",
    var.k8s_service_accounts,
  )
}

data "google_service_account" "serviceaccount" {
  account_id = var.gcp_service_account_id
}

resource "google_service_account_iam_binding" "wi_iam_binding" {
  service_account_id = data.google_service_account.serviceaccount.name
  role               = "roles/iam.workloadIdentityUser"
  members            = local.annotations
}
```

```variables.tf
variable "gcp_service_account_id" {
  type        = string
  default     = ""
  description = "GCP service account ID (before `@`)"
}

variable "k8s_service_accounts" {
  type        = list(string)
  default     = []
  description = "Kubernetes service accounts in the format of `$ns/$sa_name`"
}
```

</div></details>

### 3. KSA を作成し、GSA へのアノテーションを追加

```
$ kubectl create serviceaccount --namespace k8s-namespace ksa-name
$ kubectl annotate serviceaccount \
  --namespace k8s-namespace \
  ksa-name \
  iam.gke.io/gcp-service-account=gsa-name@project-id.iam.gserviceaccount.com
```
<details><summary>Manifest</summary><div>

```serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: $KSA_NAME
  namespace: $K8S_NAMESPACE
  annotations:
    iam.gke.io/gcp-service-account: "$GSA_NAME@$PROJECT_ID.iam.gserviceaccount.com"
```

</div></details>

### 4. Pod で KSA を指定

```deployment.yaml
apiVersion: apps/v1
kind: Deployment
# ...
spec:
  template:
    spec:
      serviceAccountName: $KSA_NAME
      # ...
      nodeSelector:
        iam.gke.io/gke-metadata-server-enabled: "true"
```