Help us understand the problem. What is going on with this article?

TLS bootstrappingでkubeletクライアント証明書の作成、更新を自動化する

More than 1 year has passed since last update.

TLS Bootstrappingとは

workerのコンポーネント(kubelet、kube-proxy)がkube-apiserverと安全に通信するためにTLSクライアント証明書を利用することが強く推奨されています。しかし、証明書を必要とするコンポーネントのセットアップするプロセスは困難なものであり、したがって、クラスタの初期化、スケールが困難となる原因となります。

このプロセスを単純化するため、Kubernetes 1.4以降、証明書要求と署名のAPIが導入されました。TLS Bootstrappingは1.12でGAとなりました。今回はTLS Bootstrappingを利用して、kubeletのクライアント証明書の作成、更新を自動化する手順を説明します。

GA.png
(出典)What’s New in Kubernetes 1.12 P.5

証明書の種類

kubeletが使用する証明書は大雑把に言って2種類あります。

  • kubeletがkube-apiserverと通信する際のクライアント証明書
  • kubeletがHTTPSエンドポイントを提供する際のサーバ証明書

本記事の主題はクライアント証明書の作成、更新です。

TLS bootstrapingの設定方法

環境

  • Ubuntu 18.04 LTS
  • kubernetes 1.12.3

kube-apiserver設定

まず、kube-apiserverの設定を変更します。以下の引数を追加します。

  • --client-ca-file=/var/lib/kubernetes/ca.pem
    • クライアント証明書認証を有効にします。パスを適宜読み替えてください。
  • --enable-bootstrap-token-auth=true
    • kubeletがkube-apiserverに接続し、証明書を要求する際に初回の認証を行う必要があります。その方法として、Bootstrap TokensとToken authentication fileが推奨されており、前者を利用します。
/etc/systemd/system/kube-apiserver.service
[Unit]
Description=Kubernetes API Server
Documentation=https://github.com/kubernetes/kubernetes

[Service]
ExecStart=/usr/local/bin/kube-apiserver \
  ... \
  --client-ca-file=/var/lib/kubernetes/ca.pem \
  --enable-bootstrap-token-auth=true
...

設定を反映します。

$ sudo systemctl daemon-reload
$ sudo systemctl restart kube-apiserver

kube-controller-manager設定

続いてkube-controller-managerの設定を変更します。以下の引数を追加します。

  • --controllers=*,bootstrapsigner,tokencleaner
    • 後述のConfigMapに署名する、失効したトークンを削除する設定です。
  • --cluster-signing-cert-file=/var/lib/kubernetes/ca.pem
    • クラスタスコープの証明書を発行するための証明書のパスを指定します。
  • --cluster-signing-key-file=/var/lib/kubernetes/ca-key.pem
    • クラスタスコープの証明書に署名するための秘密鍵のパスを指定します。
  • --experimental-cluster-signing-duration=8760h0m0s
    • 署名付き証明書の有効期間を指定します。デフォルトは8760h0m0s(365日)です。検証環境では、証明書が更新されることを早く確認するため0h30m0sとしました。
/etc/systemd/system/kube-controller-manager.service
[Unit]
Description=Kubernetes Controller Manager
Documentation=https://github.com/kubernetes/kubernetes

[Service]
ExecStart=/usr/local/bin/kube-controller-manager \
  ... \
  --controllers=*,bootstrapsigner,tokencleaner \
  --cluster-signing-cert-file=/var/lib/kubernetes/ca.pem \
  --cluster-signing-key-file=/var/lib/kubernetes/ca-key.pem \
  --experimental-cluster-signing-duration=8760h0m0s
...

設定を反映します。

$ sudo systemctl daemon-reload
$ sudo systemctl restart kube-controller-manager

ClusterRoleBinding

kubeletによるCSR作成

$ cat <<EOF | kubectl create -f -
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: create-csrs-for-bootstrapping
subjects:
- kind: Group
  name: system:bootstrappers
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: system:node-bootstrapper
  apiGroup: rbac.authorization.k8s.io
EOF

