Egress 网关的 TLS 发起过程
为 Egress 流量发起 TLS 连接 示例中演示了如何配置 Istio 以对外部服务流量实施 TLS origination。 配置 Egress Gateway 示例中演示了如何配置 Istio 来通过专门的 egress 网关服务引导 egress 流量。 本示例兼容以上两者,描述如何配置 egress 网关,为外部服务流量发起 TLS 连接。
开始之前
遵照安装指南中的指令,安装 Istio。
启动 sleep 样本应用,作为外部请求的测试源。
若已开启自动 sidecar 注入,执行
$ kubectl apply -f @samples/sleep/sleep.yaml@
否则,必须在部署
sleep
应用之前手动注入 sidecar:$ kubectl apply -f <(istioctl kube-inject -f @samples/sleep/sleep.yaml@)
注意每一个可以执行
exec
和curl
操作的 pod,都需要注入。创建一个 shell 变量,来保存向外部服务发送请求的源 pod 的名称。 若使用 sleep 样例,运行:
$ export SOURCE_POD=$(kubectl get pod -l app=sleep -o jsonpath={.items..metadata.name})
通过 egress 网关发起 TLS 连接
本节描述如何使用 egress 网关发起与示例为 Egress 流量发起 TLS 连接中一样的 TLS。 注意,这种情况下,TLS 的发起过程由 egress 网关完成,而不是像之前示例演示的那样由 sidecar 完成。
为
edition.cnn.com
定义一个ServiceEntry
:$ kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
name: cnn
spec:
hosts:
- edition.cnn.com
ports:
- number: 80
name: http
protocol: HTTP
- number: 443
name: https
protocol: HTTPS
resolution: DNS
EOF
发送一个请求至 http://edition.cnn.com/politics,验证
ServiceEntry
已被正确应用。$ kubectl exec -it $SOURCE_POD -c sleep -- curl -sL -o /dev/null -D - http://edition.cnn.com/politics
HTTP/1.1 301 Moved Permanently
...
location: https://edition.cnn.com/politics
...
command terminated with exit code 35
如果在输出中看到 _301 Moved Permanently_,说明
ServiceEntry
配置正确。为 edition.cnn.com 创建一个 egress
Gateway
,端口 443,以及一个 sidecar 请求的目标规则,sidecar 请求被直接导向 egress 网关。根据需要开启源 pod 与 egress 网关之间的双向 TLS 认证,选择相应的命令。
若开启双向 TLS ,则源 pod 与 egress 网关之间的流量为加密状态。 此外,双向 TLS 将允许 egress 网关监控源 pods 的身份,并开启 Mixer 基于该身份标识的强制策略实施。
$ kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: istio-egressgateway
spec:
selector:
istio: egressgateway
servers:
- port:
number: 80
name: https
protocol: HTTPS
hosts:
- edition.cnn.com
tls:
mode: MUTUAL
serverCertificate: /etc/certs/cert-chain.pem
privateKey: /etc/certs/key.pem
caCertificates: /etc/certs/root-cert.pem
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: egressgateway-for-cnn
spec:
host: istio-egressgateway.istio-system.svc.cluster.local
subsets:
- name: cnn
trafficPolicy:
loadBalancer:
simple: ROUND_ROBIN
portLevelSettings:
- port:
number: 80
tls:
mode: ISTIO_MUTUAL
sni: edition.cnn.com
EOF
$ kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: istio-egressgateway
spec:
selector:
istio: egressgateway
servers:
- port:
number: 80
name: http-port-for-tls-origination
protocol: HTTP
hosts:
- edition.cnn.com
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: egressgateway-for-cnn
spec:
host: istio-egressgateway.istio-system.svc.cluster.local
subsets:
- name: cnn
EOF
定义一个
VirtualService
来引导流量流经 egress 网关,以及一个DestinationRule
为访问edition.cnn.com
的请求发起 TLS 连接:$ kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
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: 443
weight: 100
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: originate-tls-for-edition-cnn-com
spec:
host: edition.cnn.com
trafficPolicy:
loadBalancer:
simple: ROUND_ROBIN
portLevelSettings:
- port:
number: 443
tls:
mode: SIMPLE # initiates HTTPS for connections to edition.cnn.com
EOF
发送一个 HTTP 请求至 http://edition.cnn.com/politics。
$ kubectl exec -it $SOURCE_POD -c sleep -- curl -sL -o /dev/null -D - http://edition.cnn.com/politics
HTTP/1.1 200 OK
...
content-length: 150793
...
输出将与在示例为 Egress 流量发起 TLS 连接中显示的一样,发起 TLS 连接后,不再显示 301 Moved Permanently 消息。
检查
istio-egressgateway
pod 的日志,将看到一行与请求相关的记录。 若 Istio 部署在istio-system
命名空间中,打印日志的命令为:$ kubectl logs -l istio=egressgateway -c istio-proxy -n istio-system | tail
将看到类似如下一行:
"[2018-06-14T13:49:36.340Z] "GET /politics HTTP/1.1" 200 - 0 148528 5096 90 "172.30.146.87" "curl/7.35.0" "c6bfdfc3-07ec-9c30-8957-6904230fd037" "edition.cnn.com" "151.101.65.67:443"
清除 TLS 启动实例
删除创建的 Istio 配置项:
$ kubectl delete gateway istio-egressgateway
$ kubectl delete serviceentry cnn
$ kubectl delete virtualservice direct-cnn-through-egress-gateway
$ kubectl delete destinationrule originate-tls-for-edition-cnn-com
$ kubectl delete destinationrule egressgateway-for-cnn
通过 egress 网关发起双向 TLS 连接
与前一章节类似,本章节描述如何配置一个 egress 网关,为外部服务发起 TLS 连接,只是这次服务要求双向 TLS。
本示例要求更高的参与性,首先需要:
- 生成客户端和服务器证书
- 部署一个支持双向 TLS 的外部服务
- 使用所需的证书重新部署 egress 网关
然后才可以配置出口流量流经 egress 网关,egress 网关将发起 TLS 连接。
生成客户端和服务器的证书与密钥
克隆示例代码库 https://github.com/nicholasjackson/mtls-go-example:
$ git clone https://github.com/nicholasjackson/mtls-go-example
进入克隆的代码库目录:
$ cd mtls-go-example
为
nginx.example.com
生成证书。 使用任意 password 执行如下命令:$ ./generate.sh nginx.example.com <password>
所有出现的提示,均选择
y
。将证书迁移至
nginx.example.com
目录:$ mkdir ../nginx.example.com && mv 1_root 2_intermediate 3_application 4_client ../nginx.example.com
返回至上一级目录:
$ cd ..
部署一个双向 TLS 服务器
为了模拟一个真实的支持双向 TLS 协议的外部服务, 在 Kubernetes 集群中部署一个 NGINX 服务器,该服务器运行在 Istio 服务网格之外,譬如:运行在一个没有开启 Istio sidecar proxy 注入的命名空间中。
创建一个命名空间,表示 Istio 网格之外的服务,
mesh-external
。注意在这个命名空间中,sidecar 自动注入是没有开启的,不会在 pods 中自动注入 sidecar proxy。$ kubectl create namespace mesh-external
创建 Kubernetes Secrets ,保存服务器和 CA 的证书。
$ kubectl create -n mesh-external secret tls nginx-server-certs --key nginx.example.com/3_application/private/nginx.example.com.key.pem --cert nginx.example.com/3_application/certs/nginx.example.com.cert.pem
$ kubectl create -n mesh-external secret generic nginx-ca-certs --from-file=nginx.example.com/2_intermediate/certs/ca-chain.cert.pem
生成 NGINX 服务器的配置文件:
$ cat <<EOF > ./nginx.conf
events {
}
http {
log_format main '$remote_addr - $remote_user [$time_local] $status '
'"$request" $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
error_log /var/log/nginx/error.log;
server {
listen 443 ssl;
root /usr/share/nginx/html;
index index.html;
server_name nginx.example.com;
ssl_certificate /etc/nginx-server-certs/tls.crt;
ssl_certificate_key /etc/nginx-server-certs/tls.key;
ssl_client_certificate /etc/nginx-ca-certs/ca-chain.cert.pem;
ssl_verify_client on;
}
}
EOF
生成 Kubernetes ConfigMap 保存 NGINX 服务器的配置文件:
$ kubectl create configmap nginx-configmap -n mesh-external --from-file=nginx.conf=./nginx.conf
部署 NGINX 服务器:
$ kubectl apply -f - <<EOF
apiVersion: v1
kind: Service
metadata:
name: my-nginx
namespace: mesh-external
labels:
run: my-nginx
spec:
ports:
- port: 443
protocol: TCP
selector:
run: my-nginx
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-nginx
namespace: mesh-external
spec:
selector:
matchLabels:
run: my-nginx
replicas: 1
template:
metadata:
labels:
run: my-nginx
spec:
containers:
- name: my-nginx
image: nginx
ports:
- containerPort: 443
volumeMounts:
- name: nginx-config
mountPath: /etc/nginx
readOnly: true
- name: nginx-server-certs
mountPath: /etc/nginx-server-certs
readOnly: true
- name: nginx-ca-certs
mountPath: /etc/nginx-ca-certs
readOnly: true
volumes:
- name: nginx-config
configMap:
name: nginx-configmap
- name: nginx-server-certs
secret:
secretName: nginx-server-certs
- name: nginx-ca-certs
secret:
secretName: nginx-ca-certs
EOF
为
nginx.example.com
定义一个ServiceEntry
和一个VirtualService
,指示 Istio 引导目标为nginx.example.com
的流量流向 NGINX 服务器:$ kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
name: nginx
spec:
hosts:
- nginx.example.com
ports:
- number: 80
name: http
protocol: HTTP
- number: 443
name: https
protocol: HTTPS
resolution: DNS
endpoints:
- address: my-nginx.mesh-external.svc.cluster.local
ports:
https: 443
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: nginx
spec:
hosts:
- nginx.example.com
tls:
- match:
- port: 443
sni_hosts:
- nginx.example.com
route:
- destination:
host: nginx.example.com
port:
number: 443
weight: 100
EOF
部署一个容器测试 nginx 部署
生成 Kubernetes Secrets ,保存客户端和 CA 的证书:
$ kubectl create secret tls nginx-client-certs --key nginx.example.com/4_client/private/nginx.example.com.key.pem --cert nginx.example.com/4_client/certs/nginx.example.com.cert.pem
$ kubectl create secret generic nginx-ca-certs --from-file=nginx.example.com/2_intermediate/certs/ca-chain.cert.pem
基于挂载的客户端和 CA 证书,部署 sleep 样本应用,测试发送请求至 NGINX 服务器:
$ kubectl apply -f - <<EOF
# Copyright 2017 Istio Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
##################################################################################################
# Sleep service
##################################################################################################
apiVersion: v1
kind: Service
metadata:
name: sleep
labels:
app: sleep
spec:
ports:
- port: 80
name: http
selector:
app: sleep
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: sleep
spec:
replicas: 1
template:
metadata:
labels:
app: sleep
spec:
containers:
- name: sleep
image: tutum/curl
command: ["/bin/sleep","infinity"]
imagePullPolicy: IfNotPresent
volumeMounts:
- name: nginx-client-certs
mountPath: /etc/nginx-client-certs
readOnly: true
- name: nginx-ca-certs
mountPath: /etc/nginx-ca-certs
readOnly: true
volumes:
- name: nginx-client-certs
secret:
secretName: nginx-client-certs
- name: nginx-ca-certs
secret:
secretName: nginx-ca-certs
EOF
定义一个环境变量保存
sleep
pod 的名称:$ export SOURCE_POD=$(kubectl get pod -l app=sleep -o jsonpath={.items..metadata.name})
使用部署的 sleep pod 向 NGINX 服务器发送请求。 由于
nginx.example.com
不是真实存在的,DNS 无法解析,后面的curl
命令使用--resolve
选项手动解析主机名。 –resolve 选项传递的 IP 值(下方所示,1.1.1.1)没有意义。除 127.0.0.1 之外的任意值都可以使用。 一般情况下,目标主机名对应着一个 DNS 项,无需使用curl
的--resolve
选项。$ kubectl exec -it $SOURCE_POD -c sleep -- curl -v --resolve nginx.example.com:443:1.1.1.1 --cacert /etc/nginx-ca-certs/ca-chain.cert.pem --cert /etc/nginx-client-certs/tls.crt --key /etc/nginx-client-certs/tls.key https://nginx.example.com
...
Server certificate:
subject: C=US; ST=Denial; L=Springfield; O=Dis; CN=nginx.example.com
start date: 2018-08-16 04:31:20 GMT
expire date: 2019-08-26 04:31:20 GMT
common name: nginx.example.com (matched)
issuer: C=US; ST=Denial; O=Dis; CN=nginx.example.com
SSL certificate verify ok.
> GET / HTTP/1.1
> User-Agent: curl/7.35.0
> Host: nginx.example.com
...
< HTTP/1.1 200 OK
< Server: nginx/1.15.2
...
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...
验证服务器要求客户端的证书:
$ kubectl exec -it $(kubectl get pod -l app=sleep -o jsonpath={.items..metadata.name}) -c sleep -- curl -k --resolve nginx.example.com:443:1.1.1.1 https://nginx.example.com
<html>
<head><title>400 No required SSL certificate was sent</title></head>
<body bgcolor="white">
<center><h1>400 Bad Request</h1></center>
<center>No required SSL certificate was sent</center>
<hr><center>nginx/1.15.2</center>
</body>
</html>
使用客户端证书重新部署 egress 网关
生成 Kubernetes Secrets 保存客户端和 CA 的证书。
$ kubectl create -n istio-system secret tls nginx-client-certs --key nginx.example.com/4_client/private/nginx.example.com.key.pem --cert nginx.example.com/4_client/certs/nginx.example.com.cert.pem
$ kubectl create -n istio-system secret generic nginx-ca-certs --from-file=nginx.example.com/2_intermediate/certs/ca-chain.cert.pem
部署
istio-egressgateway
挂载新生成的 secrets 的 volume。使用的参数选项与生成istio.yaml
中的一致:$ istioctl manifest generate --set values.gateways.istio-ingressgateway.enabled=false \
--set values.gateways.istio-egressgateway.enabled=true \
--set 'values.gateways.istio-egressgateway.secretVolumes[0].name'=egressgateway-certs \
--set 'values.gateways.istio-egressgateway.secretVolumes[0].secretName'=istio-egressgateway-certs \
--set 'values.gateways.istio-egressgateway.secretVolumes[0].mountPath'=/etc/istio/egressgateway-certs \
--set 'values.gateways.istio-egressgateway.secretVolumes[1].name'=egressgateway-ca-certs \
--set 'values.gateways.istio-egressgateway.secretVolumes[1].secretName'=istio-egressgateway-ca-certs \
--set 'values.gateways.istio-egressgateway.secretVolumes[1].mountPath'=/etc/istio/egressgateway-ca-certs \
--set 'values.gateways.istio-egressgateway.secretVolumes[2].name'=nginx-client-certs \
--set 'values.gateways.istio-egressgateway.secretVolumes[2].secretName'=nginx-client-certs \
--set 'values.gateways.istio-egressgateway.secretVolumes[2].mountPath'=/etc/nginx-client-certs \
--set 'values.gateways.istio-egressgateway.secretVolumes[3].name'=nginx-ca-certs \
--set 'values.gateways.istio-egressgateway.secretVolumes[3].secretName'=nginx-ca-certs \
--set 'values.gateways.istio-egressgateway.secretVolumes[3].mountPath'=/etc/nginx-ca-certs > \
./istio-egressgateway.yaml
重新部署
istio-egressgateway
:$ kubectl apply -f ./istio-egressgateway.yaml
deployment "istio-egressgateway" configured
验证密钥和证书被成功装载入
istio-egressgateway
pod:$ kubectl exec -it -n istio-system $(kubectl -n istio-system get pods -l istio=egressgateway -o jsonpath='{.items[0].metadata.name}') -- ls -al /etc/nginx-client-certs /etc/nginx-ca-certs
tls.crt
与tls.key
在/etc/istio/nginx-client-certs
中,而ca-chain.cert.pem
在/etc/istio/nginx-ca-certs
中。
为 egress 流量配置双向 TLS
为
nginx.example.com
创建一个 egressGateway
端口为 443,以及目标规则和虚拟服务来引导流量流经 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:
- nginx.example.com
tls:
mode: MUTUAL
serverCertificate: /etc/certs/cert-chain.pem
privateKey: /etc/certs/key.pem
caCertificates: /etc/certs/root-cert.pem
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: egressgateway-for-nginx
spec:
host: istio-egressgateway.istio-system.svc.cluster.local
subsets:
- name: nginx
trafficPolicy:
loadBalancer:
simple: ROUND_ROBIN
portLevelSettings:
- port:
number: 443
tls:
mode: ISTIO_MUTUAL
sni: nginx.example.com
EOF
定义一个
VirtualService
引导流量流经 egress 网关,一个DestinationRule
发起双向 TLS 连接:$ kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: direct-nginx-through-egress-gateway
spec:
hosts:
- nginx.example.com
gateways:
- istio-egressgateway
- mesh
http:
- match:
- gateways:
- mesh
port: 80
route:
- destination:
host: istio-egressgateway.istio-system.svc.cluster.local
subset: nginx
port:
number: 443
weight: 100
- match:
- gateways:
- istio-egressgateway
port: 443
route:
- destination:
host: nginx.example.com
port:
number: 443
weight: 100
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: originate-mtls-for-nginx
spec:
host: nginx.example.com
trafficPolicy:
loadBalancer:
simple: ROUND_ROBIN
portLevelSettings:
- port:
number: 443
tls:
mode: MUTUAL
clientCertificate: /etc/nginx-client-certs/tls.crt
privateKey: /etc/nginx-client-certs/tls.key
caCertificates: /etc/nginx-ca-certs/ca-chain.cert.pem
sni: nginx.example.com
EOF
发送一个 HTTP 请求至
http://nginx.example.com
:$ kubectl exec -it $SOURCE_POD -c sleep -- curl -s --resolve nginx.example.com:80:1.1.1.1 http://nginx.example.com
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...
检查
istio-egressgateway
pod 日志,有一行与请求相关的日志记录。 如果 Istio 部署在命名空间istio-system
中,打印日志的命令为:$ kubectl logs -l istio=egressgateway -n istio-system | grep 'nginx.example.com' | grep HTTP
将显示类似如下的一行:
[2018-08-19T18:20:40.096Z] "GET / HTTP/1.1" 200 - 0 612 7 5 "172.30.146.114" "curl/7.35.0" "b942b587-fac2-9756-8ec6-303561356204" "nginx.example.com" "172.21.72.197:443"
清除双向 TLS 连接示例
删除创建的 Kubernetes 资源:
$ kubectl delete secret nginx-server-certs nginx-ca-certs -n mesh-external
$ kubectl delete secret nginx-client-certs nginx-ca-certs
$ kubectl delete secret nginx-client-certs nginx-ca-certs -n istio-system
$ kubectl delete configmap nginx-configmap -n mesh-external
$ kubectl delete service my-nginx -n mesh-external
$ kubectl delete deployment my-nginx -n mesh-external
$ kubectl delete namespace mesh-external
$ kubectl delete gateway istio-egressgateway
$ kubectl delete serviceentry nginx
$ kubectl delete virtualservice direct-nginx-through-egress-gateway
$ kubectl delete destinationrule originate-mtls-for-nginx
$ kubectl delete destinationrule egressgateway-for-nginx
删除用于生成证书和仓库的路径:
$ rm -rf nginx.example.com mtls-go-example
删除生成并应用于示例中的配置文件
$ rm -f ./nginx.conf ./istio-egressgateway.yaml
清除
删除 sleep
服务和部署:
$ kubectl delete service sleep
$ kubectl delete deployment sleep
相关内容
管控出口流量的备选方案比较,包括性能因素。
使用 Istio 的出口流量管控来阻止相关出口流量攻击。
涉及出口流量攻击和出口流量管控要求。
评估加入 Egress gateway 对性能造成的影响。
描述了一个基于 Istio 的 Bookinfo 示例的简单场景。
描述如何配置 Istio 进行 HTTP Egress 流量监控和访问策略。