传统的网络安全是基于边界的安全。通过将网络划分成外网和内网,在边界上部署防火墙,从而建立了一个基本假设——内网比外网安全。位于网络边界的前置代理服务器 (Nginx) 接收外部到达的加密 TLS 流量,将其解密为 HTTP 明文,然后再将流量转发到内网某个服务。在实践中,绝大多数内部服务都是以 HTTP 明文的方式通信。

随着云计算的兴起,跨云部署等场景正在逐步模糊网络安全边界。服务与服务通信不得不跨越互联网鸿沟,明文流量显而易见不再安全。

为了支持跨云部署场景下的 gRPC 服务调用,配置 mTLS 是必然的选择。笔者将在下文中详细描述整个配置方案。

下列工程使用 gRPCgrpc-spring-boot-starter

gRPC 端到端直连 mTLS 配置

point-to-point mTLS

首先为客户端与服务端准备必要的证书。证书链如下

这里不再详述如何签署证书,下文会提供自动化签署的方案

Certificate Chain

CA.crt
server.crtserver.key
client.crtclient.key

为 gRPC 客户端与服务端做直连配置。

##### 服务端配置 #####
# 开启 TLS
grpc.server.security.enabled=true 
# TLS 服务端使用的证书(链)
grpc.server.security.certificateChain=file:/tmp/tls/server.crt
# TLS 服务端使用的私钥
grpc.server.security.privateKey=file:/tmp/tls/server.key
# 启用客户端身份认证,设置为 必须 (另有选项 NONE,OPTIONAL)
grpc.server.security.clientAuth=REQUIRE
# 配置用于认证客户端身份的可信证书集
grpc.server.security.trustCertCollection=file:/tmp/tls/ca.crt


##### 客户端配置 #####
# 配置服务端地址
grpc.client.__SERVER_NAME__.address=static://localhost:9090
# 配置通信类型(TLS or PLAINTEXT)
grpc.client.__SERVER_NAME__.negotiationType=TLS
# 配置用于认证服务端身份的可信证书集
grpc.client.__SERVER_NAME__.security.trustCertCollection=file:/tmp/tls/ca.crt
# 启用客户端身份认证
grpc.client.__SERVER_NAME__.security.clientAuthEnabled=true
# TLS 客户端使用的证书(链)
grpc.client.__SERVER_NAME__.security.certificateChain=file:/tmp/tls/client.crt
# TLS 客户端使用的私钥
grpc.client.__SERVER_NAME__.security.privateKey=file:/tmp/tls/client.key

构建 TLS 信道就这么简单。

但经过试验,密钥长度为 521 比特的 ECDSA 算法生成的证书无法很好的工作。

gRPC TLS 透传+负载均衡配置

典型的微服务部署中,通常会部署复数个 Server ,并前置一个负载均衡器。七层负载均衡器通常都提供 TLS 终止的功能。但眼下,Nginx 更应该作为四层路由,透出 TLS 流量。

ingress-nginx 已经提供了预定的选项。首先需要启用特性 --enable-ssl-passthrough ,再为需要透传 TLS 的 ingress 添加声明 nginx.ingress.kubernetes.io/ssl-passthrough

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/backend-protocol: GRPCS
    nginx.ingress.kubernetes.io/ssl-passthrough: "true"
  name: server-service
spec:
  rules:
  - host: server.ffutop.com
    http:
      paths:
      - backend:
          service:
            name: server-service
            port:
              number: 9090
        path: /
        pathType: Prefix
  tls:
  - hosts:
    - '*.ffutop.com'
    secretName: server-service-tls

自动化证书签署与配置

前两节为了快速验证方案的可行性,使用了静态签署的客户端与服务端证书,但这种方案显然无法满足生产使用的需要。

  • 自动化签署证书
  • 自动化分发证书
  • 为每种服务独立提供证书

cert-manager 提供了便捷的解决方案。为两个集群提供一个企业自签名的证书作为根证书,后续所有的证书都将由这个根证书签发。

关于 cert-manager 的部署与使用教程不再赘述,可以自行查找官方教程。

签署证书需要创建一个 Certificate 资源。

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: server-service
spec:
  commonName: server-service	# 通用名
  duration: 8760h0m0s				# 证书有效期
  issuerRef:
    group: cert-manager.io
    kind: ClusterIssuer
    name: selfsigned-ca
  privateKey:
    algorithm: ECDSA
    encoding: PKCS8
    size: 256
  dnsNames:
  - "*.ffutop.com"
  secretName: server-service-tls	# 证书作为 Secret 资源存储时的名字
  subject:
    organizations:
    - ffutop.com
  usages:						# 证书用途
  - signing
  - key encipherment
  - server auth			# 用于做服务端认证
  - client auth			# 用于做客户端认证

经过 cert-manager 的处理之后,就可以检索到特定名称的 Secret 资源。为了让 gRPC 服务能够直接使用这个证书,还需要将证书挂载到容器中。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: server-service
spec:
  replicas: 1
  template:
    spec:
      containers:
      - name: server-service
        image: registry.cn-hangzhou.aliyuncs.com/ffutop/server-service:latest
        imagePullPolicy: Always
        volumeMounts:
        - mountPath: /usr/local/tls/
          name: tls
          readOnly: true
      volumes:
      - name: tls
        secret:
          secretName: server-service-tls

写在最后

这只是一个小小的尝试,还有颇多不足。实际未投入生产使用,仅仅只是为了实现测试环境与开发环境的跨云互联罢了。