作者:张怀龙 杨爱林
两位作者都就职于 Intel 公司 ,张怀龙在服务网格团队担任云原生软件开发工程师,主要从事云原生服务网格等技术研发,是 Istio 和 Linkerd 社区的贡献者和 Istio 社区 maintainer,是 KubeCon、IstioCo、ServiceMeshCon、InfoQ/QCon 以及 GOTC 等大会的技术演讲者,在加入 Intel 之前,在阿尔卡特朗讯,百度,IBM 等企业从事云计算研发相关的工作。杨爱林,Cloud Orchestration Engineer, 主要负责 intel cloud native 项目等技术咨询和客户合作。
Istio 社区基于 Ingress Gateway 的使用案例中,我们发现一个安全隐患,而且这类安全隐患并不只存在于 Istio,可能也存在于云原生诸多类似的场景中。本文将会进一步说明这个发现,并且给出问题的解决方法。
对于 Istio 的用户证书,密钥生成和分发机制,其中针对工作负载私钥的生成有两种方式,分别是:
- 网格内工作负载创建时生成 mTLS 私钥
- 网格外通过 Ingress gateway 上传对外暴露的工作负载的私钥
本文主要介绍第二种方式,上述两种私钥在 Istio 中生成的主要区别在于,第 2 个用例多了一个由用户通过 k8s secret 上传工作负载的私钥的过程,其他情况,比如私钥在服务网格中的分发和存储的过程差不多。根据 Istio 官方文档 Secure Gateways 的描述,我们可以了解到如何使用 TLS 或 mTLS 安全地暴露服务网格中的 HTTPS 服务,同时我们也不难分析出目前的服务网格中工作负载的私钥可能存在着如下的风险:
- 私钥几乎是明文
- 私钥存储在内存中
- 私钥在外部上传(如下图 Figure 1 所示的 ingress gateway 上传工作负载私钥用例)
Figure 1
所谓私钥是明文的存在,主要是指用户在通过 k8s secret 上传工作负载的私钥和证书时,应用负载的私钥是 base64 加密的,这几乎是等同于明文的存在,如下图 Figure 2 httpbin-credential 这个 secret 的 tls.key 中内容所示。
Figure 2
对于这种暴露工作负载私钥的 secret 的监听,在 k8s 环境中是非常容易做到的(也许只是一个简单的监听 secret 的 controller),也因此 tls.key 中的内容是很容易就能获得的。其次,得到这个经过 base64 加密后的私钥,很容易通过工具,比如:
openssl rsa -in private_file_name -text –noout
来解密,获取工作负载的私钥。其结果如下 Figure 3 and Figure 4:
Figure 3
Figure 4
最后,外界与服务网格中相应工作负载的数据通信对于攻击者来说就相当于是以明文的方式通信了,不仅如此攻击者还可以利用这份密钥做任何想做的事情,比如伪装成网格内想要暴露的工作负载,来窃取用户信息等。
另外工作负载的私钥被存储在 sidecar 内存中的情况,这个时候基本上说工作负载的私钥已经完成分发了,其分发完的结果如下,请看下图 Figure 5:
Figure 5
上图中 private_key 内容为:W3JlZGFjdGVkXQ== ,解码后是:[redacted] 。这表明用户的私钥实际上是存储在 Envoy 的内存中,这里不会导出。但是该私钥会被分发到所有的 sidecar 中,这个隐患表明,任何一个攻击者只要能部署到网格中他就会拥有一份包含该私钥的配置在内存中。当然对于这种存在内存中的 credential 信息也并不安全,我们通过 gdb 可以简单的说明一下如何去找到自己想要的信息。其过程如下:查找到对应的 envoy 进程 ID,然后通过 gdb attach,命令如下:
gdb
attach <Process ID>
本例中进程 id 便是对应的 envoy 进程的 ID,我们可以通过在宿主机上运行 ps -ef| grep envoy 命令来搜索(注意需要排除 Istio ingress gateway 等无关的 Envoy 进程)。本例中工作负载的进程 ID 是:472652 查看进程对应的地址空间:info proc mappings,结果如图 Figure 6:
Figure 6
我们可以根据上图发现对应的 envoy 进程所关联的代码段,堆和栈等的内存地址范围。需要关注上面红色方框中所示的 Envoy 的堆内存的地址范围,后面会提到。
通过查看 envoy 的代码可以发现,对于工作负载的 private_key 的处理代码在 resolveSecret 函数中调用 resolveDataSource 进行处理。
于是我们执行命令
info functions resolveSecret
得到如下 Figure 7 内容,并且通过查阅源码锁定第一个地址:0x0000564a15c36460 是真正调用处理私钥的函数。
Figure 7
通过 find 去匹配关键信息。
如下图 Figure 8 所示私钥会包含 BEGIN PRIVATE KEY 和 END PRIVATE KEY 两个固定模式的信息,于是我们首先需要得到他们对应的十六进制
Figure 8
执行命令:echo -n “BEGIN PRIVATE KEY” | xxd -p 得到其对应的十六进制输出 424547494e2050524956415445204b4559 执行命令:echo -n “END PRIVATE KEY” | xxd -p 得到其对应的十六进制输出 454e442050524956415445204b4559 理论上说工作负载的私钥应该是存储在堆内存中,根据第 2 步我们知道堆内存的地址范围应该是:起始地址:0x564a17af0000 到终止地址:0x564a195f0000。于是我们可以在堆内存空间中根据上面得到的私钥中固定模式信息的十六进制继续执行命令进行搜索:搜索私钥的开头部分:find /b 0x564a17af0000, 0x564a195f0000, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x20, 0x50, 0x52, 0x49, 0x56, 0x41, 0x54, 0x45, 0x20, 0x4b, 0x45, 0x59
搜索私钥的结尾部分:find /b 0x564a17af0000, 0x564a195f0000, 0x45, 0x4e, 0x44, 0x20, 0x50, 0x52, 0x49, 0x56, 0x41, 0x54, 0x45, 0x20, 0x4b, 0x45, 0x59
如上述所示,尽管私钥的开头部分我们找到 3 个结果,结尾部分我们找到 6 个结果,但是我们依然可以从地址的对应情况上看出这里可能有三对可以匹配上的私钥数据:0x564a18b5e005 到 0x564a18b5e693 0x564a18b87c05 到 0x564a18b88293 0x564a18b88305 到 0x564a18b88993 剩下的工作就一一验证,本文将不再赘述。使用 gdb 并不是最简单的办法,我们也可以考虑使用其他的内存 dump 工具来帮助在内存中找到工作负载的各种私密信息。根据上面的描述我们可以知道,即使把工作负载私钥数据存储在内存中也是不安全的。
综上所述,我们认识到对安全等级高的工作负载其私钥数据的保护应该做到从其私钥的产生,存储到使用都进行严密保护。为此 Istio 社区基于 Intel Software Guard Extension (SGX) 技术提供了一个全新的保护办法,具体内容克参阅这个blog 文章[1] , 也可以关注HSM SDS Server[2]了解更多相关信息。
参考资料
[1]blog 文章: https://www.cncf.io/blog/2021/12/06/hardware-based-security-for-service-mesh-keys/
[2]HSM SDS Server: https://github.com/istio-ecosystem/hsm-sds-server
转载请注明出处:https://www.cloudnative-tech.com/technology/5968.html