CSR自動承認設定

新規ノード追加時のCSRの自動承認を有効にします。

$ cat <<EOF | kubectl create -f -
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: auto-approve-csrs-for-group
subjects:
- kind: Group
  name: system:bootstrappers
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: system:certificates.k8s.io:certificatesigningrequests:nodeclient
  apiGroup: rbac.authorization.k8s.io
EOF

kubeletのクライアント証明書を自動更新する際のCSRの自動承認を有効にします。

$ cat <<EOF | kubectl create -f -
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: auto-approve-renewals-for-nodes
subjects:
- kind: Group
  name: system:nodes
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: system:certificates.k8s.io:certificatesigningrequests:selfnodeclient
  apiGroup: rbac.authorization.k8s.io
EOF

Bootstrap Token生成

Bootstrap Tokenのフォーマットはabcdef.0123456789abcdef(正規表現 [a-z0-9]{6}\.[a-z0-9]{16})です。.の前をToken ID、後をToken Secretと呼びます。

$ TOKEN_ID=$(openssl rand -hex 3)
$ TOKEN_SECRET=$(openssl rand -hex 8)
$ BOOTSTRAP_TOKEN="${TOKEN_ID}.${TOKEN_SECRET}"
$ echo $BOOTSTRAP_TOKEN 
14f2fc.98e93207235685a1

Bootstrap Token Secret作成

下記フォーマットでSecretを作成します。

bootstrap-token.yaml
apiVersion: v1
kind: Secret
metadata:
  # Name MUST be of form "bootstrap-token-<token id>"
  name: bootstrap-token-14f2fc
  namespace: kube-system

# Type MUST be 'bootstrap.kubernetes.io/token'
type: bootstrap.kubernetes.io/token
stringData:
  # Human readable description. Optional.
  description: "The default bootstrap token generated by 'kubeadm init'."

  # Token ID and secret. Required.
  token-id: 14f2fc
  token-secret: 98e93207235685a1

  # Expiration. Optional.
  expiration: 2018-12-05T12:00:00Z

  # Allowed usages.
  usage-bootstrap-authentication: "true"
  usage-bootstrap-signing: "true"

  # Extra groups to authenticate the token as. Must start with "system:bootstrappers:"
  auth-extra-groups: system:bootstrappers:worker,system:bootstrappers:ingress

反映します。

$ kubectl create -f bootstrap-token.yaml

cluster-info ConfigMap作成

kube-publicネームスペースにcluster-info ConfigMapを作成します。
ノードはこの情報を参照します。

$ kubectl config set-cluster bootstrap \
  --kubeconfig=bootstrap-kubeconfig  \
  --server='https://10.240.0.10:6443' \ 
  --certificate-authority=/var/lib/kubernetes/ca.pem \
  --embed-certs=true

$ kubectl -n kube-public create configmap cluster-info \
  --from-file=kubeconfig=bootstrap-kubeconfig

ConfigMapの中身を確認します。
JWS(JSON Web Signature)の署名が付与されています。
kubeconfigはcertificate-authority-dataとserverのみ指定されていればよいです。

$ kubectl -n kube-public get configmap cluster-info -o yaml
apiVersion: v1
data:
  jws-kubeconfig-14f2fc: eyJhbGciOiJIUzI1NiIsImtpZCI6IjE0ZjJmYyJ9..kyRzUiFOp2wBDpXxWxQ2xJMLUhy4dAi-13EoyHpb-m4
  kubeconfig: |
    apiVersion: v1
    clusters:
    - cluster:
        certificate-authority-data: LS0tLS1CRU...
        server: https://10.240.0.10:6443
      name: bootstrap
    contexts: []
    current-context: ""
    kind: Config
    preferences: {}
    users: []
kind: ConfigMap
metadata:
  creationTimestamp: 2018-12-04T13:13:24Z
  name: cluster-info
  namespace: kube-public
  resourceVersion: "16528"
  selfLink: /api/v1/namespaces/kube-public/configmaps/cluster-info
  uid: 615a981c-f7c6-11e8-b676-42010af0000a

