Egress 网关的 TLS 发起过程

    • 遵照安装指南中的指令,安装 Istio。

    • 启动 样本应用,作为外部请求的测试源。

      若已开启自动 sidecar 注入,执行

      否则,必须在部署 应用之前手动注入 sidecar:

      Zip

      1. $ kubectl apply -f <(istioctl kube-inject -f @samples/sleep/sleep.yaml@)

      注意每一个可以执行 execcurl 操作的 pod,都需要注入。

    • 创建一个 shell 变量,来保存向外部服务发送请求的源 pod 的名称。 若使用 样例,运行:

      1. $ export SOURCE_POD=$(kubectl get pod -l app=sleep -o jsonpath={.items..metadata.name})
    • 部署 Istio egress 网关

    本节描述如何使用 egress 网关发起与示例为 Egress 流量发起 TLS 连接中一样的 TLS。 注意,这种情况下,TLS 的发起过程由 egress 网关完成,而不是像之前示例演示的那样由 sidecar 完成。

    1. edition.cnn.com 定义一个 ServiceEntry

      1. $ kubectl apply -f - <<EOF
      2. apiVersion: networking.istio.io/v1alpha3
      3. kind: ServiceEntry
      4. metadata:
      5. name: cnn
      6. spec:
      7. hosts:
      8. - edition.cnn.com
      9. ports:
      10. - number: 80
      11. name: http
      12. protocol: HTTP
      13. - number: 443
      14. name: https
      15. protocol: HTTPS
      16. resolution: DNS
      17. EOF
    2. 发送一个请求至 ,验证 ServiceEntry 已被正确应用。

      1. $ kubectl exec -it $SOURCE_POD -c sleep -- curl -sL -o /dev/null -D - http://edition.cnn.com/politics
      2. HTTP/1.1 301 Moved Permanently
      3. ...
      4. location: https://edition.cnn.com/politics
      5. ...
      6. command terminated with exit code 35

      如果在输出中看到 _301 Moved Permanently_,说明 ServiceEntry 配置正确。

    3. edition.cnn.com 创建一个 egress Gateway,端口 443,以及一个 sidecar 请求的目标规则,sidecar 请求被直接导向 egress 网关。

      根据需要开启源 pod 与 egress 网关之间的双向 TLS 认证,选择相应的命令。

      若开启双向 TLS ,则源 pod 与 egress 网关之间的流量为加密状态。 此外,双向 TLS 将允许 egress 网关监控源 pods 的身份,并开启 Mixer 基于该身份标识的强制策略实施。

      1. $ kubectl apply -f - <<EOF
      2. apiVersion: networking.istio.io/v1alpha3
      3. kind: Gateway
      4. metadata:
      5. name: istio-egressgateway
      6. spec:
      7. selector:
      8. istio: egressgateway
      9. servers:
      10. - port:
      11. number: 80
      12. name: https
      13. protocol: HTTPS
      14. hosts:
      15. - edition.cnn.com
      16. tls:
      17. mode: MUTUAL
      18. serverCertificate: /etc/certs/cert-chain.pem
      19. privateKey: /etc/certs/key.pem
      20. caCertificates: /etc/certs/root-cert.pem
      21. ---
      22. apiVersion: networking.istio.io/v1alpha3
      23. kind: DestinationRule
      24. metadata:
      25. name: egressgateway-for-cnn
      26. spec:
      27. host: istio-egressgateway.istio-system.svc.cluster.local
      28. subsets:
      29. - name: cnn
      30. trafficPolicy:
      31. loadBalancer:
      32. simple: ROUND_ROBIN
      33. portLevelSettings:
      34. - port:
      35. number: 80
      36. tls:
      37. mode: ISTIO_MUTUAL
      38. sni: edition.cnn.com
      39. EOF
      1. $ kubectl apply -f - <<EOF
      2. apiVersion: networking.istio.io/v1alpha3
      3. kind: Gateway
      4. metadata:
      5. name: istio-egressgateway
      6. spec:
      7. selector:
      8. istio: egressgateway
      9. servers:
      10. - port:
      11. number: 80
      12. name: http-port-for-tls-origination
      13. protocol: HTTP
      14. hosts:
      15. - edition.cnn.com
      16. ---
      17. apiVersion: networking.istio.io/v1alpha3
      18. kind: DestinationRule
      19. metadata:
      20. name: egressgateway-for-cnn
      21. spec:
      22. host: istio-egressgateway.istio-system.svc.cluster.local
      23. subsets:
      24. - name: cnn
      25. EOF
    4. 定义一个 VirtualService 来引导流量流经 egress 网关,以及一个 DestinationRule 为访问 edition.cnn.com 的请求发起 TLS 连接:

      1. $ kubectl apply -f - <<EOF
      2. apiVersion: networking.istio.io/v1alpha3
      3. kind: VirtualService
      4. metadata:
      5. name: direct-cnn-through-egress-gateway
      6. spec:
      7. hosts:
      8. - edition.cnn.com
      9. gateways:
      10. - istio-egressgateway
      11. - mesh
      12. http:
      13. - match:
      14. - gateways:
      15. - mesh
      16. port: 80
      17. route:
      18. - destination:
      19. host: istio-egressgateway.istio-system.svc.cluster.local
      20. subset: cnn
      21. port:
      22. number: 80
      23. weight: 100
      24. - match:
      25. - gateways:
      26. - istio-egressgateway
      27. port: 80
      28. route:
      29. - destination:
      30. host: edition.cnn.com
      31. port:
      32. number: 443
      33. weight: 100
      34. ---
      35. apiVersion: networking.istio.io/v1alpha3
      36. kind: DestinationRule
      37. metadata:
      38. name: originate-tls-for-edition-cnn-com
      39. spec:
      40. host: edition.cnn.com
      41. trafficPolicy:
      42. loadBalancer:
      43. simple: ROUND_ROBIN
      44. portLevelSettings:
      45. - port:
      46. number: 443
      47. tls:
      48. mode: SIMPLE # initiates HTTPS for connections to edition.cnn.com
      49. EOF
      1. $ kubectl exec -it $SOURCE_POD -c sleep -- curl -sL -o /dev/null -D - http://edition.cnn.com/politics
      2. HTTP/1.1 200 OK
      3. ...
      4. content-length: 150793
      5. ...

      输出将与在示例中显示的一样,发起 TLS 连接后,不再显示 301 Moved Permanently 消息。

    5. 检查 istio-egressgateway pod 的日志,将看到一行与请求相关的记录。 若 Istio 部署在 istio-system 命名空间中,打印日志的命令为:

      1. $ kubectl logs -l istio=egressgateway -c istio-proxy -n istio-system | tail

      将看到类似如下一行:

      1. "[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"

    删除创建的 Istio 配置项:

    1. $ kubectl delete gateway istio-egressgateway
    2. $ kubectl delete serviceentry cnn
    3. $ kubectl delete virtualservice direct-cnn-through-egress-gateway
    4. $ kubectl delete destinationrule originate-tls-for-edition-cnn-com
    5. $ kubectl delete destinationrule egressgateway-for-cnn

    与前一章节类似,本章节描述如何配置一个 egress 网关,为外部服务发起 TLS 连接,只是这次服务要求双向 TLS。

    本示例要求更高的参与性,首先需要:

    1. 生成客户端和服务器证书
    2. 部署一个支持双向 TLS 的外部服务
    3. 使用所需的证书重新部署 egress 网关

    然后才可以配置出口流量流经 egress 网关,egress 网关将发起 TLS 连接。

    生成客户端和服务器的证书与密钥

    1. 克隆示例代码库 :

      1. $ git clone https://github.com/nicholasjackson/mtls-go-example
    2. 进入克隆的代码库目录:

    3. 将证书迁移至 nginx.example.com 目录:

      1. $ mkdir ../nginx.example.com && mv 1_root 2_intermediate 3_application 4_client ../nginx.example.com
    4. 返回至上一级目录:

      1. $ cd ..

    为了模拟一个真实的支持双向 TLS 协议的外部服务, 在 Kubernetes 集群中部署一个 NGINX 服务器,该服务器运行在 Istio 服务网格之外,譬如:运行在一个没有开启 Istio sidecar proxy 注入的命名空间中。

    1. 创建一个命名空间,表示 Istio 网格之外的服务,mesh-external。注意在这个命名空间中,sidecar 自动注入是没有的,不会在 pods 中自动注入 sidecar proxy。

      1. $ kubectl create namespace mesh-external
    2. 创建 Kubernetes Secrets ,保存服务器和 CA 的证书。

      1. $ 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
      2. $ kubectl create -n mesh-external secret generic nginx-ca-certs --from-file=nginx.example.com/2_intermediate/certs/ca-chain.cert.pem
    3. 生成 NGINX 服务器的配置文件:

      1. $ cat <<EOF > ./nginx.conf
      2. events {
      3. http {
      4. log_format main '$remote_addr - $remote_user [$time_local] $status '
      5. '"$request" $body_bytes_sent "$http_referer" '
      6. '"$http_user_agent" "$http_x_forwarded_for"';
      7. access_log /var/log/nginx/access.log main;
      8. error_log /var/log/nginx/error.log;
      9. server {
      10. listen 443 ssl;
      11. root /usr/share/nginx/html;
      12. index index.html;
      13. server_name nginx.example.com;
      14. ssl_certificate /etc/nginx-server-certs/tls.crt;
      15. ssl_certificate_key /etc/nginx-server-certs/tls.key;
      16. ssl_client_certificate /etc/nginx-ca-certs/ca-chain.cert.pem;
      17. ssl_verify_client on;
      18. }
      19. }
      20. EOF
    4. 生成 Kubernetes 保存 NGINX 服务器的配置文件:

      1. $ kubectl create configmap nginx-configmap -n mesh-external --from-file=nginx.conf=./nginx.conf
      1. $ kubectl apply -f - <<EOF
      2. apiVersion: v1
      3. kind: Service
      4. metadata:
      5. name: my-nginx
      6. namespace: mesh-external
      7. labels:
      8. run: my-nginx
      9. spec:
      10. ports:
      11. - port: 443
      12. protocol: TCP
      13. selector:
      14. run: my-nginx
      15. ---
      16. apiVersion: apps/v1
      17. kind: Deployment
      18. metadata:
      19. name: my-nginx
      20. namespace: mesh-external
      21. spec:
      22. selector:
      23. matchLabels:
      24. run: my-nginx
      25. replicas: 1
      26. template:
      27. metadata:
      28. labels:
      29. run: my-nginx
      30. spec:
      31. containers:
      32. - name: my-nginx
      33. image: nginx
      34. ports:
      35. - containerPort: 443
      36. volumeMounts:
      37. - name: nginx-config
      38. mountPath: /etc/nginx
      39. readOnly: true
      40. - name: nginx-server-certs
      41. mountPath: /etc/nginx-server-certs
      42. readOnly: true
      43. - name: nginx-ca-certs
      44. mountPath: /etc/nginx-ca-certs
      45. readOnly: true
      46. volumes:
      47. - name: nginx-config
      48. configMap:
      49. name: nginx-configmap
      50. - name: nginx-server-certs
      51. secret:
      52. secretName: nginx-server-certs
      53. - name: nginx-ca-certs
      54. secret:
      55. secretName: nginx-ca-certs
      56. EOF
    5. nginx.example.com 定义一个 ServiceEntry 和一个 VirtualService,指示 Istio 引导目标为 nginx.example.com 的流量流向 NGINX 服务器:

      1. $ kubectl apply -f - <<EOF
      2. apiVersion: networking.istio.io/v1alpha3
      3. kind: ServiceEntry
      4. metadata:
      5. name: nginx
      6. spec:
      7. hosts:
      8. - nginx.example.com
      9. ports:
      10. - number: 80
      11. name: http
      12. protocol: HTTP
      13. - number: 443
      14. name: https
      15. protocol: HTTPS
      16. resolution: DNS
      17. endpoints:
      18. - address: my-nginx.mesh-external.svc.cluster.local
      19. ports:
      20. https: 443
      21. ---
      22. apiVersion: networking.istio.io/v1alpha3
      23. kind: VirtualService
      24. metadata:
      25. name: nginx
      26. spec:
      27. hosts:
      28. - nginx.example.com
      29. tls:
      30. - match:
      31. - port: 443
      32. sni_hosts:
      33. - nginx.example.com
      34. route:
      35. - destination:
      36. host: nginx.example.com
      37. port:
      38. number: 443
      39. weight: 100
      40. EOF

    部署一个容器测试 nginx 部署

    1. 生成 Kubernetes ,保存客户端和 CA 的证书:

      1. $ 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
      2. $ kubectl create secret generic nginx-ca-certs --from-file=nginx.example.com/2_intermediate/certs/ca-chain.cert.pem
    2. 基于挂载的客户端和 CA 证书,部署 sleep 样本应用,测试发送请求至 NGINX 服务器:

      1. $ kubectl apply -f - <<EOF
      2. # Copyright 2017 Istio Authors
      3. #
      4. # Licensed under the Apache License, Version 2.0 (the "License");
      5. # you may not use this file except in compliance with the License.
      6. # You may obtain a copy of the License at
      7. #
      8. # http://www.apache.org/licenses/LICENSE-2.0
      9. #
      10. # Unless required by applicable law or agreed to in writing, software
      11. # distributed under the License is distributed on an "AS IS" BASIS,
      12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      13. # See the License for the specific language governing permissions and
      14. # limitations under the License.
      15. ##################################################################################################
      16. # Sleep service
      17. ##################################################################################################
      18. apiVersion: v1
      19. kind: Service
      20. metadata:
      21. name: sleep
      22. labels:
      23. app: sleep
      24. spec:
      25. ports:
      26. - port: 80
      27. name: http
      28. selector:
      29. app: sleep
      30. ---
      31. apiVersion: apps/v1
      32. kind: Deployment
      33. metadata:
      34. name: sleep
      35. spec:
      36. replicas: 1
      37. template:
      38. metadata:
      39. app: sleep
      40. spec:
      41. containers:
      42. - name: sleep
      43. image: tutum/curl
      44. command: ["/bin/sleep","infinity"]
      45. imagePullPolicy: IfNotPresent
      46. volumeMounts:
      47. - name: nginx-client-certs
      48. mountPath: /etc/nginx-client-certs
      49. readOnly: true
      50. mountPath: /etc/nginx-ca-certs
      51. readOnly: true
      52. volumes:
      53. - name: nginx-client-certs
      54. secret:
      55. secretName: nginx-client-certs
      56. - name: nginx-ca-certs
      57. secret:
      58. secretName: nginx-ca-certs
      59. EOF
    3. 定义一个环境变量保存 sleep pod 的名称:

      1. $ export SOURCE_POD=$(kubectl get pod -l app=sleep -o jsonpath={.items..metadata.name})
    4. 使用部署的 pod 向 NGINX 服务器发送请求。 由于 nginx.example.com 不是真实存在的,DNS 无法解析,后面的 curl 命令使用 --resolve 选项手动解析主机名。 –resolve 选项传递的 IP 值(下方所示,1.1.1.1)没有意义。除 127.0.0.1 之外的任意值都可以使用。 一般情况下,目标主机名对应着一个 DNS 项,无需使用 curl--resolve 选项。

    5. 验证服务器要求客户端的证书:

      1. $ 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
      2. <html>
      3. <head><title>400 No required SSL certificate was sent</title></head>
      4. <body bgcolor="white">
      5. <center><h1>400 Bad Request</h1></center>
      6. <center>No required SSL certificate was sent</center>
      7. <hr><center>nginx/1.15.2</center>
      8. </body>
      9. </html>

    使用客户端证书重新部署 egress 网关

    1. 生成 Kubernetes 保存客户端和 CA 的证书。

      1. $ 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
      2. $ kubectl create -n istio-system secret generic nginx-ca-certs --from-file=nginx.example.com/2_intermediate/certs/ca-chain.cert.pem
    2. 部署 istio-egressgateway 挂载新生成的 secrets 的 volume。使用的参数选项与生成 istio.yaml 中的一致:

      1. $ istioctl manifest generate --set values.gateways.istio-ingressgateway.enabled=false \
      2. --set values.gateways.istio-egressgateway.enabled=true \
      3. --set 'values.gateways.istio-egressgateway.secretVolumes[0].name'=egressgateway-certs \
      4. --set 'values.gateways.istio-egressgateway.secretVolumes[0].secretName'=istio-egressgateway-certs \
      5. --set 'values.gateways.istio-egressgateway.secretVolumes[0].mountPath'=/etc/istio/egressgateway-certs \
      6. --set 'values.gateways.istio-egressgateway.secretVolumes[1].name'=egressgateway-ca-certs \
      7. --set 'values.gateways.istio-egressgateway.secretVolumes[1].secretName'=istio-egressgateway-ca-certs \
      8. --set 'values.gateways.istio-egressgateway.secretVolumes[1].mountPath'=/etc/istio/egressgateway-ca-certs \
      9. --set 'values.gateways.istio-egressgateway.secretVolumes[2].name'=nginx-client-certs \
      10. --set 'values.gateways.istio-egressgateway.secretVolumes[2].secretName'=nginx-client-certs \
      11. --set 'values.gateways.istio-egressgateway.secretVolumes[2].mountPath'=/etc/nginx-client-certs \
      12. --set 'values.gateways.istio-egressgateway.secretVolumes[3].name'=nginx-ca-certs \
      13. --set 'values.gateways.istio-egressgateway.secretVolumes[3].secretName'=nginx-ca-certs \
      14. --set 'values.gateways.istio-egressgateway.secretVolumes[3].mountPath'=/etc/nginx-ca-certs > \
      15. ./istio-egressgateway.yaml
    3. 重新部署 istio-egressgateway

      1. $ kubectl apply -f ./istio-egressgateway.yaml
      2. deployment "istio-egressgateway" configured
    4. 验证密钥和证书被成功装载入 istio-egressgateway pod:

      1. $ 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.crttls.key/etc/istio/nginx-client-certs 中,而 ca-chain.cert.pem/etc/istio/nginx-ca-certs 中。

    1. nginx.example.com 创建一个 egress Gateway 端口为 443,以及目标规则和虚拟服务来引导流量流经 egress 网关并从 egress 网关流向外部服务。

      1. $ kubectl apply -f - <<EOF
      2. apiVersion: networking.istio.io/v1alpha3
      3. kind: Gateway
      4. metadata:
      5. name: istio-egressgateway
      6. spec:
      7. selector:
      8. istio: egressgateway
      9. servers:
      10. - port:
      11. number: 443
      12. name: https
      13. protocol: HTTPS
      14. hosts:
      15. - nginx.example.com
      16. tls:
      17. mode: MUTUAL
      18. serverCertificate: /etc/certs/cert-chain.pem
      19. privateKey: /etc/certs/key.pem
      20. caCertificates: /etc/certs/root-cert.pem
      21. ---
      22. apiVersion: networking.istio.io/v1alpha3
      23. kind: DestinationRule
      24. metadata:
      25. name: egressgateway-for-nginx
      26. spec:
      27. host: istio-egressgateway.istio-system.svc.cluster.local
      28. subsets:
      29. - name: nginx
      30. trafficPolicy:
      31. loadBalancer:
      32. simple: ROUND_ROBIN
      33. portLevelSettings:
      34. - port:
      35. number: 443
      36. tls:
      37. mode: ISTIO_MUTUAL
      38. sni: nginx.example.com
      39. EOF
    2. 定义一个 VirtualService 引导流量流经 egress 网关,一个 DestinationRule 发起双向 TLS 连接:

      1. $ kubectl apply -f - <<EOF
      2. apiVersion: networking.istio.io/v1alpha3
      3. kind: VirtualService
      4. metadata:
      5. name: direct-nginx-through-egress-gateway
      6. spec:
      7. hosts:
      8. - nginx.example.com
      9. gateways:
      10. - istio-egressgateway
      11. - mesh
      12. http:
      13. - match:
      14. - gateways:
      15. - mesh
      16. port: 80
      17. route:
      18. - destination:
      19. host: istio-egressgateway.istio-system.svc.cluster.local
      20. subset: nginx
      21. port:
      22. number: 443
      23. weight: 100
      24. - match:
      25. - gateways:
      26. - istio-egressgateway
      27. port: 443
      28. route:
      29. - destination:
      30. host: nginx.example.com
      31. port:
      32. number: 443
      33. weight: 100
      34. ---
      35. apiVersion: networking.istio.io/v1alpha3
      36. kind: DestinationRule
      37. metadata:
      38. name: originate-mtls-for-nginx
      39. spec:
      40. host: nginx.example.com
      41. trafficPolicy:
      42. loadBalancer:
      43. simple: ROUND_ROBIN
      44. portLevelSettings:
      45. - port:
      46. number: 443
      47. tls:
      48. mode: MUTUAL
      49. clientCertificate: /etc/nginx-client-certs/tls.crt
      50. privateKey: /etc/nginx-client-certs/tls.key
      51. caCertificates: /etc/nginx-ca-certs/ca-chain.cert.pem
      52. sni: nginx.example.com
      53. EOF
    3. 发送一个 HTTP 请求至 http://nginx.example.com

      1. $ kubectl exec -it $SOURCE_POD -c sleep -- curl -s --resolve nginx.example.com:80:1.1.1.1 http://nginx.example.com
      2. <!DOCTYPE html>
      3. <html>
      4. <head>
      5. <title>Welcome to nginx!</title>
      6. ...
    4. 检查 istio-egressgateway pod 日志,有一行与请求相关的日志记录。 如果 Istio 部署在命名空间 istio-system 中,打印日志的命令为:

      1. $ kubectl logs -l istio=egressgateway -n istio-system | grep 'nginx.example.com' | grep HTTP

      将显示类似如下的一行:

      1. [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 连接示例

    1. 删除创建的 Kubernetes 资源:

      1. $ kubectl delete secret nginx-server-certs nginx-ca-certs -n mesh-external
      2. $ kubectl delete secret nginx-client-certs nginx-ca-certs
      3. $ kubectl delete secret nginx-client-certs nginx-ca-certs -n istio-system
      4. $ kubectl delete configmap nginx-configmap -n mesh-external
      5. $ kubectl delete service my-nginx -n mesh-external
      6. $ kubectl delete deployment my-nginx -n mesh-external
      7. $ kubectl delete namespace mesh-external
      8. $ kubectl delete gateway istio-egressgateway
      9. $ kubectl delete serviceentry nginx
      10. $ kubectl delete virtualservice direct-nginx-through-egress-gateway
      11. $ kubectl delete destinationrule originate-mtls-for-nginx
      12. $ kubectl delete destinationrule egressgateway-for-nginx
    2. 删除用于生成证书和仓库的路径:

      1. $ rm -rf nginx.example.com mtls-go-example

    删除 sleep 服务和部署:

    1. $ kubectl delete deployment sleep