GitLab Runnerのジョブ内で利用するServiceAccountを変更する

GitLab RunnerのKubernetes Executorのジョブ内で利用するServiceAccountを変更したときのメモ。

## 前置き

GitLab RunnerをHelmでインストールした場合、GitLab Runnerは標準で[Kubernetes Executor](https://docs.gitlab.com/runner/executors/kubernetes.html)でジョブを実行する。Kubernetes Executorはジョブ実行用のPodを起動する。\
以下、ジョブを実行した時のログである。
```
Running with gitlab-runner 14.7.0 (98daeee0)
  on gitlab-runner-gitlab-runner-69495c846c-2th5p aXyxVB79
Preparing the "kubernetes" executor
00:00
Using Kubernetes namespace: gitlab
Using Kubernetes executor with image harbor.hoge/tkg/docker-aws-kp ...
Using attach strategy to execute scripts...
Preparing environment
Waiting for pod gitlab/runner-axyxvb79-project-2-concurrent-0dpx7t to be running, status is Pending
Running on runner-axyxvb79-project-2-concurrent-0dpx7t via gitlab-runner-gitlab-runner-69495c846c-2th5p...
```
`runner-axyxvb79-project-2-concurrent-0dpx7t`というPodでジョブを実行している事が分かる。\
これのServiceAccountは以下となっている。
```
k get pod -n gitlab runner-axyxvb79-project-2-concurrent-0dpx7t -o yaml | grep -w serviceAccount
  serviceAccount: default
```
これはどういうことかというと、このコンテナ内でkubectlを実行した場合、以下の条件下で動作する。
- 接続先ホスト：環境変数`KUBERNETES_SERVICE_HOST`で定義されたホスト
- 接続先ポート：環境変数`KUBERNETES_SERVICE_PORT`で定義されたポート
- RBACの権限：PodのServiceAccount(上の例では`default`)に紐付いた権限

`KUBECONFIG`はなく、起動時に自動で付与される環境変数を元にkubectlはクラスタへの接続を試みる。\
これは、GitLabのジョブ内でkubectlを叩くと、Namespaceがgitlab内のService Account defaultの権限相当で同じクラスタ内の操作が出来ることになる。

今の権限を確認する。
```$ kubectl-rbac_lookup default
SUBJECT                         SCOPE          ROLE
：（省略）
gitlab:default                  cluster-wide   ClusterRole/cluster-admin
```
cluster-adminがついているので、クラスタに対して何でも出来ることになる。まずい状態である。

## ServiceAccountの変更

### ServiceAccountの作成

現状、GitLabのProjectごとにNamespaceを切った運用としているため、ProjectごとにServiceAccountを作成し、そのProjectで必要な権限のみをServiceAccountに付与するようにする。

`cicd`というProjectでは[kpack](https://github.com/pivotal/kpack)を利用したイメージのbuildのみGitLab Runner上で実行したいため、`cicd`というNamespace上にServiceAccountを作成して、ビルドに必要な権限のみ持つRoleをbindする。

最初に`kp-account`というServiceAccountを作成する。
```
kubectl create sa kp-account -n cicd
```

次に、以下のようなClusterRoleを`build-service-user-role`という名前で予め用意していたので、こちらをbindする。
```
PolicyRule:
  Resources                                             Non-Resource URLs  Resource Names  Verbs
  ---------                                             -----------------  --------------  -----
  secrets                                               []                 []              [create update patch delete]
  configmaps                                            []                 []              [get list create update delete patch watch]
  builders.kpack.io                                     []                 []              [get list watch create patch update delete deletecollection]
  images.kpack.io                                       []                 []              [get list watch create update delete patch]
  pods/log                                              []                 []              [get list watch]
  pods                                                  []                 []              [get list watch]
  clusterbuilders.kpack.io                              []                 []              [get list watch]
  clusterstacks.kpack.io                                []                 []              [get list watch]
  clusterstores.kpack.io                                []                 []              [get list watch]
  customstacks.stacks.stacks-operator.tanzu.vmware.com  []                 []              [get list watch]
  serviceaccounts                                       []                 [default]       [get update patch]
  builds.kpack.io                                       []                 []              [patch get list watch update]
  ```

```
$ kubectl create clusterrolebinding kp-account-binding --clusterrole build-service-user-role --serviceaccount cicd:kp-account
clusterrolebinding.rbac.authorization.k8s.io/kp-account-binding created
```

### GitLab Runnerの設定変更
GitLab RunnerをHelmでインストールした場合、`gitlab-runner-gitlab-runner`のようなGitLab Runnerの設定が入ったConfigMapが存在する。こちらを修正していく。
`kubectl edit cm gitlab-runner-gitlab-runner -n gitlab`でConfigMapを開き、`runners.kubernetes`に`service_account_overwrite_allowed`と`namespace_overwrite_allowed`を追加する。

```
  config.template.toml: |
     [[runners]]
       [runners.kubernetes]
         namespace = "gitlab"
         image = "ubuntu:16.04"
         Shared = true
         service_account_overwrite_allowed = ".*"
         namespace_overwrite_allowed = ".*"
```
これにより、`.gitlab-ci.yml`で環境変数を設定すると、利用するServiceAccountやジョブ用Podが稼働するNamespaceを変更することが出来るようになる。パラメタの詳細については[Kubernetes executorの説明ページ](https://docs.gitlab.com/runner/executors/kubernetes.html#other-configtoml-settings)を参考のこと。

### .gitlab-ci.ymlの修正
`.gitlab-ci.yml`のvariablesでNamespaceとServiceAccountをそれぞれ上書きするよう環境変数を設定する。
```
variables:
  KUBERNETES_NAMESPACE_OVERWRITE: cicd
  KUBERNETES_SERVICE_ACCOUNT_OVERWRITE: kp-account
```

これでgitlab Namespaceのdefault ServiceAccountではなく、cicd Namespaceのkp-accountというServiceAccountに権限が変更される。

## 実機確認
先程作成したServiceAccountでは、podのlistは取得できるが、deploymentのlistは取得できない。実際に取得できなくなったことを確認する。\
`.gitlab-ci.yml`に以下のようなコードを記載しておく。
```
build-image:
  image: harbor.hoge/tkg/docker-aws-kp
  stage: build-image
  rules:
  - if: '$CI_PIPELINE_SOURCE == "push"'
  script:
  - |
    set -xe
    kubectl get pod
    kubectl get deploy
```
実行結果はこちら。deploymentのみ権限不足で失敗した。
```
++ kubectl get pod -n cicd
NAME                                          READY   STATUS      RESTARTS   AGE
runner-serpks1d-project-2-concurrent-084vms   2/2     Running     0          5s
++ kubectl get deploy -n cicd
Error from server (Forbidden): deployments.apps is forbidden: User "system:serviceaccount:cicd:kp-account" cannot list resource "deployments" in API group "apps" in the namespace "cicd"
ERROR: Job failed: command terminated with exit code 1
```
なお、先ほどの環境変数をコメントアウトすると以下の結果となる。
```
++ kubectl get pod -n cicd
No resources found in cicd namespace.
++ kubectl get deploy -n cicd
No resources found in cicd namespace.
```

これにより、ServiceAccountが適切に設定されていることが分かる。