【问题标题】:How to communicate with gRPC server hosted on Google Compute Engine over SSL/TSL?如何通过 SSL/TLS 与托管在 Google Compute Engine 上的 gRPC 服务器通信?
【发布时间】:2020-07-13 02:55:04
【问题描述】:

上下文

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

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

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

到目前为止有什么效果?

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

  • 通过安全 SSL/TLS 通道,以防我在本地 Windows 10 计算机上自托管服务器;和

  • 通过不安全的通道,以防我如上所述在GCE 上托管服务器

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

# 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 命令。对于公用名,我使用的是 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 需要更改的内容。这也意味着与我上面描述的相比,我没有更改“后端”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 容器?

【问题讨论】:

  • 作为定位问题的初始诊断步骤:端口 GCE_VM_IP_Address:443 是否可以从客户端 VM 获得?例如,客户端 VM 上的命令 telnet GCE_VM_IP_Address 443 的输出是什么?
  • @mebius99,虽然我没有使用 telnet 测试连接性,但我成功地运行了 OpenAPI echo-api 示例,并在同一个 GCE VM 上启用了 SSL。我用curl 对其进行了测试,使用像https://[GCE_VM_IP_Address]:443/echo?key=[API_KEY]这样的URL

标签: ssl google-cloud-platform google-compute-engine grpc grpc-java


【解决方案1】:

根据 Google 小组中的 Wayne Zhang 在 Google Cloud Endpoints 上关于为 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 客户端)和配置(用于 gRPC 服务器和启用 SSL 的 ESP 的 Docker 命令)中的所有其他内容都是正确的。 nginx.crtnginx.key 这两个文件必须转移到 GCE VM 并存储在 Docker 命令中挂载的文件夹 /etc/nginx/ssl 中。

【讨论】:

    猜你喜欢
    • 2021-12-25
    • 2015-02-19
    • 2013-10-09
    • 2023-03-04
    • 2020-09-02
    • 1970-01-01
    • 1970-01-01
    • 2020-11-12
    • 2013-11-14
    相关资源
    最近更新 更多