ここまでで下準備が完了しました。

bootstrap-kubeconfig作成

続いてノード上でbootstrap-kubeconfigを作成します。
kubeletはこの内容を元に証明書要求を行います。

worker:~$ kubectl config set-cluster bootstrap \
  --kubeconfig=bootstrap-kubeconfig \
  --server='https://10.240.0.10:6443' \
  --certificate-authority=ca.pem \
  --embed-certs=true

worker:~$ kubectl config set-credentials kubelet-bootstrap \
  --kubeconfig=bootstrap-kubeconfig \
  --token=14f2fc.98e93207235685a1

worker:~$ kubectl config set-context bootstrap \
  --kubeconfig=bootstrap-kubeconfig \
  --user=kubelet-bootstrap \
  --cluster=bootstrap

worker:~$ kubectl config --kubeconfig=bootstrap-kubeconfig use-context bootstrap
worker:~$ sudo mv bootstrap-kubeconfig /var/lib/kubelet/

kubelet設定

kubeletの設定をします。以下の引数を指定します。

  • --bootstrap-kubeconfig=/var/lib/kubelet/bootstrap-kubeconfig
    • 先ほど作成したbootstrap-kubeconfigのパスを指定します。
  • --cert-dir=/var/lib/kubelet/
    • 自動生成される証明書の保存先を指定します。
  • --kubeconfig=/var/lib/kubelet/kubeconfig
    • 自動生成されるkubeconfigの保存先を指定します。
  • --rotate-certificates=true
    • 証明書の自動更新を有効にします。失効が近くなると自動的に更新します。
/etc/systemd/system/kubelet.service
[Unit]
Description=Kubernetes Kubelet
Documentation=https://github.com/kubernetes/kubernetes

[Service]
ExecStart=/usr/local/bin/kubelet \
  --bootstrap-kubeconfig=/var/lib/kubelet/bootstrap-kubeconfig \
  --cert-dir=/var/lib/kubelet/ \
  --config=/var/lib/kubelet/kubelet-config.yaml \
  --kubeconfig=/var/lib/kubelet/kubeconfig \
  --network-plugin=cni \
  --register-node=true \
  --rotate-certificates=true \
  --v=2
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

設定を反映します。

worker:~$ sudo systemctl daemon-reload
worker:~$ sudo systemctl start kubelet

ノード追加確認

system:bootstrap:14f2fcから要求されたCSRが承認され、ノードが追加されました。

$ kubectl get csr
NAME                                                   AGE    REQUESTOR                 CONDITION
node-csr-i_OQmDmW2Umvb55BL2p6CWIt0ACC8ujSNGnliS2YrB8   17m    system:bootstrap:14f2fc   Approved,Issued

$ kubectl get nodes
NAME       STATUS   ROLES    AGE   VERSION
worker-0   Ready    <none>   19m   v1.12.3

ノード上はkubelet--cert-dirで指定したディレクトリにクライアント証明書が作成されました。

worker:~$ ls -l /var/lib/kubelet/
total 48
-rw------- 1 root root 2106 Dec  4 13:22 bootstrap-kubeconfig
-rw-r--r-- 1 root root 1318 Dec  4 13:15 ca.pem
-rw------- 1 root root 2236 Dec  4 13:28 kubeconfig
-rw------- 1 root root 1273 Dec  4 13:28 kubelet-client-2018-12-04-13-28-08.pem
lrwxrwxrwx 1 root root   55 Dec  4 13:44 kubelet-client-current.pem -> /var/lib/kubelet/kubelet-client-2018-12-04-13-28-08.pem
-rw-r--r-- 1 root root  330 Dec  4 13:28 kubelet-config.yaml
-rw-r--r-- 1 root root 2165 Dec  4 13:27 kubelet.crt
-rw------- 1 root root 1675 Dec  4 13:27 kubelet.key
...

クライアント証明書の失効が近くなると、system:node:${instance}からCSRが要求され、自動で承認されました。

