Kubernetes the hard way (GCP版)の続きです。
前回の記事はこちら。 taxintt.hatenablog.com
Chap4. Provisioning a CA and Generating TLS Certificates (CAのプロビジョニングとTLS証明書の生成)
このチャプターでは、Kubernetesのクラスター内で利用するTLS証明書の作成を行います。
認証局(CA)の構成ファイル・CA証明書を作成し、それらを利用してTLS証明書を作成するという流れとなっています。
先に、ハンズオンの手順を一通り記載した後に解説を記載していきます。
CAのプロビジョニング
最初にTLS証明書の作成に必要な認証局(CA)の構成ファイルを作成します。
構成ファイルの中では、証明書の利用用途と有効期限をセットした証明書発行に利用するプロファイル(Kubernetes
)が定義されています。
TLS証明書の作成時には、作成した構成ファイルとプロファイル(Kubernetes
)を指定します。
// 認証局(CA)の構成ファイルを作成する ~/w/k/h/04 ❯❯❯ { cat > ca-config.json <<EOF { "signing": { "default": { "expiry": "8760h" }, "profiles": { "kubernetes": { "usages": ["signing", "key encipherment", "server auth", "client auth"], "expiry": "8760h" } } } } EOF
次に、CA証明書とCAの秘密鍵の作成を行います。
証明書の作成に必要なCSR (証明書署名要求) を作成した上で、CSRを基に証明書と秘密鍵を作成します。
// CSRを作成する cat > ca-csr.json <<EOF { "CN": "Kubernetes", "key": { "algo": "rsa", "size": 2048 }, "names": [ { "C": "US", "L": "Portland", "O": "Kubernetes", "OU": "CA", "ST": "Oregon" } ] } EOF // CSRを基にCA証明書とCAの秘密鍵を生成する cfssl gencert -initca ca-csr.json | cfssljson -bare ca } 2020/02/29 11:00:07 [INFO] generating a new CA key and certificate from CSR 2020/02/29 11:00:07 [INFO] generate received request 2020/02/29 11:00:07 [INFO] received CSR 2020/02/29 11:00:07 [INFO] generating key: rsa-2048 2020/02/29 11:00:07 [INFO] encoded CSR 2020/02/29 11:00:07 [INFO] signed certificate with serial number 254290516728701407670608002579038214846567871824 // CA証明書(ca.pem)とCAの秘密鍵(ca-key.pem)が生成されている ~/w/k/h/04 ❯❯❯ ls ca-config.json ca-csr.json ca-key.pem ca.csr ca.pem
TLS証明書の作成
認証局(CA)の構成ファイル、CA証明書、CAの秘密鍵を基に各種証明書を作成します。
作成する証明書は下記の通りです。
- adminユーザー用クライアント証明書
- Kubelet用クライアント証明書
- Controller Manager用クライアント証明書
- Kube Proxy用クライアント証明書
- Scheduler用クライアント証明書
- KubernetesのAPI Server用証明書
- サービスアカウントのキーペア
CSR (証明書署名要求) を作成し、それを基にTLS証明書と秘密鍵を作成する部分は先程の手順と同じです。
これ以降は、認証局(CA)の構成ファイル、CA証明書と秘密鍵を指定して証明書の作成を行います。
最初に、adminユーザー用のクライアント証明書を作成します。
特記事項は下記の通りです。
- CSRの識別名(DN)の一つであるOrganizationで
system:masters
を指定する
~/w/k/h/04 ❯❯❯ { cat > admin-csr.json <<EOF { "CN": "admin", "key": { "algo": "rsa", "size": 2048 }, "names": [ { "C": "US", "L": "Portland", "O": "system:masters", "OU": "Kubernetes The Hard Way", "ST": "Oregon" } ] } EOF // クライアント証明書の作成時にCAの証明書・秘密鍵・構成ファイルを引数として渡している cfssl gencert \ -ca=ca.pem \ -ca-key=ca-key.pem \ -config=ca-config.json \ -profile=kubernetes \ admin-csr.json | cfssljson -bare admin } 2020/02/29 11:13:46 [INFO] generate received request 2020/02/29 11:13:46 [INFO] received CSR 2020/02/29 11:13:46 [INFO] generating key: rsa-2048 2020/02/29 11:13:47 [INFO] encoded CSR 2020/02/29 11:13:47 [INFO] signed certificate with serial number 679971876887522464623843539440976113444619121269 2020/02/29 11:13:47 [WARNING] This certificate lacks a "hosts" field. This makes it unsuitable for websites. For more information see the Baseline Requirements for the Issuance and Management of Publicly-Trusted Certificates, v.1.1.6, from the CA/Browser Forum (https://cabforum.org); specifically, section 10.2.3 ("Information Requirements"). //admin-key.pem, admin.pemが作成されている ~/w/k/h/04 ❯❯❯ ls admin-csr.json admin-key.pem admin.csr admin.pem ca-config.json ca-csr.json ca-key.pem ca.csr ca.pem
次に、Kubelet用のクライアント証明書を作成します。 特記事項は下記の通りです。
- 識別名(DN)のOrganizationとして
system:nodes
を指定する - API Server側でNode Authorizerを利用したリクエストの許可設定を行うために、証明書のCommon Name(CN)として
system:node:${instance}
を指定する hostname
としてノードのホスト名(${instance}
)とIP(${INTERNAL_IP}
)、(Chap3で作成した)外部公開用のIPアドレス(${EXTERNAL_IP}
)を渡している
Kubeletは(API Serverに対しての)クライアントでもありますが、サーバとしての役割も果たすため-hostname
で複数のホスト名を渡します。
これにより、証明書にはCNの別名であるSANs(Subject Alternative Names)が指定されます。
また、-profile=kubernetes
でKubernetes
のプロファイルからusage
を参照することで、作成した証明書がクライアント兼サーバ証明書として利用できるようになってると考えられます。
~/w/k/h/04 ❯❯❯ for instance in worker-0 worker-1 worker-2; do cat > ${instance}-csr.json <<EOF { "CN": "system:node:${instance}", "key": { "algo": "rsa", "size": 2048 }, "names": [ { "C": "US", "L": "Portland", "O": "system:nodes", "OU": "Kubernetes The Hard Way", "ST": "Oregon" } ] } EOF EXTERNAL_IP=$(gcloud compute instances describe ${instance} \ --format 'value(networkInterfaces[0].accessConfigs[0].natIP)') INTERNAL_IP=$(gcloud compute instances describe ${instance} \ --format 'value(networkInterfaces[0].networkIP)') cfssl gencert \ -ca=ca.pem \ -ca-key=ca-key.pem \ -config=ca-config.json \ -hostname=${instance},${EXTERNAL_IP},${INTERNAL_IP} \ -profile=kubernetes \ ${instance}-csr.json | cfssljson -bare ${instance} done 2020/02/29 11:16:58 [INFO] generate received request 2020/02/29 11:16:58 [INFO] received CSR 2020/02/29 11:16:58 [INFO] generating key: rsa-2048 2020/02/29 11:16:58 [INFO] encoded CSR 2020/02/29 11:16:58 [INFO] signed certificate with serial number 359886775191352144581584441997016046901027647498 2020/02/29 11:17:01 [INFO] generate received request 2020/02/29 11:17:01 [INFO] received CSR 2020/02/29 11:17:01 [INFO] generating key: rsa-2048 2020/02/29 11:17:01 [INFO] encoded CSR 2020/02/29 11:17:01 [INFO] signed certificate with serial number 496107270789966248075618951568412965762056311780 2020/02/29 11:17:04 [INFO] generate received request 2020/02/29 11:17:04 [INFO] received CSR 2020/02/29 11:17:04 [INFO] generating key: rsa-2048 2020/02/29 11:17:04 [INFO] encoded CSR 2020/02/29 11:17:04 [INFO] signed certificate with serial number 356879757777462442721200413749147882778190289592 // 各ノード用の証明書(worker-X.pem)と秘密鍵(worker-X-key.pem)が作成されている ~/w/k/h/04 ❯❯❯ ls admin-csr.json admin.pem ca-key.pem worker-0-csr.json worker-0.pem worker-1.csr worker-2-key.pem admin-key.pem ca-config.json ca.csr worker-0-key.pem worker-1-csr.json worker-1.pem worker-2.csr admin.csr ca-csr.json ca.pem worker-0.csr worker-1-key.pem worker-2-csr.json worker-2.pem
Controller Manager〜Scheduler用のクライアント証明書の作成部分は省略します。
(adminユーザー用のクライアント証明書の作成と同じことをしているため)
次に、KubernetesのAPI Server用の証明書を作成します。
Kubelet用のクライアント証明書同様、-hostname
でSANsを指定してTLS証明書を作成しています。
特記事項は下記の通りです。
hostname
としてAPI ServerのServiceのIPアドレス(10.32.0.1
)、各Control PlaneのIP(10.240.0.10, 10.240.0.11, 10.240.0.12
)、外部公開用のIPアドレス(${EXTERNAL_IP}
)、ループバックアドレス(127.0.0.1
)、クラスタ内で利用するホスト名(${KUBERNETES_HOSTNAMES}
)を渡している
Kubernetesのクラスタを作成するとデフォルトでServiceが作成されていますが、その正体はAPI Servier用のService(Cluster IP)です。
内部DNSでは、kubernetes.default
のホスト名でAレコードが登録されるため、それらのホスト名も指定する必要があります。
また、クラスタ内で利用するホスト名(${KUBERNETES_HOSTNAMES}
)については、別で作成予定のKubernetesのネットワークに関する記事の中で調べた内容をまとめる予定です。
~/w/k/h/04 ❯❯❯ { KUBERNETES_PUBLIC_ADDRESS=$(gcloud compute addresses describe kubernetes-the-hard-way \ --region $(gcloud config get-value compute/region) \ --format 'value(address)') KUBERNETES_HOSTNAMES=kubernetes,kubernetes.default,kubernetes.default.svc,kubernetes.default.svc.cluster,kubernetes.svc.cluster.local cat > kubernetes-csr.json <<EOF { "CN": "kubernetes", "key": { "algo": "rsa", "size": 2048 }, "names": [ { "C": "US", "L": "Portland", "O": "Kubernetes", "OU": "Kubernetes The Hard Way", "ST": "Oregon" } ] } EOF cfssl gencert \ -ca=ca.pem \ -ca-key=ca-key.pem \ -config=ca-config.json \ -hostname=10.32.0.1,10.240.0.10,10.240.0.11,10.240.0.12,${KUBERNETES_PUBLIC_ADDRESS},127.0.0.1,${KUBERNETES_HOSTNAMES} \ -profile=kubernetes \ kubernetes-csr.json | cfssljson -bare kubernetes } 2020/02/29 11:25:41 [INFO] generate received request 2020/02/29 11:25:41 [INFO] received CSR 2020/02/29 11:25:41 [INFO] generating key: rsa-2048 2020/02/29 11:25:41 [INFO] encoded CSR 2020/02/29 11:25:41 [INFO] signed certificate with serial number 603669054680267544191086535903497288645234008053
最後に、サービスアカウントに必要なキーペアを作成します。
Controller Manager(正確にはToken Controller)では、サービスアカウントのAPIリクエストに必要なトークンの生成を行います。
その際に作成したここで作成するキーペアを使用して、サービスアカウント用のトークンを生成して署名します。
~/w/k/h/04 ❯❯❯ { cat > service-account-csr.json <<EOF { "CN": "service-accounts", "key": { "algo": "rsa", "size": 2048 }, "names": [ { "C": "US", "L": "Portland", "O": "Kubernetes", "OU": "Kubernetes The Hard Way", "ST": "Oregon" } ] } EOF cfssl gencert \ -ca=ca.pem \ -ca-key=ca-key.pem \ -config=ca-config.json \ -profile=kubernetes \ service-account-csr.json | cfssljson -bare service-account } 2020/02/29 11:26:20 [INFO] generate received request 2020/02/29 11:26:20 [INFO] received CSR 2020/02/29 11:26:20 [INFO] generating key: rsa-2048 2020/02/29 11:26:20 [INFO] encoded CSR 2020/02/29 11:26:20 [INFO] signed certificate with serial number 372200916003664381145141033359158655751518600952 2020/02/29 11:26:20 [WARNING] This certificate lacks a "hosts" field. This makes it unsuitable for websites. For more information see the Baseline Requirements for the Issuance and Management of Publicly-Trusted Certificates, v.1.1.6, from the CA/Browser Forum (https://cabforum.org); specifically, section 10.2.3 ("Information Requirements").
作成が完了した証明書は各ノードに配布します。
(CA証明書を各ノードに配布している理由については後述します。)
~/w/k/h/04 ❯❯❯ for instance in worker-0 worker-1 worker-2; do gcloud compute scp ca.pem ${instance}-key.pem ${instance}.pem ${instance}:~/ done Warning: Permanently added 'compute.7526976075834096736' (ECDSA) to the list of known hosts. Enter passphrase for key '/Users/.../.ssh/google_compute_engine': ca.pem 100% 1318 13.7KB/s 00:00 worker-0-key.pem 100% 1675 16.2KB/s 00:00 worker-0.pem 100% 1493 14.5KB/s 00:00 Warning: Permanently added 'compute.6311120260820139134' (ECDSA) to the list of known hosts. Enter passphrase for key '/Users/.../.ssh/google_compute_engine': ca.pem 100% 1318 12.1KB/s 00:00 worker-1-key.pem 100% 1675 15.8KB/s 00:00 worker-1.pem 100% 1493 11.5KB/s 00:00 Warning: Permanently added 'compute.3606445772456098939' (ECDSA) to the list of known hosts. Enter passphrase for key '/Users/.../.ssh/google_compute_engine': ca.pem 100% 1318 12.2KB/s 00:00 worker-2-key.pem 100% 1675 16.4KB/s 00:00 worker-2.pem ~/w/k/h/04 ❯❯❯ for instance in controller-0 controller-1 controller-2; do gcloud compute scp ca.pem ca-key.pem kubernetes-key.pem kubernetes.pem \ service-account-key.pem service-account.pem ${instance}:~/ done Enter passphrase for key '/Users/.../.ssh/google_compute_engine': ca.pem 100% 1318 12.9KB/s 00:00 ca-key.pem 100% 1675 16.6KB/s 00:00 kubernetes-key.pem 100% 1679 16.9KB/s 00:00 kubernetes.pem 100% 1663 16.5KB/s 00:00 service-account-key.pem 100% 1679 15.7KB/s 00:00 service-account.pem 100% 1440 13.7KB/s 00:00 Warning: Permanently added 'compute.3758247453048208529' (ECDSA) to the list of known hosts. Enter passphrase for key '/Users/.../.ssh/google_compute_engine': ca.pem 100% 1318 13.6KB/s 00:00 ca-key.pem 100% 1675 17.3KB/s 00:00 kubernetes-key.pem 100% 1679 17.5KB/s 00:00 kubernetes.pem 100% 1663 15.9KB/s 00:00 service-account-key.pem 100% 1679 17.3KB/s 00:00 service-account.pem 100% 1440 14.6KB/s 00:00 Warning: Permanently added 'compute.3703518325472787566' (ECDSA) to the list of known hosts. Enter passphrase for key '/Users/.../.ssh/google_compute_engine': ca.pem 100% 1318 11.8KB/s 00:00 ca-key.pem 100% 1675 16.3KB/s 00:00 kubernetes-key.pem 100% 1679 16.7KB/s 00:00 kubernetes.pem 100% 1663 15.1KB/s 00:00 service-account-key.pem 100% 1679 16.6KB/s 00:00 service-account.pem
Control Planeに関連する証明書
Worker Nodeに関連する証明書
(図の横幅の都合でWorker-2の記載は省いています)
ここまでがChap4のハンズオンの説明となります。
(+α) 証明書の役割とTLSの概要
そもそも、KubernetesにおいてTLS証明書がどのような役割を果たすかを整理しておきます。
結論から記載すると、KubernetesにおいてTLS証明書は通信の暗号化と各コンポーネントの認証のために利用されています。
Kubernetesの公式ドキュメント内の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
下の方にこのような記載もありましたが、Hard wayの内容に関係ない部分なので割愛します。 (front-proxyとetcdのクラスタ内で利用される証明書に関する説明だと思われます。)
- Client and server certificates for the front-proxy
- etcd also implements mutual TLS to authenticate clients and peers
上記から、API server (もしくはAPI Serverと通信するコンポーネント) がTLS証明書を利用していることがわかります。
また、Client certificate
とServer certificate
の2種類のTLS証明書が発行されていることがわかると思います。
TLSの仕様と照らし合わせながら、KubernetesでのTLS証明書の利用用途を説明します。
この記事では、コンポーネントの認証に関連するTLSにおける認証に関する話をメインでまとめます。
TLSの仕様では通信相手 (=サーバー側) の認証が必須となっています。
具体的には、サーバー証明書の正当性検証とサーバーからの署名情報の検証によってサーバの認証(真正性の確認)を行います。
上記の目的のために、サーバー側であるAPI ServerはServer certificate
を利用します。
また、TLSではクライアント証明書を利用したクライアント側の認証が可能となっています。
milestone-of-se.nesuke.com
こちらの記事にも、TLSクライアント認証に関する説明が記載されています。 jovi0608.hatenablog.com
このオプションを利用してAPI Serverに対して通信するクライアントの認証を行うため、クライアント側 (=通信元) である各コンポーネントはClient certificate
を利用します。
TLSの一連のシーケンスについて図示すると下記のようになります。
証明書の正当性検証と署名情報の検証部分を詳細に図示するとこのようになります。
サーバー証明書の正当性検証のために、CA証明書(正確には証明書にバンドルされている公開鍵)を利用していることがわかります。
上記のフローを実現するために、手順の中でWorker Nodeに認証局(CA)の証明書 (=ca.pem)を配布する必要があります。
また、署名情報の検証の部分では鍵交換で使うデータを秘密鍵で署名してクライアントに送付し、クライアントはサーバー証明書にバンドルされた公開鍵を用いて署名の検証を行っています。
> for instance in worker-0 worker-1 worker-2; do gcloud compute scp ca.pem ${instance}-key.pem ${instance}.pem ${instance}:~/ done Warning: Permanently added 'compute.7526976075834096736' (ECDSA) to the list of known hosts. Enter passphrase for key '/Users/.../.ssh/google_compute_engine': // Worker Node(クライアント側となるNode)に認証局(CA)の証明書 (=ca.pem)を配布している ca.pem worker-0-key.pem worker-0.pem
また、クライアント証明書の検証について図示するとこのようになります。
Client Certificate
以前のハンドシェイクのデータを秘密鍵で署名してサーバ側に送付し、サーバ側はクライアント証明書にバンドルされた公開鍵を用いて署名の検証を行っています。
上記のようなフローにより、クライアント・サーバ側の両者の認証が行われます。
余談ですが、クライアント認証も行うTLSのことをmTLS (Mutual TLS authentication)と言います。(Istioに詳しい方は頻繁に聞く単語かもしれません。)
今回は、Chap4の内容をまとめました。
TLS周りの話は正確に理解する必要があるので、図や参考にした資料のリンクを全て載せておきました。
他に参考にした資料のリンクはこちらです。
今なぜHTTPS化なのか?インターネットの信頼性のために、技術者が知っておきたいTLSの歴史と技術背景 - エンジニアHub|若手Webエンジニアのキャリアを考える!