问题一
"kubelet 和 APIServer 之间的双向 TLS(mTLS)到底是怎么跑起来的?"
本文按从底层原理到 Kubernetes 视角,分两条线讲:
- 纯 TLS 协议视角:双方如何握手、校验证书
- Kubernetes 视角:APIServer 怎么把证书身份变成
system:node:<nodeName>这个"用户",再做鉴权
一、先对齐:什么是「双向 TLS / mTLS」
- 单向 TLS(普通 HTTPS) :
客户端校验服务器证书(我连的是不是正版网站?)
服务器不校验客户端是谁(除非用额外认证,比如 token、cookie)。 - 双向 TLS(mTLS) :
✅ 服务器校验客户端证书(你是谁?)
✅ 客户端也校验服务器证书(你是不是正版?)
在 Kubernetes 里:kubelet = TLS 客户端、APIServer = TLS 服务器、两边都带证书 → mTLS
二、从「网络协议」角度看 mTLS 握手流程
以 kubelet 正常工作时调用 APIServer 为例(比如上报心跳、获取 PodSpec):
0️⃣ 建立 TCP 连接
kubelet 先拨号:
text
kubelet → TCP → apiserver:6443
成功后,开始 TLS 握手。
1️⃣ ClientHello(kubelet 发起)
kubelet 发:
- 支持的 TLS 版本
- 支持的密码套件(cipher suites)
- 随机数等参数
此时 还没有证书,也还没认证任何身份。
2️⃣ ServerHello + ServerCertificate(APIServer 证明自己的身份)
APIServer 回:
- ServerHello:选定 TLS 版本 + 密码套件
- Certificate :发自己的服务器证书链(
--tls-cert-file对应) - 可选:一些扩展,如 ALPN 等
ServerHelloDone
kubelet 做的事:
- 拿到 APIServer 的证书链
- 用本地的 CA 证书校验:
- 这个证书是不是被我信任的 CA 签发的?
- 证书没过期吗?
- 证书里的 SAN / CN 是否匹配我要访问的主机名或 IP?
这里 kubelet用的是:
bash
/etc/kubernetes/pki/ca.crt
或者通过 kubeconfig 里的 certificate-authority-data。
如果校验失败:
kubelet 直接认为 "API Server 不可信",握手失败,连接断开。
3️⃣ CertificateRequest(APIServer 要客户端也出示证书)
因为 APIServer 配置了:
bash
--client-ca-file=/etc/kubernetes/pki/ca.crt
表示:
"我要求客户端也用 CA 签发的证书来证明自己的身份。"
所以在握手过程中,APIServer 会发一个 CertificateRequest 消息,意思是:
"请你也发一个客户端证书给我,我要认证你。"
4️⃣ kubelet 发送 ClientCertificate + 证明自己确实拥有私钥
kubelet 现在要出示自己"办好的工牌":
- 从 kubeconfig 配置中读到:
yaml
client-certificate: /var/lib/kubelet/pki/kubelet-client-current.pem
client-key: /var/lib/kubelet/pki/kubelet-client-current.pem
(这个文件里既有证书,也有私钥)
然后在握手中发送:
-
Client Certificate:
- 里面的 Subject:
CN = system:node:<nodeName>O = system:nodes
- Issuer = 集群 CA
- 里面的 Subject:
-
ClientKeyExchange + CertificateVerify:
-
用私钥对部分握手数据签名,证明:
"这个证书真的是我自己的,不是偷来的。"
-
APIServer 收到后,会:
- 用
--client-ca-file提供的 CA,校验证书:- 签发者是否是这个 CA
- 是否未过期
- 用证书中的公钥校验签名,确认"对方确实拥有私钥"
如果都 OK,则 客户端认证成功。
5️⃣ 双方生成会话密钥,握手完成
接下来就是标准 TLS 流程:
- 双方根据前面交换的信息,生成对称会话密钥
- 后续 HTTP 请求和响应,全部用这个对称密钥加密传输
至此,从传输层视角 已经建立了一个:
"既机密、又认证了双方身份"的双向 TLS 通道。
三、从「Kubernetes」视角看:APIServer 如何把证书 → 用户身份
TLS 层做完之后,接下来就是:
"把 X.509 证书里的信息映射成 Kubernetes 里的 user / group 身份。"
APIServer 在配置了 --client-ca-file 后,会启用 x509 client cert authenticator,流程大致是:
1️⃣ 从证书中抽取 Subject
证书 subject 一般长这样:
CN=system:node:<nodeName>O=system:nodes
Kubernetes 约定:
- CN(Common Name) → username
- O(Organization) → group(可以多个)
于是 APIServer 把 kubelet 视为:
text
username = system:node:<nodeName>
groups = ["system:nodes", "system:authenticated"]
这就是我们平时在日志 or
kubectl auth can-i中看到的身份。
2️⃣ 然后交给「授权(Authorization)」阶段
认证只是告诉 APIServer:
"对方是谁。"
接下来要问:
"这个身份 能 做什么?"
APIServer 内部的授权模块(如 RBAC + NodeAuthorizer)会:
- 根据请求内容:
verb(get/list/watch/create/update/delete)
resource(pods/nodes/secrets/...)
namespace(default/kube-system/...)
name(具体对象) - 结合请求发起者的:
- username =
system:node:<nodeName> - groups =
system:nodes等
- username =
查一遍 RBAC 规则 和 NodeAuthorizer 规则:
- 节点可以:
- 获取/更新自己的 Node 对象
- 获取/更新和自己有关的 PodStatus
- 获取需要运行的 PodSpec 等
- 但不能:
- 随便看其他 Node 的敏感信息
- 读取任意 Secret
- 改其他 Namespace 的资源(有严格限制)
如果授权通过:
请求被执行(例如 kubelet 上报心跳 / 获取 Pod)。
如果授权失败:
kubelet 收到 403 Forbidden,日志里能看到对应错误。
四、把这两条线合并成一个"完整故事"
当 kubelet 和 APIServer 通信时,每一次 HTTP 请求,都可以拆成两部分理解:
传输安全层(TLS)
- kubelet 拨 API Server 端口
- TLS 握手完成 mTLS:
- APIServer 身份通过服务器证书被 kubelet 验证
- kubelet 身份通过客户端证书被 APIServer 验证
- 建立加密通道
Kubernetes 认证 + 授权
- APIServer 读取客户端证书 Subject:
- 映射为
user = system:node:<nodeName>group = system:nodes
- 映射为
- 交给 RBAC + NodeAuthorizer:
- 看这个"用户"是否有权限干这件事
- 如果允许 → 正常返回
如果不允许 → 返回 403
整个过程对应用层来说是透明的,kubelet 只管:
-
配好 kubeconfig(证书+私钥+CA)
-
发 HTTP 请求
其余的都由 TLS + APIServer 完成。
五、和 bootstrap token 阶段对比一下,更好理解
之前的 bootstrap 认证是:
kubelet 用 token 来当临时凭证,只能做少数事情(例如发 CSR)。
现在 mTLS 阶段是:
kubelet 用 证书+私钥 来证明自己是
system:node:<nodeName>,有完整的 Node 角色能力(但仍受限于 RBAC&NodeAuthorizer)。
我们可以这么理解:
- token=一次性访客码
- 证书 + 私钥=正式工牌 + 指纹
六、如果想"现场观察"这个过程,可以做几件有趣的实验
如果是在真实集群里玩,可以尝试:
-
在 kubelet 节点上查看证书信息:
bashopenssl x509 -in /var/lib/kubelet/pki/kubelet-client-current.pem -noout -text -
在 APIServer 节点上看启动参数(kube-apiserver 静态 Pod 的 manifest):
bashcat /etc/kubernetes/manifests/kube-apiserver.yaml | grep client-ca-file -
在 master 上用:
bashkubectl auth can-i get pods --as=system:node:<nodeName> --as-group=system:nodes看看这个 Node 身份能做什么,不能做什么。
七、总结
kubelet ↔ APIServer 双向 TLS 的本质是:用集群 CA 签发的客户端证书,把 kubelet 认证为 system:node:<nodeName>,然后再通过 RBAC + NodeAuthorizer 控制它能访问哪些资源,所有请求都跑在加密的 mTLS 通道之上。
Server(API Server) Client(kubelet) Server(API Server) Client(kubelet) 前提(mTLS,按 TLS 1.2 语义简化): 1) C 本地信任 CA 证书 ca.crt(用于验证 S 的证书) 2) S 本地信任 CA 证书 ca.crt(用于验证 C 的证书) 3) S 持有服务端证书+私钥(ServerCert/ServerKey) 4) C 持有客户端证书+私钥(ClientCert/ClientKey) Client 侧校验 Server: - 用本地 ca.crt 验证证书链签名 - 校验有效期 / KeyUsage / EKU - 校验 SAN 中主机名/IP 匹配(仅 Client 侧) 失败则中断 Server 侧校验 Client: - 用本地 ca.crt 验证证书链签名 - 校验有效期 / KeyUsage / EKU(ClientAuth) - 不做"主机名/IP 匹配"(这是 Server 证书校验语义) 私钥持有证明: - 用 ClientCert 中公钥验证签名 Verify(pubKey, handshake_hash, signature) 成功 ⇒ 证明 C 持有对应私钥 至此 TLS 通道建立: 1) 双方完成证书链校验(mTLS 身份真实性) 2) 双方确认对方"持有私钥" 3) 协商出对称会话密钥,后续数据加密传输 TLS 成功后进入 Kubernetes 认证/鉴权(非 TLS 协议本身): - X.509 Authenticator:从 ClientCert 取 Subject CN → username(如 system:node:<nodeName>) O → groups(如 system:nodes) - Authorization:RBAC + NodeAuthorizer 决定可执行操作 ClientHello (TLS 版本、cipher suites、随机数...) 1 ServerHello (选择版本 & cipher) 2 ServerCertificate (服务端证书链:ServerCert + intermediates) 3 CertificateRequest (请求客户端证书,用于 mTLS) 4 ServerHelloDone 5 ClientCertificate (客户端证书链:ClientCert + intermediates) 6 ClientKeyExchange (密钥协商相关数据) 7 CertificateVerify (ClientKey 对握手摘要签名) 8 ChangeCipherSpec 9 Finished(已加密) 10 ChangeCipherSpec 11 Finished(已加密) 12 HTTPS API Request(加密通道内) 13 HTTPS API Response(加密通道内) 14
问题二
客户端用本地 CA 公钥验证"证书是否由受信 CA 签发",这个证书指的是谁的?怎么一步步验证?
答案分两层:
✔️ 这里验证的证书是 服务器的证书(在 mTLS 下,后来也会验证客户端证书)
✔️ 验证的核心动作是:用"CA 公钥"去验证"证书里的签名" ,如果能验证通过 → 说明:这个证书确实是那个 CA 签发的,没被篡改。
一、谁的证书在被客户端验证?
在 TLS 握手早期:
ClientHello →
← ServerHello + ServerCertificate
服务器把 自己的证书 发给客户端,证书里面写着:
- Server 公钥
- 服务器身份(域名 / IP)
- 颁发者:某个 CA
- 有效时间
- 证书用途
- 以及 CA 对它做的数字签名
所以,当我们说 "客户端用 CA 公钥验证证书" ,指的就是:客户端在检查服务器证书是否真的由那个 CA 签发。
二、CA 给证书做了什么(签名原理)
当服务器向 CA 申请证书时:
1️⃣ 服务器生成公钥/私钥
2️⃣ 把「公钥 + 身份信息」打包成 CSR
3️⃣ 发送给 CA
4️⃣ CA 审核通过后,做两件事:
A = (服务器公钥 + 身份 + 有效期 + 用途 + 颁发 CA 信息 + ...)
B = hash(A)
signature = Sign( B , CA_private_key )
然后把 signature 和 A 一起打包成证书(X.509)。
📌 这意味着:只有 拥有 CA 私钥 的一方才能生成合法 signature。
三、客户端验证的过程(关键步骤)
客户端拿到证书之后,会做这样的事:
第一步:提取内容和签名
从证书中分出两部分:
证书内容(A)
签名(signature)
第二步:重新计算 hash
B' = hash(A)
第三步:用 CA 公钥做"验证"
Verify( CA_public_key , B' , signature )
如果验证成功:
说明是 用这个 CA 的私钥签发的(可信) 且证书内容未被修改(完整性 OK)
如果失败:
这个证书要么被篡改,要么不是受信 CA 签的 → 直接拒绝。
四、如何判断"是否是受信 CA"?
客户端的系统 / 程序内置一组:
- 信任 CA 列表
例如:
- Linux:
/etc/ssl/certs/ - 浏览器内置 CA store
- Kubernetes/kubelet:
ca.crt
TLS 规则:
只有在 信任列表中的 CA 才可以给证书背书。
如果证书的签发者不在列表中:
UNTRUSTED CERTIFICATE
连接会被拒绝。
五、那"服务器身份"又是怎么验证的?
除了检查是不是受信 CA 签发外,客户端还会:
✔ 读取证书中的:
Subject Alternative Name (SAN)
里面列着:
- 域名 / IP 白名单
然后:
如果请求的域名/IP 不在证书允许的列表中 → 失败
比如:
证书签发给
api.cluster.local但你访问的是1.2.3.4→ 会报 证书不匹配。
六、回到最初的问题
问:客户端用本地 CA 公钥验证证书是否由授信 CA 签发,这个证书是服务器的吗?
✔ 是的------在 TLS 握手初期,是服务器证书。(在 mTLS 中服务器也会验证客户端证书,但原理相同。)
问:具体是怎么验证的?(几个步骤浓缩一下)
服务器 → 发证书(含公钥 & CA 签名)
客户端:
1)取出证书内容 & 签名
2)重新计算 hash
3)用 CA 公钥验证签名
4)确认证书没改过,且证书确实由受信 CA 签发
5)检查域名/IP 是否匹配
6)检查是否过期
完全通过后:客户端确认 ------ "对方是真的服务器。"
七、实验一下
下载任意 HTTPS 站点证书:
bash
echo | openssl s_client -connect www.google.com:443 -showcerts
然后验证:
bash
openssl verify -CAfile <系统CA路径> server-cert.pem
就会看到:
server-cert.pem: OK
这就是我们刚刚讲的验证过程。
八、总结
客户端验证的是 服务器证书 ;验证方法是:用受信 CA 的公钥,检查证书中的数字签名是否正确,以此确认证书真的来自受信 CA,而且没有被篡改。
问题三
从证书中分出两部分:证书内容(A) 、签名(signature) 。这两部分分别是什么?
我们把证书拆开来看一下。
| 部分 | 含义 | 是否可读 | 谁生成 |
|---|---|---|---|
| 证书内容(A = TBSCertificate) | 证书真正携带的信息(公钥、身份、有效期等) | 可以解析并查看 | 由证书持有人/CA 构造 |
| 签名(signature) | CA 用自己的私钥对 A 的摘要 做的数字签名 | 机器可验证 | 由 CA 生成 |
🔹 A 是"要声明的内容"
🔹 signature 是"CA 的背书证明"
一、先看「证书内容(A)」到底包含什么
在 X.509 规范里,它的正式名字是:
TBSCertificate(To Be Signed Certificate ------ "待签名部分")
我们可以用 openssl 查看:
bash
openssl x509 -in cert.pem -noout -text
我们看到的所有可读内容,基本都属于 A:
🧾 A(TBSCertificate)包含内容(简化)
✔ 版本号 (v1/v3)
✔ 序列号(唯一 ID)
✔ 签名算法标识(未来要用哪种算法签名)
✔ Issuer(颁发者)
✔ Validity(有效期)
-
Not Before
-
Not After
✔ Subject(证书拥有者身份)
- CN / O / OU / C 等
✔ Subject Public Key Info(主体公钥信息)
- 公钥算法(RSA/ECDSA...)
- 公钥值
✔ 扩展字段(Extensions)
- Key Usage(密钥用途)
- Extended Key Usage(client auth / server auth)
- Subject Alternative Name(域名/IP)
- Basic Constraints(是否为 CA)
- CRL / OCSP 信息
- ...
👉 总结:A = 证书自身所有"声明的内容",并且 A 是可读且透明的(并非加密的),任何人可以解析看到里面写了什么。
二、再看「签名(signature)」是什么
我们把 A 计算一次摘要:
digest = HASH(A)
HASH 取决于证书里记录的算法,比如:SHA256、SHA384 等等。
然后:
signature = SIGN( digest , CA_private_key )
也就是:
用 CA 的 私钥 对 A 的 hash 进行数字签名。
签名结果就是一个大块二进制数据,放在证书的 签名值字段中。
三、完整证书结构(抽象结构)
证书的逻辑结构可以写成:
Certificate {
tbsCertificate = A
signatureAlgorithm = ...
signatureValue = signature
}
我们可以理解为:
「内容(A)」 + 「签名算法」 + 「签名值」。
四、验证时两部分如何配合?
客户端验证时:
① 重新计算 A 的 hash
digest' = HASH(A)
② 使用 CA 公钥验证 signature
Verify( CA_public_key , digest' , signature )
如果成功说明:
- 这张证书确实由 该 CA 的私钥签过
- A 的内容没有被篡改
👉 要是有人篡改了 A:
digest' != 原来的 digest
验证立刻失败。
五、可以用 openssl 看到两部分
🔍 只打印 "待签名部分 A"
openssl x509 -in cert.pem -noout -text -certopt no_signature
🔍 查看签名本身
openssl x509 -in cert.pem -noout -signature -text
我们会看到一个大段 Base64 的签名值。
六、再用一句"生活比喻"巩固
A = 合同正文(写了双方身份、条款等)
signature = 公证处盖章(确保内容真实、未被改动)
合同可以阅读;章不是加密,而是 验证用。
七、总结
证书内容(A = TBSCertificate)
是"要被签名的全部信息":身份、公钥、用途、有效期、扩展等。
签名(signature)
是 CA 用私钥对
hash(A)做的数字签名,用来证明:"这个证书确实是我签过的,而且没被改过。"
问题四
客户端证书和服务器证书,一开始是从哪儿来的?谁颁发?怎么拿到?
答案其实是:
都来自同一个"证书体系(PKI)",由一个受信任的 CA(证书颁发机构)签发。
不过在不同系统里(例如 Kubernetes、浏览器 HTTPS、企业内部系统)获取方式有所不同。
下面,我们分三层讲:
1️⃣ 先讲通用原理:证书是怎么"被签出来"的
2️⃣ 然后讲 Kubernetes 里的 server/client 证书
3️⃣ 最后讲互联网 HTTPS 的证书来源
一、通用原理:证书的来源(PKI 工作流)
任何一张合法证书(客户端或服务器)都会经历这 4 步:
① 生成密钥对(本地完成)
无论是客户端/服务器,都要先创建:
公钥(public key)
私钥(private key)
示例命令:
bash
openssl genrsa -out server.key 2048
👉 私钥 永远不离开本机
👉 公钥用于"公开 + 放进证书中"。
② 生成 CSR(证书申请)
接下来生成:
CSR(Certificate Signing Request)= 证书签名请求
里面包含:
- 公钥
- 希望被写进证书的身份信息(域名、组织、用途等)
例如:
bash
openssl req -new -key server.key -out server.csr
③ 发送给 CA 审核 & 签名
把 CSR 交给 证书颁发机构 CA:
- 可能是公司内部 CA(自建 PKI)
- 或互联网 CA(比如 DigiCert、Let's Encrypt)
- Kubernetes 集群自己的 CA(内置)
CA 做两件事:
✔ 验证"你是谁"
✔ 用 CA 私钥 对 CSR 内容签名(生成证书)
得到证书:server.crt
④ 安装证书 & 配置系统
- 服务器 → 配置 server.crt + server.key
- 客户端可能拿到 client.crt + client.key
然后双方都要:
预先信任 CA 的 公钥(根证书)
二、在 Kubernetes 里:证书来源
K8S 自己有一个 内置 CA,默认由 kubeadm 初始化:
📁 放在控制节点:
/etc/kubernetes/pki/ca.crt # CA 公钥
/etc/kubernetes/pki/ca.key # CA 私钥(绝密)
1️⃣ 服务器证书(API Server)
初始化时:
kubeadm init
会自动:
- 生成 APIServer 公钥/私钥
- 生成 CSR
- 用 CA 私钥签发 server 证书
得到:
/etc/kubernetes/pki/apiserver.crt
/etc/kubernetes/pki/apiserver.key
👉 这个就是:
API Server 的 HTTPS 服务器证书
2️⃣ 客户端证书(kubelet / controller / admin)
客户端证书有三类常见来源:
✔ kubelet(节点)
- kubelet 生成密钥对
- 提 CSR 给 APIServer
- kube-controller-manager 自动审批
- CA 签发客户端证书
存放:
/var/lib/kubelet/pki/kubelet-client-current.pem
✔ 管理员 kubeconfig
kubeadm 也会创建 admin 的客户端证书:
/etc/kubernetes/admin.conf
里面包含:
- client-certificate
- client-key
- cluster CA
我们 kubectl 的时候就是用它认证。
✔ 各控制组件(scheduler / controller)
类似:
/etc/kubernetes/scheduler.conf
/etc/kubernetes/controller-manager.conf
都包含:
- 组件客户端证书
- 组件私钥
- 集群 CA
全部由 CA 签发。
关键点
**在 Kubernetes 里,绝大多数 server/client 证书都由"集群内置 CA" 自动签发,**管理员很少手动发证书(除非特别场景)。
三、在互联网上(HTTPS 网站)
当我们访问:
https://www.google.com
服务器证书的来源是:
✔ 网站向 Public CA (DigiCert / Let'sEncrypt / GlobalSign...)申请
✔ CA 验证域名所有权
✔ 签发 server.crt
浏览器信任的 CA 列表,存放在Chrome / Firefox 内置 或 OS 提供
客户端证书(互联网少见)
少量企业系统会要求客户端也必须提供证书(mTLS)
客户端证书来源:
- 通常由 企业内部 CA 系统 签发
- 发到员工电脑/手机
- 安装在浏览器 / 证书存储里
总结
1️⃣ 证书本身不是"凭空生成",而是 CA 根据 CSR 签出来的。
2️⃣ 客户端 & 服务器证书都可以由同一个 CA 签发(Kubernetes 就是这样)。
3️⃣ 只要双方都信任同一个 CA,就能够通过 TLS 建立安全身份信任。
问题五
Kubernetes API Server 的核心安全文件:
/etc/kubernetes/pki/apiserver.crt
/etc/kubernetes/pki/apiserver.key
它们分别是什么?有什么用?什么时候参与 TLS?
| 文件 | 含义 | 作用 |
|---|---|---|
| apiserver.crt | API Server 的 服务器证书(X.509 证书) | 发给客户端(kubelet / kubectl / controller 等)用来验证"你连接的是正版 APIServer" |
| apiserver.key | API Server 的 私钥 | 用于 TLS 握手签名证明"证书确实属于我",绝不能泄漏 |
👉 **这两者一起构成 API Server 的 HTTPS 身份,**就像网站的 server.crt + server.key。
一、apiserver.crt ------ API Server 的"身份证"
这是一个 公开证书,内容包括:
- API Server 的 公钥
- 主机身份(域名/IP):
kuberneteskubernetes.default10.x.x.x(Service IP)- 节点 IP
- 有效期
- 颁发者(
CN=kubernetes这种 CA) - 证书用途:
- TLS Web Server Authentication
- TLS Web Client Authentication(部分环境)
我们可以查看:
bash
openssl x509 -in /etc/kubernetes/pki/apiserver.crt -noout -text
客户端(比如 kubelet、kubectl、controller-manager)在连接 API Server 时:
1️⃣ 接收这个证书
2️⃣ 使用 CA (/etc/kubernetes/pki/ca.crt) 验证:
- 证书是否由受信 CA 签发
- 证书是否未过期
- 域名/IP 是否匹配目标 API 地址
验证成功:
客户端确认 ------ "我连的就是集群里真正的 API Server。"
二、apiserver.key ------ API Server 的私钥(极其敏感)
这个文件包含与 apiserver.crt 公钥成对的 私钥
它的作用是:
在 TLS 握手过程中,证明:"我确实拥有和证书匹配的私钥。"
流程是:
1️⃣ 客户端发起握手
2️⃣ API Server 发送 apiserver.crt
3️⃣ TLS 协议要求服务器用 私钥 对某些握手数据签名
4️⃣ 客户端用证书中的公钥验证这份签名
如果验证成功,客户端确认 ------ "不仅证书是真的,而且服务器真的掌握对应私钥,没有被冒充。"
⚠️ 一旦 apiserver.key 泄漏:
- 攻击者可以伪装成 API Server
- 截获 kubelet/kubectl/token/证书
- 甚至接管整个集群
👉 所以,apiserver.key 必须只在控制节点、root 可读。
三、这两者是在什么时候生成的?
在我们执行 kubeadm init 时生成的:
-
Kubeadm 自动创建 server 证书
-
用集群 CA(
ca.key)签发:/etc/kubernetes/pki/ca.crt
/etc/kubernetes/pki/ca.key
并把它配置到:
/etc/kubernetes/manifests/kube-apiserver.yaml
我们可以在里面看到:
yaml
- --tls-cert-file=/etc/kubernetes/pki/apiserver.crt
- --tls-private-key-file=/etc/kubernetes/pki/apiserver.key
- --client-ca-file=/etc/kubernetes/pki/ca.crt
四、谁会用到 apiserver.crt?
✔ kubelet
✔ scheduler
✔ controller-manager
✔ admin (kubectl)
✔ metrics-server 等外部组件
每个客户端都要 通过 APIServer 证书确认"服务端身份"。
五、总结
apiserver.crt = API Server 的服务器证书(公开,可分发)
apiserver.key = 对应的私钥(只能服务器自己使用)两者组合 → 支撑 API Server 的 HTTPS / mTLS 通信。
问题六
/var/lib/kubelet/pki/kubelet-client-current.pem里既有 CERTIFICATE 又有 PRIVATE KEY可是这个 CERTIFICATE 和
/etc/kubernetes/pki/ca.crt里显示的 证书内容不一样为什么?难道有问题吗?
👉 答案:完全正常,而且这是 TLS 工作的正常设计。
kubelet-client-current.pem 里的是:kubelet 自己的客户端证书
ca.crt 里的是:集群 CA(证书颁发机构)的证书(根证书)
🔹 两者 本来就不一样
🔹 但 kubelet 的证书是由 ca.crt 对应的 CA 签发的
所以,不是要内容相同 ,而是 kubelet 证书的 签名 可被 ca.crt 成功验证 ------ 这才是"可信"的关键。
一、对比一下这两类证书是谁的
1️⃣ kubelet-client-current.pem(客户端证书)
用途:
kubelet → 连接 API Server → mTLS 客户端身份认证
内容包括:
| 字段 | 说明 |
|---|---|
| Subject | CN=system:node:<node-name> |
| Group | O=system:nodes |
| 公钥 | kubelet 生成 |
| 用途 | Client Authentication |
| 签名 | 由 CA 私钥签名 |
查看:
bash
openssl x509 -in /var/lib/kubelet/pki/kubelet-client-current.pem -noout -text
2️⃣ ca.crt(CA 根证书)
用途:
作为"证书颁发机构"的身份,用来验证别人(包括 kubelet)的证书是否可信。
特点:
✔ Subject = Issuer(自签名)
✔ 通常标为:
CA: TRUE
keyCertSign: TRUE
查看:
bash
openssl x509 -in /etc/kubernetes/pki/ca.crt -noout -text
二、为什么看起来"不一样"?
因为 它们的角色完全不同。
| 文件 | 角色 | 谁的"身份证" |
|---|---|---|
| kubelet-client-current.pem | 客户端证书 | kubelet(节点) |
| ca.crt | 根证书(颁发机构) | CA 自己 |
就像:
- 你的身份证(个人证件)
- 公安局的公章证书(认证机构)
当然不会一样。
三、但它们之间一定有"数学关系"
虽然内容不一样,但有一个地方是会相互对应的:
🔍 kubelet 证书里的 Issuer 字段
查看 kubelet 证书:
Issuer: CN = kubernetes
查看 CA 证书:
Subject: CN = kubernetes
👉 说明:
kubelet 证书是 由这个 CA(ca.crt)签发的。
🔍 进一步:校验签名看看
bash
openssl verify -CAfile /etc/kubernetes/pki/ca.crt \
/var/lib/kubelet/pki/kubelet-client-current.pem
如果输出:
...: OK
→ 这就是完整证明:
虽然内容不同,但 kubelet 的证书 是由集群 CA 签名认证的,可信。
四、为什么把 CERTIFICATE & PRIVATE KEY 放在同一个 pem?
便于 kubelet:
- 读取一个文件即可
- 同时获取:
- 客户端证书
- 匹配的私钥
🔹 证书 = 公开信息 (可以给 API Server 看)
🔹 私钥 = 绝密,仅用于签名(永远不发送)
五、如果有一天kubelet-client-current.pem与ca.crt真的"完全一样",反而是灾难
如果:
kubelet-client-current.pem == ca.crt
意味着:
kubelet 拿着 CA 证书当作自己证书
那后果是:
- kubelet 伪装成 CA
- 可以伪造无限证书
- 集群安全 = 直接崩溃
👉 所以 不一样才是对的。
六、总结
/var/lib/kubelet/pki/kubelet-client-current.pem= kubelet 本机的 客户端证书 + 私钥
/etc/kubernetes/pki/ca.crt= 集群根 CA(签发别人的证书)
两者不一样是正常的。正确关系是:kubelet 证书由 ca.crt 对应 CA 签名。
问题七
kubeconfig 的核心结构 : /etc/kubernetes/admin.conf 里的这些字段展开介绍:
clusters:
- cluster:
certificate-authority-data: ???(看起来不像 ca.crt)
users:
- name: kubernetes-admin
user:
client-certificate-data: ???
client-key-data: ???
| 字段 | 含义 | 实际内容 | 为什么和 ca.crt/其他证书不一样 |
|---|---|---|---|
| certificate-authority-data | 集群 CA 证书(根证书) | ca.crt 的 Base64 内嵌版本 |
因为是 Base64 编码;不是逐字一致,但解码后内容相同 |
| client-certificate-data | admin 的客户端证书 | admin.crt |
它是"管理员自己的证书",当然不会等同 CA 证书 |
| client-key-data | admin 的私钥 | admin.key |
只给本地使用,永远不与别人相同 |
👉 admin.conf 内嵌了三样东西:
✔ 信任"谁签的证书"(CA)
✔ 证明"我是管理员"(客户端证书)
✔ 证明"证书真的属于我"(私钥)
1️⃣ certificate-authority-data ------ 集群 CA 证书(Base64)
问:
certificate-authority-data 为什么跟 /etc/kubernetes/pki/ca.crt 不一样?
原因是:
它其实就是
ca.crt,但被 Base64 编码后直接放进 kubeconfig 了。
我们可以验证:
bash
echo "<certificate-authority-data 内容>" | base64 -d
我们会看到熟悉的:
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
📌 作用:kubectl / API 客户端通过它验证 API Server 的证书是否合法
也就是:
- "这个 API Server 的证书是不是这个 CA 签的?"
- "这个证书有没有被篡改?"
2️⃣ client-certificate-data ------ kubernetes-admin 的客户端证书
这是 管理员(kubernetes-admin)的 X.509 客户端证书
它通常对应:
/etc/kubernetes/pki/admin.crt
内容里:
- CN =
kubernetes-admin - O =
system:masters
👉 通过 RBAC,这个 group 允许管理员拥有集群所有权限。
我们可以解码查看:
bash
echo "<client-certificate-data>" | base64 -d | openssl x509 -text
📌 关键点:
它是 用 CA 签发出来的 ,但 不是 CA 证书,所以内容当然与CA证书不一样。
就像:
- 公安局有"公章"
- 你有"身份证"
两者不是一张证件,但身份证是公安局盖章的。
3️⃣ client-key-data ------ kubernetes-admin 的私钥
这是管理员证书的 私钥。
对应:
/etc/kubernetes/pki/admin.key
Base64 解码:
bash
echo "<client-key-data>" | base64 -d
会看到:
-----BEGIN PRIVATE KEY-----
...
-----END PRIVATE KEY-----
📌 作用:
当 kubectl 连接 API Server 时,通过 TLS 签名证明:"我确实拥有 kubernetes-admin 这张证书。"
⚠️ 极其重要:
- 不能泄漏
- 泄漏 = 别人可以假装超级管理员
🔍 再整体看一眼 admin.conf 的工作方式
当我们运行:
kubectl get pods
流程是:
1️⃣ kubectl 读取 admin.conf
2️⃣ 用 client-certificate-data + client-key-data
→ 作为客户端证书(管理员身份)
3️⃣ 用 certificate-authority-data
→ 验证 API Server 证书
4️⃣ mTLS 成功 → API Server 识别为:
username = kubernetes-admin
groups = system:masters
然后 RBAC 允许几乎所有操作。
总结
certificate-authority-data = 集群 CA(信任谁)【Base64】
client-certificate-data = 管理员证书(我是谁)
client-key-data = 管理员私钥(证明证书是我的)
👉 看起来不一样,是因为:
- 一个是 CA(根证书)
- 一个是 admin 的个人证书
- 而且都经过 Base64 编码
不是问题,是设计如此。
问题八
/etc/kubernetes/pki/ca.crt难道本身不是 Base64 吗?
那为什么 kubeconfig 里还要再 Base64 一层?
答案是:
ca.crt 既是,也不是"Base64"。
它是 PEM 格式,而 PEM 里面确实包含了一段 Base64。
接下来,我们逐步介绍。
| 文件 | 实际格式 | 里面是否有 Base64 | 备注 |
|---|---|---|---|
/etc/kubernetes/pki/ca.crt |
PEM(文本) | ✔ 包含 Base64 数据 | 已经外层带有 BEGIN/END CERTIFICATE |
certificate-authority-data |
纯 Base64 字符串 | ✔ 对 PEM 再 Base64 一次 | 方便直接嵌入 YAML |
📌 关键区别不是 是否 Base64,而是:
PEM = Base64 + 头尾 + 换行 + 元信息
kubeconfig 里的 data = 原封不动再包一层 Base64
1️⃣ /etc/kubernetes/pki/ca.crt 的真实结构
打开看:
-----BEGIN CERTIFICATE-----
MIIC2DCC...
...
u8Q==
-----END CERTIFICATE-----
这就是 PEM(Privacy Enhanced Mail)格式。
PEM 由三部分组成:
1️⃣ 头部
-----BEGIN CERTIFICATE-----
2️⃣ Base64 编码的 DER(二进制证书)
3️⃣ 尾部
-----END CERTIFICATE-----
👉 所以我们看到的正文:
MIIC2DCC...
确实是 Base64 ------ 但它是带包装的。
2️⃣ kubeconfig 为什么还需要 Base64?
在 kubeconfig 中:
certificate-authority-data: <长串...>
这里存储的是 整个 PEM 文件再次 Base64 编码后的字符串。
为什么要再包一层?
✔ 1. YAML / JSON 需要安全传输
PEM 包含:
-----BEGIN CERTIFICATE-----
换行符
等号 =
斜杠 /
直接嵌入 YAML / JSON:
- 容易破坏格式
- 解析器不稳定
- 缩进 & 空格容易导致错误
✔ 2. 方便跨平台 & API 传输
Base64 是 通用"安全字符串"
- 可嵌入环境变量
- REST API
- ConfigMap
- 任意格式文件
所以 kubeconfig 采用:
PEM → Base64 → 放入 YAML
3️⃣ 我们可以验证两者是不是同一个证书
🔍 解码 kubeconfig
bash
echo "<certificate-authority-data>" | base64 -d > decoded-ca.crt
然后对比:
bash
diff decoded-ca.crt /etc/kubernetes/pki/ca.crt
大概率:
完全一致(可能仅换行不同)
4️⃣ 对比
| 名称 | 长什么样 | 含义 |
|---|---|---|
| PEM 文件 | -----BEGIN CERTIFICATE----- |
Base64 + 头尾 |
| Base64 纯字符串 | MIIC2DCC... |
只是编码后的一串文本 |
| kubeconfig data | TUlJQzJEQ0... |
对 PEM 整体再 Base64 |
总结
/etc/kubernetes/pki/ca.crt= PEM 文件 内含 Base64 数据,但格式可读。
certificate-authority-data= 把 PEM 整个 Base64 再编码一遍后的字符串。两者解码后内容其实是 同一个 CA 证书。
问题九
openssl x509 -in /var/lib/kubelet/pki/kubelet-client-current.pem -noout -text是什么意思?在做什么?
**这条命令是:用 openssl 读取一个 X.509 证书文件,并"人类可读地打印出来"。**而且它只会显示证书内容,不会输出原始 PEM(因为用了 -noout)。
🔍 我们逐个拆解参数
1️⃣ openssl
openssl 是一个 通用的加密工具箱,包含:
- 证书解析
- 编解码
- TLS 测试
- 签名/加密
- 生成证书等
2️⃣ x509
告诉 openssl:"我要操作的是 X.509 证书。"
X.509 是 TLS/HTTPS/Kubernetes 常用的证书标准格式。
3️⃣ -in /var/lib/kubelet/pki/kubelet-client-current.pem
指定 要读取哪个证书文件
这个文件是:
kubelet 的 客户端证书(附带私钥)
4️⃣ -noout
默认 openssl 会输出:
- 原始 PEM
- 再加解析输出
-noout 的作用:
不输出原始内容,只显示解析后的信息。
5️⃣ -text
表示:
以"人类可读"的格式打印证书结构
例如:
- 版本
- 序列号
- 签发者(Issuer)
- 持有者(Subject)
- 有效期
- 公钥信息
- 证书用途
- 扩展字段
- 证书签名算法
- 证书签名本身
实际输出示例(关键部分)
我们会看到类似:
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 12345
Signature Algorithm: sha256WithRSAEncryption
Issuer: CN = kubernetes
Validity
Not Before: Jan 1 00:00:00 2026 GMT
Not After : Jan 1 00:00:00 2027 GMT
Subject: CN = system:node:<hostname>, O = system:nodes
...
X509v3 Key Usage:
Digital Signature, Key Encipherment
X509v3 Extended Key Usage:
TLS Web Client Authentication
关键字段解释:
| 字段 | 含义 |
|---|---|
| Issuer | CA(谁签发的) |
| Subject | 证书属于谁(system:node:xxx) |
| Validity | 有效期 |
| Key Usage | 允许的用途(数字签名、认证等) |
| Ext Key Usage | client auth / server auth |
| Public Key | 公钥 |
为什么我们常用这条命令?
因为它能回答很多关键问题:
✔ 证书是谁签发的?
✔ 证书属于哪个节点?
✔ 是否为客户端证书?
✔ 是否快过期?
✔ 支持哪些用途?
✔ 是否由正确的 CA 签发?
在 Kubernetes 故障排查中必备。
总结
openssl x509 -in FILE -noout -text
把证书解析成人类可读形式,用来检查证书内容。
对于 kubelet:
/var/lib/kubelet/pki/kubelet-client-current.pem
👉 就是检查 kubelet 的客户端证书是否正确。
问题十
Kubernetes 证书过期了怎么办?会影响什么?怎么修?
我们分三层回答(从概念 → 判断 → 修复):
1️⃣ 哪些证书会过期、过期会发生什么
2️⃣ 如何判断是谁过期了
3️⃣ 最常见的 修复(续期)方案
1. 先弄清:哪些证书会过期(影响不同)
K8S 里常见证书:
| 证书 | 作用 | 过期后后果 |
|---|---|---|
API Server (apiserver.crt) |
API Server HTTPS | 整个集群几乎瘫痪 |
| Controller / Scheduler | 控制平面组件访问 API | 控制平面异常 |
Admin (admin.conf) |
kubectl 管理员 |
你登录不了,但集群还能跑 |
| kubelet-client | kubelet 访问 API | Node 变 NotReady |
| kubelet-server | kubelet 对外 TLS | 少量功能异常 |
👉 最危险的是:
apiserver.crt / controller / scheduler
2. 先确认:谁真的过期了?
在目标节点查看:
bash
openssl x509 -in <证书路径> -noout -dates
例如:
bash
openssl x509 -in /etc/kubernetes/pki/apiserver.crt -noout -dates
输出类似:
notBefore=...
notAfter=Jan 28 12:00:00 2026 GMT ← 过期时间
如果已经过去 → 就是 expired
3. 修复(按场景分)
场景 A:kubeadm 部署(最常见)
👉 kubeadm 提供了 一键续期
① 查看证书过期情况
bash
kubeadm certs check-expiration
② 续期(控制平面证书)
bash
kubeadm certs renew all
它会自动:
✔ 生成新证书
✔ 用当前 CA 签名
✔ 覆盖旧文件
③ 重启控制平面 Pod
证书更新后,重启 kube-apiserver / scheduler / controller:
bash
docker ps | grep kube-apiserver # containerd 的话用 crictl
通常只需要:
bash
systemctl restart kubelet
因为控制平面是 static pod,kubelet 会重新加载。
场景 B:kubelet 证书过期
kubelet 证书默认 会自动轮换(只要 API 可用)
但如果已经失效导致无法轮换:
解决方法:
1️⃣ 停止 kubelet
bash
systemctl stop kubelet
2️⃣ 删除 kubelet 证书(会自动重新申请 CSR)
bash
rm -f /var/lib/kubelet/pki/kubelet-client-current.pem
rm -f /var/lib/kubelet/pki/kubelet-client-*.pem
3️⃣ 重启 kubelet
bash
systemctl start kubelet
然后:
kube-controller-manager 会自动 approve CSR,kubelet 获得新证书。
场景 C:CA 证书过期(最复杂)
/etc/kubernetes/pki/ca.crt
如果 CA 本身过期:
所有证书都必须重新签发,集群需要"重建 trust 链"
这是 重大变更,通常建议:
✔ 维护窗口
✔ 官方文档步骤
✔ 甚至重新创建集群 + 迁移 workloads(视情况而定)
4. 特别常见的问题
❓ 管理员 kubectl 不能用:是致命吗?
不是。
~/.kube/config 或 admin.conf 证书过期
👉 只影响你的 CLI
👉 集群本身照样运行
解决:
bash
cp /etc/kubernetes/admin.conf ~/.kube/config
(如果 admin.conf 续了)
❓ 我改证书了,但服务还在用旧的?
→ 重启 kubelet(static pod reload)
bash
systemctl restart kubelet
❓ kubeadm certs renew all 会重建 CA 吗?
❌ **不会。**它只续签"依赖 CA 的组件证书"。
5、总结
发现报错 / 连接失败
↓
openssl x509 -in FILE -noout -dates ← 看谁过期
↓
kubeadm 环境?
↓ 是
kubeadm certs check-expiration
kubeadm certs renew all
systemctl restart kubelet
↓ 否
手工重新签发(需要 PKI 流程)
问题十一
/etc/kubernetes/pki/ 下的这些文件都是什么?
bash
apiserver.crt
apiserver-etcd-client.crt
apiserver-etcd-client.key
apiserver.key
apiserver-kubelet-client.crt
apiserver-kubelet-client.key
ca.crt
ca.key
etcd
front-proxy-ca.crt
front-proxy-ca.key
front-proxy-client.crt
front-proxy-client.key
sa.key
sa.pub
总览:它们大致分 4 大类
| 类别 | 说明 |
|---|---|
| API Server 相关 | 用于 HTTPS & 访问 etcd / kubelet |
| 集群 CA(根证书) | 签发其他证书 |
| Front-proxy 证书 | aggregator API 代理使用 |
| ServiceAccount 密钥 | Pod ServiceAccount Token 签名 |
| etcd 证书 | etcd 自己的 TLS(在 etcd 目录里) |
接下来逐个说明:
1️⃣ API Server 本身的证书(对外 HTTPS)
✔ apiserver.crt
👉 API Server 的服务器证书
- 包含 API Server 的公钥
- 写了允许访问的域名 / IP(
kubernetes、clusterIP、节点IP等) - 发给客户端(kubectl、kubelet、controller 等)用于验证服务端身份
作用:让别人确认:你连的是"正版" API Server
✔ apiserver.key
👉 API Server 的私钥(极度敏感)
- 与
apiserver.crt成对 - 在 TLS 握手中证明:"这张证书确实属于我"
⚠️ 一旦泄漏 → 可以伪装 API Server → 集群几乎失控
2️⃣ API Server 用来访问 etcd 的证书
✔ apiserver-etcd-client.crt
👉 API Server 访问 etcd 时使用的客户端证书
✔ apiserver-etcd-client.key
👉 对应的私钥
etcd 启用了 TLS 双向认证(mTLS):
-
etcd 校验:你是不是合法客户端
-
API Server 用这个证书访问 etcd
⚠️ 过期 → API Server 读不到 etcd → 集群崩溃
3️⃣ API Server 访问 kubelet 的证书
✔ apiserver-kubelet-client.crt
👉 API Server 调 kubelet API 时的客户端证书
✔ apiserver-kubelet-client.key
👉 对应私钥
API Server 需要查看 Pod 日志 / Exec / 端口转发 等时,会通过 kubelet 访问节点。
⚠️ 过期后:
kubectl logskubectl exec- 端口转发
等功能 会失败。
4️⃣ 集群 CA(根证书)
✔ ca.crt
👉 集群的根 CA 证书(公开)
- 所有组件的证书都由它签发
- 客户端通过它验证 APIServer 证书是否可信
✔ ca.key
👉 CA 私钥(绝密)
- 用来 签发所有控制平面证书
⚠️ 一旦泄漏:
任何人都能伪造合法证书 → 彻底失控
⚠️ 一旦过期:
所有证书必须重新签发,这是最复杂、最危险情况
5️⃣ front-proxy(聚合层 API 代理证书)
K8S 支持 API Aggregation(聚合 API),比如 metrics-server、apiserver-extension。
这时 API Server 需要 代理请求 给别的 API 服务。
✔ front-proxy-ca.crt
👉 代理系统专用的 CA 证书
✔ front-proxy-ca.key
👉 代理系统 CA 私钥
✔ front-proxy-client.crt
👉 API Server 作为"代理客户端"时使用
✔ front-proxy-client.key
👉 对应私钥
⚠️ 过期影响:metrics-server、扩展 API 可能变红、监控失败
6️⃣ ServiceAccount token 签名密钥(Pod 身份)
✔ sa.key(私钥)
✔ sa.pub(公钥)
作用:用来 签名和验证 ServiceAccount JWT Token,Pod 访问 API Server 时,就是拿这个 token 认证的。
⚠️ 如果删除/替换:
- 旧 Pod 的 token 失效
- 需要重新创建 Pod
7️⃣ etcd 目录(etcd 自己的 TLS)
/etc/kubernetes/pki/etcd/
里面通常有:
server.crt / server.key
peer.crt / peer.key
etcd/ca.crt
👉 用于:
- etcd 集群节点间 mTLS
- API Server 访问 etcd
⚠️ 任何 etcd 证书过期 → 集群立即出现严重问题。
总结
| 文件 | 作用 | 过期影响 |
|---|---|---|
| apiserver.crt/key | API Server HTTPS | 集群几乎瘫痪 |
| apiserver-etcd-client.* | API ↔ etcd mTLS | 控制面挂掉 |
| apiserver-kubelet-client.* | API ↔ kubelet | logs/exec 失效 |
| ca.crt | 根证书 | 校验证书 |
| ca.key | 根私钥 | 泄漏则毁灭级 |
| front-proxy-* | 聚合API | metrics / extensions 异常 |
| sa.key/sa.pub | Pod Token | 旧Pod失效 |
| etcd/* | etcd TLS | 直接崩溃 |
问题十二
apiserver-kubelet-client.key(私钥)在 "API Server 访问 kubelet" 这件事里到底起什么作用?
apiserver-kubelet-client.key
= API Server 的 "客户端私钥"
= 用来在 mTLS 握手中 证明:「我确实是合法的 API Server」
kubelet 通过验证它,才允许:kubectl logs、kubectl exec、端口转发 ...
没有它,API Server 无法建立受信任连接到 kubelet。
场景复盘:谁访问谁?
当我们执行:
kubectl logs pod
流程是:
kubectl → API Server → kubelet(对应节点)
📌 关键点:
访问 kubelet 的 不是 kubectl ,是 API Server 作为客户端。
所以:
- kubelet = 服务器(Server)
- API Server = 客户端(Client)
👉 这是 一个新的 TLS 连接(和 kubectl→API Server 不同)。
连接 kubelet 是 "双向 TLS(mTLS)"
Kubelet 配置了:
--client-ca-file=/etc/kubernetes/pki/ca.crt
意思是:"我只接受:由这个 CA 签发的客户端证书。"
于是 TLS 流程变为:
1️⃣ API Server 发起连接(客户端角色)
API Server → kubelet
2️⃣ kubelet 发送自己的服务器证书
(供 API Server 验证)
3️⃣ kubelet 要求:
"你也必须提供客户端证书。"
于是 API Server 发送:
- apiserver-kubelet-client.crt(客户端证书)
- 并使用 apiserver-kubelet-client.key 完成握手签名
那私钥(apiserver-kubelet-client.key)在 TLS 里具体做了什么?
它主要完成 两件关键事情:
作用 1:证明证书真的属于 API Server(数字签名)
TLS 会要求:"请用你的客户端私钥,对这次握手的摘要签名。"
过程:
signature = Sign(privateKey, handshake_hash)
然后 kubelet 用证书里的 公钥 去验:
Verify(publicKey, handshake_hash, signature)
验证成功:
kubelet 确认:
✔ 证书不是偷来的
✔ API Server 真的是合法客户端
✔ 而且证书内容未被篡改
作用 2:参与密钥协商(建立加密通道)
在 TLS 握手中:
- 一些数据会使用 客户端私钥参与签名
- 以确保中间人无法伪造连接
最终,双方协商出:
一个新的对称加密会话密钥
之后传输的:
- Pod 日志数据
- exec 指令数据
全部走 加密 TLS 通道
如果这个私钥丢失 / 损坏 / 权限错了
我们会看到报错类似:
Error from server: error dialing backend: x509: certificate signed by unknown authority
或:
unable to upgrade connection: unauthorized
具体表现:
❌ kubectl logs 失败
❌ kubectl exec 失败
❌ 端口转发失败
但:
✔ API Server 还能正常工作(只是不能访问 kubelet)
如果这个私钥泄漏(最危险)
攻击者可以假装 API Server 直接与 kubelet 通信
意味着:
⚠️ 可以在节点上执行命令
⚠️ 读取 Pod 日志
⚠️ 操作容器
→ 严重安全风险
👉 所以:
-rw------- root root
权限设计是对的。
与其他证书的对比
| 文件 | 用途 | API Server 是谁 |
|---|---|---|
| apiserver.crt/key | 提供 HTTPS 给客户端 | 服务器 |
| apiserver-etcd-client.* | 访问 etcd | 客户端 |
| apiserver-kubelet-client.* | 访问 kubelet | 客户端 |
📌 记住一句:
API Server 既是"服务器",也是"多个下游服务的客户端"。
总结
apiserver-kubelet-client.key 是 API Server 访问各 kubelet 节点时,在 mTLS 握手中证明身份的 客户端私钥。
✔ 证明"我是合法 API Server"
✔ 确保 kubelet 信任并建立加密通道
❌ 永远不能泄漏
❌ 删除/损坏 → logs/exec 等失效
问题十三
"握手摘要(handshake hash)到底是什么?"
TLS 为什么要让客户端用私钥签它?
握手摘要 = 把"到目前为止双方交换过的所有握手消息" 做一次(或多次)哈希得到的结果。
然后客户端用自己的 私钥 对这个哈希签名,服务器用客户端 证书里的公钥 验证。
👉 这样能证明:
✔ "发送这些握手消息的人,就是证书的拥有者"
✔ "消息没有被篡改"
✔ "中间人不能插入自己内容"
1. 握手过程中,双方都在记录"聊天记录"
假设在一个 TLS 连接里,已发生的消息:
ClientHello
ServerHello
ServerCertificate
ServerKeyExchange
CertificateRequest
ServerHelloDone
ClientCertificate
ClientKeyExchange
...
TLS 规范要求:
客户端 & 服务器都要把这些消息(原始二进制)拼在一起保存。
称为:
handshake_messages
2. 计算"握手摘要(handshake hash)"
当需要客户端证明身份时:
handshake_hash = HASH(handshake_messages)
HASH 通常是:
- SHA256
- SHA384
(取决于 cipher suite)
👉 得到的是一个固定长度的摘要值。
3. 客户端用私钥签名握手摘要
客户端执行:
signature = SIGN(private_key, handshake_hash)
并把 signature 作为 CertificateVerify 消息发给服务器。
4. 服务器如何验证?
服务器已经有:
handshake_messages(同样记录过)- 客户端证书里的 公钥
- 客户端的签名
signature
于是它做:
handshake_hash' = HASH(handshake_messages)
is_valid = VERIFY(public_key, handshake_hash', signature)
如果验证成功:
✔ 证明客户端确实拥有私钥
✔ 而且握手过程没有被篡改
这就是 客户端身份认证(Client Authentication)。
5. 为什么要签"整个握手历史"?
关键安全目标:
❌ 防止中间人攻击(MITM)
想象一个攻击者:
Client <---> Attacker <---> Server
如果只签一个随机值:
攻击者可能替换掉握手消息,再骗两边同时握手
但签的是:
整个握手历史(包括 Server 证书、密钥交换参数、随机数等)
那么:
- 任何改动 → 将导致 hash 不一致 → 签名验证失败。
❌ 防止重放攻击
旧签名 不能在新连接中复用,因为:
- 握手里包含了双方的 随机数(nonces)
- 新链路 → 新随机数
→ 新 hash
→ 旧签名再也不匹配
6. TLS1.2 vs TLS1.3
TLS 1.2
明确有 CertificateVerify = Sign( handshake_hash )
TLS 1.3
仍然签握手哈希,但计算方式更复杂,引入更多绑定(如 Finished keys)。
但概念不变:依然签的是"握手历史摘要"。
7. 总结
握手摘要 = 到当前为止包含所有 TLS 握手消息的数据 → 做 hash。
客户端用私钥签它 → 证明自己身份
服务器用公钥验证 → 确保证书真的属于你,并且握手没被篡改。
问题十四
apiserver-kubelet-client.key:API Server 访问 kubelet 时的私钥
这次是:apiserver-etcd-client.key:API Server 访问 etcd 时的私钥
本质一样:API Server 作为"客户端",跟下游组件做 mTLS。但因为 etcd 是整个集群的"数据库",这对证书 & 私钥更关键。
apiserver-etcd-client.key 是:
API Server 连接 etcd 时的"客户端私钥"。
它在 TLS 中用来:
-
证明「我真的是受信的 API Server 客户端」
-
配合
apiserver-etcd-client.crt完成 mTLS 握手 -
建立 API Server ↔ etcd 之间的加密、可信通道
❌ 泄漏 = 别人可以伪装 API Server 直接读写 etcd
❌ 过期/损坏 = API Server 不能访问 etcd → 集群直接半挂甚至瘫痪
1️⃣ 场景复盘:API Server 是 etcd 的"客户端"
Kubernetes 控制面是这样连 etcd 的:
在 kube-apiserver.yaml 中一般能看到类似配置:
yaml
- --etcd-servers=https://127.0.0.1:2379
- --etcd-cafile=/etc/kubernetes/pki/etcd/ca.crt
- --etcd-certfile=/etc/kubernetes/pki/apiserver-etcd-client.crt
- --etcd-keyfile=/etc/kubernetes/pki/apiserver-etcd-client.key
而 etcd 这边(通常在 /etc/etcd/etcd.conf 或 systemd 参数里)会有:
bash
--client-cert-auth=true
--trusted-ca-file=/etc/kubernetes/pki/etcd/ca.crt
这说明:
- etcd:要求客户端必须带合法证书(开启 client auth)
- API Server:用 apiserver-etcd-client.crt/.key 作为客户端证书
👉 所以:在这个链路上:
- etcd = 「服务器」
- API Server = 「客户端」
2️⃣ TLS 连接 etcd 时,私钥在干嘛?
完整链路:
text
API Server (client)
↓ TLS 握手 + mTLS
etcd (server)
握手关键步骤里:
- etcd 发送自己的服务器证书(给 API Server 校验)
- etcd 要求:客户端也必须提供证书(因为
client-cert-auth=true) - API Server 发送:
apiserver-etcd-client.crt(客户端证书)- 用
apiserver-etcd-client.key对握手摘要签名
- etcd 用客户端证书里的"公钥"验证签名
这里 apiserver-etcd-client.key 具体做的两件事:
作用 1:证明"证书确实属于 API Server"
-
etcd 随机产生/或者协议内定义握手数据(包含双边随机数等)
-
API Server 使用 私钥 生成签名:
textsignature = Sign(privateKey, handshake_hash) -
etcd 使用客户端证书里的 public key 验证:
textVerify(publicKey, handshake_hash, signature)
验证成功 → etcd 知道:
"这个连接的对方,确实拥有证书对应的私钥,不是偷来的证书。"
作用 2:确保握手过程不可伪造,协商出加密通道
在这个过程中,握手 hash + 签名参与了最终会话密钥的协商,
防止中间人修改握手参数。
最后,API Server ↔ etcd 之间
➜ 有一个 对称加密 的 TLS 通道
➜ kubectl get/describe/apply 等所有 API 操作,都经由 API Server → etcd 写入/读取。
3️⃣ 为什么必须用"客户端证书 + 私钥",而不是 token?
因为对 etcd 来说:
- 它不是 Kubernetes 内的一个"普通工作负载",
- 它是 直接存集群状态的分布式数据库,
- 不懂 kubeconfig / RBAC / ServiceAccount。
etcd 支持的身份认证方式很简单、传统:
- TLS 客户端证书(最典型、最常用)
- 或用户名密码 / token(一般不用在 kubeadm 这类部署里)
所以,最稳妥方式就是:mTLS + 客户端证书 也就是 apiserver-etcd-client.crt/.key 这对组合。
4️⃣ 如果这个私钥出问题,会发生什么?
1. 被删除 / 损坏 / 权限不对
API Server 启动或运行时连接 etcd 会失败:
-
启动失败
-
日志类似:
textfailed to create client for etcd: open /etc/kubernetes/pki/apiserver-etcd-client.key: no such file or directory -
整个控制面挂掉(
kubectl get pods等都报错)
因为 API Server 没法连 etcd,就无法读/写任何集群状态。
2. 证书或私钥不匹配
比如:
- 某次误操作替换了证书/私钥
- 或 etcd CA 换了但客户端不中断升级
握手会失败,报:
text
remote error: tls: bad certificate
tls: failed to verify client's certificate
3. 私钥泄漏(最危险情况)
如果:
text
apiserver-etcd-client.key
apiserver-etcd-client.crt
etcd/ca.crt
全部泄漏,对攻击者意味着:
可以假装"API Server 客户端"直接连 etcd
做到什么程度?
- 直接读/写 etcd 所有 key
(包含所有 Secret 的原始加密/明文视环境而定) - 伪造 Node / Pod / Role / Binding / Token
- 基本等于"数据库级别拿下整个集群"
⚠️ 这类文件要视为高敏感资产,建议:
- 仅 root 可读
- 内部备份需加密
- 变更需走严格流程
5️⃣ 和 apiserver-kubelet-client.key 的对比
| 私钥文件 | 下游服务 | API Server角色 | 主要影响 |
|---|---|---|---|
apiserver-kubelet-client.key |
kubelet | 客户端 | logs/exec/端口转发 等调试能力 |
apiserver-etcd-client.key |
etcd | 客户端 | 控制面读写状态能力(全局) |
👉 可以这么记:
kubelet那条链断了 → 调试不方便
etcd那条链断了 → 集群脑子没了
6️⃣ 实操建议
如果想进一步"摸清楚"自己的环境,可以:
-
看一下证书内容:
bashopenssl x509 -in /etc/kubernetes/pki/apiserver-etcd-client.crt -noout -text关注:
Issuer(签发者,应是 etcd CA)Subject(通常类似CN=kube-apiserver-etcd-client之类)- 有效期(Not Before / Not After)
-
看 kube-apiserver 启动参数里是否正确引用:
bashgrep -E "etcd-servers|etcd-certfile|etcd-keyfile" /etc/kubernetes/manifests/kube-apiserver.yaml -
如果要续期,一般用:
bashkubeadm certs renew apiserver-etcd-client systemctl restart kubelet # 让 apiserver static pod 重启加载新证书
总结
apiserver-etcd-client.key 是
API Server 访问 etcd 的客户端私钥。
它配合 apiserver-etcd-client.crt:
-
在 mTLS 中证明"我是合法 API Server 客户端"
-
与 etcd 建立安全、加密、可认证的连接
❌ 损坏/过期 → API Server 无法连 etcd,控制面瘫痪
❌ 泄漏 → 攻击者可以伪装成 API Server 操控整个 etcd(极高风险)
问题十五
kubectl → API Server → kubelet(对应节点) kubelet起什么作用?
kubelet 就是每个节点上的 "Node Agent / 小管家" ,
专门负责:
- 跟 API Server 打交道
- 把 "Pod 规范" 变成真正跑起来的容器
- 上报节点和 Pod 的真实状态
- 提供日志、exec 等接口给 API Server 用
我们可以把它理解成:"Kubernetes 控制面的远程执行器 + 节点状态汇报员 + Pod 生命周期经理"。
1️⃣ 在这条链路里:kubectl → API Server → kubelet
以刚才那条调用链为例:
text
kubectl → API Server → kubelet(对应节点)
常见操作都是这样走的:
kubectl get pods- kubectl 只请求 API Server
- API Server 从 etcd 读 "期望状态 + 当前状态"
- 不直接找 kubelet(除非需要实时拉状态)
kubectl logs/kubectl exec/ 端口转发- kubectl 请求 API Server
- API Server 再通过 HTTPS 去调用 对应 Node 上的 kubelet API
- kubelet 去和容器 runtime 通信(比如 containerd),拿日志/开交互/转发数据
这条链上的分工:
| 组件 | 角色 |
|---|---|
| kubectl | 用户 CLI 客户端 |
| API Server | 控制面入口 / 网关 |
| kubelet | 每台节点上的 "执行代理" |
所以可以记住一句:
凡是"跟节点上容器打交道"的具体动作,都是 kubelet 在干。
2️⃣ kubelet 的核心职责拆解
① 接收 "期望状态":PodSpec → 真正容器
控制平面通过 API Server 下发:
- "在 Node X 上应该跑哪些 Pod(以及怎么跑)"
kubelet 在本节点做的事:
- watch API Server 上和自己这个 node 相关的 Pod 对象
- 对比:
- 当前节点上"实际运行"的容器情况
- 与 Pod 对象里"期望状态"是否一致
- 如果发现:
- 少了一个 Pod → 创建容器
- 多了一个 Pod → 停掉
- Pod 配置变化 → 重建、更新
真正调用 container runtime(CRI)的,就是 kubelet:
- containerd / dockershim / CRI-O 等
- 通过 CRI gRPC 协议拉镜像、创建 sandbox、启动容器等
控制面只说:"我要 3 个 nginx 副本",具体 "在哪个节点启动哪个容器、怎么启动" 是 kubelet 与 runtime 一起完成的。
② 管理 Pod 生命周期与健康检测
kubelet 负责:
- 创建 / 启动 / 停止 容器
- 挂载 Volume(本地目录、NFS、CSI 存储等)
- 设置环境变量、secret、configMap 注入
- 处理 probe:
- livenessProbe
- readinessProbe
- startupProbe
探针也是 kubelet 执行的:
- HTTP GET /exec / TCP check
- 失败次数超阈值 → 杀容器、重启
- 结果上报给 API Server(Pod 是否 Ready/Running)
③ 上报 Node 和 Pod 状态(NodeStatus / PodStatus)
kubelet 周期性地:
- 向 API Server 上报 Node 状态:
- 当前 CPU / 内存 容量与使用情况
- Conditions(Ready / NotReady / DiskPressure / MemoryPressure 等)
- 节点标签、污点变化等
- 上报 PodStatus:
- 容器状态(Running / Waiting / Terminated)
- 重启次数
- ExitCode
- 最后一次退出原因 / 消息
调度器(kube-scheduler)和控制器都是基于这些状态做决策的。
没有 kubelet 的上报,控制面就 看不到真实世界里发生了什么。
④ 作为 "节点 API" 被 API Server 调用
我们看到的:
kubectl logskubectl execkubectl port-forward
背后都是:
- kubectl 请求 API Server
- API Server 建立到 对应 Node 上 kubelet 的连接
- kubelet 再去跟容器 runtime 通信,执行相应操作
- 读容器日志
- 建立 tty 通道
- 建立端口转发隧道
这条链路里:
- kubelet 暴露一个 HTTPS 服务(kubelet API)
- API Server 用
apiserver-kubelet-client.crt/key以客户端证书身份访问 - kubelet 的
--client-ca-file校验"对方是否是合法 API Server"
这里 kubelet 相当于"节点上的本地代理控制器",外面所有对容器的操作必须通过它。
⑤ 集成 CNI / CSI / 其他插件
kubelet 是网络和存储插件的落地方:
- 网络(CNI) :
- Pod 创建时,kubelet 调 CNI 插件(如 calico、flannel、cilium)
- 为 Pod 设置网络命名空间与 IP
- Pod 删除时调用 CNI cleanup
- 存储(CSI) :
- 按照 PVC / PV 绑定结果
- 调 CSI driver 完成卷的 attach / mount / unmount / detach
它自己不会直接"配置网卡 / 存储",而是负责调度相应的插件。
⑥ TLS 引导、证书轮换、安全接入
前面已经深挖过:
- kubelet 在加入集群时:
- 用 bootstrap token 加入
- 提交 CSR
- 拿到
kubelet-client证书
- 后面运行期:
- 自动轮换客户端证书
- 使用
/var/lib/kubelet/pki/kubelet-client-current.pem与 API Server mTLS 通信
所有这些 安全接入、持续认证 都是 kubelet 在节点上完成的。
3️⃣ 整体架构视角:kubelet 在 Kubernetes 里的位置
可以画成这样:
text
+---------------------------+
| Control Plane |
| |
User → kubectl → kube-apiserver → etcd |
| ↑ |
+------+--------------------+
|
| (watch Pod/Node, report status)
v
+------------------------+
| Node |
| |
| +-----------------+ |
| | kubelet |<-----------------+
| +--------+--------+ |
| | |
| | CRI | CNI / CSI / plugins
| +-------v--------+ |
| | containerd | |
| +-------+--------+ |
| | |
| [containers / pods] |
+--------------------------------------+
- 上连:API Server(控制面)
- 下管:容器 runtime、网络、存储
- 对外:对 API Server 与运维工具提供日志/exec/etc 能力
4️⃣ 如果 kubelet 出问题,会发生什么?
常见现象:
- Node 变成
NotReady - Pod 无法调度到该 Node
- 已有 Pod 状态不更新 / 重启不生效
kubectl logs/exec失败:Error from server: error dialing backend
- 控制面仍然可以工作(其它节点正常)
排查通常从以下命令开始:
bash
systemctl status kubelet
journalctl -u kubelet -f
5️⃣ 总结
kubelet = 每个节点上跑的 Kubernetes "智能管家":
- 接收 API Server 下发的 Pod 期望状态
- 调用 container runtime / CNI / CSI 让 Pod 真正跑起来
- 周期性上报节点和 Pod 状态
- 提供日志、exec、端口转发等能力(给 API Server 用)
- 通过 mTLS 与 API Server 建立安全信任关系
没有 kubelet:
- 控制面就只剩 "期望的 YAML",
- 和真实的机器、容器世界完全脱节。
问题十六
kubelet "watch API Server 上和自己这个 node 相关的 Pod 对象" 到底是什么意思?
我们把它拆成 非常直观的三步:
先想象一个简单的问题:
API Server 怎么告诉某个具体节点:"你要运行这个 Pod"?
👉 它 不会主动去推送 。
👉 它也 不会一台一台机子发命令。
Kubernetes 设计的思路是:
API Server 只维护"状态",
kubelet 主动来"订阅/监听"。
第 1 步:API Server 里存的,是"全局 Pod 列表"
例如系统期望运行这些 Pod:
| Pod name | 被调度到哪个 Node |
|---|---|
| nginx-1 | nodeA |
| nginx-2 | nodeB |
| mysql-0 | nodeB |
| busybox | nodeC |
这些对象都存在 etcd 中,通过 API Server 暴露出来:
/api/v1/pods
它只是数据库,并不执行任何命令。
第 2 步:调度器给 Pod 贴上 nodeName
当 scheduler 选定一个 node:
例如:
busybox → 决定放到 nodeB
就会把 Pod 对象改成这样:
yaml
spec:
nodeName: nodeB
于是 API Server 中有这么一条记录:
"busybox 这个 Pod,期望运行在 nodeB 上"
📌 注意:只是写入记录,还没人真的去创建容器。
第 3 步:kubelet 只盯"属于自己的 Pod"
每台 Node 上的 kubelet,会向 API Server 发送一个 Watch 请求:
text
给我所有 nodeName = <当前节点> 的 Pod
一旦有变化,请实时通知我。
等价于(简化理解):
bash
GET /api/v1/pods?fieldSelector=spec.nodeName=<my-node>&watch=true
👉 这就是:
watch API Server 上与自己这个 Node 相关的 Pod 对象。
举个非常直观的例子
假设当前节点叫:nodeB
kubelet 会持续监听:
所有被调度到 nodeB 的 Pod
API Server 里当记录发生这些变化:
| 事件 | Pod 状态 | kubelet 行为 |
|---|---|---|
| 新建一个 Pod,并调度到 nodeB | ADDED | kubelet:创建容器 |
| 修改 Pod 规范(镜像、环境变量等) | MODIFIED | kubelet:必要时重建 |
| Pod 被删除 | DELETED | kubelet:停止并清理容器 |
📌 关键思想:不是 API Server 命令 kubelet 做事,而是 kubelet"自己看到变化后就去做"。
为什么要这么设计?
不是"命令执行"模型(那叫中控)
比如传统系统:
中控系统 → SSH 到节点 → 执行命令
问题:
- 强耦合
- 中控单点失败
- 网络抖动 = 整体失控
Kubernetes 采用"期望状态 + 自己驱动"
- API Server 只存"我想让系统成为的样子"
- kubelet 读取并执行,让现实逐渐向"期望"靠近
这是所谓 声明式(Declarative)模型。
总结
kubelet 会一直向 API Server 订阅:"所有被调度到我这台机器的 Pod。"
只要这些 Pod 对象发生:
- 新增
- 修改
- 删除
kubelet 就会:
创建
更新
删除
节点上的真实容器。
API Server 不发命令;kubelet 主动 watch 变化,然后执行。
问题十七
"双方都要预先信任 CA 公钥(根证书)是什么意思?
客户端和服务端真的都要提前配置吗?
这个公钥又是从哪里来的?"
这正是 PKI/证书体系的核心。
1️⃣ 什么叫"预先信任 CA 的公钥(根证书)"?
先记一句:
TLS 不是"相信对方",而是"相信签发对方证书的 CA"。
而 CA 的"身份证"就是:
根证书(Root CA Certificate)
根证书里面包含:
- CA 的 公钥
- CA 的身份信息
- CA 自己对自己的 自签名
⚠️ 关键点:
只要你信任某个 CA 的根证书,你自然就会信任它签发的一切服务器 / 客户端证书。
所以:
- 浏览器信任一些"公共 CA 根证书"
- Kubernetes 集群内部,组件信任 自己的集群 CA
这就叫:
预先信任 CA 公钥。
2️⃣ 那是不是"客户端 & 服务端都要事先配置"?
👉 是的(在需要双向认证 mTLS 时,双方都要配置)。
我们拆一下:
场景 A:普通 HTTPS(单向 TLS)
例如访问:
https://www.google.com
- 客户端(浏览器) :
✔ 预装"公共 CA 根证书集合" → 用来验证网站证书。 - 服务器(Google) :
❌ 不需要知道客户端是谁
✔ 只需要配置自己的 server 证书和私钥。
只有 客户端预信任 CA 即可。
场景 B:mTLS(双向认证,例如 Kubernetes 组件内部)
例如:
API Server ↔ kubelet
API Server ↔ etcd
这时:
| 角色 | 必须预先信任 | 目的 |
|---|---|---|
| 客户端(API Server) | 服务端证书对应的 CA | 验证:我连的是真 kubelet / etcd |
| 服务端(kubelet/etcd) | 客户端证书对应的 CA | 验证:对方真的是合法 API Server |
结论:双方都必须配置各自信任的 CA 根证书。
3️⃣ 最关键:CA 的"公钥(根证书)"是从哪里来的?
在 Kubernetes 内部(kubeadm 集群)
根 CA 存在 master 节点:
/etc/kubernetes/pki/ca.crt ← 公开(公钥)
/etc/kubernetes/pki/ca.key ← 私钥(绝密)
流程:
1️⃣ kubeadm 创建 CA
2️⃣ 用 ca.key 签发:
- apiserver.crt
- kubelet 证书
- controller/scheduler 证书
- admin.crt
- apiserver-etcd-client.crt
- 等等...
3️⃣ kubeadm 同时把 ca.crt(公钥)复制给需要信任它的组件:
- 放进 kubeconfig (
certificate-authority-data) - 或显式配置:
yaml
--client-ca-file=/etc/kubernetes/pki/ca.crt
👉 所以 Kubernetes 组件确实是:
提前拿到同一个
ca.crt并保存下来。
在互联网 HTTPS 场景
浏览器的 CA 列表来自:
- 操作系统(Windows / macOS / Linux)
- 浏览器内置(Chrome / Firefox)
这些 CA 根证书:
✔ 由厂商事先审核
✔ 安装到你的系统
✔ 每次升级还会更新
所以当你访问:
https://example.com
流程是:
- 网站提供 server 证书 + 中间证书链
- 浏览器沿着证书链一路追溯,直到某个 根证书
- 发现这个根证书在"已信任列表"中
→ OK,这个站可信。
为什么必须"预先分发 CA 根证书"?
因为你不可能在连接时,再去问对方:"请告诉我应该信任谁。"
那会产生死循环:
- 想下载 CA,需要先信任连接
- 连接需要验证 CA
所以:
✔ CA 必须通过 安全渠道提前部署
✔ 以后所有连接,只需要:
"验证证书是否由这个 CA 签发就行了。"
总结
TLS 信任 = 信 CA,而不是信服务器本身。
因此,客户端要预装 CA 根证书,在 mTLS 中,服务端也必须预装。
CA 根证书来自:
-
Kubernetes:
/etc/kubernetes/pki/ca.crt -
互联网:操作系统 / 浏览器的 CA 目录
问题十八
浏览器"沿着证书链一路追溯到根证书",是不是"不断用 CA 公钥去解密"?
浏览器验证证书链时:
❌ 不会去解密证书
✔️ 而是用上一级 CA 的公钥验证下一级证书的"签名"
证书链验证的是:"是不是由一条受信 CA 链一路签下来" ,不是 "用公钥把什么东西解开"。
1️⃣ 证书里没有"加密内容",只有"签名"
假设网站证书是:
www.example.com 证书
里面包含:
- 网站的 公钥
- 主题(Subject) = example.com
- 有效期
- 用途(Server Auth)
- 签发者 Issuer = 中间 CA
- CA 的签名
📌 关键点:证书本身不是加密文件,是 "公开信息 + 数字签名"。
2️⃣ 那签名验证到底是做什么?
看一条证书链:
Root CA → Intermediate CA → Server cert(网站)
浏览器做:
第一步:验证服务器证书
用 中间 CA 的公钥 去验证:
Verify( intermediate_pubkey , server_cert_signature )
问的问题是:
"这个网站证书,
真的由中间 CA 用它的私钥签的吗?"
第二步:验证中间 CA 证书
用 根 CA 的公钥 验证:
Verify( root_pubkey , intermediate_cert_signature )
问的问题是:
"中间 CA 自己是不是被根 CA 授权的?"
第三步(最后一步):根证书
根证书是自签名:
Verify( root_pubkey , root_signature ) ✔ 总能通过
问题变成:
"这个根 CA 是否在我的"受信列表"里?"
✔ 如果在 → 整条链可信
❌ 如果不在 → UNTRUSTED CERTIFICATE
浏览器只是不断"验章",不是不断"解密"。
就像:合同 → 县公章 → 市公章 → 省公章
最终看:省公章是不是国家承认的。
3️⃣ 那"解密"到底是谁干的?
| 阶段 | 用途 | 是否解密 |
|---|---|---|
| 证书链验证 | 认证对方身份 | ❌ 不解密,只验签 |
| TLS 握手 | 生成会话密钥 | ✔️ 用到公钥算法(但不是解密证书) |
| 真正通信 | 加密 HTTP 内容 | ✔️ 对称加密(非常快) |
证书本身 永远不会被"解密"。
用一个直观比喻:
证书链验证 = 核对"每一级公证是否由上一级授权"(数字签名验证)
TLS 加密 = 用钥匙锁箱子,双方都有钥匙(对称加密)
不同概念,不要混淆。
4️⃣ 实验验证
查看某站证书链:
bash
echo | openssl s_client -connect www.google.com:443 -showcerts
然后逐个验证:
bash
openssl verify -CAfile root.pem intermediate.pem server.pem
我们会发现:
每一步都是 verify(验证签名) ,而不是 decrypt(解密)。
总结
1️⃣ 证书链验证不是解密,是逐级签名验证
2️⃣ 浏览器只信"在系统信任库里的根 CA"
3️⃣ 解密只发生在 TLS 握手和后续会话中(与证书验证分开)
问题十九
如果浏览器(或客户端)没有中间 CA 的公钥,那还怎么验证服务器证书?
答案是:
服务器会把中间 CA 证书"顺带一起发过来"。
浏览器通常 不需要提前保存中间 CA 的公钥。
下面详细说明:
1️⃣ 证书链一般长这样
Root CA → Intermediate CA → Server Cert
浏览器通常只预装 Root CA:
✔ Root CA(可信)
❌ 不存储 Intermediate CA(数量太多)
2️⃣ 那浏览器怎么拿到"中间 CA 证书"?
服务器在 TLS 握手时,会把"中间证书"一起发回来。
这个阶段叫:
Certificate message
服务器发送:
[ server certificate,
intermediate certificate,
(可能还有更多)]
注意:这些中间证书不是秘密,是公开的。
3️⃣ 验证过程变成这样
浏览器拿到这些证书后:
① 验证:Server Cert 是否由 Intermediate 签发
Verify( intermediate_pubkey , server_signature )
中间证书就在上一步握手消息里。
② 验证:Intermediate 是否由 Root CA 签发
浏览器看:
Issuer = Root CA
然后在本地"受信根列表"中查:
✔ 找到 Root CA
✔ 取出它的 公钥
用来验证:
Verify( root_pubkey , intermediate_signature )
③ 确认 Root CA 是系统信任的终点
如果 Root CA 存在于信任库:整条链"闭合" → 证书可信。
那如果服务器没发中间证书怎么办?
这是现实中经常出现的问题:
- 有的运维忘了配置完整证书链
- 服务器只发了"Server Cert",没发 Intermediate
结果:
✔ Root CA 仍然被系统信任
❌ 但中间证书缺失
浏览器会报错:
certificate chain incomplete
unable to get local issuer certificate
Chrome/Wget/cURL 等工具的报错常见:
SSL certificate problem: unable to get local issuer certificate
👉 解决方法:在服务器上正确配置"证书链(bundle)",包含:Server Cert + Intermediate Cert。
Kubernetes 里对应的做法
在 Kubernetes 内部组件中:
-
一般 没有中间 CA
-
大多是:
Root CA(ca.crt,自签) → 各组件证书
如果引入企业 CA / 多级 CA 架构:
- kube-apiserver/kubelet 等组件必须配置 完整证书链文件
例如:
--tls-cert-file=/etc/kubernetes/pki/apiserver.crt (可能包含中间证书)
--tls-private-key-file=/etc/kubernetes/pki/apiserver.key
再强调一句避免误解:
❌ 证书链不是"一级一级解密"
✔️ 是一张"一级一级验签的授权关系图"
缺少中间证书不是加密失败,而是 缺少签署证明
总结
问:
如果没有"中间 CA 的公钥",还能验证吗?
答:
✔️ 服务器会把中间证书一起发给你
❌ 你不需要预装它
✔️ 浏览器用服务器发来的 中间证书 + 本地 Root CA
✓ 完成整条链的验证
问题二十
"中间证书就在上一步握手消息里"到底是什么意思?它在握手的哪一步?谁发的?怎么发的?
整体 TLS 握手流程(简化版)
(不区分 1.2/1.3 的细节)
ClientHello →
← ServerHello
← Certificate
← (CertificateRequest, 可选)
← ServerHelloDone
ClientCertificate (可选) →
CertificateVerify (可选) →
Finished →
← Finished
中间证书在哪里?
👉 就在上面的「Certificate」消息中。
服务器发送:
Certificate message
内容其实是一个 证书列表(chain):
[
Server certificate,
Intermediate CA certificate,
(可能还有更多中间 CA),
]
📌 这一整串由"服务器发给客户端"。
展开来看 ------ Server 在做什么?
当客户端说:
我要 HTTPS
服务器这样回应:
✉️ Certificate(证书链)
服务器打包:
server_cert.pem
intermediate_CA.pem
(optional another intermediate)
然后一次性发给浏览器。
所以,"中间证书在握手消息里" 就是说:它是随着 TLS 握手数据包一起,从服务器传回来的。
验证
运行:
bash
echo | openssl s_client -connect www.google.com:443 -showcerts
我们会看到类似输出:
Certificate chain
0 s: CN=www.google.com
i: CN=GTS CA 1O1
-----BEGIN CERTIFICATE-----
(服务器证书)
-----END CERTIFICATE-----
1 s: CN=GTS CA 1O1
i: CN=GTS Root R1
-----BEGIN CERTIFICATE-----
(中间 CA)
-----END CERTIFICATE-----
解释:
0= Server cert1= Intermediate cert
👉 它们就是 服务器在握手时一次性发给你的。
为什么服务器必须带上"中间证书"?
因为客户端通常:
- 只内置 Root CA
- 没有中间 CA
如果服务器不发,浏览器就没法验证"谁签了服务器证书"。
它会直接报错:
unable to get local issuer certificate
总结
当浏览器与网站握手时,服务器在 Certificate 握手消息 中主动把自己的证书、中间 CA 证书(甚至多级中间 CA)一起打包发送给浏览器。
所以我们说:中间证书"就在握手消息里"。