保护连接

直到现在,我们还没有使我们的连接安全——我们使用了不安全的凭证。在 gRPC 中,我们可以使用 TLSmTLSATLS 连接。第一个使用单向认证,客户端可以验证服务器的身份;第二种是双向通信,服务器验证客户端的身份,客户端也验证服务器的身份;最后,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_libclient_lib 目标来完成。

首先,我们需要在 certs 文件夹中创建一个 BUILD.bazel 文件,内容如下:

exports_files([
  "server_cert.pem",
  "server_key.pem",
  "ca_cert.pem"
])

然后,在 server 目录下的 BUILD 文件中,我们可以像下面这样添加对 server_certserver_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 证书。我们还使用了自签名证书,但在生产环境中,这些证书应由证书颁发机构自动生成。最后,我们展示了如何创建 ServerOptionDialOption 来启用 gRPC 中的 TLS。