Egress Gateway

此例子对 Minikube 无效。

控制 Egress 流量任务展示了如何配置 Istio 以允许网格内部的应用程序访问外部 HTTP 和 HTTPS 服务,但那个任务实际上是通过 sidecar 直接调用的外部服务。而这个示例会展示如何配置 Istio 以通过专用的 egress gateway 服务间接调用外部服务。

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

使用场景

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

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

开始之前

  • 按照安装指南中的说明安装 Istio。

  • 启动 sleep 示例,以获取发送请求的测试源。 如果您启用了自动 sidecar 注入,运行以下命令部署示例应用程序:

    Zip

    1. $ kubectl apply -f @samples/sleep/sleep.yaml@

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

    Zip

    1. $ kubectl apply -f <(istioctl kube-inject -f @samples/sleep/sleep.yaml@)

    您可以使用任何安装了 curl 的 pod 作为测试源。

  • 为了发送请求,您需要创建 SOURCE_POD 环境变量来存储源 pod 的名称:

    1. $ export SOURCE_POD=$(kubectl get pod -l app=sleep -o jsonpath={.items..metadata.name})
  • 启用 Envoy 访问日志

部署 Istio egress gateway

  1. 检查 Istio egress gateway 是否已布署:

    1. $ kubectl get pod -l istio=egressgateway -n istio-system

    如果没有 pod 返回,通过接下来的步骤来部署 Istio egress gateway。

  2. 执行以下命令:

    1. $ istioctl manifest apply --set values.global.istioNamespace=istio-system \
    2. --set values.gateways.istio-ingressgateway.enabled=false \
    3. --set values.gateways.istio-egressgateway.enabled=true

以下说明为 default 命名空间中的 egress gateway 创建了一条 destination rule,并且我们假定客户端 SOURCE_POD 也已经运行在 default 命名空间中。 如果没有创建 destination rule,destination rule 查找路径会找不到这条 destination rule,客户端请求将失败。

定义 Egress gateway 并引导 HTTP 流量

首先创建一个 ServiceEntry,允许流量直接访问一个外部服务。

  1. edition.cnn.com 定义一个 ServiceEntry

    1. $ kubectl apply -f - <<EOF
    2. apiVersion: networking.istio.io/v1alpha3
    3. kind: ServiceEntry
    4. metadata:
    5. name: cnn
    6. spec:
    7. hosts:
    8. - edition.cnn.com
    9. ports:
    10. - number: 80
    11. name: http-port
    12. protocol: HTTP
    13. - number: 443
    14. name: https
    15. protocol: HTTPS
    16. resolution: DNS
    17. EOF
  2. 发送 HTTPS 请求到 https://edition.cnn.com/politics,验证 ServiceEntry 是否已正确应用。

    1. $ kubectl exec -it $SOURCE_POD -c sleep -- curl -sL -o /dev/null -D - http://edition.cnn.com/politics
    2. HTTP/1.1 301 Moved Permanently
    3. ...
    4. location: https://edition.cnn.com/politics
    5. ...
    6. HTTP/1.1 200 OK
    7. Content-Type: text/html; charset=utf-8
    8. ...
    9. Content-Length: 151654
    10. ...

    输出结果应该与 发起 TLS 的 Egress 流量 中的 配置对外部服务的访问 示例相同,都还没有发起 TLS。

  3. edition.cnn.com 端口 80 创建 egress Gateway。并为指向 egress gateway 的流量创建一个 destination rule。

    1. $ kubectl apply -f - <<EOF
    2. apiVersion: networking.istio.io/v1alpha3
    3. kind: Gateway
    4. metadata:
    5. name: istio-egressgateway
    6. spec:
    7. selector:
    8. istio: egressgateway
    9. servers:
    10. - port:
    11. number: 80
    12. name: http
    13. protocol: HTTP
    14. hosts:
    15. - edition.cnn.com
    16. ---
    17. apiVersion: networking.istio.io/v1alpha3
    18. kind: DestinationRule
    19. metadata:
    20. name: egressgateway-for-cnn
    21. spec:
    22. host: istio-egressgateway.istio-system.svc.cluster.local
    23. subsets:
    24. - name: cnn
    25. EOF
  4. 定义一个 VirtualService,将流量从 sidecar 引导至 egress gateway,再从 egress gateway 引导至外部服务:

    1. $ kubectl apply -f - <<EOF
    2. apiVersion: networking.istio.io/v1alpha3
    3. kind: VirtualService
    4. metadata:
    5. name: direct-cnn-through-egress-gateway
    6. spec:
    7. hosts:
    8. - edition.cnn.com
    9. gateways:
    10. - istio-egressgateway
    11. - mesh
    12. http:
    13. - match:
    14. - gateways:
    15. - mesh
    16. port: 80
    17. route:
    18. - destination:
    19. host: istio-egressgateway.istio-system.svc.cluster.local
    20. subset: cnn
    21. port:
    22. number: 80
    23. weight: 100
    24. - match:
    25. - gateways:
    26. - istio-egressgateway
    27. port: 80
    28. route:
    29. - destination:
    30. host: edition.cnn.com
    31. port:
    32. number: 80
    33. weight: 100
    34. EOF
  5. 再次发送 HTTP 请求到 https://edition.cnn.com/politics

    1. $ kubectl exec -it $SOURCE_POD -c sleep -- curl -sL -o /dev/null -D - http://edition.cnn.com/politics
    2. HTTP/1.1 301 Moved Permanently
    3. ...
    4. location: https://edition.cnn.com/politics
    5. ...
    6. HTTP/1.1 200 OK
    7. Content-Type: text/html; charset=utf-8
    8. ...
    9. Content-Length: 151654
    10. ...

    The output should be the same as in the step 2.

  6. 检查 istio-egressgateway pod 的日志,并查看与我们的请求对应的行。如果 Istio 部署在 istio-system 命名空间中,则打印日志的命令是:

    1. $ kubectl logs -l istio=egressgateway -c istio-proxy -n istio-system | tail

    你应该会看到一行类似于下面这样的内容:

    1. [2019-09-03T20:57:49.103Z] "GET /politics HTTP/2" 301 - "-" "-" 0 0 90 89 "10.244.2.10" "curl/7.64.0" "ea379962-9b5c-4431-ab66-f01994f5a5a5" "edition.cnn.com" "151.101.65.67:80" outbound|80||edition.cnn.com - 10.244.1.5:80 10.244.2.10:50482 edition.cnn.com -

    Note that you only redirected the traffic from port 80 to the egress gateway. The HTTPS traffic to port 443 went directly to edition.cnn.com.

清理 HTTP gateway

在继续下一步之前删除先前的定义:

  1. $ kubectl delete gateway istio-egressgateway
  2. $ kubectl delete serviceentry cnn
  3. $ kubectl delete virtualservice direct-cnn-through-egress-gateway
  4. $ kubectl delete destinationrule egressgateway-for-cnn

用 Egress gateway 发起 HTTPS 请求

接下来尝试使用 Egress Gateway 发起 HTTPS 请求(TLS 由应用程序发起)。您需要在相应的 ServiceEntry、egress GatewayVirtualService 中指定 TLS 协议的端口 443。

  1. edition.cnn.com 定义 ServiceEntry

    1. $ kubectl apply -f - <<EOF
    2. apiVersion: networking.istio.io/v1alpha3
    3. kind: ServiceEntry
    4. metadata:
    5. name: cnn
    6. spec:
    7. hosts:
    8. - edition.cnn.com
    9. ports:
    10. - number: 443
    11. name: tls
    12. protocol: TLS
    13. resolution: DNS
    14. EOF
  2. 发送 HTTPS 请求到 https://edition.cnn.com/politics,验证您的 ServiceEntry 是否已正确生效。

    1. $ kubectl exec -it $SOURCE_POD -c sleep -- curl -sL -o /dev/null -D - https://edition.cnn.com/politics
    2. HTTP/1.1 200 OK
    3. Content-Type: text/html; charset=utf-8
    4. ...
    5. Content-Length: 151654
    6. ...
  3. edition.cnn.com 创建一个 egress Gateway。除此之外还需要创建一个 destination rule 和一个 virtual service,用来引导流量通过 egress gateway,并通过 egress gateway 与外部服务通信。

    1. $ kubectl apply -f - <<EOF
    2. apiVersion: networking.istio.io/v1alpha3
    3. kind: Gateway
    4. metadata:
    5. name: istio-egressgateway
    6. spec:
    7. selector:
    8. istio: egressgateway
    9. servers:
    10. - port:
    11. number: 443
    12. name: tls
    13. protocol: TLS
    14. hosts:
    15. - edition.cnn.com
    16. tls:
    17. mode: PASSTHROUGH
    18. ---
    19. apiVersion: networking.istio.io/v1alpha3
    20. kind: DestinationRule
    21. metadata:
    22. name: egressgateway-for-cnn
    23. spec:
    24. host: istio-egressgateway.istio-system.svc.cluster.local
    25. subsets:
    26. - name: cnn
    27. ---
    28. apiVersion: networking.istio.io/v1alpha3
    29. kind: VirtualService
    30. metadata:
    31. name: direct-cnn-through-egress-gateway
    32. spec:
    33. hosts:
    34. - edition.cnn.com
    35. gateways:
    36. - mesh
    37. - istio-egressgateway
    38. tls:
    39. - match:
    40. - gateways:
    41. - mesh
    42. port: 443
    43. sni_hosts:
    44. - edition.cnn.com
    45. route:
    46. - destination:
    47. host: istio-egressgateway.istio-system.svc.cluster.local
    48. subset: cnn
    49. port:
    50. number: 443
    51. - match:
    52. - gateways:
    53. - istio-egressgateway
    54. port: 443
    55. sni_hosts:
    56. - edition.cnn.com
    57. route:
    58. - destination:
    59. host: edition.cnn.com
    60. port:
    61. number: 443
    62. weight: 100
    63. EOF
  4. 发送 HTTPS 请求到 https://edition.cnn.com/politics。输出结果应该和之前一样。

    1. $ kubectl exec -it $SOURCE_POD -c sleep -- curl -sL -o /dev/null -D - https://edition.cnn.com/politics
    2. HTTP/1.1 200 OK
    3. Content-Type: text/html; charset=utf-8
    4. ...
    5. Content-Length: 151654
    6. ...
  5. 检查 egress gateway 代理的日志。如果 Istio 部署在 istio-system 命名空间中,则打印日志的命令是:

    1. $ kubectl logs -l istio=egressgateway -n istio-system

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

    1. [2019-01-02T11:46:46.981Z] "- - -" 0 - 627 1879689 44 - "-" "-" "-" "-" "151.101.129.67:443" outbound|443||edition.cnn.com 172.30.109.80:41122 172.30.109.80:443 172.30.109.112:59970 edition.cnn.com

清理 HTTPS gateway

  1. $ kubectl delete serviceentry cnn
  2. $ kubectl delete gateway istio-egressgateway
  3. $ kubectl delete virtualservice direct-cnn-through-egress-gateway
  4. $ kubectl delete destinationrule egressgateway-for-cnn

其他安全注意事项

注意,Istio 中定义的 egress Gateway 本身并没有为其所在的节点提供任何特殊处理。集群管理员或云提供商可以在专用节点上部署 egress gateway,并引入额外的安全措施,从而使这些节点比网格中的其他节点更安全。

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

应用 Kubernetes 网络策略

本节中展示了如何创建 Kubernetes 网络策略来阻止绕过 egress gateway 的出站流量。为了测试网络策略,首先创建一个 test-egress 命名空间,并在其中部署 sleep 示例应用,然后尝试发送一个会通过安全网关的外部服务请求。

  1. 参考用 Egress gateway 发起 HTTPS 请求一节中的步骤。

  2. 创建 test-egress 命名空间:

    1. $ kubectl create namespace test-egress
  3. test-egress 命名空间中部署 sleep 示例应用:

    Zip

    1. $ kubectl apply -n test-egress -f @samples/sleep/sleep.yaml@
  4. 检查生成的 Pod,其中应该只有一个容器,也就是说没有注入 Istio Sidecar:

    1. $ kubectl get pod $(kubectl get pod -n test-egress -l app=sleep -o jsonpath={.items..metadata.name}) -n test-egress
    2. NAME READY STATUS RESTARTS AGE
    3. sleep-776b7bcdcd-z7mc4 1/1 Running 0 18m
  5. test-egress 命名空间的 sleep pod 中向 https://edition.cnn.com/politics 发送 HTTPS 请求。因为没有任何限制,所以这一请求应该会成功:

    1. $ kubectl exec -it $(kubectl get pod -n test-egress -l app=sleep -o jsonpath={.items..metadata.name}) -n test-egress -c sleep -- curl -s -o /dev/null -w "%{http_code}\n" https://edition.cnn.com/politics
    2. 200
  6. 给 Istio 组件(控制平面和 gateway)所在的命名空间打上标签。例如,你的 Istio 组件部署在 istio-system 命名空间中,则命令是:

    1. $ kubectl label namespace istio-system istio=system
  7. kube-system 命名空间打标签:

    1. $ kubectl label ns kube-system kube-system=true
  8. 创建一个 NetworkPolicy,来限制 test-egress 命名空间的出站流量,只允许目标为 kube-system DNS(端口 53)的请求,以及目标为 istio-system 命名空间的所有请求:

    1. $ cat <<EOF | kubectl apply -n test-egress -f -
    2. apiVersion: networking.k8s.io/v1
    3. kind: NetworkPolicy
    4. metadata:
    5. name: allow-egress-to-istio-system-and-kube-dns
    6. spec:
    7. podSelector: {}
    8. policyTypes:
    9. - Egress
    10. egress:
    11. - to:
    12. - namespaceSelector:
    13. matchLabels:
    14. kube-system: "true"
    15. ports:
    16. - protocol: UDP
    17. port: 53
    18. - to:
    19. - namespaceSelector:
    20. matchLabels:
    21. istio: system
    22. EOF
  9. 重新发送前面的 HTTPS 请求到 https://edition.cnn.com/politics。这次请求就不会成功了,这是因为流量被网络策略拦截了。sleep pod 无法绕过 istio-egressgateway。要访问 edition.cnn.com,只能通过 Istio sidecar 代理,让流量经过 istio-egressgateway 才能完成。这种配置表明,即使一些恶意的 pod 绕过了 sidecar,也会被网络策略拦截,而无法访问到外部站点。

    1. $ kubectl exec -it $(kubectl get pod -n test-egress -l app=sleep -o jsonpath={.items..metadata.name}) -n test-egress -c sleep -- curl -v https://edition.cnn.com/politics
    2. Hostname was NOT found in DNS cache
    3. Trying 151.101.65.67...
    4. Trying 2a04:4e42:200::323...
    5. Immediate connect fail for 2a04:4e42:200::323: Cannot assign requested address
    6. Trying 2a04:4e42:400::323...
    7. Immediate connect fail for 2a04:4e42:400::323: Cannot assign requested address
    8. Trying 2a04:4e42:600::323...
    9. Immediate connect fail for 2a04:4e42:600::323: Cannot assign requested address
    10. Trying 2a04:4e42::323...
    11. Immediate connect fail for 2a04:4e42::323: Cannot assign requested address
    12. connect to 151.101.65.67 port 443 failed: Connection timed out
  10. 接下来在 test-egress 命名空间的 sleep Pod 上注入 Sidecar,启用 test-egress 命名空间的自动注入:

    1. $ kubectl label namespace test-egress istio-injection=enabled
  11. 重新部署 sleep

    Zip

    1. $ kubectl delete deployment sleep -n test-egress
    2. $ kubectl apply -f @samples/sleep/sleep.yaml@ -n test-egress
  12. 检查生成的 Pod,其中应该有了两个容器,其中包含了注入的 sidecar(istio-proxy):

    1. $ kubectl get pod $(kubectl get pod -n test-egress -l app=sleep -o jsonpath={.items..metadata.name}) -n test-egress -o jsonpath='{.spec.containers[*].name}'
    2. sleep istio-proxy
  13. default 命名空间中为 sleep pod 创建一个相同的 destination rule 用来引导流量经过 egress gateway:

    1. $ kubectl apply -n test-egress -f - <<EOF
    2. apiVersion: networking.istio.io/v1alpha3
    3. kind: DestinationRule
    4. metadata:
    5. name: egressgateway-for-cnn
    6. spec:
    7. host: istio-egressgateway.istio-system.svc.cluster.local
    8. subsets:
    9. - name: cnn
    10. EOF
  14. https://edition.cnn.com/politics 发送 HTTP 请求,这次会成功,原因是网络策略允许流量流向 istio-system 中的 istio-egressgatewayistio-egressgateway 最终把流量转发到 edition.cnn.com

    1. $ kubectl exec -it $(kubectl get pod -n test-egress -l app=sleep -o jsonpath={.items..metadata.name}) -n test-egress -c sleep -- curl -s -o /dev/null -w "%{http_code}\n" https://edition.cnn.com/politics
    2. 200
  15. 检查 Egress gateway 中的代理统计数据。如果 Istio 部署在 istio-system 命名空间,那么打印日志的命令就是:

    1. $ kubectl logs -l istio=egressgateway -n istio-system

    你应该会看到一行类似于下面这样的内容:

    1. [2020-03-06T18:12:33.101Z] "- - -" 0 - "-" "-" 906 1352475 35 - "-" "-" "-" "-" "151.101.193.67:443" outbound|443||edition.cnn.com 172.30.223.53:39460 172.30.223.53:443 172.30.223.58:38138 edition.cnn.com -

清理网络策略

  1. 删除本节中建立的资源:

    Zip

    1. $ kubectl delete -f @samples/sleep/sleep.yaml@ -n test-egress
    2. $ kubectl delete destinationrule egressgateway-for-cnn -n test-egress
    3. $ kubectl delete networkpolicy allow-egress-to-istio-system-and-kube-dns -n test-egress
    4. $ kubectl label namespace kube-system kube-system-
    5. $ kubectl label namespace istio-system istio-
    6. $ kubectl delete namespace test-egress
  2. 请参考清理 HTTPS gateway一节的内容。

故障排除

  1. 如果启用了双向 TLS 认证,请验证 egress gateway 证书的正确性:

    1. $ kubectl exec -i -n istio-system $(kubectl get pod -l istio=egressgateway -n istio-system -o jsonpath='{.items[0].metadata.name}') -- cat /etc/certs/cert-chain.pem | openssl x509 -text -noout | grep 'Subject Alternative Name' -A 1
    2. X509v3 Subject Alternative Name:
    3. URI:spiffe://cluster.local/ns/istio-system/sa/istio-egressgateway-service-account
  2. HTTPS 透传流量情况(由应用而不是 egress 发起 TLS),需要使用 openssl 命令测试流量。openssl-servername 选项可以用来设置 SNI:

    1. $ kubectl exec -it $SOURCE_POD -c sleep -- openssl s_client -connect edition.cnn.com:443 -servername edition.cnn.com
    2. CONNECTED(00000003)
    3. ...
    4. Certificate chain
    5. 0 s:/C=US/ST=California/L=San Francisco/O=Fastly, Inc./CN=turner-tls.map.fastly.net
    6. i:/C=BE/O=GlobalSign nv-sa/CN=GlobalSign CloudSSL CA - SHA256 - G3
    7. 1 s:/C=BE/O=GlobalSign nv-sa/CN=GlobalSign CloudSSL CA - SHA256 - G3
    8. i:/C=BE/O=GlobalSign nv-sa/OU=Root CA/CN=GlobalSign Root CA
    9. ---
    10. Server certificate
    11. -----BEGIN CERTIFICATE-----
    12. ...

    如果在上面命令的输出中看到了类似的证书信息,就表明路由是正确的。接下来检查 egress gateway 的代理,查找对应请求的计数器(由 opensslcurl 发送,目标是 edition.cnn.com):

    1. $ kubectl exec $(kubectl get pod -l istio=egressgateway -n istio-system -o jsonpath='{.items[0].metadata.name}') -c istio-proxy -n istio-system -- pilot-agent request GET stats | grep edition.cnn.com.upstream_cx_total
    2. cluster.outbound|443||edition.cnn.com.upstream_cx_total: 2

清理

关闭 sleep 服务:

Zip

  1. $ kubectl delete -f @samples/sleep/sleep.yaml@

相关内容

Istio 中安全管控出口流量,第三部分

管控出口流量的备选方案比较,包括性能因素。

Istio 中的安全管控出口流量,第二部分

使用 Istio 的出口流量管控来阻止相关出口流量攻击。

Istio 中的安全管控出口流量,第一部分

涉及出口流量攻击和出口流量管控要求。

Egress gateway 性能测试

评估加入 Egress gateway 对性能造成的影响。

使用外部 MongoDB 服务

描述了一个基于 Istio 的 Bookinfo 示例的简单场景。

HTTP Egress 流量监控和访问策略

描述如何配置 Istio 进行 HTTP Egress 流量监控和访问策略。