GitLab Container RegistryをKubernetesで利用するためのトークン入りSecretの作り方

この記事は [Kubernetes2 Advent Calendar 2018](https://qiita.com/advent-calendar/2018/kubernetes2) の14日目の記事です。
過去の日付ですが、未参加だったので、せっかくなので埋めようと思います。

GitLabにはContainer Registryが統合されています。[参考](https://docs.gitlab.com/ee/user/project/container_registry.html)
このRegistryをKubernetesで使おう、という記事です。
KubernetesではGitLabへの認証情報をSecretとして保持できるので、このSecretの作り方をまとめます。

Kubernetes上にGitLabを立てる、という記事ではないので、ご注意ください。

# TL;DR

1. GitLabで個人ごとにトークンを作成
1. そのトークンを次の形式でbase64エンコード
    ```
    任意の文字列:トークン
    ```
1. できた文字列を次の形式でbase64エンコード
    ```
    {"GitLabのアドレス":{"auth":"さっき作った文字列"}}
    ```
1. できた文字列をdockercfgにしてk8s secretを作成
1. podを立てるときにyamlのimagePullSecretsに作ったsecretを指定する

以下、GitLabのためのsecretをどうやって作るか、記載していきます。
無駄に回り道をしているので、[結論だけ知りたい方は一番最後を見てください。](#gitlabトークンを使ってsecretを作る)

# 前提
- kubernetes が構築されている （今回のバージョンはv1.10.2）
- GitLabにContainer Registryが構築されている （今回のバージョンはGitLab CE 11.4.7）
- イメージがGitLab Container Registryにpush済み

GitLab Container Registryの利用方法などは省略します。[参考](https://qiita.com/masakura/items/802f4b8ce322d2543c80)

# ユーザ名＆パスワードではなくトークンを使う理由

GitLabへの認証は、トークンではなくユーザ名＆パスワードでも行えます。
実際、Kubernetesのマニュアルでは、その方法が記載されています。

しかし、この通りにはやらない方が良いと思うので、その理由をまずは書いていきます。
結論から言うと、この方法だとパスワードがKubernetes利用者or管理者に晒されてしまうためです。

## マニュアル通りにkubectlでsecretを作った場合

公式に手順が書いてあるので、この通りにやってみます。
https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/

### secretを作る

kubectlのコマンドでsecretは生成できるようです。やってみます。

```shell-session
$ sudo kubectl create secret docker-registry sekikatsutest --docker-server=gitlab.hogehoge.co.jp:5005 --docker-username=sekikatsu --docker-password=asdfqwer1234 --docker-email=sekikatsu@hogehoge.co.jp
secret "sekikatsutest" created
$
```

これでsecretが作成できました。

![CreateSecretWithKubectl.png](https://qiita-image-store.s3.amazonaws.com/0/299048/921e97f8-a02b-660a-83c8-8c161d5a2ce2.png)

### podを立てる

後はPodを作る時のyamlでこれを指定するだけです。あるいはServiceAccountで指定することもできますが、ここでは省略します。

```yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: "gitlab-test"
  labels:
    app: "gitlab-test"
spec:
  parallelism: 1
  completions: 1
  backoffLimit: 0
  template:
    metadata:
      labels:
        app: "gitlab-test"
    spec:
      restartPolicy: Never
      terminationGracePeriodSeconds: 5
      containers:
        - name: kq-container
          image: "gitlab.hogehoge.co.jp:5005/sekikatsu/sampleprj/ubuntu:18.04"
          imagePullPolicy: Always
          securityContext:
            privileged: false
          resources:
            limits:
              cpu: 2
              memory: 4Gi
          command:
            - "/bin/sh"
            - "-c"
            - "echo 'hello gitlab containers!'"
      imagePullSecrets:
        - name: "sekikatsutest"
```

ポイントは以下二つです。

- imageの指定
    - GitLabのRegistryの項目を見ればイメージ名・タグ名のフォーマットが書いてあるので、それに従った名前を入れましょう。
- imagePullSecretsの指定
    - 作ったsecretの名前を入れましょう。

これだけです。簡単デスネ。

### 問題点

自分一人でkubernetesを使っているならこれで良いのですが、kubernetesを組織的に使っている場合を考えてください。

secretの中身を見てみます。

![ShowSecretWithKubectl.png](https://qiita-image-store.s3.amazonaws.com/0/299048/111befd7-f08e-63f2-ce32-1d3ac539bee0.png)

**パ ス ワ ー ド が 衆 目 に 晒 さ れ て い る**

いけません。これはいけません。
大事な大事なパスワードが悪意無き同僚の目に触れてしまいます。

少なくともKubernetesのダッシュボードを複数人が触っている状態では、この方法は使えません。

## docker loginからsecretを作る場合

そこでdocker loginで作成されるdockercfgを使う方法を考えます。

### dockercfgを見てみる

まずはdocker loginを試しましょう

```shell-session
$ docker login gitlab.hogehoge.co.jp:5005
Username: sekikatsu
Password:
Login Succeeded
$
```

これで~/.docker/config.jsonが生成されました。

```sh
root@pc0719:~# cat .docker/config.json
{
        "auths": {
                "gitlab.hogehoge.co.jp:5005": {
                        "auth": "c2VraWthdHN1OmFzZGZxd2VyMTIzNA=="
                }
        }
}
```

さて、先ほど作ったsecretを編集してみましょう。

![EditSecretWithKubectl.png](https://qiita-image-store.s3.amazonaws.com/0/299048/f125105a-48ec-f77b-bc5e-cefcfa50e077.png)

何やらdockerconfigjsonという項目がありますね。
この値をbase64デコードします。

```json
{"auths":{"gitlab.hogehoge.co.jp:5005":{"username":"sekikatsu","password":"asdfqwer1234","email":"sekikatsu@hogehoge.co.jp","auth":"c2VraWthdHN1OmFzZGZxd2VyMTIzNA=="}}}
```

見てわかる通り、この中身は先ほどのdockercfgです。
なので、dockercfgを自作してそれをbase64エンコードすれば、secretは自作できます。

### secretを作る

dockercfgにユーザ名とパスワードは必須ではないので、上記のdockercfgを手で修正して、消してしまいましょう。
わざわざ消さなくても、生成した.docker/config.jsonをそのまま使うこともできます。
その後再びbase64エンコードして値を戻してあげます。

```json
{"auths":{"gitlab.hogehoge.co.jp:5005":{"email":"sekikatsu@hogehoge.co.jp","auth":"c2VraWthdHN1OmFzZGZxd2VyMTIzNA=="}}}
```
これを
`eyJhdXRocyI6eyJnaXRsYWIuaG9nZWhvZ2UuY28uanA6NTAwNSI6eyJlbWFpbCI6InNla2lrYXRzdUBob2dlaG9nZS5jby5qcCIsImF1dGgiOiJjMlZyYVd0aGRITjFPbUZ6WkdaeGQyVnlNVEl6TkE9PSJ9fX0=`
こうじゃ！

そしてsecretを編集すれば、これでパスワードは見えなくなりますね。

![ShowSecretWithDockercfg.png](https://qiita-image-store.s3.amazonaws.com/0/299048/b2d70096-9e1e-0ab2-7b72-ee9d6ba49fb0.png)

不安で眠れない夜を過ごさずに済みそうな気がします。

### 問題点

ところで、このauthって何でしょう。

```json
{"auths":{"gitlab.hogehoge.co.jp:5005":{"email":"sekikatsu@hogehoge.co.jp","auth":"c2VraWthdHN1OmFzZGZxd2VyMTIzNA=="}}}
```

答えはbase64デコードすればわかります。
```c2VraWthdHN1OmFzZGZxd2VyMTIzNA==```
をbase64デコードすると
```sekikatsu:asdfqwer1234```

**パ ス ワ ー ド が 漏 れ て い る**

いけません。これはいけません。
大事な大事なパスワードが悪意ある同僚の目に触れてしまいます。

dockerの仕様上、base64エンコードされていようが、ログインした環境にはパスワードが置き去りにされます。
secretはk8s管理者に生で晒されてしまうので、パスワードを使うのはやめましょう。

k8s関係ないところでも、.docker/config.jsonにパスワードが容易に復元可能な形で置かれるため、日ごろから注意が必要です。
使いまわしているアカウントでssh loginして、docker loginは自分のアカウントで、みたいなことをやってしまうと、周囲に自分のパスワードを晒してしまうことになります。

# GitLabトークンを使ってsecretを作る

ようやく本題です。

GitLabの場合、ユーザ名＆パスワードではなく、トークンを利用して認証を行うことができます。
トークンには以下のような利点があるため、ぜひこちらを利用しましょう。

- 期限や権限範囲を絞れる
- いつでも手動で失効させられる

![GitLabToken.png](https://qiita-image-store.s3.amazonaws.com/0/299048/1d9dd604-20a6-f191-0ef5-c86e560e7d69.png)

まずは、上記画面からRegistry用のトークンを作っておいてください。  
今回は以下のトークンが得られたとします。
```qAwSeDrFtGyHuJiKoLp```

## authを作る

まずは、以下のフォーマットの文字列をbase64エンコードします。
```"{任意の文字列}:{トークン}"```

これまでは"{ユーザ名}:{パスワード}"の形で作っていたauthの文字列は、トークンの場合は上記フォーマットになります。

パスワードの代わりにトークンを使うのは良いとして、ユーザ名の代わりに好きな文字列を入れられるのは不思議な感じがします。
でも必須です。トークンだけの場合はフォーマットエラーで"unable to parse auth field"というエラーが発生します。
また空文字はアウトです。"Access denied"というエラーが発生します。
この仕様にだいぶハマりました。

今回は
```gitlab:qAwSeDrFtGyHuJiKoLp```
をbase64エンコードして
```Z2l0bGFiOnFBd1NlRHJGdEd5SHVKaUtvTHA=```
という値を作りました。

## dockercfgを作る

続いて、これを元にdockercfgを作ります。フォーマットは以下です。username, emailは任意です。識別子として使いたい人は適宜入力しましょう。
トークンを使う場合、authsを含めるとなぜかうまくいかなかったので、中身だけにします。（詳しくは調べてない）
```{"サーバURL":{"username":"ユーザ名","email":"アドレス","auth":"トークン"}}```

今回は以下のようになりました。
```{"gitlab.hogehoge.co.jp:5005":{"auth":"Z2l0bGFiOnFBd1NlRHJGdEd5SHVKaUtvTHA="}}```
これをbase64エンコードした
```eyJnaXRsYWIuaG9nZWhvZ2UuY28uanA6NTAwNSI6eyJhdXRoIjoiWjJsMGJHRmlPbkZCZDFObFJISkdkRWQ1U0hWS2FVdHZUSEE9In19```
がdockercfgになりますので、この値をsecretに埋め込みます。

## secretを作る

dockercfgができれば、あとは編集画面で.dockerconfigjsonの値を書き換えるだけです。

![EditSecretWithKubectl.png](https://qiita-image-store.s3.amazonaws.com/0/299048/f125105a-48ec-f77b-bc5e-cefcfa50e077.png)

既存のsecretがない場合、以下のようにyamlで作っても良いです。

```yaml
apiVersion: v1
kind: Secret
metadata:
  name: sekikatsutest
  namespace: default
type: kubernetes.io/dockercfg
data:
  .dockercfg: eyJnaXRsYWIuaG9nZWhvZ2UuY28uanA6NTAwNSI6eyJhdXRoIjoiWjJsMGJHRmlPbkZCZDFObFJISkdkRWQ1U0hWS2FVdHZUSEE9In19
```

[あとは上述したように](#podを立てる)Podを立てるときにimagePullSecretsを指定するだけです。

# まとめ

再掲です。

1. GitLabで個人ごとにトークンを作成
1. そのトークンを次の形式でbase64エンコード
    ```
    任意の文字列:トークン
    ```
1. できた文字列を次の形式でbase64エンコード
    ```
    {"GitLabのアドレス":{"auth":"さっき作った文字列"}}
    ```
1. できた文字列をdockercfgにしてk8s secretを作成
1. podを立てるときにyamlのimagePullSecretsに作ったsecretを指定する

まとめると簡単ですね。
私はトークンの使い方が分からず、見事にハマって時間を浪費したので、同じ悩みを持つ方の参考になれば幸いです。