云 运行 - 通过服务帐户进行 gRPC 身份验证 - Java

Cloud Run - gRPC authentication through service account - Java

我已经部署到 Google 云 运行(完全托管)gRPC 服务器,选项 "Required Authentication" 设置为 true。

我正在尝试通过 Google 服务帐户对来自我的 gRPC 客户端的调用进行身份验证,但我总是遇到异常。

Exception in thread "main" io.grpc.StatusRuntimeException: UNAUTHENTICATED: HTTP status code 401

下面是我如何创建 gRPC 通道并附加服务帐户。

public GrpcClient(Channel channel) throws IOException {
    Credentials credentials = GoogleCredentials.getApplicationDefault();

    blockingStub = CalculatorServiceGrpc
            .newBlockingStub(channel)
            .withCallCredentials(MoreCallCredentials.from(credentials));
}

Obs.: env var GOOGLE_APPLICATION_CREDENTIALS 设置了 SA 的路径,并且 SA 具有 Cloud 运行 Invoker 权限

有什么我遗漏的吗?

从通用 HTTP 客户端调用云 运行 服务器时,设置 GOOGLE_APPLICATION_CREDENTIALS 无效。 (只有当您使用 Google 客户端库调用 Google 的 API 时才有效。

即使部署到云 运行,gRPC 也只是 HTTP/2,因此在 Service-to-Service Authentication 页面上记录了云 运行 服务的身份验证。简而言之,这涉及:

  • 从容器内的元数据服务端点获取 JWT(身份令牌)
  • 根据对云 运行 应用程序的请求将其设置为 header,如 Authorization: Bearer [[ID_TOKEN]]

在 gRPC 中,header 被称为 "metadata",因此您应该找到等效的 gRPC Java 方法来设置它。 (这可能是一个 per-RPC 选项。)

阅读有关 Go example here 的文章,它基本上向您解释了云 运行 上的 gRPC 服务器 运行 仍然以相同的方式进行身份验证。在这种情况下,还要确保告诉 Java:

  • 您需要连接到 domain:443(不是 :80)
  • gRPC Java 需要使用机器根 CA 证书来验证云 运行 提供的 TLS 证书的有效性(而不是跳过 TLS 验证)

经过更多研究后,我能够使用 IdTokenCredentials 对请求进行身份验证。结果见下方。

public GrpcClient(Channel channel) throws IOException {
    ServiceAccountCredentials saCreds = ServiceAccountCredentials
            .fromStream(new FileInputStream("path\to\sa"));

    IdTokenCredentials tokenCredential = IdTokenCredentials.newBuilder().setIdTokenProvider(saCreds)
            .setTargetAudience("https://{projectId}-{hash}-uc.a.run.app").build();

    blockingStub = CalculatorServiceGrpc
            .newBlockingStub(channel)
            .withCallCredentials(MoreCallCredentials.from(tokenCredential));
}

我在寻找与 Python 相关的答案时遇到了这个 post。 所以对于那些想使用 Python 客户端解决这个问题的人:

import google.oauth2
import google.oauth2.id_token
import google.auth
import google.auth.transport
import google.auth.transport.requests

TARGET_CHANNEL = "your-app-name.run.app:443"

token = google.oauth2.id_token.fetch_id_token(google.auth.transport.requests.Request(), TARGET_CHANNEL)
call_cred = grpc.access_token_call_credentials(auth.get_identification_token(
        "https://" + TARGET_CHANNEL.strip(':443')))
channel_cred = grpc.composite_channel_credentials(grpc.ssl_channel_credentials(), call_cred)
channel = grpc.secure_channel(TARGET_CHANNEL, credentials=channel_cred)`