从 ComputeEngineCredentials 获取 ServiceAccountCredentials 进行用户模拟

Get ServiceAccountCredentials from ComputeEngineCredentials to do user impersonation

我正在部署一个 Java API,它通过 Domain-widge Delegation 模拟用户来访问用户的日历。为此,我创建了一个服务帐户,完成了委派并赋予它正确的权限和访问用户日历的权​​限。

在本地开发时,我一直在使用 JSON 格式的服务帐户下载密钥,并使用 GOOGLE_APPLICATION_CREDENTIALS 环境变量指向它。在我的代码中,我使用 Java 客户端库创建这样的凭据:

@Bean
@Qualifier("userCredentials")
public GoogleCredentials impersonateCalendarOwner() throws IOException {
    final List<String> scopes = Collections.singletonList(CalendarScopes.CALENDAR);

    return  ((ServiceAccountCredentials) GoogleCredentials.getApplicationDefault())
            .toBuilder()
            .setServiceAccountUser(GOOGLE_CALENDARS_OWNER)
            .build()
            .createScoped(scopes);
}

这在本地工作正常,但是当 运行 在云端 运行 我得到:

nested exception is java.lang.ClassCastException: 
class com.google.auth.oauth2.ComputeEngineCredentials cannot be cast to 
class com.google.auth.oauth2.ServiceAccountCredentials

经过几个小时的调试,我想我终于明白了 getApplicationDefault 是如何工作的。我认为本地发生的事情是这样的:

  1. getApplicationDefault 首先查看环境变量 GOOGLE_APPLICATION_CREDENTIALS,因为它已设置,所以它会从该文件中读取凭据
  2. 因为该文件是一个服务帐户密钥文件,所以它创建了一个 ServiceAccountCredentials 实例,因此“转换”成功。

而在 Cloud 运行 中,它是这样发生的:

  1. getApplicationDefault 首先查看环境变量 GOOGLE_APPLICATION_CREDENTIALS,但在 Cloud 运行 上未设置
  2. 因此它回退到服务帐户身份,修订版是 运行,因为通过从元数据服务器获取凭据并将它们 returns 作为 ComputeEngineCredentials 的实例(因为元数据服务器不不要分发服务帐户的密钥)。
  3. 然后它尝试将 ComputeEngineCredentials 转换为 ServiceAccountCredentials,这显然不起作用。

所以现在我的问题仍然存在:

  1. 如何将一些 ComputeEngineCredentials 转换为 ServiceAccountCredentials?
  2. 是否有其他方法可以将凭据作为 ServiceAccountCredentials 的实例获取?
  3. 是否有其他不需要 ServiceAccountCredentials 实例的用户模拟方式?

我目前的想法是使用我获得的 ComputeEngineCredentials,在服务启动时从 Secret Manager 获取服务帐户 JSON 密钥文件,然后将该文件传递给 ServiceAccountCredentials.fromStream 但感觉就像一个额外的步骤,因为我将使用的 ComputeEngineCredentials 已经是我将为其获取密钥的同一服务帐户的凭据。

您需要将 Cloud 运行 服务告知 run as your service account:

gcloud run deploy --image IMAGE_URL --service-account SERVICE_ACCOUNT

实例元数据(元数据服务器)提供的凭据不包括签署请求所需的服务帐户私钥。

因此,您尝试使用的服务帐户模拟代码将不起作用。您将需要使用包含私钥的实际服务帐户 JSON。

我的建议是设置默认服务帐户,使其具有访问 Secret Manager 的角色。将服务帐户 JSON 密钥文件作为数据存储在 Secret Manager 中。在您的程序启动时获取服务帐户 JSON 内容并继续您的模拟代码。