如何通过 SSL/TSL 与 Google Compute Engine 上托管的 gRPC 服务器通信?

How to communicate with gRPC server hosted on Google Compute Engine over SSL/TSL?

上下文

我在Java 中开发了一个gRPC 服务器,并在C# 中开发了一个相应的gRPC 客户端。 objective 是从部署在 Windows 机器上的多个 gRPC 客户端调用 gRPC 服务器。

了解了 Azure、AWS 和 Google 云平台 (GCP) 如何支持 gRPC,我可能会在 GCP 上托管 gRPC 服务器。因此,我目前正在测试 gRPC on Compute Engine 教程中 Google 描述的 gRPC 服务器的部署方案。简而言之,这意味着 gRPC 服务器在 Google 计算引擎 (GCE) 虚拟机 (VM) 上的定制 Docker 容器中运行,就在可扩展服务代理 (ESP) 旁边,它在同一 VM 上运行在自己的预配置 Docker 容器中。

在生产中使用的一个重要方面是使用 SSL/TSL 在 gRPC 客户端和 gRPC 服务器之间建立安全通信通道的能力。这就是我在 云托管场景 中遇到问题的地方(但在自我托管场景中,这很好用)。

到目前为止有什么效果?

运行在我本地 Windows10 机器上的 gRPC 客户端与 gRPC 服务器通信成功:

我在 GCE VM 上发出了以下命令来创建 docker 容器,以便通过 不安全的通道 .[=24 成功进行客户端-服务器通信=]

# Create the container network.
sudo docker network create --driver bridge esp_net

# Create dss-signer container from docker image.
# The Java gRPC service listens on port 50051 (see esp container below).
sudo docker run \
--detach \
--name=dss-signer \
--net=esp_net \
gcr.io/[my-project-name]/dss-signer:1.1

# Create Extensible Service Proxy (ESP) container from predefined docker image.
# The ESP container's port 9000 is published as port 80 on the host machine,
# meaning a client will have to connect to port 80 on the host machine.
sudo docker run \
--detach \
--name=esp \
--publish 80:9000 \
--net esp_net \
gcr.io/endpoints-release/endpoints-runtime:1 \
--service=signer.endpoints.[my-project-name].cloud.goog \
--rollout_strategy=managed \
--http2_port=9000 \
--backend=grpc://dss-signer:50051

因此,我想说它一般都可以工作,并且 Java 或 C# 代码本身没有问题(除了与使其在 SSL/TLS 上工作相关的配置相关问题) ).

什么不起作用?

按照 Enabling SSL 上的描述,我未能在 gRPC 客户端和服务器之间建立安全通道。我的 C# 客户端总是抛出以下异常:

Grpc.Core.RpcException
  HResult=0x80131500
  Message=Status(StatusCode=Unavailable, Detail="failed to connect to all addresses")
  Source=mscorlib
  StackTrace:
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.ConfiguredTaskAwaitable.ConfiguredTaskAwaiter.GetResult()
   at SignerClient.AbstractSignatureHandler.<InitiateCall>d__4.MoveNext()

[Rest of stack trace removed as it did not contain any helpful hints.]

我尝试了什么?

这是用于创建服务器密钥和证书的 openssl 命令。对于 Common Name,我使用 Google Cloud Console 中显示的 GCE VM 的 IP 地址。我已将密钥和证书复制到 /etc/nginx/ssl 目录。

openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout ./nginx.key -out ./nginx.crt -subj "/O=[My Org]/OU=Servers/CN=[GCE VM IP Address]"

这是用于创建和启动 ESP docker 容器的 docker 命令,根据我的理解,这是启用 SSL/TLS 需要更改的内容。这也意味着与我上面描述的相比,我没有更改 "backend" gRPC 服务器。不确定是否正确。

sudo docker run \
--detach \
--name=esp \
--publish 443:443 \
--net esp_net \
--volume=/etc/nginx/ssl:/etc/nginx/ssl \
gcr.io/endpoints-release/endpoints-runtime:1 \
--service=signer.endpoints.[my-project-name].cloud.goog \
--rollout_strategy=managed \
--ssl_port=443 \
--backend=grpc://dss-signer:50051

