双向 TLS 迁移

本任务阐述如何将 Istio 服务的请求从明文模式平滑过渡至双向 TLS 模式,并确保在整个迁移过程中不干扰在线流量的正常通信。

针对多服务跨网络通信的应用场景,通常希望逐步将所有服务迁移到 Istio 网格中。如此一来,在迁移过程中,将出现只有部分服务注入了 Envoy sidecar 的情况。对于一个已注入 sidecar 的服务而言,一旦开启服务的双向 TLS 通信模式,那么传统客户端(即:没有 Envoy 的客户端)将无法与之通信,因为这些客户端没有注入 Envoy sidecar 和客户端证书。为了解决这个问题,Istio 认证策略提供了一种 “PERMISSIVE” 模式。当启用 “PERMISSIVE” 模式时,服务可以同时接收 HTTP 和双向 TLS 请求。

这样,便可以将 Istio 服务的通信模式配置为双向 TLS,与此同时,不干扰其与传统服务之间的通信。此外,可以使用 Grafana dashboard 检查哪些服务仍然向 “PERMISSIVE” 模式的服务发送明文请求,然后选择在这些服务迁移结束后关闭目标服务的 “PERMISSIVE” 模式,将其锁定为只接收双向 TLS 请求。

开始之前

  • 理解 Istio 认证策略以及相关的双向 TLS 认证概念。

  • 准备一个 Kubernetes 集群并部署好 Istio,不要开启全局双向 TLS (如:可以使用安装步骤中提供的 demo 配置 profile,或者将安装选项 global.mtls.enabled 设置为 false)。

  • demo 准备

    • 创建如下命名空间并在其中都部署上 httpbinsleep,注入 sidecar。

      • foo
      • bar
    • 创建如下命名空间并在其中部署 sleep,不注入 sidecar

      • legacy
    ZipZipZipZipZip
    $ kubectl create ns foo
    $ kubectl apply -f <(istioctl kube-inject -f @samples/httpbin/httpbin.yaml@) -n foo
    $ kubectl apply -f <(istioctl kube-inject -f @samples/sleep/sleep.yaml@) -n foo
    $ kubectl create ns bar
    $ kubectl apply -f <(istioctl kube-inject -f @samples/httpbin/httpbin.yaml@) -n bar
    $ kubectl apply -f <(istioctl kube-inject -f @samples/sleep/sleep.yaml@) -n bar
    $ kubectl create ns legacy
    $ kubectl apply -f @samples/sleep/sleep.yaml@ -n legacy
    
  • (使用 curl 命令)从每个 sleep pod (命名空间为 foobarlegacy)分别向 httpbin.foo 发送 http 请求。所有请求都应成功响应,返回 HTTP code 200。

    $ for from in "foo" "bar" "legacy"; do kubectl exec $(kubectl get pod -l app=sleep -n ${from} -o jsonpath={.items..metadata.name}) -c sleep -n ${from} -- curl http://httpbin.foo:8000/ip -s -o /dev/null -w "sleep.${from} to httpbin.foo: %{http_code}\n"; done
    sleep.foo to httpbin.foo: 200
    sleep.bar to httpbin.foo: 200
    sleep.legacy to httpbin.foo: 200
    
  • 验证没有在系统中设置认证策略或目标规则(控制面板除外):

    $ kubectl get policies.authentication.istio.io --all-namespaces
    NAMESPACE      NAME                          AGE
    istio-system   grafana-ports-mtls-disabled   3m
    
    $ kubectl get destinationrule --all-namespaces
    NAMESPACE      NAME                                 HOST                                             AGE
    istio-system   istio-multicluster-destinationrule   *.global                                         35s
    istio-system   istio-policy                         istio-policy.istio-system.svc.cluster.local      35s
    istio-system   istio-telemetry                      istio-telemetry.istio-system.svc.cluster.local   33s
    

配置客户端发送双向 TLS 请求

设置 DestinationRule,配置 Istio 服务发送双向 TLS 请求。

$ cat <<EOF | kubectl apply -n foo -f -
apiVersion: "networking.istio.io/v1alpha3"
kind: "DestinationRule"
metadata:
  name: "example-httpbin-istio-client-mtls"
