传统的网络安全是基于边界的安全。通过将网络划分成外网和内网,在边界上部署防火墙,从而建立了一个基本假设——内网比外网安全。位于网络边界的前置代理服务器 (Nginx) 接收外部到达的加密 TLS 流量,将其解密为 HTTP 明文,然后再将流量转发到内网某个服务。在实践中,绝大多数内部服务都是以 HTTP 明文的方式通信。
随着云计算的兴起,跨云部署等场景正在逐步模糊网络安全边界。服务与服务通信不得不跨越互联网鸿沟,明文流量显而易见不再安全。
为了支持跨云部署场景下的 gRPC 服务调用,配置 mTLS 是必然的选择。笔者将在下文中详细描述整个配置方案。
下列工程使用 gRPC 及 grpc-spring-boot-starter
gRPC 端到端直连 mTLS 配置
首先为客户端与服务端准备必要的证书。证书链如下
这里不再详述如何签署证书,下文会提供自动化签署的方案
CA.crt | |
---|---|
server.crt | server.key |
client.crt | client.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
写在最后
这只是一个小小的尝试,还有颇多不足。实际未投入生产使用,仅仅只是为了实现测试环境与开发环境的跨云互联罢了。