这是用于在客户端设置安全通道的 C# 代码:

const string host = "[GCE VM IP Address]";
const int port = 443;

// Create the SSL credentials.
string caCertPem = File.ReadAllText("Certs\ca.cer");
string clientCertPem = File.ReadAllText("Certs\client.cer");
string clientKeyPem = File.ReadAllText("Certs\client.key");

var keyCertificatePair = new KeyCertificatePair(clientCertPem, clientKeyPem);
var sslCredentials = new SslCredentials(caCertPem, keyCertificatePair);

// Create a client that communicates over a secure channel.
var channel = new Channel(host, port, sslCredentials);

我还尝试了上述 docker 命令的变体,例如,使用不同的 SSL 端口 (8080) 或保留 http2_port 设置。不幸的是,没有任何效果。

因此,我该如何设置才能让 gRPC 客户端和 GCE 托管的服务器通过 SSL/TSL 安全通信?我是否需要以不同方式配置 C# 客户端和 Java 服务器?我需要如何配置 ESP docker 容器?

根据 Google Cloud Endpoints Google 组中 Wayne Zhang 的有用提示,为 gRPC 客户端启用 gRPC 日志记录以及与日志中报告的错误相关的更多研究,我发现回答我自己的问题。

如何在客户端启用 gRPC 日志?

为了在我的 Windows 10 机器上的客户端 运行 上启用 gRPC 日志记录,我将 GRPC_TRACEGRPC_VERBOSITY 环境变量设置如下:

set GRPC_TRACE=all
set GRPC_VERBOSITY=DEBUG

运行 命令提示符中的 gRPC 客户端产生了非常详细的输出,其中包含足够的信息来发现问题。

错误原因是什么?

我很惊讶地发现日志中出现 SSL 握手错误。这是因为按照 Google 的操作指南中所述创建的服务器证书不包含 subjectAltName X.509 扩展名。 .NET gRPC 客户端(用 C# 编写)似乎期望如此,尽管这在我的自托管场景中从来都不是问题。

Google 的操作指南也对此保持沉默,原因有二。首先,可扩展服务代理 (ESP) 文档主要关注 OpenAPI 而不是 gRPC。其次,它与语言无关,不会解决像这样的特定于语言的问题。

我是如何解决这个问题的?

在第一次尝试中,我创建了一个自签名的 X.509 证书,其公用名 (CN) 和 subjectAltName 扩展名都设置为我的 GCE VM 的 IP 地址。但是,这仍然不起作用,因为 .NET gRPC 客户端不接受自签名服务器证书(即主题和颁发者相同的证书)。因此,我必须创建一个服务器证书请求,然后在第二步中使用我的自签名根 CA 证书和密钥创建服务器证书。

所以,这是我创建 gRPC 服务器证书的方式:

REM Create GCE server certificate in PEM encoding.
REM Set ipAddress to whatever IP address the GCE VM is using.

set ipAddress=[GCE VM IP address]
set subject=/O=[MyOrganization]/OU=[MyOrganizationalUnit]/CN=%ipAddress%
set subjectAltNameConfig=subjectAltName = IP:%ipAddress%
echo %subjectAltNameConfig% > extfile.cfg

openssl req -newkey rsa:2048 -keyout Certs\nginx.key -nodes -out Certs\Requests\nginx.csr -subj "%subject%" -addext "%subjectAltNameConfig%"

REM Sign GCE server certificate.

openssl x509 -req -extfile extfile.cfg -in Certs\Requests\nginx.csr -CA Certs\ca.cer -CAkey Certs\ca.key -passin pass:[password] -days 365 -set_serial 01 -out Certs\nginx.crt

我的代码(Java gRPC 服务器、C# gRPC 客户端)和配置(Docker gRPC 服务器和启用 SSL 的 ESP 命令)中的所有其他内容都是正确的。 nginx.crtnginx.key这两个文件必须传输到GCE VM并存储在文件夹/etc/nginx/ssl中,该文件夹是在Docker命令中挂载的。