Egress 网关的 TLS 发起过程

为 Egress 流量发起 TLS 连接 示例中演示了如何配置 Istio 以对外部服务流量实施 TLS origination配置 Egress Gateway 示例中演示了如何配置 Istio 来通过专门的 egress 网关服务引导 egress 流量。 本示例兼容以上两者,描述如何配置 egress 网关,为外部服务流量发起 TLS 连接。

开始之前

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

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

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

    Zip
    $ kubectl apply -f @samples/sleep/sleep.yaml@
    

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

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

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

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

    $ export SOURCE_POD=$(kubectl get pod -l app=sleep -o jsonpath={.items..metadata.name})
    
  • 对于 macOS 用户,确认您使用的是 openssl 版本 1.1 或更高版本:

    $ openssl version -a | grep OpenSSL
    OpenSSL 1.1.1g  21 Apr 2020
    

    如果前面的命令输出的是版本 1.1 或更高版本,如图所示,则您的 openssl 命令应该正确执行此任务中的指示。否则,升级您的 openssl 或尝试 openssl 的不同实现,像在 Linux 机器上一样。

  • 部署 Istio egress 网关

  • 开启 Envoy 的访问日志

通过 egress 网关发起 TLS 连接

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

  1. 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
    
  2. 发送一个请求至 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 配置正确。

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

    $ 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-port-for-tls-origination
          protocol: HTTPS
        hosts:
        - edition.cnn.com
        tls:
          mode: ISTIO_MUTUAL
    ---
    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
    
  4. 定义一个 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
    
  5. 发送一个 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
    ...
    

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

  6. 检查 istio-egressgateway Pod 的日志,将看到一行与请求相关的记录。 若 Istio 部署在 istio-system 命名空间中,可以通过下面的命令打印日志:

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

    将看到类似如下一行的输出:

    [2020-06-30T16:17:56.763Z] "GET /politics HTTP/2" 200 - "-" "-" 0 1295938 529 89 "10.244.0.171" "curl/7.64.0" "cf76518d-3209-9ab7-a1d0-e6002728ef5b" "edition.cnn.com" "151.101.129.67:443" outbound|443||edition.cnn.com 10.244.0.170:54280 10.244.0.170:8080 10.244.0.171:35628 - -
    

清除 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。

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

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

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

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

对于此任务,您可以使用自己喜欢的工具来生成证书和密钥。以下命令使用 openssl

  1. 为您的服务签名证书创建根证书和私钥:

    $ openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -subj '/O=example Inc./CN=example.com' -keyout example.com.key -out example.com.crt
    
  2. my-nginx.mesh-external.svc.cluster.local 创建证书和私钥:

    $ openssl req -out my-nginx.mesh-external.svc.cluster.local.csr -newkey rsa:2048 -nodes -keyout my-nginx.mesh-external.svc.cluster.local.key -subj "/CN=my-nginx.mesh-external.svc.cluster.local/O=some organization"
    $ openssl x509 -req -sha256 -days 365 -CA example.com.crt -CAkey example.com.key -set_serial 0 -in my-nginx.mesh-external.svc.cluster.local.csr -out my-nginx.mesh-external.svc.cluster.local.crt
    

    或者,如果您想要为目标启用 SAN 验证,您可以将 SubjectAltNames 添加到证书中。例如:

    $ cat > san.conf <<EOF
    [req]
    distinguished_name = req_distinguished_name
    req_extensions = v3_req
    x509_extensions = v3_req
    prompt = no
    [req_distinguished_name]
    countryName = US
    [v3_req]
    keyUsage = critical, digitalSignature, keyEncipherment
    extendedKeyUsage = serverAuth, clientAuth
    basicConstraints = critical, CA:FALSE
    subjectAltName = critical, @alt_names
    [alt_names]
    DNS = my-nginx.mesh-external.svc.cluster.local
    EOF
    $
    $ openssl req -out my-nginx.mesh-external.svc.cluster.local.csr -newkey rsa:4096 -nodes -keyout my-nginx.mesh-external.svc.cluster.local.key -subj "/CN=my-nginx.mesh-external.svc.cluster.local/O=some organization" -config san.conf
    $ openssl x509 -req -sha256 -days 365 -CA example.com.crt -CAkey example.com.key -set_serial 0 -in my-nginx.mesh-external.svc.cluster.local.csr -out my-nginx.mesh-external.svc.cluster.local.crt -extfile san.conf -extensions v3_req
    
  3. 生成客户端证书和私钥:

    $ openssl req -out client.example.com.csr -newkey rsa:2048 -nodes -keyout client.example.com.key -subj "/CN=client.example.com/O=client organization"
    $ openssl x509 -req -sha256 -days 365 -CA example.com.crt -CAkey example.com.key -set_serial 1 -in client.example.com.csr -out client.example.com.crt
    

