使用ASM出口网关访问外部mTLS服务

服务网格 ASM(Service Mesh)出口网关作为网格内流量的统一出口,可以执行TLS/mTLS请求的发起,进而实现全链路的加密通信。同时,出口网关也可以执行丰富的安全策略,实现更加精细的访问控制。在服务网格中,出口网关是实现出口流量管理的最佳方案。本文将介绍如何使用ASM出口网关管理出口流量并发起mTLS通信。

重要

请确保您已经完成在ASM入口网关上配置mTLS服务并限制特定客户端访问中的所有步骤,本文将使用此文档中在ASM和ACK环境部署的应用作为外部mTLS服务端。因此在执行本文中的步骤时,您需要额外创建一套ASM和ACK环境作为主环境。为了方便区别,本文中提到的${ASM网关ip}均代表在ASM入口网关上配置mTLS服务并限制特定客户端访问中使用的入口网关IP,“ACK集群”和“ASM实例”均为主环境资源。

前提条件

已开启sidecar自动注入。具体操作,请参见配置Sidecar注入策略

步骤一:部署测试应用sleep

部署sleep应用,具体操作,请参见部署sleep应用

使用ACK集群的kubeconfig,执行以下命令测试访问部署在另一套环境中的httpbin服务。

kubectl exec deploy/sleep -- curl --header "host:test.com" ${ASM网关ip}/status/418

预期输出:

    -=[ teapot ]=-

       _...._
     .'  _ _ `.
    | ."` ^ `". _,
    \_;`"---"`|//
      |       ;/
      \_     _/
        `"""`

步骤二:开启REGISTRY_ONLY并创建ServiceEntry

Sidecar支持配置外部访问策略为REGISTRY_ONLY,开启之后pod只能访问通过ServiceEntry注册的服务。您可以根据需要选择是否开启此项功能。具体开启步骤,请参见步骤二:开启REGISTRY_ONLY。开启之后,访问未经注册的服务将会返回502 Bad Gateway

使用以下内容,为test.com配置ServiceEntry。具体操作,请参见创建集群外服务

apiVersion: networking.istio.io/v1beta1
kind: ServiceEntry
metadata:
  name: test-com
  namespace: default
spec:
  endpoints:
    - address: ${ASM网关IP}  
  hosts:
    - test.com
  location: MESH_EXTERNAL
  ports:
    - name: http
      number: 80
      protocol: HTTP
    - name: https
      number: 443
      protocol: HTTPS
  resolution: STATIC

完成配置后,即可正常从sleep Pod中访问httpbin服务。

步骤三:创建ASM出口网关,并让HTTP协议请求经过出口网关

  1. 创建出口网关,配置HTTP协议和80端口,并打开双向TLS认证开关。此开关打开之后,网格中其他工作负载向网关发送流量时会自动使用mTLS,该mTLS的证书由网格统一管理。具体操作,请参见创建出口网关

  2. 使用以下内容,创建网关规则。具体操作,请参见创建网关规则

    apiVersion: networking.istio.io/v1alpha3
    kind: Gateway
    metadata:
      name: egress-gateway
      namespace: default
    spec:
      selector:
        istio: egressgateway
      servers:
      - hosts:
        - '*'
        port:
          name: http
          number: 80
          protocol: HTTPS
        tls:
          mode: ISTIO_MUTUAL

    网关规则中声明了出口网关在80端口上开启mTLS监听,并且使用网格统一提供的证书。

  3. 使用以下内容,创建虚拟服务。

    apiVersion: networking.istio.io/v1alpha3
    kind: VirtualService
    metadata:
      name: egressgateway-vs
    spec:
      hosts:
      - test.com
      gateways:
      - egress-gateway  # 上一步创建的网关规则名称。
      - mesh
      http:
      - match:
        - gateways:
          - mesh
          port: 80
        route:
        - destination:
            host: istio-egressgateway.istio-system.svc.cluster.local
            port:
              number: 80
          weight: 100
      - match:
        - gateways:
          - egress-gateway
          port: 80
        route:
        - destination:
            host: test.com
            port:
              number: 80
          weight: 100

    创建虚拟服务后,Sidecar中的test.com的流量将会发送给出口网关,出口网关收到的test.com流量将发送给真正的test.com ServiceEntry。

  4. 执行以下命令,在sleep应用的Pod中访问test.com

    kubectl exec deploy/sleep -- curl --header "host:test.com" ${ASM网关ip}/status/418

    预期输出:

        -=[ teapot ]=-
    
           _...._
         .'  _ _ `.
        | ."` ^ `". _,
        \_;`"---"`|//
          |       ;/
          \_     _/
            `"""`
  5. 使用ASM实例的kubeconfig,执行以下命令。

    kubectl -n istio-system logs ${出口网关pod名称}| tail -1

    预期输出:

    {"authority_for":"test.com","bytes_received":"0","bytes_sent":"135","downstream_local_address":"192.168.36.32:80","downstream_remote_address":"192.168.36.29:58146","duration":"8","istio_policy_status":"-","method":"GET","path":"/status/418","protocol":"HTTP/1.1","request_id":"9f7b5475-6e45-4700-a85a-e00835b6b6c0","requested_server_name":"outbound_.80_._.istio-egressgateway.istio-system.svc.cluster.local","response_code":"418","response_flags":"-","route_name":"-","start_time":"2024-07-29T03:05:58.421Z","trace_id":"-","upstream_cluster":"outbound|80||test.com","upstream_host":"${ASM网关IP}:80","upstream_local_address":"192.168.36.32:52838","upstream_response_time":"7","upstream_service_time":"7","upstream_transport_failure_reason":"-","user_agent":"curl/8.1.2","x_forwarded_for":"192.168.36.29"}

    可以看到,上述日志可以确认该服务经过了ASM出口网关。

