Wildcard 主机的 egress
控制 Egress 流量任务和配置一个 Egress 网关示例描述如何配置特定主机的 egress 流量,如:edition.cnn.com
。本示例描述如何为通用域中的一组特定主机开启 egress 流量,譬如:*.wikipedia.org
,无需单独配置每一台主机。
背景
假定您想要为 Istio 中所有语种的 wikipedia.org
站点开启 egress 流量。每个语种的 wikipedia.org
站点均有自己的主机名,譬如:英语和德语对应的主机分别为 en.wikipedia.org
和 de.rikipedia.org
。您希望通过通用配置项开启所有 Wikipedia 站点的 egress 流量,无需单独配置每个语种的站点。
开始之前(before-you-begin)
使用
demo
配置文件安装 Istio 以及默认阻止出站流量策略:$ istioctl install --set profile=demo --set meshConfig.outboundTrafficPolicy.mode=REGISTRY_ONLY
您可以在
demo
配置文件以外的 Istio 配置上运行此任务,只要您确保 部署 Istio egress 网关。 开启 Envoy 的访问日志和 应用默认阻止出站流量策略 在您的安装步骤中。您还需要使用自己的IstioOperator
CR 代替使用SNI代理设置出口网关中显示。部署sleep示例应用程序,以用作发送请求的测试源。如果您开启了 自动 sidecar 注入,运行以下命令以部署示例应用程序:
$ kubectl apply -f @samples/sleep/sleep.yaml@
否则,在使用以下命令部署
sleep
应用程序之前,手动注入 sidecar:$ kubectl apply -f <(istioctl kube-inject -f @samples/sleep/sleep.yaml@)
您可以在任意 pod 上使用
curl
作为测试源。将
SOURCE_POD
环境变量设置为您的源 Pod 的名称:$ export SOURCE_POD=$(kubectl get pod -l app=sleep -o jsonpath={.items..metadata.name})
引导流量流向 Wildcard 主机
访问通用域中一组主机的第一个也是最简单的方法,是使用一个 wildcard 主机配置一个简单的 ServiceEntry
,直接从 sidecar 调用服务。 当直接调用服务时(譬如:不是通过一个 egress 网关),一个 wildcard 主机的配置与任何其他主机(如:全域名主机)没有什么不同,只是当通用域中有许多台主机时,这样比较方便。
请注意,恶意应用程序很容易绕过以下配置。为了实现安全的出口流量控制,可以通过出口网关引导流量。
请注意,DNS
解析不能用于通配符主机。这就是为什么NONE
分辨率(因为它是默认)用于以下服务条目。
为
*.wikipedia.org
定义一个ServiceEntry
以及相应的VirtualSevice
:$ kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
name: wikipedia
spec:
hosts:
- "*.wikipedia.org"
ports:
- number: 443
name: https
protocol: HTTPS
EOF
发送 HTTPS 请求至 https://en.wikipedia.org and https://de.wikipedia.org:
$ kubectl exec -it $SOURCE_POD -c sleep -- sh -c 'curl -s https://en.wikipedia.org/wiki/Main_Page | grep -o "<title>.*</title>"; curl -s https://de.wikipedia.org/wiki/Wikipedia:Hauptseite | grep -o "<title>.*</title>"'
<title>Wikipedia, the free encyclopedia</title>
<title>Wikipedia – Die freie Enzyklopädie</title>
清除引导流量至 wildcard 主机
$ kubectl delete serviceentry wikipedia
配置访问 wildcard 主机的 egress 网关
能否配置通过 egress 网关访问 wildcard 主机取决于这组 wildcard 域名有唯一一个通用主机。 以 \.wikipedia.org 为例。每个语种特殊的站点都有自己的 wikipedia.org 服务器。您可以向任意一个 *.wikipedia.org* 站点的 IP 发送请求,包括 _www.wikipedia.org_,该站点管理服务所有特定主机。
通常情况下,通用域中的所有域名并不由一个唯一的 hosting server 提供服务。此时,需要一个更加复杂的配置。
单一 hosting 服务器的 Wildcard 配置
当一台唯一的服务器为所有 wildcard 主机提供服务时,基于 egress 网关访问 wildcard 主机的配置与普通主机类似,除了:配置的路由目标不能与配置的主机相同,如:wildcard 主机,需要配置为通用域集合的唯一服务器主机。
为 \.wikipedia.org* 创建一个 egress
Gateway
、一个目标规则以及一个虚拟服务,来引导请求通过 egress 网关并从 egress 网关访问外部服务。$ kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: istio-egressgateway
spec:
selector:
istio: egressgateway
servers:
- port:
number: 443
name: https
protocol: HTTPS
hosts:
- "*.wikipedia.org"
tls:
mode: PASSTHROUGH
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: egressgateway-for-wikipedia
spec:
host: istio-egressgateway.istio-system.svc.cluster.local
subsets:
- name: wikipedia
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: direct-wikipedia-through-egress-gateway
spec:
hosts:
- "*.wikipedia.org"
gateways:
- mesh
- istio-egressgateway
tls:
- match:
- gateways:
- mesh
port: 443
sniHosts:
- "*.wikipedia.org"
route:
- destination:
host: istio-egressgateway.istio-system.svc.cluster.local
subset: wikipedia
port:
number: 443
weight: 100
- match:
- gateways:
- istio-egressgateway
port: 443
sniHosts:
- "*.wikipedia.org"
route:
- destination:
host: www.wikipedia.org
port:
number: 443
weight: 100
EOF
为目标服务器 www.wikipedia.org 创建一个
ServiceEntry
。$ kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
name: www-wikipedia
spec:
hosts:
- www.wikipedia.org
ports:
- number: 443
name: tls
protocol: TLS
resolution: DNS
EOF
发送请求至 https://en.wikipedia.org 和 https://de.wikipedia.org:
$ kubectl exec -it $SOURCE_POD -c sleep -- sh -c 'curl -s https://en.wikipedia.org/wiki/Main_Page | grep -o "<title>.*</title>"; curl -s https://de.wikipedia.org/wiki/Wikipedia:Hauptseite | grep -o "<title>.*</title>"'
<title>Wikipedia, the free encyclopedia</title>
<title>Wikipedia – Die freie Enzyklopädie</title>
检查 egress 网关代理访问 \.wikipedia.org* 的计数器统计值。如果 Istio 部署在
istio-system
命名空间中,打印输出计数器的命令为:$ kubectl exec -it $(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 clusters | grep '^outbound|443||www.wikipedia.org.*cx_total:'
outbound|443||www.wikipedia.org::208.80.154.224:443::cx_total::2
清除单点服务器的 wildcard 配置
$ kubectl delete serviceentry www-wikipedia
$ kubectl delete gateway istio-egressgateway
$ kubectl delete virtualservice direct-wikipedia-through-egress-gateway
$ kubectl delete destinationrule egressgateway-for-wikipedia
任意域的 Wildcard 配置
前面章节的配置生效,是因为 \.wikipedia.org 站点可以由任意一个 wikipedia.org 服务器提供服务。然而,情况并不总是如此。 譬如,你可能想要配置 egress 控制到更为通用的 wildcard 域,如:`.com或
*.org`。
配置流量流向任意 wildcard 域,为 Istio 网关引入一个挑战。在前面的章节中,您在配置中,将 www.wikipedia.org 配置为网关的路由目标主机,将流量导向该地址。 然而,网关并不知道它接收到的请求中任意 arbitrary 主机的 IP 地址。 这是 Istio 默认的 egress 网关代理 Envoy 的限制。Envoy 或者将流量路由至提前定义好的主机,或者路由至提前定义好的 IP 地址,或者是请求的最初 IP 地址。在网关案例中,请求的最初目标 IP 被丢弃,因为请求首先路由至 egress 网关,其目标 IP 为网关的 IP 地址。
因此,基于 Envoy 的 Istio 网关无法将流量路由至没有预先配置的 arbitrary 主机,从而,无法对任意 wildcard 域实施流量控制。 为了对 HTTPS 和任意 TLS 连接开启流量控制,您需要在 Envoy 的基础上再部署一个 SNI 转发代理。Envoy 将访问 wildcard 域的请求路由至 SNI 转发代理,代理反过来将请求转发给 SNI 值中约定的目标地址。
带 SNI 代理的 egress 网关,以及相关的 Istio 架构部分如下图所示:
Egress Gateway with SNI proxy
如下章节向您展示如何重新部署带 SNI 代理的 egress 网关,并配置 Istio 通过网关将 HTTPS 请求导向任意 wildcard 域。
安装带 SNI 代理的 egress 网关
本章节,除了标准的 Istio Envoy 代理,您将再部署一个带 SNI 代理的 egress 网关。本示例使用 Nginx 作为 SNI 代理。任何一个能够根据任意的、非预先配置的 SNI 值路由流量的 SNI 代理均可。 SNI 代理将监听在端口 8443
上,您可以绑定任意其它端口,egress Gateway
和 VirtualServices
中配置的端口除外。SNI 代理将流量转发至端口 443
。
创建一个 Nginx SNI 代理的配置文件。您可以按需编辑文件指定附加的 Nginx 设置。注意
server
的listen
原语指定端口为8443
,其proxy_pass
原语使用ssl_preread_server_name
端口为443
,ssl_preread
为on
以开启SNI
读。$ cat <<EOF > ./sni-proxy.conf
# 设置不需要根访问权限的自定义路径
pid /tmp/nginx.pid;
events {
}
stream {
log_format log_stream '\$remote_addr [\$time_local] \$protocol [\$ssl_preread_server_name]'
'\$status \$bytes_sent \$bytes_received \$session_time';
access_log /var/log/nginx/access.log log_stream;
error_log /var/log/nginx/error.log;
# SNI 的 TCP 转发代理
server {
resolver 8.8.8.8 ipv6=off;
listen 127.0.0.1:8443;
proxy_pass \$ssl_preread_server_name:443;
ssl_preread on;
}
}
EOF
创建一个 Kubernetes ConfigMap 来保存 Nginx SNI 代理的配置文件:
$ kubectl create configmap egress-sni-proxy-configmap -n istio-system --from-file=nginx.conf=./sni-proxy.conf
创建一个
IstioOperator
CR,以使用SNI代理添加新的出口网关:$ istioctl manifest generate -f - <<EOF > ./egressgateway-with-sni-proxy.yaml
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
spec:
# Only generate a gateway component defined below.
# Using this with "istioctl install" will reconcile and remove existing control-plane components.
# Instead use "istioctl manifest generate" or "kubectl create" if using the istio operator.
profile: empty
components:
egressGateways:
- name: istio-egressgateway-with-sni-proxy
enabled: true
label:
app: istio-egressgateway-with-sni-proxy
istio: egressgateway-with-sni-proxy
k8s:
service:
ports:
- port: 443
targetPort: 8443
name: https
overlays:
- kind: Deployment
name: istio-egressgateway-with-sni-proxy
patches:
- path: spec.template.spec.containers[-1]
value: |
name: sni-proxy
image: nginx
volumeMounts:
- name: sni-proxy-config
mountPath: /etc/nginx
readOnly: true
securityContext:
runAsNonRoot: true
runAsUser: 101
- path: spec.template.spec.volumes[-1]
value: |
name: sni-proxy-config
configMap:
name: egress-sni-proxy-configmap
defaultMode: 292 # 0444
EOF
部署新的 egress 网关:
$ kubectl apply -f ./egressgateway-with-sni-proxy.yaml
验证新的 egress 网关正在运行。注意 pod 有两个容器(一个是 Envoy 代理,另一个是 SNI 代理)。
$ kubectl get pod -l istio=egressgateway-with-sni-proxy -n istio-system
NAME READY STATUS RESTARTS AGE
istio-egressgateway-with-sni-proxy-79f6744569-pf9t2 2/2 Running 0 17s
创建一个 service entry,静态地址为 127.0.0.1 (
localhost
),关闭发送至新 service entry 的双向 TLS 请求。$ kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
name: sni-proxy
spec:
hosts:
- sni-proxy.local
location: MESH_EXTERNAL
ports:
- number: 8443
name: tcp
protocol: TCP
resolution: STATIC
endpoints:
- address: 127.0.0.1
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: disable-mtls-for-sni-proxy
spec:
host: sni-proxy.local
trafficPolicy:
tls:
mode: DISABLE
EOF
配置流量流经包含 SNI 代理的 egress 网关
为
*.wikipedia.org
定义一个ServiceEntry
:$ cat <<EOF | kubectl create -f -
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
name: wikipedia
spec:
hosts:
- "*.wikipedia.org"
ports:
- number: 443
name: tls
protocol: TLS
EOF
为 \.wikipedia.org 创建一个 egress
Gateway
,端口 443,协议 TLS,以及一个虚拟服务负责引导目标为 *.wikipedia.org* 的流量流经网关。$ kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: istio-egressgateway-with-sni-proxy
spec:
selector:
istio: egressgateway-with-sni-proxy
servers:
- port:
number: 443
name: tls-egress
protocol: TLS
hosts:
- "*.wikipedia.org"
tls:
mode: ISTIO_MUTUAL
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: egressgateway-for-wikipedia
spec:
host: istio-egressgateway-with-sni-proxy.istio-system.svc.cluster.local
subsets:
- name: wikipedia
trafficPolicy:
loadBalancer:
simple: ROUND_ROBIN
portLevelSettings:
- port:
number: 443
tls:
mode: ISTIO_MUTUAL
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: direct-wikipedia-through-egress-gateway
spec:
hosts:
- "*.wikipedia.org"
gateways:
- mesh
- istio-egressgateway-with-sni-proxy
tls:
- match:
- gateways:
- mesh
port: 443
sniHosts:
- "*.wikipedia.org"
route:
- destination:
host: istio-egressgateway-with-sni-proxy.istio-system.svc.cluster.local
subset: wikipedia
port:
number: 443
weight: 100
tcp:
- match:
- gateways:
- istio-egressgateway-with-sni-proxy
port: 443
route:
- destination:
host: sni-proxy.local
port:
number: 18443
weight: 100
---
# 下面的 filter 用于将最初的 SNI (应用发送的)转换为双向 TLS 连接的 SNI。
# 双向 TLS 连接。
# 转换后的 SNI 将被报告给 Mixer,以基于初始 SNI 的值强制实施策略。
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: forward-downstream-sni
spec:
configPatches:
- applyTo: NETWORK_FILTER
match:
context: SIDECAR_OUTBOUND
listener:
portNumber: 443
filterChain:
filter:
name: istio.stats
patch:
operation: INSERT_BEFORE
value:
name: forward_downstream_sni
config: {}
EOF
Add an
EnvoyFilter
to the gateway, to prevent it from being deceived.$ kubectl apply -n istio-system -f - <<EOF
# The following filter verifies that the SNI of the mutual TLS connection is
# identical to the original SNI issued by the client (the SNI used for routing by the SNI proxy).
# The filter prevents the gateway from being deceived by a malicious client: routing to one SNI while
# reporting some other value of SNI. If the original SNI does not match the SNI of the mutual TLS connection,
# the filter will block the connection to the external service.
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: egress-gateway-sni-verifier
spec:
workloadSelector:
labels:
app: istio-egressgateway-with-sni-proxy
configPatches:
- applyTo: NETWORK_FILTER
match:
context: GATEWAY
listener:
portNumber: 443
filterChain:
filter:
name: istio.stats
patch:
operation: INSERT_BEFORE
value:
name: sni_verifier
config: {}
EOF
发送 HTTPS 请求至 https://en.wikipedia.org and https://de.wikipedia.org:
$ kubectl exec -it $SOURCE_POD -c sleep -- sh -c 'curl -s https://en.wikipedia.org/wiki/Main_Page | grep -o "<title>.*</title>"; curl -s https://de.wikipedia.org/wiki/Wikipedia:Hauptseite | grep -o "<title>.*</title>"'
<title>Wikipedia, the free encyclopedia</title>
<title>Wikipedia – Die freie Enzyklopädie</title>
检查 Egress 网关 Envoy 代理的日志。如果 Istio 被部署在
istio-system
命名空间中,打印日志的命令为:$ kubectl logs -l istio=egressgateway-with-sni-proxy -c istio-proxy -n istio-system
您将看到类似如下的内容:
[2019-01-02T16:34:23.312Z] "- - -" 0 - 578 79141 624 - "-" "-" "-" "-" "127.0.0.1:8443" outbound|8443||sni-proxy.local 127.0.0.1:55018 172.30.109.84:443 172.30.109.112:45346 en.wikipedia.org
[2019-01-02T16:34:24.079Z] "- - -" 0 - 586 65770 638 - "-" "-" "-" "-" "127.0.0.1:8443" outbound|8443||sni-proxy.local 127.0.0.1:55034 172.30.109.84:443 172.30.109.112:45362 de.wikipedia.org
检查 SNI 代理的日志。如果 Istio 被部署在
istio-system
命名空间中,打印日志的命令为:$ kubectl logs -l istio=egressgateway-with-sni-proxy -n istio-system -c sni-proxy
127.0.0.1 [01/Aug/2018:15:32:02 +0000] TCP [en.wikipedia.org]200 81513 280 0.600
127.0.0.1 [01/Aug/2018:15:32:03 +0000] TCP [de.wikipedia.org]200 67745 291 0.659
清除任意域的 wildcard 配置
删除 \.wikipedia.org* 的配置项:
$ kubectl delete serviceentry wikipedia
$ kubectl delete gateway istio-egressgateway-with-sni-proxy
$ kubectl delete virtualservice direct-wikipedia-through-egress-gateway
$ kubectl delete destinationrule egressgateway-for-wikipedia
$ kubectl delete --ignore-not-found=true envoyfilter forward-downstream-sni
$ kubectl delete --ignore-not-found=true envoyfilter -n istio-system egress-gateway-sni-verifier
删除部署
egressgateway-with-sni-proxy
的配置项:$ kubectl delete serviceentry sni-proxy
$ kubectl delete destinationrule disable-mtls-for-sni-proxy
$ kubectl delete configmap egress-sni-proxy-configmap -n istio-system
$ kubectl delete -f ./egressgateway-with-sni-proxy.yaml
删除您创建的配置文件:
$ rm ./sni-proxy.conf ./egressgateway-with-sni-proxy.yaml
清除
- 关闭服务 sleep:
$ kubectl delete -f @samples/sleep/sleep.yaml@
从您的集群中卸载 Istio:
$ istioctl x uninstall --purge