部署一个双向 TLS 服务器

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

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

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

    $ kubectl create -n mesh-external secret tls nginx-server-certs --key my-nginx.mesh-external.svc.cluster.local.key --cert my-nginx.mesh-external.svc.cluster.local.crt
    $ kubectl create -n mesh-external secret generic nginx-ca-certs --from-file=example.com.crt
    
  3. 生成 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 my-nginx.mesh-external.svc.cluster.local;
        ssl_certificate /etc/nginx-server-certs/tls.crt;
        ssl_certificate_key /etc/nginx-server-certs/tls.key;
        ssl_client_certificate /etc/nginx-ca-certs/example.com.crt;
        ssl_verify_client on;
      }
    }
    EOF
    
  4. 生成 Kubernetes ConfigMap 保存 NGINX 服务器的配置文件:

    $ kubectl create configmap nginx-configmap -n mesh-external --from-file=nginx.conf=./nginx.conf
    
  5. 部署 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
    
  6. 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
    

为 egress 流量配置双向 TLS

  1. 创建 Kubernetes Secret 保存客户端证书:

    $ kubectl create secret -n istio-system generic client-credential --from-file=tls.key=client.example.com.key \
      --from-file=tls.crt=client.example.com.crt --from-file=ca.crt=example.com.crt
    

    Secret 所在的命名空间必须与出口网关部署的位置一只,在本例中为 istio-system 命名空间。

    为了支持与各种工具的集成,Istio 支持多种 Secret 格式。

    在本例中,使用了一个具有关键字 tls.keytls.crtca.crt 的通用 Secret。

  2. my-nginx.mesh-external.svc.cluster.local 创建一个 egress Gateway 端口为 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:
        - my-nginx.mesh-external.svc.cluster.local
        tls:
          mode: ISTIO_MUTUAL
    ---
    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: my-nginx.mesh-external.svc.cluster.local
    EOF
    
  3. 定义一个 VirtualService 引导流量流经 egress 网关:

    $ kubectl apply -f - <<EOF
    apiVersion: networking.istio.io/v1alpha3
    kind: VirtualService
    metadata:
      name: direct-nginx-through-egress-gateway
    spec:
      hosts:
      - my-nginx.mesh-external.svc.cluster.local
      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: my-nginx.mesh-external.svc.cluster.local
            port:
              number: 443
          weight: 100
    EOF
    
  4. 添加 DestinationRule 执行双向 TLS

    $ kubectl apply -n istio-system -f - <<EOF
    apiVersion: networking.istio.io/v1alpha3
    kind: DestinationRule
    metadata:
      name: originate-mtls-for-nginx
    spec:
      host: my-nginx.mesh-external.svc.cluster.local
      trafficPolicy:
        loadBalancer:
          simple: ROUND_ROBIN
        portLevelSettings:
        - port:
            number: 443
          tls:
            mode: MUTUAL
            credentialName: client-credential # 这必须与之前创建的用于保存客户端证书的 Secret 相匹配
            sni: my-nginx.mesh-external.svc.cluster.local
            # subjectAltNames: # 如果证书是随着上一节中指定的 SAN 生成的,则可以被启用
            # - my-nginx.mesh-external.svc.cluster.local
    EOF
    
  5. 发送一个 HTTP 请求至 http://my-nginx.mesh-external.svc.cluster.local

    $ kubectl exec "$(kubectl get pod -l app=sleep -o jsonpath={.items..metadata.name})" -c sleep -- curl -sS http://my-nginx.mesh-external.svc.cluster.local
    <!DOCTYPE html>
    <html>
    <head>
    <title>Welcome to nginx!</title>
    ...
    
  6. 检查 istio-egressgateway Pod 日志,有一行与请求相关的日志记录。 如果 Istio 部署在命名空间 istio-system 中,打印日志的命令为:

    $ kubectl logs -l istio=egressgateway -n istio-system | grep 'my-nginx.mesh-external.svc.cluster.local' | 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" "my-nginx.mesh-external.svc.cluster.local" "172.21.72.197:443"
    

清除双向 TLS 连接示例

  1. 删除创建的 Kubernetes 资源:

    $ kubectl delete secret nginx-server-certs nginx-ca-certs -n mesh-external
    $ kubectl delete secret client-credential -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 virtualservice direct-nginx-through-egress-gateway
    $ kubectl delete destinationrule -n istio-system originate-mtls-for-nginx
    $ kubectl delete destinationrule egressgateway-for-nginx
    
  2. 删除证书和私钥:

    $ rm example.com.crt example.com.key my-nginx.mesh-external.svc.cluster.local.crt my-nginx.mesh-external.svc.cluster.local.key my-nginx.mesh-external.svc.cluster.local.csr client.example.com.crt client.example.com.csr client.example.com.key
    
  3. 删除生成并应用于示例中的配置文件

    $ rm ./nginx.conf
    $ rm ./gateway-patch.json
    

清除

删除 sleep 的 Service 和 Deployment:

$ kubectl delete service sleep
$ kubectl delete deployment sleep
这些信息有用吗?
您是否有更多建议和改进意见?

感谢您的反馈!