# 使用ksniff和Wireshark在Kubernetes中验证service mesh TLS
除了来自[HashiCorp](https://www.hashicorp.com/)的[Nic Jackson](https://twitter.com/sheriffjackson),我最近在几次会议和网络研讨会上介绍了在现代应用程序中需要跨end-to-end或“[user to service](https://www.cncf.io/event/securing-cloud-native-communication-from-end-user-to-service/)”的传输级加密。从用户浏览器到应用边缘的TLS加密(和termination)一直是API网关、CDNS和edge proxies的长期feature,但是直到最近,service mesh技术才使得TLS服务于服务流量成为我们大多数人的现实方法。
许多service mesh都承诺实现低接触TLS,允许使用人员通过一个配置选项或一个几行的YAML来启用它。但是,您如何知道集群间的通信量实际上已经被成功加密了?当然,您可以在Kubernetes集群中运行的Pod中启动tcpdump,但这可能很难管理,特别是对于那些不太熟悉Linux工具的人。在最近一系列的service mesh调查和TLS调试之后,我偶然遇到了Eldad Rudich的[ksniff](https://github.com/eldadru/ksniff) kubectl插件,这已经被证明是一个非常有用的工具,可以用来检查集群内部的流量。
现在我将分享使用ksniff的一些经验教训,并根据我最近对API网关与service mesh的第一个内部跳之间的TLS通信的调查,提供一些示例。
# ksniff — 拥有Wireshark的所有优点,可在Kubernetes中运行
根据该项目的GitHub repo,ksniff是一个“使用tcpdump和wireshark来轻松嗅探kubernetes pod的插件”。我使用tcpdump和Wireshark来检查网络流量已经很多年了,但是我发现在Kubernetes中使用它有些困难。而使用像ksniff这样的简单的kubectl插件,几乎消除了配置两个流量监听工具的所有手动工作。
您可以使用kubectl插件包管理器[krew](https://github.com/GoogleContainerTools/krew)安装[ksniff](https://github.com/GoogleContainerTools/krew):
“`
$ kubectl krew install sniff
“`
我以前曾手动安装了ksniff(因为我想使用krew软件包中当时不提供的功能),这很容易做到,并且也有据可查。
您还需要在本地计算机上安装[Wireshark](https://www.wireshark.org/)。我通常通过网站下载完成此操作,但是您也可以通过apt和brew等大多数软件包管理器找到Wireshark。
现在,您已经安装了工具,让我们进行演示。
# Sniffing 未加密的 edge-to-service 流量
我已经通过Helm 将[Ambassador API网关](https://www.getambassador.io/)和[HashiCorp的Consulservice mesh](https://www.hashicorp.com/products/consul/service-mesh)部署到GKE托管的Kubernetes集群中。由于[Ambassador和Consul](https://www.consul.io/docs/platform/k8s/ambassador.html)之间的集成,我可以向通过Ambassador(正在管理GCP负载均衡器)公开的API端点发出请求,并可以进行动态路由此请求,通过TLS连接将其从网关路由到任何内部服务。我还可以使用Consul作为Ambassador的一个简单的服务发现机制,它允许动态路由流量,但不使用传输加密。我们先这样做,这样就可以通过ksniff看到未加密的通信量。
我已经安装了Ambassador,Consul和“quote of the moment (QOTM)(QOTM)”服务,如gateway/mesh集成文档前半部分所述。在集群中,我看到以下服务正在运行:
我可以向通过`/qotm-consul/`端点公开的QOTM应用程序发出外部请求,该请求通过Ambassador和Consul进行路由
“`
$ curl -v 34.67.222.12/qotm-consul/
* Trying 34.67.222.12…
* TCP_NODELAY set
* Connected to 34.67.222.12 (34.67.222.12) port 80 (#0)
> GET /qotm-consul/ HTTP/1.1
> Host: 34.67.222.12
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 200 OK
< content-type: application/json
< content-length: 174
< server: envoy
< date: Mon, 05 Aug 2019 08:30:18 GMT
< x-envoy-upstream-service-time: 1
<
{“hostname”:”qotm-7fcb555cdf-xb27c”,”ok”:true,”quote”:”Nihilism gambles with lives, happiness, and even destiny itself!”,”time”:”2019-08-05T08:30:18.802450″,”version”:”1.7″}
* Connection #0 to host 34.67.222.12 left intact
“`
有了这个请求,一切看起来都很好。
我们再发出一个请求,但通过ksniff查看内部集群间网络流量。首先,我需要获取QOTM服务的Pod的名称,因为这是我要附加ksniff的地方
现在,我可以通过本地计算机通过简单的命令将ksniff附加到此Pod:
您可以在CLI输出中看到所有tcpdump配置,如果一切顺利,Wireshark应该启动,它将显示以下窗口:
一开始,Wireshark UI可能看起来有些令人生畏,但实际上并不太复杂。顶部的菜单栏允许您启动和停止网络流量捕获,还可以搜索和导航捕获的流量数据。菜单栏下方还有一个显示过滤器。顶部的窗口显示了来往Pod网络接口的流量数据包,中间的窗口提供了流量的概述(例如协议详细信息和标头元数据),底部的窗口显示了流量数据包的内容。
您只需在过滤器框中(菜单栏下方)键入“ http”并点击回车键,即可将显示过滤器仅显示HTTP流量。现在,如果您通过网关发出请求,则应该看到Pod处理该请求并生成响应:
“`
$ curl 34.67.222.12/qotm-consul/
{“hostname”:”qotm-7fcb555cdf-xb27c”,”ok”:true,”quote”:”Nihilism gambles with lives, happiness, and even destiny itself!”,”time”:”2019–08–05T08:40:28.469624″,”version”:”1.7″}
“`
您可以忽略大量的GET/health HTTP请求,因为这些请求是通过Kubernetes节点的kubelet生成的,在部署了这个Pod后,这是部署成功就绪后检查的结果。
有趣的是GET/HTTP请求,它由红色框突出显示。您可以看到入站HTTP请求来自10.60.2.2,这将产生一个由QOTM服务的Python服务器(通过第一个红色箭头显示)生成的200 HTTP状态代码的响应,HTTP负载(通过第二个红色箭头显示)与向集群发出curl请求时查看的结果相同。
如果查看所有`10.60.2.2`Pod 的配置,可以看到群集中的流量源IP 属于Ambassador Pod,而目的地则`10.60.1.6`属于QOTM Pod。这似乎是合理的,因为我向外部请求Ambassador通过Consul服务查找路由到Pod IP。
在这一点上,我鼓励您向集群提出更多请求,也许还可以部署自己的服务,并继续探索在集群周围流动的请求。
现在让我们启用Ambassador和Consul服务mesh mTLS集成,它将对来自边缘和service-to-service的所有流量进行加密,并查看使用此配置生成的流量。
# 达到加密的edge-to-service流量的峰值
为简单起见,我建议您终止与QOTM Pod的当前ksniff连接,并从Kubernetes集群中删除当前的QOTM服务和映射。
之后,您可以返回到[Ambassador和Consul集成文档中](https://www.getambassador.io/user-guide/consul/#encrypted-tls)的[“加密”部分,](https://www.getambassador.io/user-guide/consul/#encrypted-tls)并安装第二版QOTM服务,该服务使用Consul Connect和Envoy sidecar来管理往返于Pod(以及如果您继续关注,请不要忘记应用`ambassador-consul-connector.yaml`,否则将无法正常运行演示。)
一切正常运行之后,您应该能够通过新端点向服务的此修改版本发出请求 `/qotm-consul-tls/`
现在,将ksniff附加到新的QOTM pod。如果您在Pods处进行检查,您会注意到此版本的QOTM有两个容器,一个用于QOTM服务,一个用于Consul管理的Envoy sidecar:
您可以describe pod以获取容器名称,这还将显示许多有关如何使用init容器引导Envoy sidecar的有趣信息:
下面突出显示了Envoy sidecar init容器中有趣的细节,它显示了在启动时生成并加载到sidecar中的Envoy配置。您可以看到正在侦听端口5000的QOTM服务的详细信息,还可以看到Envoy sidecar将侦听Pod网络接口上的端口20000。记下这一点,因为稍后将在博客文章中使用此信息。
“`
…
cat <<EOF >/consul/connect-inject/service.hcl
services {
id = “${POD_NAME}-qotm-sidecar-proxy”
name = “qotm-sidecar-proxy”
kind = “connect-proxy”
address = “${POD_IP}”
port = 20000
proxy {
destination_service_name = “qotm”
destination_service_id = “qotm”
local_service_address = “127.0.0.1”
local_service_port = 5000
}
…
“`
如果您此时不想使用完整的describe命令,则还可以使用一些kubectl magic从标记为Pod的“ qotm-mtls”中获取容器名称:
“`
$ kubectl get pods -l app=qotm-mtls -o jsonpath={.items[].spec.containers[*].name}
qotm consul-connect-envoy-sidecar
“`
在这里,您可以看到Consul注入的Envoy sidecar被称为“ consul-connect-envoy-sidecar”。
将ksniff附加到具有多个容器的Pod时,需要指定要附加到哪个容器。由于一个Pod中的所有容器共享一个网络名称空间,容器的选择通常取决于您可以成功附加到哪个容器,例如哪个容器具有正确的权限,并且没有运行scratch base映像等(请在下面的“高级技术”部分了解更多信息)。
我现在将ksniff附加到我的QOTM pod和Envoy sidecar容器上:
如果将“ http”过滤器添加到Wireshark并向我的QOTM服务发出请求,这就是我看到的内容:
除了注意源IP地址和目标IP地址,所有内容看起来都与上一个示例非常相似:它们都是`127.0.0.1`。您在这里看到的是Consul Envoy sidecar和QOTM服务之间的未加密流量,它们通过Pod的localhost loopback进行通信。这在容器之间有效,因为Pod中的所有容器共享一个网络namespace。另外,值得注意的是,在生产中,QOTM服务应仅绑定到Pod的loopback,因为您不希望Pod之外的任何进程通过不安全的传输介质(即HTTP)与其进行通信。但是,这将意味着HTTP readiness checks(通过Node的kubelet进行)将无法正常工作,因此您必须使用其他形式的[readiness checks](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/)。
看到到QOTM服务的未加密通信只在loopback上发生,这只是TLS验证过程的一部分。理想情况下,您希望看到加密的通信流在集群中流动。您可以通过删除“http”过滤器来实现这一点,而不是添加一个显示过滤器来仅显示目标IP地址为QOTM Pod、目标端口为20000的TCP通信量,您可以看到Envoy sidecar正在通过先前发出的kubectl descripe命令监听该通信量。
如果速度很快(通常在群集周围有很多运行状况检查的流量),那么您应该能够看到类似以下内容的内容:
虽然这不像HTTP流量那么容易阅读,但您可以清楚地看到由Ambassador Pod(10.60.0.11)向我的QOTM应用程序(10.60.2.7)发起的“Client Hello”TLS握手,然后是使用包含不可读加密数据的TLSv1.2协议的一系列其他数据包。
将这两个结果放在一起,一个QOTM通过Pod的loopback响应领事使节代理的请求,另一个是Ambassador Pod通过TLS与QOTM Consul Envoy sidecar通信,这应该证明您正确配置了TLS。
# 更多技术
本博客文章涉及的主题范围很广,但我可能会在以后的文章中探讨它们。第一种是使用Wireshark进行附加的[高级筛选](https://www.wireshark.org/docs/wsug_html_chunked/ChWorkDisplayFilterSection.html),当您尝试调试在不同IP和端口上公开的特定服务时,这将很有用。同样有趣的是,它可以[解密](https://redflagsecurity.net/2019/03/10/decrypting-tls-wireshark/)群集周围流过的[TLS流量](https://redflagsecurity.net/2019/03/10/decrypting-tls-wireshark/),例如,通过将Consul提供的私钥加载到Wireshark中。最后,ksniff还具有在[特权模式下](https://github.com/eldadru/ksniff#user-content-non-privileged-and-scratch-pods)执行的[功能](https://github.com/eldadru/ksniff#user-content-non-privileged-and-scratch-pods),以嗅探从头开始构建的容器中的流量,或者禁止附加进程。
有几个主题超出了本文的讨论范围,但我可以在以后的文章中对它们进行探讨。第一种是使用Wireshark进行额外的高级筛选,这在尝试调试在不同IP和端口上公开的特定服务时非常有用。另一个有趣的功能是解密围绕集群流动的TLS流量,例如,通过将Consul提供的私钥加载到Wireshark中。最后,ksniff还能够以特权模式执行,以便嗅探从头构建的容器中的流量,或者不允许附加进程。
# 结论
我发现ksniff是一个非常有价值的工具,它可以用来探索使用各种service mesh(如consul、Linkerd和Istio)的 [Ambassador](https://www.getambassador.io/)API集成。当然,您可以使用tcpdump,但我发现有时配置它很有挑战性,而且我喜欢Wireshark提供的强大GUI。
如果你想问这个问题,你可以在Twitter上的@danielbryantuk找到我,或者在Datawire OSS Slack上使用相同的句柄。
翻译自:https://blog.getambassador.io/verifying-service-mesh-tls-in-kubernetes-using-ksniff-and-wireshark-454b1e3f4dc9