出口网关
该访问外部服务任务显示如何配置 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://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 发起。
为出口流量到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://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 中的相同。
检查出口网关 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
中使用协议TLS
指定 443 端口。
为
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://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 ...
为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://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 ...
检查出口网关代理的日志。
如果 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 网络策略还可以禁止所有非出口网关发起的出口流量(有关示例,请参阅下一节)。此外,集群管理员或云提供商可以配置网络以确保应用程序节点只能通过网关访问互联网。为此,集群管理员或云提供商可以阻止为除网关之外的 Pod 分配公网 IP,并可以配置 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://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
标记 Istio 控制平面和出口网关正在运行的命名空间。如果您在
istio-system
命名空间中部署了 Istio,则命令为
$ 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://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
现在通过首先在
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://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
检查出口网关代理的日志。
如果 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@