対称暗号化の基本
対称暗号では、通信する二者が同一の秘密鍵(共有鍵)と共通の暗号アルゴリズムを用いる。送信者は平文を共有鍵と暗号関数で暗号化して送り、受信者は同じ鍵で復号する。この方式はシンプルだが、鍵の管理が問題になる。すべての通信相手と同じ鍵を使い回すと、鍵が漏洩した際に全通信が危険にさらされる。
非対称暗号の登場
非対称暗号では、公開鍵と秘密鍵のペアを用いる。公開鍵で暗号化したデータは対応する秘密鍵でしか復号できず、秘密鍵で暗号化したデータは公開鍵でしか復号できない。サーバは鍵ペアを生成し、公開鍵をクライアントに配布する。クライアントがサーバの公開鍵でリクエストを暗号化すれば、サーバだけが秘密鍵で内容を読める。しかし、サーバからの応答を秘密鍵で暗号化すると、公開鍵を持つ誰もが復号できてしまうため、機密性が保てない。
ハイブリッド方式と中間者攻撃
実際の通信では、対称暗号と非対称暗号を組み合わせる。クライアントが一時的なセッション鍵(共通鍵)を生成し、サーバの公開鍵で暗号化して送る。サーバは秘密鍵でセッション鍵を復号し、以降はそのセッション鍵を使った対称暗号で通信する。しかし、攻撃者が通信経路に割り込み、クライアントに偽の公開鍵を渡せば、すべての通信を盗聴・改ざんできる。問題は、クライアントが受け取った公開鍵を無条件に信頼してしまう点にある。
認証局(CA)による信頼の確立
この問題を解決するため、信頼できる第三者機関である認証局(CA)が導入された。CAは自身の公開鍵・秘密鍵を持ち、あらかじめ信頼されている。サーバは自身の公開鍵とドメイン情報をCAに登録し、CAはそれを自身の秘密鍵で署名した「証明書」を発行する。クライアントはCAの公開鍵を用いて証明書の署名を検証し、ドメイン名と一致することを確認してから、サーバの公開鍵を取り出す。攻撃者が偽の証明書を作っても、CAの署名がないか、ドメインが一致しないため検出される。
Kubernetesクラスタにおける証明書の役割
Kubernetesクラスタでは、APIサーバが中心的なコンポーネントとなり、Controller Manager、Scheduler、kubeletなどがクライアントとして通信する。APIサーバは自身のサーバ証明書(公開鍵証明書)と秘密鍵を持ち、kubeletなどのクライアントはCA証明書(CAの公開鍵)を使ってAPIサーバの証明書を検証する。これにより、APIサーバのなりすましを防ぎ、安全な通信が確立される。
ここでは、実際にcfsslを使って自己署名証明書を作成し、APIサーバに証明書を発行する手順を例示する。まず、cfsslとcfssljsonをダウンロードする。
curl -L "https://github.com/cloudflare/cfssl/releases/download/v1.6.4/cfssl_1.6.4_linux_amd64" -o /usr/local/bin/cfssl
curl -L "https://github.com/cloudflare/cfssl/releases/download/v1.6.4/cfssljson_1.6.4_linux_amd64" -o /usr/local/bin/cfssljson
chmod 755 /usr/local/bin/cfssl /usr/local/bin/cfssljson
次に、CAとして機能するためのルート証明書を作成する。設定ファイル ca-csr.json を以下のように用意する。
{
"CN": "cluster-ca",
"key": {
"algo": "ecdsa",
"size": 256
},
"names": [
{
"C": "JP",
"ST": "Tokyo",
"L": "Tokyo",
"O": "MyCluster",
"OU": "DevOps"
}
],
"ca": {
"expiry": "87600h"
}
}
以下のコマンドでCAの秘密鍵(ca-key.pem)と証明書(ca.pem)を生成する。
cfssl gencert -initca ca-csr.json | cfssljson -bare /etc/kubernetes/pki/ca
APIサーバ用の証明書要求(CSR)を apiserver-csr.json に定義する。このファイルには、APIサーバが使用するホスト名やIPアドレスを列挙する。
{
"CN": "kube-apiserver",
"key": {
"algo": "ecdsa",
"size": 256
},
"names": [
{
"C": "JP",
"ST": "Tokyo",
"L": "Tokyo",
"O": "MyCluster",
"OU": "DevOps"
}
],
"hosts": [
"127.0.0.1",
"192.168.1.10",
"kubernetes",
"kubernetes.default",
"kubernetes.default.svc",
"kubernetes.default.svc.cluster.local",
"10.96.0.1",
"10.96.0.2",
"10.96.0.3"
]
}
署名プロファイルを定義する ca-config.json も作成する。ここでは、サーバ認証とクライアント認証の両方の用途を許可する。
{
"signing": {
"default": {
"expiry": "87600h"
},
"profiles": {
"kubernetes": {
"usages": [
"signing",
"key encipherment",
"server auth",
"client auth"
],
"expiry": "87600h"
}
}
}
}
最後に、CAの鍵と証明書を使ってAPIサーバの証明書を発行する。
cfssl gencert \
-ca=/etc/kubernetes/pki/ca.pem \
-ca-key=/etc/kubernetes/pki/ca-key.pem \
-config=ca-config.json \
-profile=kubernetes \
apiserver-csr.json | cfssljson -bare /etc/kubernetes/pki/apiserver
これにより、APIサーバの証明書(apiserver.pem)と秘密鍵(apiserver-key.pem)が生成される。APIサーバは起動時にこれらのファイルを指定し、kubeletなどのクライアントはCA証明書(ca.pem)を信頼してAPIサーバとの通信を確立する。