Skip to content

5、Ingress 出口流量

Egress Gateway

image-20231118075454655

默认情况下,Istio 网关中 Pod 的所有出站流量都会重定向到其 Sidecar 代理,集群外部 URL 的可访问性取决于代理的配置。默认情况下,Istio 将 Envoy 代理配置为允许传递未知服务的请求,这样当然是非常方便的,但是有的时候可能我们也需要更加严格的控制。

目录

[toc]

本节实战

实战名称
🚩 实战:访问外部服务-2023.11.18(测试成功)
🚩 实战:Egress 出口网关-2023.11.18(测试成功)

访问外部服务

==🚩 实战:访问外部服务-2023.11.18(测试成功)==

image-20231118110525697

实验环境:

bash
k8sv1.27.6(containerd:istiov1.19.3(--setprofile=demo)

实验软件:

链接:https:echo$SOURCE_POD

1.Envoy 转发流量到外部服务

image-20231118085832105

Istio 有一个安装选项 global.outboundTrafficPolicy.mode,它配置 Sidecar 对外部服务(没有在 Istio 的内部服务注册中定义的服务)的处理方式。如果这个选项设置为 ALLOW_ANY, Istio 代理允许调用未知的服务。如果这个选项设置为 REGISTRY_ONLY,那么 Istio 代理会阻止任何没有在网格中定义的 HTTP 服务或 Service Entry的主机。ALLOW_ANY是默认值,不控制对外部服务的访问。

  • 我们可以运行以下命令来查看该配置的值:
bash
kubectlgetistiooperatorinstalled-state-nistio-system-ojsonpath='{.spec.meshConfig.outboundTrafficPolicy.mode}'

正常该命令会输出 ALLOW_ANY或没有任何输出(默认为 ALLOW_ANY)。

如果之前已经设置为了 REGISTRY_ONLY,则可以使用 istioctl命令来修改:

bash
istioctlinstall<flags-you-used-to-install-Istio>--setmeshConfig.outboundTrafficPolicy.mode=ALLOW_ANY

image-20231118090437146

  • 我们可以从 SOURCE_POD向外部 HTTPS 服务发出两个请求来进行验证:
bash
$kubectlexec"$SOURCE_POD"-csleep--curl-sSIhttps:HTTP/1.1200OKHTTP/1.1200OK

如果得到 200 状态码,说明我们成功地从网格中发送了 Egress 流量,这是因为我们的网格中的 Sidecar 代理允许访问任何外部服务。当然这种方式虽然很简单,但是丢失了对外部服务流量的 Istio 监控和控制,比如外部服务的调用没有记录到 Mixer 的日志中。

测试结束。😘

2.控制对外部服务的访问

要控制对外部服务的访问,我们需要用到 Istio 提供的 ServiceEntry这个 CRD 对象,它用来定义网格中的服务。接下来我们将来了解下如何在不丢失 Istio 的流量监控和控制特性的情况下,配置对外部 HTTP 服务(httpbin.org)和外部 HTTPS 服务(www.baidu.com) 的访问。

为了控制对外部服务的访问,我们需要将 global.outboundTrafficPolicy.mode选项,从 ALLOW_ANY模式改为 REGISTRY_ONLY模式。

如果你使用的是 IstioOperator来安装 Istio,则只需要在配置中添加以下字段即可:

yaml
spec:meshConfig:outboundTrafficPolicy:mode:REGISTRY_ONLY
  • 当然也可以使用如下所示的 istio install命令来修改:
bash
$istioctlinstall--setprofile=demo--setmeshConfig.outboundTrafficPolicy.mode=REGISTRY_ONLYThiswillinstalltheIstio1.19.3"demo"profile(with components:Istiocore,Istiod,Ingressgateways,andEgressgateways) into the cluster. Proceed?(y/N) yIstiocoreinstalledIstiodinstalledIngressgatewaysinstalledEgressgatewaysinstalledInstallationcompleteMadethisinstallationthedefaultforinjectionandvalidation.

安装完成后可以使用下面的命令来查看出口流量策略的配置:

bash
$kubectlgetistiooperatorinstalled-state-nistio-system-ojsonpath='{.spec.meshConfig.outboundTrafficPolicy.mode}'REGISTRY_ONLY

如果输出结果为 REGISTRY_ONLY则说明我们已经成功修改了出口流量策略。

  • 然后我们再从 SOURCE_POD向外部 HTTPS 服务发出几个请求,来验证它们现在是否被阻止:
bash
$kubectlexec"$SOURCE_POD"-csleep--curl-sSIhttps:commandterminatedwithexitcode35commandterminatedwithexitcode35

image-20231118091340561

正常情况下,这里会返回 35 错误码,说明我们已经成功地阻止了对外部服务的访问。

配置更改后可能需要一小段时间才能生效,所以可能仍然可以得到成功的响应,等待一段时间后再重新执行上面的命令即可。

接下来我们就可以自己定义 ServiceEntry对象来配置对外部服务的访问了。使用 服务条目资源(ServiceEntry)可以将条目添加到 Istio 内部维护的服务注册表中,添加服务条目后,Envoy 代理可以将流量发送到该服务,就好像该服务条目是网格中的服务一样。通过配置服务条目,可以管理在网格外部运行的服务的流量。此外,还可以配置虚拟服务和目标规则,以更精细的方式控制到服务条目的流量,就像为网格中的其他任何服务配置流量一样。

  • 这里我们创建一个如下所示的 ServiceEntry对象:
yaml
#httpbin-ext.yamlapiVersion:networking.istio.io/v1alpha3kind:ServiceEntrymetadata:name:httpbin-extspec:hosts:- httpbin.orgports:- number:80name:httpprotocol:HTTPresolution:DNS# 主机的服务发现模式location:MESH_EXTERNAL# 指定服务是否应被视为网格外部的一部分还是网格的一部分

该资源对象中我们在 hosts中指定了 httpbin.org服务的主机名,然后在 ports中指定了需要暴露的端口及其属性,表示该 ServiceEntry对象代表对 http:[root@master1 istio-1.19.3]#kubectl exec "$SOURCE_POD"-c sleep -- curl -sS http:serviceentry.networking.istio.io/httpbin-extcreated$kubectlexec"$SOURCE_POD"-csleep--curl-sShttp:{"headers":{"Accept":"**","Host":"httpbin.org",...}}

与通过 HTTP 和 HTTPS 访问外部服务不同,我们不会看到任何与 Istio Sidecar 有关的请求头,并且发送到外部服务的请求既不会出现在 Sidecar 的日志中,也不会出现在 Mixer 日志中。 绕过 Istio Sidecar 意味着不能再监视对外部服务的访问了。

  • 最后记得清理上面创建的资源对象。
bash
kubectldelete-fhttpbin-ext.yamlkubectldelete-fhttpbin-ext-vs.yamlkubectldelete-fbaidu-ext.yaml

总结

这里我们学习了从 Istio 网格调用外部服务的三种方法:

  • 配置 Envoy 以允许访问任何外部服务。
  • 使用 ServiceEntry将一个可访问的外部服务注册到网格中,这是推荐的方法。
  • 配置 Istio Sidecar 以从其重新映射的 IP 表中排除外部 IP。

第一种方法通过 Istio Sidecar 代理来引导流量,包括对网格内部未知服务的调用。使用这种方法时,无法监控对外部服务的访问或无法利用 Istio 的流量控制功能。要轻松为特定的服务切换到第二种方法,只需为那些外部服务创建 ServiceEntry 即可,此过程使您可以先访问任何外部服务,然后再根据需要决定是否启用控制访问、流量监控、流量控制等功能。

第二种方法可以使用 Istio 服务网格所有的功能去调用集群内或集群外的服务。这里我们学习了如何监控对外部服务的访问并设置对外部服务的调用的超时规则。

第三种方法绕过了 Istio Sidecar 代理,使你的服务可以直接访问任意的外部服务。但是,以这种方式配置代理需要了解集群提供商相关知识和配置。 与第一种方法类似,您也将失去对外部服务访问的监控,并且无法将 Istio 功能应用于外部服务的流量。

Egress 出口网关

上面我们了解了位于服务网格内部的应用应如何访问网格外部的 HTTP 和 HTTPS 服务,我们学习了如何通过 ServiceEntry对象配置 Istio 以受控的方式访问外部服务,这种方式实际上是通过 Sidecar 直接调用的外部服务,但是有时候我们可能需要通过专用的 Egress Gateway 服务来调用外部服务,这种方式可以更好的控制对外部服务的访问。

Istio 使用 Ingress 和 Egress Gateway 配置运行在服务网格边缘的负载均衡,Ingress Gateway 允许定义网格所有入站流量的入口。 Egress Gateway 是一个与 Ingress Gateway 对称的概念,它定义了网格的出口。Egress Gateway 允许我们将 Istio 的功能(例如,监视和路由规则)应用于网格的出站流量。

==使用场景==

比如有一个对安全要求非常严格的团队,要求服务网格所有的出站流量必须经过一组专用节点。专用节点运行在专门的机器上,与集群中运行应用程序的其他节点隔离,这些专用节点用于实施 Egress 流量的策略,并且受到比其余节点更严密地监控。

另一个使用场景是集群中的应用节点没有公有 IP,所以在该节点上运行的网格服务都无法访问互联网,那么我们就可以通过定义 Egress gateway,将公有 IP 分配给 Egress Gateway 节点,用它引导所有的出站流量,可以使应用节点以受控的方式访问外部服务。

==🚩 实战:Egress 出口网关-2023.11.18(测试成功)==

img

实验环境:

bash
k8sv1.27.6(containerd:istiov1.19.3(--setprofile=demo)

实验软件:

链接:https:NAMEREADYSTATUSRESTARTSAGEistio-egressgateway-556f6f58f4-hkzdd1/1Running014d

如果没有 Pod 返回,可以通过下面的步骤来部署 Istio Egress Gateway。如果你使用 IstioOperator安装 Istio,请在配置中添加以下字段:

否则使用如下的 istioctl install命令来安装:

否则,在使用以下命令部署 sleep 应用程序之前,手动注入 Sidecar:

1.用 Egress gateway 发起 HTTP 请求

  • 首先创建一个 ServiceEntry对象来允许流量直接访问外部的 edition.cnn.com服务。
yaml
#cnn-ext.yamlapiVersion:networking.istio.io/v1alpha3kind:ServiceEntrymetadata:name:cnnspec:hosts:- edition.cnn.comports:- number:80name:http-portprotocol:HTTP- number:443name:httpsprotocol:HTTPSresolution:DNS#location没指定,默认就是一个外部服务
  • 默认没做配置,我们去访问是访问不了的
bash
[root@master1 istio-1.19.3]#kubectl exec "$SOURCE_POD"-c sleep -- curl -sSL -o /dev/null -D - http:HTTP/1.1502BadGatewaydate:Sat,18Nov202303:17:10GMTserver:envoycontent-length:0
  • 部署,然后发送 HTTPS 请求到 https:serviceentry.networking.istio.io/cnncreated$kubectlexec"$SOURCE_POD"-csleep--curl-sSL-o/dev/null-D-http:# 输出如下内HTTP/1.1301MovedPermanently# ......location:https:# ......HTTP/2200Content-Type:text/html;charset=utf-8# ......

image-20231118113139948

  • 然后为 edition.cnn.com的 80 端口创建一个 egress Gateway,并为指向 Egress Gateway 的流量创建一个 DestinationRule规则,如下所示:
yaml
#cnn-egress-gateway.yamlapiVersion:networking.istio.io/v1alpha3kind:Gatewaymetadata:name:istio-egressgatewayspec:selector:istio:egressgateway# 匹配 Egress Gateway Pod 的标签servers:- port:number:80name:httpprotocol:HTTPhosts:- edition.cnn.com# 也支持通配符 *的形式---apiVersion:networking.istio.io/v1alpha3kind:DestinationRulemetadata:name:egressgateway-for-cnnspec:host:istio-egressgateway.istio-system.svc.cluster.local# 目标规则为 Egress Gatewaysubsets:- name:cnn# 定义一个子集 cnn,没有指定 labels,则 subset 会包含所有符合 host 字段指定的服务的 Pod (需要注意下。)

在上面的对象中我们首先定义了一个 Gateway对象,不过这里我们定义的是一个 Egress Gateway,通过 istio:egressgateway匹配 Egress Gateway Pod 的标签,并在 servers中定义了 edition.cnn.com服务的 80 端口。然后定义了一个 DestinationRule对象,指定了目标规则为 istio-egressgateway.istio-system.svc.cluster.local,并定义了一个子集 cnn

这里的子集名称是 cnn,但没有指定 labels。这意味着,这个 subset 会涵盖所有属于 istio-egressgateway.istio-system.svc.cluster.local服务的 Pod。这种情况下,subset 的作用主要是为了在其他 Istio 配置中提供一个方便的引用名称,而不是为了区分不同的 Pod 子集。

  • 如何再定义一个 VirtualService对象将流量从 Sidecar 引导至 Egress Gateway,再从 Egress Gateway 引导至外部服务,如下所示:
yaml
#cnn-egress-gateway.yamlapiVersion:networking.istio.io/v1alpha3kind:VirtualServicemetadata:name:direct-cnn-through-egress-gatewayspec:hosts:- edition.cnn.comgateways:- istio-egressgateway# Egress Gateway- mesh# 网格内部的流量http:- match:- gateways:- mesh# 这条规则适用于从服务网格内发出的流量port:80route:- destination:host:istio-egressgateway.istio-system.svc.cluster.local# 流量将被路由到 egress gatewaysubset:cnnport:number:80weight:100- match:- gateways:- istio-egressgateway# 这条规则适用于通过 istio-egressgateway 的流量port:80route:- destination:host:edition.cnn.com# 流量将被路由到外部服务port:number:80weight:100

在上面的 VirtualService对象中通过 hosts指定 edition.cnn.com,表示该虚拟服务用于该服务的请求,gateways字段中定义了 istio-egressgatewaymesh两个值,istio-egressgateway是上面我们定义的 Egress Gateway,mesh表示该虚拟服务用于网格内部的流量,也就是说这个虚拟服务指定了如何处理来自服务网格内部以及通过 istio-egressgateway的流量。

mesh是一个特殊的关键字,在 Istio 中表示服务网格内的所有 Sidecar 代理。当使用 mesh作为网关时,这意味着 VirtualService中定义的路由规则适用于服务网格内的所有服务,即所有装有 Istio sidecar 代理的服务。

http字段中定义了两个 match,第一个 match用于匹配 mesh网关,第二个 match用于匹配 istio-egressgateway网关,然后在 route中定义了两个 destination,第一个 destination用于将流量引导至 Egress Gateway 的 cnn 子集,第二个 destination用于将流量引导至外部服务。

总结来说,这个 VirtualService的作用是控制服务网格内部到 edition.cnn.com的流量。当流量起始于服务网格内时,它首先被路由到 istio-egressgateway,然后再路由到 edition.cnn.com,这种配置有助于统一和控制从服务网格内部到外部服务的流量,可以用于流量监控、安全控制或实施特定的流量策略。

  • 应用上面的资源对象后,我们再次向 edition.cnn.com/politics端点发出 curl 请求:
bash
[root@master1 istio-1.19.3]#kubectl apply -f cnn-egress-gateway.yaml gateway.networking.istio.io/istio-egressgatewaycreateddestinationrule.networking.istio.io/egressgateway-for-cnncreatedvirtualservice.networking.istio.io/direct-cnn-through-egress-gatewaycreated$kubectlexec"$SOURCE_POD"-csleep--curl-sSL-o/dev/null-D-http:# ......HTTP/1.1301MovedPermanentlylocation:https:# ......HTTP/2200Content-Type:text/html;charset=utf-8# ......

image-20231118115153442

  • 正常和前面的一次测试输出结果是一致的,但是这次在请求是经过 istio-egressgatewayPod 发出的,我们可以查看日志来验证:
bash
kubectllogs-listio=egressgateway-cistio-proxy-nistio-system|tail

正常会看到一行类似于下面这样的内容:

bash
[2023-11-15T08:48:38.683Z] "GET /politics HTTP/2"301 - via_upstream - "-"0 0 204 203 "10.244.1.73""curl/7.81.0-DEV""6c2c4550-92d4-955c-b6cb-83bf2b0e06f4""edition.cnn.com""151.101.3.5:80"outbound|80||edition.cnn.com10.244.2.184:4662010.244.2.184:808010.244.1.73:49924--

image-20231118115301649

==因为我们这里只是将 80 端口的流量重定向到 Egress Gateway 了,所以重定向后 443 端口的 HTTPS 流量将直接进入 edition.cnn.com,所以没有看到 443 端口的日志,==但是我们可以通过 SOURCE_POD的 Sidecar 代理的日志来查看到:

bash
$kubectllogs"$SOURCE_POD"-cistio-proxy|tail# ......[2023-11-15T08:55:55.513Z] "GET /politics HTTP/1.1"301 - via_upstream - "-"0 0 191 191 "-""curl/7.81.0-DEV""12ce15aa-1247-9b7e-8185-4224f96f5ea0""edition.cnn.com""10.244.2.184:8080"outbound|80|cnn|istio-egressgateway.istio-system.svc.cluster.local10.244.1.73:49926151.101.195.5:8010.244.1.73:41576--[2023-11-15T08:55:55.753Z] "- - -"0 - - - "-"839 2487786 1750 - "-""-""-""-""151.101.195.5:443"outbound|443||edition.cnn.com10.244.1.73:45246151.101.67.5:44310.244.1.73:42998edition.cnn.com-

image-20231118115450675

  • 清除刚才创建的资源
bash
[root@master1 istio-1.19.3]#kubectl delete -f cnn-ext.yaml [root@master1 istio-1.19.3]#kubectl delete -f cnn-egress-gateway.yaml

测试成功。😘

2.用 Egress gateway 发起 HTTPS 请求

上面我们已经学习了如何通过 Egress Gateway 发起 HTTP 请求,接下来我们再来学习下如何通过 Egress Gateway 发起 HTTPS 请求。

原理都是一样的,只是我们需要在相应的 ServiceEntryEgress GatewayVirtualService中指定 TLS协议的端口 443。

  • 首先为 edition.cnn.com定义 ServiceEntry服务:
yaml
#cnn-https.yamlapiVersion:networking.istio.io/v1alpha3kind:ServiceEntrymetadata:name:cnnspec:hosts:- edition.cnn.comports:- number:443name:tlsprotocol:TLSresolution:DNS
  • 应用该资源对象后,发送 HTTPS 请求到 https:serviceentry.networking.istio.io/cnncreated$kubectlexec"$SOURCE_POD"-csleep--curl-sSL-o/dev/null-D-https:...HTTP/2200Content-Type:text/html;charset=utf-8...
  • 接下来同样的方式为 edition.cnn.com创建一个 Egress Gateway。除此之外还需要创建一个目标规则和一个虚拟服务,用来引导流量通过 Egress Gateway,并通过 Egress Gateway 与外部服务通信。
yaml
#cnn-https.yamlapiVersion:networking.istio.io/v1alpha3kind:Gatewaymetadata:name:istio-egressgatewayspec:selector:istio:egressgatewayservers:- port:number:443name:tlsprotocol:TLShosts:- edition.cnn.comtls:mode:PASSTHROUGH# 透传---apiVersion:networking.istio.io/v1alpha3kind:DestinationRulemetadata:name:egressgateway-for-cnnspec:host:istio-egressgateway.istio-system.svc.cluster.localsubsets:- name:cnn---apiVersion:networking.istio.io/v1alpha3kind:VirtualServicemetadata:name:direct-cnn-through-egress-gatewayspec:hosts:- edition.cnn.comgateways:- mesh- istio-egressgatewaytls:- match:- gateways:- meshport:443sniHosts:- edition.cnn.comroute:- destination:host:istio-egressgateway.istio-system.svc.cluster.localsubset:cnnport:number:443- match:- gateways:- istio-egressgatewayport:443sniHosts:- edition.cnn.comroute:- destination:host:edition.cnn.comport:number:443weight:100

上面对象中定义的 Gateway对象和前面的一样,只是将端口改为了 443,然后在 tls中指定了 mode:PASSTHROUGH,表示该 Gateway对象用于 TLS 协议的请求。然后在后面的 VirtualService对象中就是配置 spec.tls属性,用于指定 TLS 协议的请求的路由规则,配置方法和前面 HTTP 方式类似,只是注意要将端口改为 443,并且在 match中指定 sniHostsedition.cnn.com,表示该虚拟服务用于处理 edition.cnn.com的 TLS 请求。

  • 应用上面的资源对象后,我们现在发送 HTTPS 请求到 https:serviceentry.networking.istio.io/cnnunchangedgateway.networking.istio.io/istio-egressgatewaycreateddestinationrule.networking.istio.io/egressgateway-for-cnncreatedvirtualservice.networking.istio.io/direct-cnn-through-egress-gatewaycreated$kubectlexec"$SOURCE_POD"-csleep--curl-sSL-o/dev/null-D-https:...HTTP/2200Content-Type:text/html;charset=utf-8...
  • 检查 Egress Gateway 代理的日志,则打印日志的命令是:
bash
kubectllogs-listio=egressgateway-nistio-system

应该会看到类似于下面的内容:

bash
[2023-11-15T08:59:55.513Z] "- - -"0 - 627 1879689 44 - "-""-""-""-""151.101.129.67:443"outbound|443||edition.cnn.com172.30.109.80:41122172.30.109.80:443172.30.109.112:59970edition.cnn.com

image-20231118121629651

  • 到这里我们就实现了通过 Egress Gateway 发起 HTTPS 请求。最后记得清理上面创建的资源对象:
bash
[root@master1 istio-1.19.3]#kubectl delete -f cnn-https.yaml

测试结束。😘

需要注意的是,Istio 无法强制让所有出站流量都经过 Egress Gateway, Istio 只是通过 Sidecar 代理实现了这种流向。攻击者只要绕过 Sidecar 代理, 就可以不经 Egress Gateway 直接与网格外的服务进行通信,从而避开了 Istio 的控制和监控。出于安全考虑,集群管理员和云供应商必须确保网格所有的出站流量都要经过 Egress Gateway。这需要通过 Istio 之外的机制来满足这一要求。例如,集群管理员可以配置防火墙,拒绝 Egress Gateway 以外的所有流量。Kubernetes NetworkPolicy也能禁止所有不是从 Egress Gateway 发起的出站流量,但是这个需要 CNI 插件的支持。 此外,集群管理员和云供应商还可以对网络进行限制,让运行应用的节点只能通过 gateway 来访问外部网络。要实现这一限制,可以只给 Gateway Pod 分配公网 IP,并且可以配置 NAT 设备, 丢弃来自 Egress Gateway Pod 之外的所有流量。

Egress TLS Origination

1.Egress TLS Origination

img

接下来我们将学习如何通过配置 Istio 去实现对发往外部服务的流量的 TLS Origination(TLS 发起)。 若此时原始的流量为 HTTP,则 Istio 会将其转换为 HTTPS 连接。TLS Origination 的概念前面我们也已经介绍过了。

假设有一个传统应用正在使用 HTTP 和外部服务进行通信,如果有一天突然有一个新的需求,要求必须对所有外部的流量进行加密。此时,使用 Istio 便可通过修改配置实现此需求,而无需更改应用中的任何代码。该应用可以发送未加密的 HTTP 请求,由 Istio 为请求进行加密。

从应用源头发起未加密的 HTTP 请求,并让 Istio 执行 TLS 升级的另一个好处是可以产生更好的遥测并为未加密的请求提供更多的路由控制

下面我们就来学习下如何配置 Istio 实现 TLS Origination。

🚩 实战:Egress TLS Origination-2023.11.19(failed)

实验环境:

bash
k8sv1.27.6(containerd:istiov1.19.3(--setprofile=demo)

应用程序在以下链接里:

链接:https:echo$SOURCE_POD

配置对外部服务的访问

我们之前已经默认配置了.spec.meshConfig.outboundTrafficPolicy.mode=REGISTRY_ONLY了,因此开始创建如下 ServiceEntry

上面的 ServiceEntry对象中我们指定了 edition.cnn.com服务的主机名,然后在 ports中指定了需要暴露的端口及其属性,表示该 ServiceEntry对象代表对 edition.cnn.com的访问,这里我们定义了 80443两个端口,分别对应 httphttps服务,resolution:DNS定义了如何解析指定的 hosts,这里我们使用 DNS 来解析。

上面我们在使用 curl命令的时候添加了一个 -L标志,该标志指示 curl将遵循重定向。 在这种情况下,服务器将对到 http:#tls-ori.yamlapiVersion:networking.istio.io/v1alpha3kind:Gatewaymetadata:name:istio-egressgatewayspec:selector:istio:egressgatewayservers:- port:number:80name:tls-origination-portprotocol:HTTPhosts:- edition.cnn.com

yaml
---#tls-ori.yamlapiVersion:networking.istio.io/v1alpha3kind:DestinationRulemetadata:name:egressgateway-for-cnnspec:host:istio-egressgateway.istio-system.svc.cluster.localsubsets:- name:cnn
yaml
---#tls-ori.yamlapiVersion:networking.istio.io/v1alpha3kind:VirtualServicemetadata:name:direct-cnn-through-egress-gatewayspec:hosts:- edition.cnn.comgateways:- istio-egressgateway# Egress Gateway- mesh# 网格内部的流量http:- match:- gateways:- meshport:80route:- destination:host:istio-egressgateway.istio-system.svc.cluster.localsubset:cnnport:number:80weight:100- match:- gateways:- istio-egressgatewayport:80route:- destination:host:edition.cnn.comport:number:443# 443 端口weight:100---apiVersion:networking.istio.io/v1alpha3kind:DestinationRulemetadata:name:originate-tls-for-edition-cnn-comspec:host:edition.cnn.comtrafficPolicy:loadBalancer:simple:ROUND_ROBINportLevelSettings:- port:number:443tls:mode:SIMPLE# initiates HTTPS for connections to edition.cnn.com

需要注意的是上面最后针对 edition.cnn.comDestinationRule对象,在 trafficPolicy中指定了 portLevelSettings用于对不同的端口定义不同的流量策略,这里我们定义了 443端口的 tls模式为 SIMPLE,表示当访问 edition.cnn.com的 HTTP 请求时执行 TLS 发起。

image-20231119073546995

这次将会收到唯一的 200 OK 响应,因为 Istio 为 curl 执行了 TLS 发起,原始的 HTTP 被升级为 HTTPS 并转发到 edition.cnn.com。服务器直接返回内容而无需重定向,这消除了客户端与服务器之间的请求冗余,使网格保持加密状态,隐藏了你的应用获取 edition.cnn.compolitics端点的信息。


⚠️ 注意:

自己测试后,这里一直报503错误,服务不可用。

服务不可用(503)。

image-20231119074150936

看下istio-egressgateway-556f6f58f4-7kcjc日志:(无输出)

image-20231119074458588

看下sleep-9454cc476-5v8hbistio-proxy日志:

image-20231119074744374

自己删除后续的用于 egress 流量的 TLS 发起部分,再次测试,还是可以正常访问目的网站的呀,但是一配置后,就不行了,老师的是可以的,奇怪。。。🤣

先这样吧,搁置。。。

bash
[root@master1 istio-1.19.3]#kubectl delete -f tls-ori.yaml serviceentry.networking.istio.io"edition-cnn-com"deleteddestinationrule.networking.istio.io"egressgateway-for-cnn"deletedvirtualservice.networking.istio.io"direct-cnn-through-egress-gateway"deleteddestinationrule.networking.istio.io"originate-tls-for-edition-cnn-com"deleted

如果我们在代码中有去访问外部服务,那么我们就可以不用修改代码了,只需要通过配置 Istio 来获得 TLS 发起即可,而无需更改一行代码。

到这里我们就学习了如何通过配置 Istio 实现对外部服务的 TLS 发起。

TLS 与 mTLS 基本概念

前面我们学习了如何通过配置 Istio 实现对外部服务的 TLS 发起,这里其实还有一个 mTLS 的概念呢,由于 TLS 本身就比较复杂,对于双向 TLS(mTLS)就更复杂了。

CA:证书颁发机构

TLS 是一个连接层协议,旨在为 TCP 连接提供安全保障。TLS 在连接层工作,可以与任何使用 TCP 的应用层协议结合使用。例如,HTTPS 是 HTTP 与 TLS 的结合(HTTPS 中的 S 指的是 SSL,即 TLS 的前身),TLS 认证的流程大致如下所示:

img

当然 HTTPS 的工作流程和这个过程基本就一致了:

img

当然双向 TLS 就更为复杂了,Mutual TLS(双向 TLS),或称 mTLS,对于常规的 TLS,只需要服务端认证,mTLS 相对来说有一个额外的规定:客户端也要经过认证在 mTLS 中,客户端和服务器都有一个证书,并且双方都使用它们的公钥/私钥对进行身份验证

TLS 保证了真实性,但默认情况下,这只发生在一个方向上:客户端对服务器进行认证,但服务器并不对客户端进行认证。为什么 TLS 的默认只在一个方向进行认证?因为客户端的身份往往是不相关的。例如我们在访问优点知识的时候,你的浏览器已经验证了要访问的网站服务端的身份,但服务端并没有验证你的浏览器的身份,它实际上并不关心你的浏览器的身份,这对于互联网上的 Web 项目来说足够了。但是在某些情况下,服务器确实需要验证客户端的身份,例如,当客户端需要访问某些敏感数据时,服务器可能需要验证客户端的身份,以确保客户端有权访问这些数据,这就是 mTLS 的用武之地,mTLS 是保证微服务之间跨服务通信安全的好方法

接下来我们就来测试下如何通过 egress 网关发起双向 TLS 连接。

2.通过 egress 网关发起双向 TLS 连接

img

🚩 实战:通过 egress 网关发起双向 TLS 连接-2023.11.19(测试成功)

实验环境:

bash
k8sv1.27.6(containerd:istiov1.19.3(--setprofile=demo)

应用程序在以下链接里:

链接:https:# 生成 CA 证书和私钥

bash
# 为 my-nginx.mesh-external.svc.cluster.local 创建私钥和证书签名请求$opensslreq-outmy-nginx.mesh-external.svc.cluster.local.csr-newkeyrsa:2048-nodes-keyoutmy-nginx.mesh-external.svc.cluster.local.key-subj"/CN=my-nginx.mesh-external.svc.cluster.local/O=some organization"# 使用 CA 公钥和私钥以及证书签名请求为 my-nginx.mesh-external.svc.cluster.local 创建证书$opensslx509-req-sha256-days365-CAexample.com.crt-CAkeyexample.com.key-set_serial0-inmy-nginx.mesh-external.svc.cluster.local.csr-outmy-nginx.mesh-external.svc.cluster.local.crt
bash
# 为 client.example.com 创建私钥和证书签名请求$opensslreq-outclient.example.com.csr-newkeyrsa:2048-nodes-keyoutclient.example.com.key-subj"/CN=client.example.com/O=client organization"# 使用相同的 CA 公钥和私钥以及证书签名请求为 client.example.com 创建证书$opensslx509-req-sha256-days365-CAexample.com.crt-CAkeyexample.com.key-set_serial1-inclient.example.com.csr-outclient.example.com.crt

接着我们来部署一个双向 TLS 服务器,为了模拟一个真实的支持双向 TLS 协议的外部服务,我们在 Kubernetes 集群中部署一个 NGINX 服务,该服务运行在 Istio 服务网格之外,比如运行在一个没有开启 Istio Sidecar proxy 注入的命名空间中。

nginx里默认是支持双向认证的。

image-20231119090006681

bash
kubectlcreatenamespacemesh-external
bash
$kubectlcreate-nmesh-externalsecrettlsnginx-server-certs--keymy-nginx.mesh-external.svc.cluster.local.key--certmy-nginx.mesh-external.svc.cluster.local.crt$kubectlcreate-nmesh-externalsecretgenericnginx-ca-certs--from-file=example.com.crt
json
$ cat <<\EOF >./nginx.confevents {}http {log_formatmain'$remote_addr-$remote_user[$time_local]$status''"$request"$body_bytes_sent"$http_referer"''"$http_user_agent""$http_x_forwarded_for"';access_log/var/log/nginx/access.logmain;error_log/var/log/nginx/error.log;server{listen443ssl;root/usr/share/nginx/html;indexindex.html;server_namemy-nginx.mesh-external.svc.cluster.local;ssl_certificate/etc/nginx-server-certs/tls.crt;ssl_certificate_key/etc/nginx-server-certs/tls.key;ssl_client_certificate/etc/nginx-ca-certs/example.com.crt;ssl_verify_clienton;}}EOF
bash
kubectlcreateconfigmapnginx-configmap-nmesh-external--from-file=nginx.conf=./nginx.conf
bash
kubectlapply-f-<<EOFapiVersion:v1kind:Servicemetadata:name:my-nginxnamespace:mesh-externallabels:run:my-nginxspec:ports:- port:443protocol:TCPselector:run:my-nginx---apiVersion:apps/v1kind:Deploymentmetadata:name:my-nginxnamespace:mesh-externalspec:selector:matchLabels:run:my-nginxtemplate:metadata:labels:run:my-nginxspec:containers:- name:my-nginximage:nginxports:- containerPort:443volumeMounts:- name:nginx-configmountPath:/etc/nginxreadOnly:true- name:nginx-server-certsmountPath:/etc/nginx-server-certsreadOnly:true- name:nginx-ca-certsmountPath:/etc/nginx-ca-certsreadOnly:truevolumes:- name:nginx-configconfigMap:name:nginx-configmap- name:nginx-server-certssecret:secretName:nginx-server-certs- name:nginx-ca-certssecret:secretName:nginx-ca-certsEOF
bash
[root@master1 istio-1.19.3]#kubectl exec "$(kubectlget pod -lapp=sleep -ojsonpath={.items..metadata.name})"-c sleep -- curl -sS http:[root@master1 istio-1.19.3]#kubectl exec "$(kubectlget pod -lapp=sleep -ojsonpath={.items..metadata.name})"-c sleep -- curl -sS https:curl:(60) SSL certificate problem:unable to get localissuer certificateMoredetailshere:https:curlfailedtoverifythelegitimacyoftheserverandthereforecouldnotestablishasecureconnectiontoit.Tolearnmoreaboutthissituationandhowtofixit,pleasevisitthewebpagementionedabove.commandterminatedwithexitcode60[root@master1 istio-1.19.3]#kubectl exec "$(kubectlget pod -lapp=sleep -ojsonpath={.items..metadata.name})"-c sleep -- curl -k -sS https:<html><head><title>400 No required SSL certificate was sent</title></head><body><center><h1>400 Bad Request</h1></center><center>No required SSL certificate was sent</center><hr><center>nginx/1.21.5</center></body></html>

为 egress 流量配置双向 TLS

image-20231119093149372

bash
kubectlcreatesecret-nistio-systemgenericclient-credential--from-file=tls.key=client.example.com.key\--from-file=tls.crt=client.example.com.crt--from-file=ca.crt=example.com.crt

Secret 所在的命名空间必须与出口网关部署的位置一致,我们这里是 istio-system命名空间。

yamL
kubectl apply -f - <<EOFapiVersion:networking.istio.io/v1alpha3kind:Gatewaymetadata:name:istio-egressgatewayspec:selector:istio:egressgatewayservers:- port:number:443name:httpsprotocol:HTTPShosts:- my-nginx.mesh-external.svc.cluster.localtls:mode:ISTIO_MUTUAL---apiVersion:networking.istio.io/v1alpha3kind:DestinationRulemetadata:name:egressgateway-for-nginxspec:host:istio-egressgateway.istio-system.svc.cluster.localsubsets:- name:nginxtrafficPolicy:loadBalancer:simple:ROUND_ROBINportLevelSettings:- port:number:443tls:mode:ISTIO_MUTUALsni:my-nginx.mesh-external.svc.cluster.localEOF

上面我们定义的 Gateway对象和前面的一样,只是将端口改为了 443,然后在 tls中指定了 mode:ISTIO_MUTUAL,表示该 Gateway对象用于 TLS 双向认证协议的请求。

然后同样在后面的 DestinationRule对象中配置了通过 istio-egressgateway的流量的规则,这里我们定义了 443端口的 tls模式为 ISTIO_MUTUAL,表示当访问 my-nginx.mesh-external.svc.cluster.local的 TLS 请求时执行 TLS 双向认证。

yaml
kubectl apply -f - <<EOFapiVersion:networking.istio.io/v1alpha3kind:VirtualServicemetadata:name:direct-nginx-through-egress-gatewayspec:hosts:- my-nginx.mesh-external.svc.cluster.localgateways:- istio-egressgateway- mesh# 网格内部的流量http:- match:- gateways:- meshport:80route:- destination:host:istio-egressgateway.istio-system.svc.cluster.localsubset:nginxport:number:443weight:100- match:- gateways:- istio-egressgatewayport:443route:- destination:host:my-nginx.mesh-external.svc.cluster.localport:number:443weight:100EOF

上面的 VirtualService对象定义网格内部对 my-nginx.mesh-external.svc.cluster.local服务的访问引导至 istio-egressgateway,然后再由 istio-egressgateway引导流量流向外部服务。

yaml
kubectl apply -n istio-system -f - <<EOFapiVersion:networking.istio.io/v1alpha3kind:DestinationRulemetadata:name:originate-mtls-for-nginxspec:host:my-nginx.mesh-external.svc.cluster.localtrafficPolicy:loadBalancer:simple:ROUND_ROBINportLevelSettings:- port:number:443tls:mode:MUTUALcredentialName:client-credential# 这必须与之前创建的用于保存客户端证书的 Secret 相匹配sni:my-nginx.mesh-external.svc.cluster.localEOF