根证书一换,谁就不再信任谁
一句话总结
istiod 默认用自己生成的根来签发工作负载证书。插件 CA(plugin CA)是把由组织的根签发的中间 CA,以 cacerts Secret 的形式接入 istiod,把网格的身份纳入组织 PKI 之下的方法。在已经运行的网格上更改这一点的瞬间,只信任旧根的工作负载和拿到新证书的工作负载就会互不信任。
为什么需要它
自签名根起步方便,但有三个麻烦。根密钥位于集群内部(istio-ca-secret),集群被攻破,根也就被攻破。每个集群的根不同,两个集群的工作负载无法互相信任。而且它处于组织的审计、撤销体系之外。
Istio 文档推荐的结构是这样的——把根 CA 放在安全性强的离线机器上,用该根为每个集群签发中间 CA,交给各自的 istiod。istiod 用该中间 CA 签发工作负载证书,并把管理员提供的根作为信任根分发给工作负载。即使一个中间 CA 泄露,根也不受影响,同一个根之下的集群之间可以互相信任对方的工作负载。
工作原理
cacerts 的四个文件
| 键 | 内容 |
|---|---|
ca-cert.pem |
istiod 要使用的中间 CA 证书 |
ca-key.pem |
其私钥 |
root-cert.pem |
要分发给工作负载的根 |
cert-chain.pem |
从中间 CA 连到根的证书链 |
istiod 启动时会查找 istio-system/cacerts。如果有就使用它(日志中会记录从 cacerts 读取了根),没有就使用自己生成的根。正在运行的 istiod 不会察觉后来才出现的 Secret,所以需要重启。
工作负载证书由 sidecar 获取。sidecar 中的 agent 会生成密钥并请求 istiod 签名,获得有效期很短(默认 24 小时)的证书,通过 SDS 放入 Envoy,并在到期之前自动更换。身份不在 subject 中,而在 SAN 的 SPIFFE URI(spiffe://cluster.local/ns/bank/sa/client)中。授权策略的 principals 就是这个值,所以即使更换 CA,只要信任域相同,策略就保持不变。
更换根的瞬间。新的 istiod 会把每个命名空间的 istio-ca-root-cert 换成新根,新启动的 Pod 信任新根,并获得由新中间 CA 签发的证书。但已经在运行的 sidecar 持有的只有旧根。mTLS 要求双方互相验证对方的证书链,所以拿到新证书的 web 与只信任旧根的 client 之间无法建立连接(503)。直到全部重启或证书轮换为止,这个状态会一直持续。
Istio 1.31 文档的插件 CA 流程针对的是安装之前就接入 cacerts 的情况。在已经运行的网格上更换,就会出现上面的中断,这一点在本实验中亲眼看到。从这个观察得出的结论只有一个——中断的原因是“互相不认识对方的根”,所以要在运行中更换,就必须把让所有工作负载信任新根和开始用新 CA 签发这两件事分开来排定顺序(两个根同时被信任的时期),在全部拿到新证书之后再去掉旧根。
在现场相遇的样子
“放入了 cacerts,证书 issuer 却没变”——没有重启 istiod,或者 Pod 还在使用旧证书。把 istiod 日志和 sidecar 的 proxy-config secret 分开来看。
“更换 CA 之后,只有部分服务之间是 503”——是只有一方被重启的那一对。把两个 sidecar 的 ROOTCA 和工作负载证书的 issuer 并排对照,马上就能看出来。
“把两个集群连起来了,彼此却不能 mTLS”——两个 istiod 使用的根不同。必须在同一个根之下分别接入各自的中间 CA。
官方文档:Plug in CA Certificates、Security — PKI、Identity and certificate management
下一项实验要做什么
先确认 istiod 自己生成的根,再用发行包的 Makefile 生成根和集群用的中间 CA,作为 cacerts 接入。只重启 web,体验只有一方拿到新证书时的 503,再重启到 client 使其恢复,然后直接从证书中读出:新证书链是否连到我们的根,以及身份和有效期。