spec:
  host: httpbin.foo.svc.cluster.local
  trafficPolicy:
    tls:
      mode: ISTIO_MUTUAL
EOF

sleep.foosleep.bar 开始向 httpbin.foo 发送双向 TLS 请求。因为 sleep.legacy 没有注入 sidecar,DestinationRule 不会对其起作用,所以 sleep.legacy 仍然向 httpbin.foo 发送明文请求。

现在,我们确认一下,所有发送至 httpbin.foo 的请求仍会响应成功。

$ for from in "foo" "bar" "legacy"; do kubectl exec $(kubectl get pod -l app=sleep -n ${from} -o jsonpath={.items..metadata.name}) -c sleep -n ${from} -- curl http://httpbin.foo:8000/ip -s -o /dev/null -w "sleep.${from} to httpbin.foo: %{http_code}\n"; done
sleep.foo to httpbin.foo: 200
sleep.bar to httpbin.foo: 200
sleep.legacy to httpbin.foo: 200

也可以指定一部分客户端使用 DestinationRule 中设置的 ISTIO_MUTUAL 双向 TLS 通信模式。 检查 Grafana to monitor 验证设置起效后,再扩大作用范围,最终应用到所有的 Istio 客户端服务。

锁定为双向 TLS

当所有客户端服务都成功迁移至 Istio 之后,注入 Envoy sidecar,便可以锁定 httpbin.foo 只接收双向 TLS 请求。

$ cat <<EOF | kubectl apply -n foo -f -
apiVersion: "security.istio.io/v1beta1"
kind: "PeerAuthentication"
metadata:
  name: "default"
spec:
  mtls:
    mode: STRICT
EOF

此时,源自 sleep.legacy 的请求将响应失败。

$ for from in "foo" "bar" "legacy"; do for to in "foo" "bar"; do kubectl exec "$(kubectl get pod -l app=sleep -n ${from} -o jsonpath={.items..metadata.name})" -c sleep -n ${from} -- curl http://httpbin.${to}:8000/ip -s -o /dev/null -w "sleep.${from} to httpbin.${to}: %{http_code}\n"; done; done
sleep.foo to httpbin.foo: 200
sleep.foo to httpbin.bar: 200
sleep.bar to httpbin.foo: 200
sleep.bar to httpbin.bar: 200
sleep.legacy to httpbin.foo: 000
command terminated with exit code 56
sleep.legacy to httpbin.bar: 200

如果你安装 Istio 时带有参数 values.global.proxy.privileged=true,那么你可以使用 tcpdump 来验证流量是否被加密。

$ kubectl exec -nfoo "$(kubectl get pod -nfoo -lapp=httpbin -ojsonpath={.items..metadata.name})" -c istio-proxy -it -- sudo tcpdump dst port 80  -A
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes

当分别从 sleep.legacysleep.foo 发送请求时,您将在输出中看到纯文本和加密文本。

若无法将所有服务迁移至 Istio (注入 Envoy sidecar),则必须开启 PERMISSIVE 模式。 然而,开启 PERMISSIVE 模式时,系统默认不对明文请求进行认证或授权检查。 推荐使用 Istio 授权来为不同的请求路径配置不同的授权策略。

锁定整个网格的 mTLS

$ kubectl apply -n istio-system -f - <<EOF
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
  name: "default"
spec:
  mtls:
    mode: STRICT
EOF

现在,foobar 命名空间都强制执行仅双向 TLS 流量,因此您应该会看到来自 sleep.legacy 的请求 访问两个命名空间的服务都失败了。

$ for from in "foo" "bar" "legacy"; do for to in "foo" "bar"; do kubectl exec "$(kubectl get pod -l app=sleep -n ${from} -o jsonpath={.items..metadata.name})" -c sleep -n ${from} -- curl http://httpbin.${to}:8000/ip -s -o /dev/null -w "sleep.${from} to httpbin.${to}: %{http_code}\n"; done; done

清除

清除所有资源。

$ kubectl delete ns foo bar legacy
Namespaces foo bar legacy deleted.
这些信息有用吗?
Do you have any suggestions for improvement?

Thanks for your feedback!