当前的请求链路是:sleep pod --> istio-egressgateway --> istio-ingressgateway --> httpbin。其中:

  • sleep pod --> istio-egressgateway这段是mTLS流量。

  • istio-egressgateway --> istio-ingressgateway这段是明文流量。

  • istio-ingressgateway --> httpbin这段是mTLS流量。

显然,两个网关之间交互通过明文是不安全的。由于服务端网关已经支持了mTLS,因此只需要客户端出口网关支持发起mTLS请求即可。

步骤四:在出口网关中将HTTP请求升级为mTLS流量

  1. 使用以下内容,更新虚拟服务。

    apiVersion: networking.istio.io/v1alpha3
    kind: VirtualService
    metadata:
      name: egressgateway-vs
    spec:
      hosts:
      - test.com
      gateways:
      - egress-gateway
      - mesh
      http:
      - match:
        - gateways:
          - mesh
          port: 80
        route:
        - destination:
            host: istio-egressgateway.istio-system.svc.cluster.local
            port:
              number: 80
          weight: 100
      - match:
        - gateways:
          - egress-gateway
          port: 80
        route:
        - destination:
            host: test.com
            port:
              number: 443  # 只修改了这一行
          weight: 100

    这里的更新只是将发送给test.comServiceEntry80端口的流量改为了443端口。

  2. 导入mTLS证书,即在ASM入口网关上配置mTLS服务并限制特定客户端访问中使用的证书。请确保此处导入的证书名称为test.client。具体操作,请参见使用ASM证书管理。您也可以使用kubectl直接创建secret完成证书导入。使用ACK集群的kubeconfig,执行以下命令。

    kubectl create -n istio-system secret generic test.client \
      --from-file=tls.key=client.key.pem \
      --from-file=tls.crt=clientcert.pem \
      --from-file=ca.crt=cacert.pem
  3. 使用以下内容,创建目标规则。

    apiVersion: networking.istio.io/v1alpha3
    kind: DestinationRule
    metadata:
      name: originate-mtls-for-test-com
    spec:
      host: test.com
      trafficPolicy:
        loadBalancer:
          simple: ROUND_ROBIN
        portLevelSettings:
        - port:
            number: 443
          tls:
            mode: MUTUAL
            credentialName: test.client
            sni: test.com
  4. 执行以下命令,再次测试访问。

    kubectl exec deployment/sleep -it -- curl --header "host:test.com" ${ASM网关IP}/status/418

    预期输出:

    RBAC: access denied%

    可以看到请求被拒绝。这是由于这里复用了在ASM入口网关上配置mTLS服务并限制特定客户端访问中的客户端证书,其中配置了禁止test.client访问status/418路径。

  5. 执行以下命令,访问status/200路径。

    kubectl exec deploy/sleep -it -- curl --header "host:test.com" ${ASM网关IP}/status/200 -I

    预期输出:

    HTTP/1.1 200 OK
    server: envoy
    date: Mon, 29 Jul 2024 03:33:50 GMT
    content-type: text/html; charset=utf-8
    access-control-allow-origin: *
    access-control-allow-credentials: true
    content-length: 0
    x-envoy-upstream-service-time: 5

    可以看到访问正常。

  6. 执行以下命令,查看出口网关访问日志。

    kubectl -n istio-system logs ${出口网关pod名称}| tail -1

    预期输出:

    {"authority_for":"test.com","bytes_received":"0","bytes_sent":"19","downstream_local_address":"192.168.36.32:80","downstream_remote_address":"192.168.36.29:58146","duration":"3","istio_policy_status":"-","method":"GET","path":"/status/418","protocol":"HTTP/1.1","request_id":"82394ec4-cf13-45cb-ae4c-cb1873fbccda","requested_server_name":"outbound_.80_._.istio-egressgateway.istio-system.svc.cluster.local","response_code":"403","response_flags":"-","route_name":"-","start_time":"2024-07-29T03:34:36.647Z","trace_id":"-","upstream_cluster":"outbound|443||test.com","upstream_host":"${ASM网关IP}:443","upstream_local_address":"192.168.36.32:42214","upstream_response_time":"2","upstream_service_time":"2","upstream_transport_failure_reason":"-","user_agent":"curl/8.1.2","x_forwarded_for":"192.168.36.29"}

    可以看到这条请求访问了入口网关的443端口,这个端口上只提供mTLS服务。

步骤五:后续配置

sleep Pod发起的是HTTP请求,在经过Sidecar和出口网关之后,全链路均使用了mTLS加密。mTLS加密是执行客户端身份认证的基础,在请求链路中,您可以在两个地方配置授权策略,限制客户端行为。

  1. 使用以下内容,在出口网关上配置授权策略,这可以限制集群内哪些服务可以访问test.com服务。

    apiVersion: security.istio.io/v1beta1
    kind: AuthorizationPolicy
    metadata:
      labels:
        gateway: egressgateway
      name: test
      namespace: istio-system
    spec:
      action: DENY
      rules:
        - from:
            - source:
                principals:
                  - cluster.local/ns/default/sa/sleep
          to:
            - operation:
                hosts:
                  - test.com
                paths:
                  - /headers
      selector:
        matchLabels:
          istio: egressgateway

    此配置限制了sleep Pod通过出口网关访问test.com/headers路径。

  2. 在入口网关上配置授权策略,可以限制直接访问入口网关的客户端身份。在步骤四中,由于之前在入口网关上配置的授权策略,使得当前出口网关无法访问/status/418路径。