GKE の Pod で デフォルトの Service Account の Credential を変更する (Workload Identity)

### これまで

- GKE で Pod から GCP の API をリクエストする場合には Secrets リソースに Google Service Account の Credential を作ってマウントしていた
- そのため Credential を発行してダウンロードするという作業が必要となる
- Google Service Account にはそのアプリケーションで必要となる API の各種権限を割り当てているのでセンシティブな情報と言える
- それにも関わらず一度は作業者のローカルや作業環境などに保存する必要があるため管理が甘い (使用後即座に消さないとか) と作業者本人も含めてアプリケーション以外で使用されてしまう
- Credential なんてダウンロードしないで Pod に Service Account を指定したい...

### 今

- Workload Identity 登場
- fin.

### 使ってみた (CronJob)

#### 前提

- 作業者に各種権限が付与されていること
    - 以下の権限が含まれていればいけそう (要らないのも混ざってるかも)

```
resourcemanager.projects.get
iam.serviceAccounts.get
iam.serviceAccounts.list
iam.serviceAccounts.create
iam.serviceAccounts.actAs
iam.serviceAccounts.setIamPolicy
iam.serviceAccounts.getIamPolicy
compute.projects.get
compute.regions.get
compute.regions.list
compute.zones.get
compute.zones.list
compute.networks.get
compute.networks.list
compute.networks.create
compute.networks.updatePolicy
compute.subnetworks.get
compute.subnetworks.list
compute.subnetworks.create
container.operations.get
container.clusters.get
container.clusters.create
```

- 各種リソースの名前は以下で統一

| Name | Description |
|------|-------------|
| ${my_pj}     | クラスタを作成する GCP のプロジェクト |
| ${my_gsa}    | CronJob で使用したい Google Service Account 名 |
| ${k8s_ns}    | Kubernetes の Namespace 名 |
| ${k8s_ksa}   | Kubernetes の Namespace 内の Service Account 名 |
| ${my_clstr}  | クラスタ名 |
| ${my_nw}     | GCP の VPC ネットワーク名 |
| ${my_subnw}  | クラスタ用のサブネットワーク名 |
| ${my_region} | サブネットワークのリージョン |
| ${my_zone}   | クラスタのゾーン (リージョンで作るとノード数多いので) |

- https://console.cloud.google.com/apis/api/iamcredentials.googleapis.com/overview にアクセスして API の許可がされていること

#### 作業

```bash
$ docker run --rm -it --name gcp-cli -w /opt google/cloud-sdk:latest

% gcloud auth login
% gcloud config set project ${my_pj}

% : Step01. ${my_gsa} の作成
% gcloud beta iam service-accounts create\
    ${my_gsa}\
    --project ${my_pj}\
    --display-name "My Google Service Account"\
    --description "Qiita Example"

% : Step02. ${my_gsa} への権限設定
% gcloud projects add-iam-policy-binding\
    ${my_pj}\
    --member ${my_gsa}@${my_pj}.iam.gserviceaccount.com\
    --role resourcemanager.projects.get

% : Step03. クラスタ用のネットワーク作成
% gcloud compute networks create\
    ${my_nw}\
    --project ${my_pj}\
    --bgp-routing-mode regional\
    --subnet-mode custom
% gcloud compute networks subnets create\
    ${my_subnw}\
    --project ${my_pj}\
    --network ${my_nw}\
    --region ${my_region}\
    --range xxx.xxx.xxx.xxx/xx\
    --enable-private-ip-google-access

% : Step04. クラスタ作成
% gcloud beta container clusters create\
    ${my_clstr}\
    --project ${my_pj}\
    --zone ${my_zone}\
    --identity-namespace ${my_pj}.svc.id.goog\
    --network ${my_nw}\
    --subnetwork ${my_subnw}\
    --machine-type n1-standard-1\
    --preemptible\
    --disk-size 100\
    --disk-type pd-standard\
    --image-type cos\
    --enable-ip-alias\
    --enable-private-nodes\
    --master-ipv4-cidr yyy.yyy.yyy.yyy/yy\
    --enable-master-authorized-networks\
    --master-authorized-networks zzz.zzz.zzz.zzz/zz\
    --num-nodes 1\
    --no-enable-legacy-authorization\
    --no-enable-basic-auth\
    --no-issue-client-certificate\
    --metadata 'disable-legacy-endpoints=true'

% : Step05. Workload Identity からの ${my_gsa} へのアクセスを許可
% gcloud iam service-accounts add-iam-policy-binding\
    ${my_gsa}@${my_pj}.iam.gserviceaccount.com\
    --role "roles/iam.workloadIdentityUser"\
    --member "serviceAccount:${my_pj}.svc.id.goog[${k8s_ns}/${k8s_ksa}]"

% : Step06. クラスタに Namespace と Service Account を作成
% cat <<EOS | kubectl apply -f -
apiVersion: v1
kind: Namespace
metadata:
  name: ${k8s_ns}
---
apiVersion: v1
kind: ServiceAccount
metadata:
  namespace: ${k8s_ns}
  name: ${k8s_ksa}
  annotations:
    iam.gke.io/gcp-service-account: ${my_gsa}@${my_pj}.iam.gserviceaccount.com
EOS

% : Step07. Pod 作ってテスト
% kubectl run\
    -it\
    --rm\
    --generator=run-pod/v1\
    --image google/cloud-sdk\
    --namespace ${k8s_ns}\
    --serviceaccount ${k8s_ksa}\
    workload-identity-test\
    -- gcloud auth list
% kubectl run\
    -it\
    --rm\
    --generator=run-pod/v1\
    --image google/cloud-sdk\
    --namespace ${k8s_ns}\
    --serviceaccount ${k8s_ksa}\
    workload-identity-test\
    -- gcloud projects describe ${my_pj}

% : Step08. CronJob を作成
% cat <<EOS | kubectl apply -f -
apiVersion: batch/v1beta1
kind: CronJob
metadata:
  namespace: ${k8s_ns}
  name: workload-identity-test
spec:
  schedule: "*/1 * * * *"
  successfulJobsHistoryLimit: 1
  failedJobsHistoryLimit: 1
  jobTemplate:
    spec:
      template:
        spec:
          restartPolicy: Never
          serviceAccountName: ${k8s_ksa}
          containers:
            - name: batch
              image: google/cloud-sdk
              imagePullPolicy: Always
              command: ["gcloud", "projects", "describe", "${my_pj}"]
EOS
% kubectl get cronjob --namespace ${k8s_ns}
% kubectl pod cronjob --namespace ${k8s_ns}
% kubectl get pod --namespace ${k8s_ns} --field-selector status.phase==Succeeded -o name\
  | kubectl logs $(cat) --namespace ${k8s_ns}
```

### 調査

#### Pod 内で `gcloud auth list` がエラー

こんなの

```
ERROR: gcloud crashed (MetadataServerException): HTTP Error 403: Forbidden
```

- Stack Driver で以下のようなフィルターでログ調査

```
resource.type="container"
resource.labels.cluster_name="${my_clstr}"
resource.labels.namespace_id="kube-system"
resource.labels.project_id="${my_pj}"
resource.labels.zone:"${my_zone}"
resource.labels.container_name="gke-metadata-server"
resource.labels.pod_id:"gke-metadata-server-"
```

- IAM とかの API を enable にしてなかったり権限が足りてなかったり

### 参考

- [Google Cloud / Docs / Kubernetes Engine / Documentation / Workload Identity](https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity)