出口网关

访问外部服务任务显示如何配置 Istio 以允许网格内应用程序访问外部 HTTP 和 HTTPS 服务。在那里,外部服务直接从客户端 sidecar 调用。此示例还显示如何配置 Istio 以调用外部服务,尽管这次是通过专用的出口网关服务间接调用。

Istio 使用入口和出口网关来配置在服务网格边缘执行的负载均衡器。入口网关允许您定义进入网格的入口点,所有传入流量都流经该入口点。出口网关是一个对称的概念;它定义了网格的出口点。出口网关允许您将 Istio 功能(例如,监控和路由规则)应用于离开网格的流量。

用例

假设一个组织有严格的安全要求,即所有离开服务网格的流量都必须流经一组专用节点。这些节点将在专用机器上运行,与集群中运行应用程序的其他节点隔离开来。这些特殊节点将用于对出口流量执行策略,并且将比其他节点更彻底地进行监控。

另一个用例是集群中的应用程序节点没有公网 IP,因此在其上运行的网内服务无法访问互联网。定义一个出口网关,将所有出口流量引导通过它,并为出口网关节点分配公网 IP,允许应用程序节点以受控的方式访问外部服务。

开始之前

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

  • 部署curl示例应用程序作为发送请求的测试源。

    压缩
    $ kubectl apply -f @samples/curl/curl.yaml@
    
  • SOURCE_POD环境变量设置为源 Pod 的名称。

    $ export SOURCE_POD=$(kubectl get pod -l app=curl -o jsonpath={.items..metadata.name})
    
  • 如果尚未启用,请启用 Envoy 的访问日志记录。例如,使用istioctl

    $ istioctl install <flags-you-used-to-install-Istio> --set meshConfig.accessLogFile=/dev/stdout
    

部署 Istio 出口网关

  1. 检查 Istio 出口网关是否已部署。

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

    如果没有返回 Pod,请执行以下步骤部署 Istio 出口网关。

  2. 如果您使用IstioOperator CR 安装 Istio,请将以下字段添加到您的配置中。

    spec:
      components:
        egressGateways:
        - name: istio-egressgateway
          enabled: true
    

    否则,将等效设置添加到原始的istioctl install命令中,例如

    $ istioctl install <flags-you-used-to-install-Istio> \
                       --set "components.egressGateways[0].name=istio-egressgateway" \
                       --set "components.egressGateways[0].enabled=true"
    

用于 HTTP 流量的出口网关

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

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

    $ kubectl apply -f - <<EOF
    apiVersion: networking.istio.io/v1
    kind: ServiceEntry
    metadata:
      name: cnn
    spec:
      hosts:
      - edition.cnn.com
      ports:
      - number: 80
        name: http-port
        protocol: HTTP
      - number: 443
        name: https
        protocol: HTTPS
      resolution: DNS
    EOF
    
  2. 通过向http://edition.cnn.com/politics发送 HTTP 请求,验证您的ServiceEntry是否已正确应用。

    $ kubectl exec "$SOURCE_POD" -c curl -- curl -sSL -o /dev/null -D - http://edition.cnn.com/politics
    ...
    HTTP/1.1 301 Moved Permanently
    ...
    location: https://edition.cnn.com/politics
    ...
    
    HTTP/2 200
    Content-Type: text/html; charset=utf-8
    ...
    

    输出应与出口流量的 TLS 发起示例中的输出相同,但没有 TLS 发起。

  3. 为出口流量到edition.cnn.com的 80 端口创建一个Gateway

$ kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1
kind: Gateway
metadata:
  name: istio-egressgateway
spec:
  selector:
    istio: egressgateway
  servers:
  - port:
      number: 80
      name: http
      protocol: HTTP
    hosts:
    - edition.cnn.com
---
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
  name: egressgateway-for-cnn
spec:
  host: istio-egressgateway.istio-system.svc.cluster.local
  subsets:
  - name: cnn
EOF
  1. 配置路由规则,将流量从 sidecar 指向出口网关,并从出口网关指向外部服务。
$ kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
  name: direct-cnn-through-egress-gateway
spec:
  hosts:
  - edition.cnn.com
  gateways:
  - istio-egressgateway
  - mesh
  http:
  - match:
    - gateways:
      - mesh
      port: 80
    route:
    - destination:
        host: istio-egressgateway.istio-system.svc.cluster.local
        subset: cnn
        port:
          number: 80
      weight: 100
  - match:
    - gateways:
      - istio-egressgateway
      port: 80
    route:
    - destination:
        host: edition.cnn.com
        port:
          number: 80
      weight: 100
EOF
  1. 重新发送到http://edition.cnn.com/politics的 HTTP 请求。

    $ kubectl exec "$SOURCE_POD" -c curl -- curl -sSL -o /dev/null -D - http://edition.cnn.com/politics
    ...
    HTTP/1.1 301 Moved Permanently
    ...
    location: https://edition.cnn.com/politics
    ...
    
    HTTP/2 200
    Content-Type: text/html; charset=utf-8
    ...
    

    输出应与步骤 2 中的相同。

  2. 检查出口网关 Pod 的日志,查看与我们的请求相对应的一行。

如果 Istio 部署在istio-system命名空间中,则打印日志的命令为

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

您应该会看到类似于以下内容的行

[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 -

请注意,您只将 80 端口的 HTTP 流量重定向到出口网关。到 443 端口的 HTTPS 流量直接发送到edition.cnn.com

清理 HTTP 网关

在继续下一步之前,请删除之前的定义。

$ kubectl delete serviceentry cnn
$ kubectl delete gateway istio-egressgateway
$ kubectl delete virtualservice direct-cnn-through-egress-gateway
$ kubectl delete destinationrule egressgateway-for-cnn

用于 HTTPS 流量的出口网关

在本节中,您将 HTTPS 流量(由应用程序发起的 TLS)引导通过出口网关。您需要在相应的ServiceEntry和出口Gateway中使用协议TLS指定 443 端口。

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

    $ kubectl apply -f - <<EOF
    apiVersion: networking.istio.io/v1
    kind: ServiceEntry
    metadata:
      name: cnn
    spec:
      hosts:
      - edition.cnn.com
      ports:
      - number: 443
        name: tls
        protocol: TLS
      resolution: DNS
    EOF
    
  2. 通过向https://edition.cnn.com/politics发送 HTTPS 请求,验证您的ServiceEntry是否已正确应用。

    $ kubectl exec "$SOURCE_POD" -c curl -- curl -sSL -o /dev/null -D - https://edition.cnn.com/politics
    ...
    HTTP/2 200
    Content-Type: text/html; charset=utf-8
    ...
    
  3. edition.cnn.com创建一个出口Gateway,并创建路由规则以将流量引导通过出口网关,以及从出口网关到外部服务。

$ kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1
kind: Gateway
metadata:
  name: istio-egressgateway
spec:
  selector:
    istio: egressgateway
  servers:
  - port:
      number: 443
      name: tls
      protocol: TLS
    hosts:
    - edition.cnn.com
    tls:
      mode: PASSTHROUGH
---
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
  name: egressgateway-for-cnn
spec:
  host: istio-egressgateway.istio-system.svc.cluster.local
  subsets:
  - name: cnn
---
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
  name: direct-cnn-through-egress-gateway
spec:
  hosts:
  - edition.cnn.com
  gateways:
  - mesh
  - istio-egressgateway
  tls:
  - match:
    - gateways:
      - mesh
      port: 443
      sniHosts:
      - edition.cnn.com
    route:
    - destination:
        host: istio-egressgateway.istio-system.svc.cluster.local
        subset: cnn
        port:
          number: 443
  - match:
    - gateways:
      - istio-egressgateway
      port: 443
      sniHosts:
      - edition.cnn.com
    route:
    - destination:
        host: edition.cnn.com
        port:
          number: 443
      weight: 100
EOF
  1. 发送到https://edition.cnn.com/politics的 HTTPS 请求。输出应与之前相同。

    $ kubectl exec "$SOURCE_POD" -c curl -- curl -sSL -o /dev/null -D - https://edition.cnn.com/politics
    ...
    HTTP/2 200
    Content-Type: text/html; charset=utf-8
    ...
    
  2. 检查出口网关代理的日志。

如果 Istio 部署在istio-system命名空间中,则打印日志的命令为

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

您应该会看到类似于以下内容的行

[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 网关

$ kubectl delete serviceentry cnn
$ kubectl delete gateway istio-egressgateway
$ kubectl delete virtualservice direct-cnn-through-egress-gateway
$ kubectl delete destinationrule egressgateway-for-cnn

其他安全注意事项

请注意,在 Istio 中定义出口Gateway本身并不会对运行出口网关服务的节点提供任何特殊处理。由集群管理员或云提供商负责在专用节点上部署出口网关,并采取额外的安全措施,使这些节点比网格中的其他节点更安全。

Istio *无法安全地强制执行* 所有出口流量都实际流经出口网关。Istio 仅通过其 sidecar 代理启用此类流量。如果攻击者绕过 sidecar 代理,他们可以直接访问外部服务,而无需遍历出口网关。因此,攻击者会逃脱 Istio 的控制和监控。集群管理员或云提供商必须确保没有流量绕过出口网关离开网格。Istio 外部的机制必须强制执行此要求。例如,集群管理员可以配置防火墙以拒绝来自非出口网关的所有流量。Kubernetes 网络策略还可以禁止所有非出口网关发起的出口流量(有关示例,请参阅下一节)。此外,集群管理员或云提供商可以配置网络以确保应用程序节点只能通过网关访问互联网。为此,集群管理员或云提供商可以阻止为除网关之外的 Pod 分配公网 IP,并可以配置 NAT 设备以丢弃非出口网关发起的报文。

应用 Kubernetes 网络策略

本节演示如何创建Kubernetes 网络策略以防止绕过出口网关。为了测试网络策略,您将创建一个命名空间test-egress,将curl示例部署到其中,然后尝试向网关保护的外部服务发送请求。

  1. 按照HTTPS 流量的出口网关部分中的步骤操作。

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

    $ kubectl create namespace test-egress
    
  3. curl示例部署到test-egress命名空间。

    压缩
    $ kubectl apply -n test-egress -f @samples/curl/curl.yaml@
    
  4. 检查已部署的 Pod 是否只有一个容器,并且没有附加 Istio sidecar。

    $ kubectl get pod "$(kubectl get pod -n test-egress -l app=curl -o jsonpath={.items..metadata.name})" -n test-egress
    NAME                     READY     STATUS    RESTARTS   AGE
    curl-776b7bcdcd-z7mc4    1/1       Running   0          18m
    
  5. test-egress命名空间中的curl Pod 向https://edition.cnn.com/politics发送 HTTPS 请求。请求将成功,因为您尚未定义任何限制性策略。

    $ kubectl exec "$(kubectl get pod -n test-egress -l app=curl -o jsonpath={.items..metadata.name})" -n test-egress -c curl -- curl -s -o /dev/null -w "%{http_code}\n"  https://edition.cnn.com/politics
    200
    
  6. 标记 Istio 控制平面和出口网关正在运行的命名空间。如果您在istio-system命名空间中部署了 Istio,则命令为

$ kubectl label namespace istio-system istio=system
  1. 标记kube-system命名空间。

    $ kubectl label ns kube-system kube-system=true
    
  2. 定义一个NetworkPolicy以将test-egress命名空间中的出口流量限制为目标为控制平面、网关和kube-system DNS 服务(53 端口)的流量。

$ cat <<EOF | kubectl apply -n test-egress -f -
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-egress-to-istio-system-and-kube-dns
spec:
  podSelector: {}
  policyTypes:
  - Egress
  egress:
  - to:
    - namespaceSelector:
        matchLabels:
          kube-system: "true"
    ports:
    - protocol: UDP
      port: 53
  - to:
    - namespaceSelector:
        matchLabels:
          istio: system
EOF
  1. 重新发送之前到https://edition.cnn.com/politics的 HTTPS 请求。现在它应该会失败,因为流量被网络策略阻止了。请注意,curl Pod 无法绕过出口网关。它访问edition.cnn.com的唯一方法是使用 Istio sidecar 代理并将流量引导到出口网关。此设置演示了即使某些恶意 Pod 设法绕过其 sidecar 代理,它也无法访问外部站点,并将被网络策略阻止。

    $ kubectl exec "$(kubectl get pod -n test-egress -l app=curl -o jsonpath={.items..metadata.name})" -n test-egress -c curl -- curl -v -sS https://edition.cnn.com/politics
    Hostname was NOT found in DNS cache
      Trying 151.101.65.67...
      Trying 2a04:4e42:200::323...
    Immediate connect fail for 2a04:4e42:200::323: Cannot assign requested address
      Trying 2a04:4e42:400::323...
    Immediate connect fail for 2a04:4e42:400::323: Cannot assign requested address
      Trying 2a04:4e42:600::323...
    Immediate connect fail for 2a04:4e42:600::323: Cannot assign requested address
      Trying 2a04:4e42::323...
    Immediate connect fail for 2a04:4e42::323: Cannot assign requested address
    connect to 151.101.65.67 port 443 failed: Connection timed out
    
  2. 现在通过首先在test-egress命名空间中启用自动 sidecar 代理注入,将 Istio sidecar 代理注入test-egress命名空间中的curl Pod。

    $ kubectl label namespace test-egress istio-injection=enabled
    
  3. 然后重新部署curl部署。

    压缩
    $ kubectl delete deployment curl -n test-egress
    $ kubectl apply -f @samples/curl/curl.yaml@ -n test-egress
    
  4. 检查已部署的 Pod 是否有两个容器,包括 Istio sidecar 代理(istio-proxy)。

$ kubectl get pod "$(kubectl get pod -n test-egress -l app=curl -o jsonpath={.items..metadata.name})" -n test-egress -o jsonpath='{.spec.containers[*].name}'
curl istio-proxy

在继续之前,您需要创建与default命名空间中的curl Pod 使用的类似目标规则,以将test-egress命名空间的流量引导通过出口网关。

$ kubectl apply -n test-egress -f - <<EOF
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
  name: egressgateway-for-cnn
spec:
  host: istio-egressgateway.istio-system.svc.cluster.local
  subsets:
  - name: cnn
EOF
  1. 发送到https://edition.cnn.com/politics的 HTTPS 请求。现在它应该会成功,因为流量流向出口网关已获得您定义的网络策略的允许。然后,网关将流量转发到edition.cnn.com

    $ kubectl exec "$(kubectl get pod -n test-egress -l app=curl -o jsonpath={.items..metadata.name})" -n test-egress -c curl -- curl -sS -o /dev/null -w "%{http_code}\n" https://edition.cnn.com/politics
    200
    
  2. 检查出口网关代理的日志。

如果 Istio 部署在istio-system命名空间中,则打印日志的命令为

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

您应该会看到类似于以下内容的行

[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. 删除本节中创建的资源。
压缩
$ kubectl delete -f @samples/curl/curl.yaml@ -n test-egress
$ kubectl delete destinationrule egressgateway-for-cnn -n test-egress
$ kubectl delete networkpolicy allow-egress-to-istio-system-and-kube-dns -n test-egress
$ kubectl label namespace kube-system kube-system-
$ kubectl label namespace istio-system istio-
$ kubectl delete namespace test-egress
  1. 按照清理 HTTPS 网关部分中的步骤操作。

清理

关闭curl服务。

压缩
$ kubectl delete -f @samples/curl/curl.yaml@
这些信息对您有帮助吗?
您对改进有什么建议?

感谢您的反馈!