master:~$ kubectl get csr
NAME                                                   AGE     REQUESTOR                 CONDITION
csr-sdk9t                                              6m26s   system:node:worker-0      Approved,Issued
node-csr-i_OQmDmW2Umvb55BL2p6CWIt0ACC8ujSNGnliS2YrB8   22m     system:bootstrap:14f2fc   Approved,Issued

ノード上は新しいクライアント証明書が作成され、リンク先が変更されました。

worker:~$ ls -l /var/lib/kubelet/
total 52
-rw------- 1 root root 2106 Dec  4 13:22 bootstrap-kubeconfig
-rw-r--r-- 1 root root 1318 Dec  4 13:15 ca.pem
-rw------- 1 root root 2236 Dec  4 13:28 kubeconfig
-rw------- 1 root root 1273 Dec  4 13:28 kubelet-client-2018-12-04-13-28-08.pem
-rw------- 1 root root 1273 Dec  4 13:44 kubelet-client-2018-12-04-13-44-02.pem
lrwxrwxrwx 1 root root   55 Dec  4 13:44 kubelet-client-current.pem -> /var/lib/kubelet/kubelet-client-2018-12-04-13-44-02.pem
-rw-r--r-- 1 root root  330 Dec  4 13:28 kubelet-config.yaml
-rw-r--r-- 1 root root 2165 Dec  4 13:27 kubelet.crt
-rw------- 1 root root 1675 Dec  4 13:27 kubelet.key
...

証明書の内容を確認します。
有効期間は--experimental-cluster-signing-durationで指定した30分間となっています。

$ sudo openssl x509 -text -noout -in /var/lib/kubelet/kubelet-client-current.pem 
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            02:f3:2e:f7:76:2f:ce:65:9f:21:70:e9:19:fa:01:be:15:ea:69:71
    Signature Algorithm: sha256WithRSAEncryption
        Issuer: C = US, ST = Oregon, L = Portland, O = Kubernetes, OU = CA, CN = Kubernetes
        Validity
            Not Before: Dec  4 23:18:00 2018 GMT
            Not After : Dec  4 23:48:00 2018 GMT
        Subject: O = system:nodes, CN = system:node:worker-0
        Subject Public Key Info:
            Public Key Algorithm: id-ecPublicKey
                Public-Key: (256 bit)
                pub:
                    04:d8:65:cc:0f:6c:7e:b0:e4:73:b6:63:1c:cc:1b:
                    69:22:c1:68:d0:16:05:c9:74:ff:11:ff:07:da:68:
                    fc:49:75:bd:c5:e4:fc:35:3b:21:63:d3:06:cd:5c:
                    01:a8:e6:4c:9e:21:c1:7c:37:dd:7d:96:8d:3f:b4:
                    21:1a:ce:38:85
                ASN1 OID: prime256v1
                NIST CURVE: P-256
        X509v3 extensions:
            X509v3 Key Usage: critical
                Digital Signature, Key Encipherment
            X509v3 Extended Key Usage: 
                TLS Web Client Authentication
            X509v3 Basic Constraints: critical
                CA:FALSE
            X509v3 Subject Key Identifier: 
                36:BF:56:59:F9:41:27:CE:3B:9E:AA:C7:F6:FB:94:A2:BC:05:26:3A
            X509v3 Authority Key Identifier: 
                keyid:81:48:9D:B5:22:5F:16:6C:A9:D8:03:98:E4:33:C9:5F:2B:41:53:F7

    Signature Algorithm: sha256WithRSAEncryption
         3c:c5:a0:6e:28:ac:90:cf:f3:7f:c2:88:e5:ca:0c:39:a1:20:
         ...

まとめ

オンプレミスでKubernetesクラスタを構築していると、証明書の管理が大変かと思います。
TLS Bootstrappingを利用してクライアント証明書の作成、更新を自動化しましょう。
サーバ証明書については機会があれば(気が向けば)別途まとめます。

参考

oke-py
OSSコミッタに憧れるセキュリティエンジニア 興味: Kubernetes/AWS/GCP
https://note.com/oke_py
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした