通过ASM网关配置基于mTLS的gRPC服务

借助ASM网关,您可以配置带有mTLS安全机制的gRPC服务,确保仅授权的客户端能够访问,并在整个数据传输过程中实施端到端加密和双向身份验证,有效防止信息被窃听、篡改及非法访问。

前提条件

背景信息

服务网格ASM的流量管理功能支持通过入口网关访问内部的gRPC服务。由于gRPC基于HTTP/2协议,所以也可以使用TLS/mLTS对传输数据进行加密,保障数据安全。ASM网关目前同样支持TLS/mTLS的gRPC协议,可以将加密的TCP数据流在网关处进行TLS终止,网格内的应用不需要再进行TLS配置。

操作步骤

步骤一:部署示例应用

  1. 登录容器服务管理控制台,在左侧导航栏选择集群

  2. 集群列表页面,单击目标集群名称,然后在左侧导航栏,选择工作负载 > 无状态

  3. 无状态页面顶部,命名空间右侧下拉列表中选择命名空间,单击使用YAML创建资源

  4. 创建页面,将如下YAML模板粘贴至模板文本框内,单击创建

    展开查看YAML

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: istio-grpc-server-v1
      labels:
        app: istio-grpc-server
        version: v1
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: istio-grpc-server
          version: v1
      template:
        metadata:
          labels:
            app: istio-grpc-server
            version: v1
        spec:
          containers:
          - args:
            - --address=0.0.0.0:8080
            image: registry.cn-hangzhou.aliyuncs.com/aliacs-app-catalog/istio-grpc-server
            imagePullPolicy: Always
            livenessProbe:
              exec:
                command:
                - /bin/grpc_health_probe
                - -addr=:8080
              initialDelaySeconds: 2
            name: istio-grpc-server
            ports:
            - containerPort: 8080
            readinessProbe:
              exec:
                command:
                - /bin/grpc_health_probe
                - -addr=:8080
              initialDelaySeconds: 2
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: istio-grpc-server
      labels:
        app: istio-grpc-server
    spec:
      ports:
      - name: grpc-backend
        port: 8080
        protocol: TCP
      selector:
        app: istio-grpc-server
      type: ClusterIP
    ---
    说明

    由于Istio的协议选择机制,此处Service配置中的ports字段的name必须以http2-或者grpc-开头,否则Istio无法正确识别服务协议。

步骤二:部署入口网关

本例中使用默认的443端口暴露服务。具体操作,请参见创建入口网关

步骤三:设置服务网格ASM的路由规则

  1. 登录ASM控制台,在左侧导航栏,选择服务网格 > 网格管理

  2. 网格管理页面,单击待配置实例的名称或者操作列中的管理

  3. 创建网关规则。

    1. 在网格详情页面左侧导航栏,选择ASM网关 > 网关规则

    2. 网关规则页面,单击使用YAML创建

    3. 创建页面,请选择命名空间default,将如下的YAML粘贴至文本框中,单击创建

      apiVersion: networking.istio.io/v1beta1
      kind: Gateway
      metadata:
        name: gw-grpc-443
        namespace: default
      spec:
        selector:
          istio: ingressgateway
        servers:
          - hosts:
              - '*'
            port:
              name: https
              number: 443
              protocol: HTTPS
            tls:
              credentialName: example-credential
              mode: MUTUAL
                                      
  4. 创建虚拟服务。

    1. 在网格详情页面左侧导航栏,选择流量管理中心 > 虚拟服务

    2. 虚拟服务页面,单击使用YAML创建

    3. 创建页面,请选择命名空间default,将如下的YAML粘贴至文本框中,单击创建

      apiVersion: networking.istio.io/v1alpha3
      kind: VirtualService
      metadata:
        name: grpc-vs
      spec:
        hosts:
        - "*"
        gateways:
        - gw-grpc-443
        http:
          - match:
              - port: 443
            route:
              - destination:
                  host: istio-grpc-server

步骤四:挂载证书

gRPC的客户端通常要求挂载SAN格式的证书。推荐您使用grpc-go示例的证书组。由于证书需要在ASM网关中使用,所以需要将证书配置在ACK集群中的istio-system命名空间下。

  • ASM实例为1.17以下

    执行以下命令,在istio-system命名空间创建Secret。

    kubectl create -n istio-system secret generic example-credential --from-file=tls.key=server_key.pem --from-file=tls.crt=server_cert.pem --from-file=ca.crt=client_ca_cert.pem
    说明

    Secret名称需要和网关规则中配置的credentialName相同。

  • ASM实例为1.17及以上

    1. 登录ASM控制台,在左侧导航栏,选择服务网格 > 网格管理

    2. 网格管理页面,单击目标实例名称,然后在左侧导航栏,选择ASM网关 > 证书管理

    3. 证书管理页面,单击创建,然后在证书信息面板,配置相关信息,单击确定

步骤五:运行gRPC客户端

本文使用gRPC-go的官网示例作为gRPC的Mtls客户端。

  1. 请参照gRPC-go官网示例安装gRPC依赖。更多内容,请参见gRPC-go

  2. 请参照gRPC-go官网示例克隆gRPC-go的代码库。更多内容,请参见GRPC-go代码

  3. 使用如下代码,覆盖/grpc-go/examples/helloworld/greeter_client/main.go文件,将address的值修改为${ASM网关地址}:443

    展开查看代码

    /*
     *
     * Copyright 2015 gRPC 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.
     *
     */
    
    // Package main implements a client for Greeter service.
    package main
    
    import (
        "context"
        "crypto/tls"
        "crypto/x509"
        "flag"
        "io/ioutil"
        "log"
        "time"
    
        "google.golang.org/grpc"
        "google.golang.org/grpc/credentials"
        pb "google.golang.org/grpc/examples/helloworld/helloworld"
    )
    
    const (
        defaultName = "world"
    )
    
    var (
        addr       = flag.String("addr", "localhost:50051", "the address to connect to")
        name       = flag.String("name", defaultName, "Name to greet")
        cert       = flag.String("cert", "./data/x509/client_cert.pem", "server cert for mTLS")
        key        = flag.String("key", "./data/x509/client_key.pem", "server key for mTLS")
        cacert     = flag.String("cacert", "./data/x509/ca_cert.pem", "ca cert for mTLS")
        servername = flag.String("servername", "x.test.example.com", "the cert name of server")
    )
    
    func main() {
        flag.Parse()
    
        certPair, err := tls.LoadX509KeyPair(*cert, *key)
        if err != nil {
            log.Fatalf("failed to load client cert: %v", err)
        }
    
        ca := x509.NewCertPool()
        caFilePath := *cacert
        caBytes, err := ioutil.ReadFile(caFilePath)
        if err != nil {
            log.Fatalf("failed to read ca cert %q: %v", caFilePath, err)
        }
        if ok := ca.AppendCertsFromPEM(caBytes); !ok {
            log.Fatalf("failed to parse %q", caFilePath)
        }
    
        tlsConfig := &tls.Config{
            ServerName:   *servername,
            Certificates: []tls.Certificate{certPair},
            RootCAs:      ca,
        }
    
        conn, err := grpc.Dial(*addr, grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig)))
        if err != nil {
            log.Fatalf("did not connect: %v", err)
        }
        defer conn.Close()
        c := pb.NewGreeterClient(conn)
    
        // Contact the server and print out its response.
        ctx, cancel := context.WithTimeout(context.Background(), time.Second)
        defer cancel()
        r, err := c.SayHello(ctx, &pb.HelloRequest{Name: *name})
        if err != nil {
            log.Fatalf("could not greet: %v", err)
        }
        log.Printf("Greeting: %s", r.GetMessage())
    }
                            
  4. 替换完成后,进入examples,执行如下命令。

    go run helloworld/greeter_client/main.go

    预期结果:

    Greeting:Hello World

    若返回结果为如下错误,则为证书错误,请您参照上述步骤,重新挂载证书。2