網域出口閘道
存取外部服務任務示範如何設定 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 網域出口閘道
檢查是否已部署 Istio 出口閘道
$ kubectl get pod -l istio=egressgateway -n istio-system
如果沒有傳回任何 Pod,請執行以下步驟來部署 Istio 出口閘道。
如果您使用
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
以允許直接流量流向外部服務。
為
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
透過傳送 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 發起。
為傳往 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
$ kubectl apply -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: cnn-egress-gateway
annotations:
networking.istio.io/service-type: ClusterIP
spec:
gatewayClassName: istio
listeners:
- name: http
hostname: edition.cnn.com
port: 80
protocol: HTTP
allowedRoutes:
namespaces:
from: Same
EOF
- 設定路由規則,以將流量從 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
$ kubectl apply -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: direct-cnn-to-egress-gateway
spec:
parentRefs:
- kind: ServiceEntry
group: networking.istio.io
name: cnn
rules:
- backendRefs:
- name: cnn-egress-gateway-istio
port: 80
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: forward-cnn-from-egress-gateway
spec:
parentRefs:
- name: cnn-egress-gateway
hostnames:
- edition.cnn.com
rules:
- backendRefs:
- kind: Hostname
group: networking.istio.io
name: edition.cnn.com
port: 80
EOF
將 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 中的輸出相同。
檢查出口閘道 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 -
使用 Istio 產生的 Pod 標籤存取對應於出口閘道的日誌
$ kubectl logs -l gateway.networking.k8s.io/gateway-name=cnn-egress-gateway -c istio-proxy | tail
您應該會看到類似以下的行
[2024-01-09T15:35:47.283Z] "GET /politics HTTP/1.1" 301 - via_upstream - "-" 0 0 2 2 "172.30.239.55" "curl/7.87.0-DEV" "6c01d65f-a157-97cd-8782-320a40026901" "edition.cnn.com" "151.101.195.5:80" outbound|80||edition.cnn.com 172.30.239.16:55636 172.30.239.16:80 172.30.239.55:59224 - default.forward-cnn-from-egress-gateway.0
請注意,您僅透過出口閘道重新導向了埠 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
$ kubectl delete serviceentry cnn
$ kubectl delete gtw cnn-egress-gateway
$ kubectl delete httproute direct-cnn-to-egress-gateway
$ kubectl delete httproute forward-cnn-from-egress-gateway
用於 HTTPS 流量的網域出口閘道
在本節中,您會透過出口閘道導向 HTTPS 流量(由應用程式發起的 TLS)。您需要在對應的 ServiceEntry
和出口 Gateway
中指定埠 443,並使用協定 TLS
。
為
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
透過傳送 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 ...
為 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
$ kubectl apply -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: cnn-egress-gateway
annotations:
networking.istio.io/service-type: ClusterIP
spec:
gatewayClassName: istio
listeners:
- name: tls
hostname: edition.cnn.com
port: 443
protocol: TLS
tls:
mode: Passthrough
allowedRoutes:
namespaces:
from: Same
---
apiVersion: gateway.networking.k8s.io/v1alpha2
kind: TLSRoute
metadata:
name: direct-cnn-to-egress-gateway
spec:
parentRefs:
- kind: ServiceEntry
group: networking.istio.io
name: cnn
rules:
- backendRefs:
- name: cnn-egress-gateway-istio
port: 443
---
apiVersion: gateway.networking.k8s.io/v1alpha2
kind: TLSRoute
metadata:
name: forward-cnn-from-egress-gateway
spec:
parentRefs:
- name: cnn-egress-gateway
hostnames:
- edition.cnn.com
rules:
- backendRefs:
- kind: Hostname
group: networking.istio.io
name: edition.cnn.com
port: 443
EOF
傳送 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 ...
檢查出口閘道代理的日誌。
如果 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
使用 Istio 產生的 Pod 標籤存取對應於出口閘道的日誌
$ kubectl logs -l gateway.networking.k8s.io/gateway-name=cnn-egress-gateway -c istio-proxy | tail
您應該會看到類似以下的行
[2024-01-11T21:09:42.835Z] "- - -" 0 - - - "-" 839 2504306 231 - "-" "-" "-" "-" "151.101.195.5:443" outbound|443||edition.cnn.com 172.30.239.8:34470 172.30.239.8:443 172.30.239.15:43956 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
$ kubectl delete serviceentry cnn
$ kubectl delete gtw cnn-egress-gateway
$ kubectl delete tlsroute direct-cnn-to-egress-gateway
$ kubectl delete tlsroute forward-cnn-from-egress-gateway
其他安全考量
請注意,在 Istio 中定義出口 Gateway
本身並不會為執行出口閘道服務的節點提供任何特殊處理。將出口閘道部署在專用節點上,並引入其他安全措施來使這些節點比網格的其餘部分更安全,這取決於叢集管理員或雲端提供者。
Istio *無法安全地強制執行* 所有出口流量實際上都流經出口閘道。Istio 僅透過其 sidecar 代理啟用此類流量。如果攻擊者繞過 sidecar 代理,他們可以直接存取外部服務,而無需經過出口閘道。因此,攻擊者會逃脫 Istio 的控制和監控。叢集管理員或雲端提供者必須確保沒有流量在繞過出口閘道的情況下離開網格。必須使用 Istio 外部的機制來強制執行此要求。例如,叢集管理員可以設定防火牆,以拒絕所有非來自出口閘道的流量。Kubernetes 網路原則也可以禁止所有非來自出口閘道的出口流量(如需範例,請參閱下一節)。此外,叢集管理員或雲端提供者可以設定網路,以確保應用程式節點只能透過閘道存取網際網路。若要執行此操作,叢集管理員或雲端提供者可以防止將公共 IP 位址分配給閘道以外的 Pod,並可以設定 NAT 裝置以捨棄非來自出口閘道的封包。
套用 Kubernetes 網路策略
本節說明如何建立 Kubernetes 網路原則,以防止繞過出口閘道。若要測試網路原則,您會建立一個命名空間 test-egress
,將 curl 範例部署到該命名空間,然後嘗試傳送請求到閘道保護的外部服務。
請按照HTTPS 流量的出口閘道章節中的步驟操作。
建立
test-egress
命名空間$ kubectl create namespace test-egress
將 curl 範例部署到
test-egress
命名空間。$ kubectl apply -n test-egress -f @samples/curl/curl.yaml@
檢查已部署的 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
從
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
標記 Istio 控制平面和出口閘道正在運行的命名空間。如果您將 Istio 部署在
istio-system
命名空間中,則命令為
$ kubectl label namespace istio-system istio=system
$ kubectl label namespace istio-system istio=system
$ kubectl label namespace default gateway=true
標記
kube-system
命名空間。$ kubectl label ns kube-system kube-system=true
定義
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
$ 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
- to:
- namespaceSelector:
matchLabels:
gateway: "true"
EOF
重新傳送先前的 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
現在,透過先啟用
test-egress
命名空間中的自動 sidecar 代理注入,將 Istio sidecar 代理注入到test-egress
命名空間中的curl
Pod 中$ kubectl label namespace test-egress istio-injection=enabled
然後重新部署
curl
部署$ kubectl delete deployment curl -n test-egress $ kubectl apply -f @samples/curl/curl.yaml@ -n test-egress
檢查已部署的 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
$ 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
發送一個 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
檢查出口閘道代理的日誌。
如果 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 -
使用 Istio 產生的 Pod 標籤存取對應於出口閘道的日誌
$ kubectl logs -l gateway.networking.k8s.io/gateway-name=cnn-egress-gateway -c istio-proxy | tail
您應該會看到類似以下的行
[2024-01-12T19:54:01.821Z] "- - -" 0 - - - "-" 839 2504837 46 - "-" "-" "-" "-" "151.101.67.5:443" outbound|443||edition.cnn.com 172.30.239.60:49850 172.30.239.60:443 172.30.239.21:36512 edition.cnn.com -
清理網路策略
- 刪除在此節中建立的資源。
$ 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
$ kubectl delete -f @samples/curl/curl.yaml@ -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 label namespace default gateway-
$ kubectl delete namespace test-egress
- 依照清除 HTTPS 閘道章節中的步驟操作。
清理
關閉 curl 服務。
$ kubectl delete -f @samples/curl/curl.yaml@