保护连接
直到现在,我们还没有使我们的连接安全——我们使用了不安全的凭证。在 gRPC 中,我们可以使用 TLS
、mTLS
和 ATLS
连接。第一个使用单向认证,客户端可以验证服务器的身份;第二种是双向通信,服务器验证客户端的身份,客户端也验证服务器的身份;最后,ATLS 类似于 TLS,但它是为 Google 的使用而设计和优化的。
如果你在处理小规模通信或使用 Google Cloud,mTLS 和 ATLS 是值得探索的。如果你对 mTLS 感兴趣,可以查看 gRPC-go GitHub 仓库中的 mTLS 文件夹: https://github.com/grpc/grpc-go/tree/master/examples/features/encryption/mTLS 。如果你想使用 ATLS,可以查看这个链接: https://grpc.io/docs/languages/go/alts/ 。 但是,在我们的例子中,我们将看到最常用的加密形式,即 TLS。
为此,我们需要创建一些自签名证书。当然,在生产环境中,这些证书将通过像 Let’s Encrypt 这样的工具自动创建。然而,一旦这些证书可用,整体设置是相同的。
为了简化,我们将从 gRPC-go 仓库的示例中下载这些证书。这些证书也可以在 chapter7
文件夹中的 certs
目录下找到。我们首先需要获取服务器证书及其密钥:
$ curl https://raw.githubusercontent.com/grpc/grpc-go/master/examples/data/x509/server_cert.pem --output server_cert.pem
$ curl https://raw.githubusercontent.com/grpc/grpc-go/master/examples/data/x509/server_key.pem --output server_key.pem
接着,我们需要获取 证书颁发机构(CA)证书:
$ curl https://raw.githubusercontent.com/grpc/grpc-go/master/examples/data/x509/ca_cert.pem --output ca_cert.pem
现在,我们可以从服务器端开始。我们将证书作为 ServerOption
添加,因为我们希望所有的调用都进行加密。为了创建证书,我们可以使用 gRPC 的凭证(credential
)包中的 NewServerTLSFromFile
函数,它会读取服务器证书和服务器密钥的两个文件:
func main() {
//...
creds, err := credentials.NewServerTLSFromFile("./certs/server_cert.pem", "./certs/server_key.pem")
if err != nil {
log.Fatalf("failed to create credentials: %v", err)
}
}
一旦创建了证书,我们可以使用 grpc.Creds
函数,它会创建一个 ServerOption
:
opts := []grpc.ServerOption{
grpc.Creds(creds),
//...
}
现在让我们看看运行服务器时会发生什么:
$ go run ./server 0.0.0.0:50051
listening at 0.0.0.0:50051
然后,我们运行客户端:
$ go run ./client 0.0.0.0:50051
--------ADD--------
rpc error: code = Unavailable desc = connection error: desc = "error reading server preface: EOF"
我们收到一个错误,基本上告诉我们客户端无法连接到服务器。为了解决这个问题,我们需要进入客户端,创建凭证的 DialOption
。
这次,我们将使用 NewClientTLSFromFile
函数,它接受 CA 证书。为了测试,我们会将主机 URL 作为第二个参数(证书的域名是 *.test.example.com
)。
creds, err := credentials.NewClientTLSFromFile("./certs/ca_cert.pem", "x.test.example.com")
if err != nil {
log.Fatalf("failed to load credentials: %v", err)
}
为了添加凭证,我们使用一个叫做 WithTransportCredentials
的函数,它会创建一个 DialOption
。
opts := []grpc.DialOption{
grpc.WithTransportCredentials(creds),
//grpc.WithTransportCredentials(insecure.NewCredentials()) // 去掉不安全的凭证
//...
}
注意,我们移除了不安全的凭证,因为现在我们希望加密通信。
现在让我们重新运行服务器:
$ go run ./server 0.0.0.0:50051
listening at 0.0.0.0:50051
然后,我们为客户端做同样的操作:
$ go run ./client 0.0.0.0:50051
一切顺利,我们应该可以通过所有之前通过的调用,但现在我们的通信是安全的。
Bazel
为了使用 Bazel 运行我们在本节中编写的代码,我们需要在我们的 BUILD 文件中包含证书文件。这可以通过导出证书文件并将其作为数据添加到 server_lib
和 client_lib
目标来完成。
首先,我们需要在 certs
文件夹中创建一个 BUILD.bazel
文件,内容如下:
exports_files([
"server_cert.pem",
"server_key.pem",
"ca_cert.pem"
])
然后,在 server
目录下的 BUILD 文件中,我们可以像下面这样添加对 server_cert
和 server_key
的依赖(在 server/BUILD.bazel
中):
go_library(
name = "server_lib",
//...
data = [
"//certs:server_cert.pem",
"//certs:server_key.pem",
],
//...
)
最后,我们可以在 client
中添加对 ca_cert
的依赖(在 client/BUILD.bazel
中):
go_library(
name = "client_lib",
//...
data = [
"//certs:ca_cert.pem",
],
//...
)
现在,你应该能够通过 Bazel 正确运行服务器和客户端,正如我们在前面的章节中所展示的那样。
总结一下,我们看到,为了在服务器端创建连接,我们需要一个服务器证书和一个服务器密钥文件,而在客户端,我们需要一个 CA 证书。我们还使用了自签名证书,但在生产环境中,这些证书应由证书颁发机构自动生成。最后,我们展示了如何创建 ServerOption
和 DialOption
来启用 gRPC 中的 TLS。