網域出口閘道

存取外部服務任務示範如何設定 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 請求至 http://edition.cnn.com/politics,驗證您的 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 請求重新傳送到 http://edition.cnn.com/politics

    $ 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 中指定埠 443,並使用協定 TLS

  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 請求至 https://edition.cnn.com/politics,驗證您的 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 請求至 https://edition.cnn.com/politics。輸出應該與之前相同。

    $ 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 網路原則也可以禁止所有非來自出口閘道的出口流量(如需範例,請參閱下一節)。此外,叢集管理員或雲端提供者可以設定網路,以確保應用程式節點只能透過閘道存取網際網路。若要執行此操作,叢集管理員或雲端提供者可以防止將公共 IP 位址分配給閘道以外的 Pod,並可以設定 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 請求至 https://edition.cnn.com/politics。請求將會成功,因為您尚未定義任何限制性原則。

    $ 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 部署在 istio-system 命名空間中,則命令為

$ 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 請求至 https://edition.cnn.com/politics。現在它應該會失敗,因為流量被網路原則封鎖。請注意,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 請求到 https://edition.cnn.com/politics。現在應該會成功,因為流量流向出口閘道是被您定義的網路策略所允許的。然後,閘道會將流量轉發到 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@
這個資訊是否有用?
您有任何改進建議嗎?

感謝您的回饋!