KubernetesのTLS証明書

[Kubernetes2 Advent Calendar 2018](https://qiita.com/advent-calendar/2018/kubernetes2) 12日目の記事です。

# はじめに

Kubernetesでは、内部の通信やClientとの通信など、様々な場所でTLSの証明書を使っています。

この内部のTLS証明書がどのように使われてるのか、どのような働きをするのか、調べたことや実験したことをまとめます。

# TLS証明書はどこで使われているか？

次のドキュメントに、Kubernetesで使われる証明書についてまとまっています。

https://kubernetes.io/docs/setup/certificates/#how-certificates-are-used-by-your-cluster

> * Client certificates for the kubelet to authenticate to the API server
* Server certificate for the API server endpoint
* Client certificates for administrators of the cluster to authenticate to the API server
* Client certificates for the API server to talk to the kubelets
* Client certificate for the API server to talk to etcd
* Client certificate/kubeconfig for the controller manager to talk to the API server
* Client certificate/kubeconfig for the scheduler to talk to the API server.
* Client and server certificates for the front-proxy

* kubeletがAPIサーバを認証するためのクライアント証明書
* APIサーバーエンドポイントのサーバー証明書
* クラスタの管理者がAPIサーバーを認証するためのクライアント証明書
* APIサーバがkubeletsと通信するためのクライアント証明書
* etcdと通信するAPIサーバのクライアント証明書
* コントローラマネージャーがAPIサーバーと通信するためのクライアント証明書とkubeconfig
* スケジューラーがAPIサーバーと通信するためのクライアント証明書とkubeconfig
* フロントプロキシのクライアント証明書とサーバー証明書


図にしてみると、以下のようになります。

![k8s-cert.png](https://qiita-image-store.s3.amazonaws.com/0/118009/fb9aec40-ad1f-565e-c73f-7011843bba2d.png)

主に、API Serverを中心に、サーバー証明書とクライアント証明書が使われていることがわかります。

# 証明書はどのタイミングで作成されるか

証明証はクラスタを新規作成した時に、一括で作成されるようです。

https://kubernetes.io/docs/reference/setup-tools/kubeadm/kubeadm-init/#synopsis

# 証明書の有効期限について

CAの有効期限はデフォルトでは10年、そのほかのサーバ証明書やクライアント証明書は1年がデフォルトのようです。

https://kubernetes.io/docs/tasks/tls/certificate-rotation/#overview

> The kubelet uses certificates for authenticating to the Kubernetes API. By default, these certificates are issued with one year expiration so that they do not need to be renewed too frequently.


# 証明書の有効期限が切れるとどうなるのか

Kubernetesの証明書はAPI Server周りで使われています。

そのため、証明書の有効期限が切れると、API Serverを使ったKubernetesの操作ができなくなることが考えられます。

## 実験

証明書の有効期限が切れると、どのような現象が起きるか実験をしてみます。
今回はminikubeを使って実験します。

### 有効期限の編集

はじめに、minikubeのソースを編集します。

```shell
$ mkdir -p $GOPATH/src/k8s.io
$ cd $GOPATH/src/k8s.io
$ git clone https://github.com/kubernetes/minikube.git
$ cd minikube
```

`pkg/util/crypto.go` の93行目を編集します。
デフォルトでは、証明書の有効期限は1年になっていますが、5分で失効するような編集を加えます。

```diff:pkg/util/crypto.go
-               NotAfter:  time.Now().Add(time.Hour * 24 * 365),
+               NotAfter:  time.Now().Add(time.Minute * 5),
```

編集を保存して、 `make` を実行します。

```shell
$ make
```

`make` 後、`output/minikube` に実行ファイルが作成されます。

### クラスタの作成

次に、`minikube start` します。

```shell
$ ./out/minikube start --kubernetes-version v1.12.3
```

minikube が起動したら、Node の状態を確認します。

```shell
$ kubectl get node
NAME       STATUS    AGE       VERSION
minikube   Ready     1m        v1.12.3
```

この時点では、正常に `kubectl` による Kubernetes の操作ができることがわかります。

### 証明書の失効の確認

今回の編集では、証明書の有効期限を5分に設定しました。
5分ほど待ってから、同じように Node の状態を確認します。

```shell
$ kubectl get nodes
Unable to connect to the server: x509: certificate has expired or is not yet valid
```

`certificate has expired` のエラーが発生し、API Serverにアクセスできない状態になっていることがわかります。

ここで追加に、API Server の証明書の状態を、 `openssl` を使って確認してみます。

```shell
$ openssl s_client -connect `minikube ip`:8443 < /dev/null 2> /dev/null | openssl x509 -noout -startdate -enddate
notBefore=Dec 10 08:04:58 2018 GMT
notAfter=Dec 11 08:09:58 2018 GMT
```

API Serverでは、設定したとおり、5分で失効する証明書が使われていることがわかります。

# 証明書を更新する

有効期限が切れた証明書を更新してみます。

Kubernetesの証明書の更新方法はいくつかありますが、
今回は `kubeadm` の `cert renew` コマンドを使います。

https://kubernetes.io/docs/reference/setup-tools/kubeadm/kubeadm-alpha/#cmd-certs-renew

## 実験

### クラスタにログイン

まず、クラスタ本体にsshでログインします。

```shell
$ minikube ssh
```

### 証明書の更新

次のコマンドを実行して、API Server の証明書を更新します。

```shell
$ sudo kubeadm alpha phase certs renew apiserver --cert-dir /var/lib/minikube/certs
```

### API Sreverの再起動

API Serverを再起動します。
次のコマンドを実行すると、自動的に新しいAPI Serverのコンテナが立ち上がります。

```shell
$ docker stop $(docker ps -q -f 'name=api')
```

### Client 証明書の更新

[この記事](https://kubernetes.io/docs/concepts/cluster-administration/certificates/#cfssl)を参考にClientの証明書を更新します。

ホストPCのShellにて、次のコマンドを実行します。

```shell:host
$ cd ~/.minikube
```

次のファイルを作成し、`ca-config.json` として保存します。 

```ca-config.json
{
  "signing": {
    "default": {
      "expiry": "8760h"
    },
    "profiles": {
      "kubernetes": {
        "usages": [
          "signing",
          "key encipherment",
          "server auth",
          "client auth"
        ],
        "expiry": "8760h"
      }
    }
  }
}
```

次のファイルを作成し、`client-csr.json` として保存します。 

```client-csr.json
{
  "CN": "minikubeCA",
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
    {
      "O": "system:masters"
    }
  ]
}
```

`cfssl`を使って新しく証明書を作成します。

```shell:host
$ cfssl gencert -ca=ca.crt -ca-key=ca.key -config=ca-config.json -profile=kubernetes client-csr.json | cfssljson -bare client
```

作成したファイルのリネームをしておきます。 (もしくは `~/.kube/config` の内容を書き換える)

```shell:host
$ mv client.pem client.crt
$ mv client-key.pem client.key
```

### 動作確認

`kubectl` コマンドを実行して、Node の情報が取得できるか確認します。
次のようなレスポンスが返って来れば、証明書の差し替えは成功です。

```shell
$ kubectl get node
NAME       STATUS    AGE       VERSION
minikube   Ready     30m       v1.12.3
```

# まとめ

KubernetesのTLS証明書に関して調べました。次のようにまとめられそうです。

* 証明書は主にAPI Server周りの認証に使われている
* 証明書はクラスタを作成するときに一括で作られる
* 証明書のデフォルトの有効期限は、CAで10年、それ以外の証明書は1年
* 証明書の有効期限が切れると、API Serverにアクセスできなくなり、ほとんどのKubernetesの操作やアクセスができなくなる
* 証明書は更新することが可能

# おわりに

Kubernetesのリリースサイクルはとても早いので、定期的にクラスタをアップデートや作成していれば、証明書の有効期限切れは防げそうです。

今回は未検証ですが、Kubernetesの証明書の自動更新については、[cert-manager](https://github.com/jetstack/cert-manager)などのツールを使えば良さそうです。

# 参考

* [Kubernetes の TLS 証明書について調べてみた](https://blog.manabusakai.com/2018/10/kubernetes-tls-certificate/)
* https://kubernetes.io/docs/setup/certificates
* https://kubernetes.io/docs/concepts/cluster-administration/certificates
* https://github.com/kelseyhightower/kubernetes-the-hard-way/blob/master/docs/04-certificate-authority.md