Google Cloud Auth for Internal CLI using Cloud Run



我们使用Cloud Run构建了多个服务。我们的目标是构建一个内部 CLI,允许我们的开发人员调用这些服务。我们在生成与位于 Cloud Run 服务前面的身份感知代理一起使用的id_token时遇到问题。

根据文档,可以使用gcloudgcloud auth print-identity-token命令调用 Cloud Run 服务。这很好用。这也避免了下载服务帐户凭据并将其传递给我们的开发人员,因为此方法利用了应用程序默认凭据。

我们尝试过在 Go 中实现一些东西来复制这个print-identity-token功能,但没有成功。生成的id_token返回 401 到我们所有的云运行 API。生成令牌的示例代码:

func GetIDToken() string {
ctx := context.Background()
tokenSource, err := google.DefaultTokenSource(ctx, "openid", "email")
if err != nil {
log.Fatal(err)
}
token, err := tokenSource.Token()
if err != nil {
log.Fatal(err)
}
return fmt.Sprintf("%v", token.Extra("id_token"))
}

这将返回一个id_token但它不适用于 API。根据文档,范围似乎是正确的。

这给我们留下了两个问题:

  1. 这是为 IAP 生成 Id 令牌的正确方法吗?
  2. 有没有更好的方法来实现我们的开发人员对这些内部 API 的身份验证?

目前还不清楚你为什么要重新实现gcloud auth print-identity-token。我强烈建议你不要试图这样做。

如果要构建开发人员 CLI,则在开发计算机中gcloud并调用此命令以检索id_token,则工作将容易得多。

重新实施print-identity-token确实会很困难。因为gcloud使用它存储的内部refresh_token调用 OAuth 令牌端点以获取id_tokenaccess_token。(使用--log-http运行命令以查看更多信息,并将相关的 gcloud 配置设置为不编辑来自 req/resp 正文的令牌。

或者,如果没有开发人员计算机上的 GSuite 账户gcloud并进行身份验证,您唯一的其他选择是将IAM 服务账户密钥分发到每台开发人员计算机。(另一方面,您需要确保这些密钥正确轮换并保持安全。

您可以使用 IAM 服务账户密钥使具有正确字段(如 client_secret、grant_type 等(的 POST tohttps://www.googleapis.com/oauth2/v4/token获取id_token。我相信最简单的方法是将 Go 程序中GOOGLE_APPLICATION_CREDENTIALSenv 变量设置为密钥文件,在令牌交换请求中指定正确的audience参数,然后像您所做的那样使用token.Extra("id_token")

旁白:我还注意到您的身份字段在您的示例代码中未设置为云运行服务的 URL。

此答案用于从服务帐户创建身份令牌。这个例子是在 Python 中。如果需要,我会用 Go 写这个。 我刚刚用Python编写了这段代码。

在下面的代码中,第一个代码块是获取服务帐号并从 Google 请求身份令牌的部分。请注意,我没有使用任何作用域。范围在请求 Google OAuth 访问令牌时使用。标识令牌中存储了标识。相反,您需要指定标识令牌的目标受众 (URL(。并非所有服务都需要有效的受众群体值。

我的代码还演示了如何解码标识令牌以查看标头和有效负载 JSON。有效负载包含谷歌IAP验证的身份。

拥有身份令牌后,请在向 Cloud Run 发出请求时包含 HTTP 标头authorization: bearer TOKEN

import google.auth.transport.requests
import google.oauth2.service_account
credentials = google.oauth2.service_account.IDTokenCredentials.from_service_account_file(
json_filename,
target_audience=aud)
request = google.auth.transport.requests.Request()
credentials.refresh(request)
  1. 更改服务帐户的路径以匹配您的软件。
  2. aud更改为云运行 URL。

完整源代码示例:

'''
This program creates an OIDC Identity Token from a service account
'''
import json
import base64
import google.auth.transport.requests
import google.oauth2.service_account
# The service account JSON key file to use to create the Identity Token
json_filename = '/config/service-account.json'
# The audience that this ID token is intended for (example Google Cloud Run service URL)
aud = 'http://localhost'
def pad(data):
""" pad base64 string """
missing_padding = len(data) % 4
data += '=' * (4 - missing_padding)
return data
def print_jwt(signed_jwt):
""" Print a JWT Header and Payload """
s = signed_jwt.decode('utf-8').split('.')
print('Header:')
h = base64.urlsafe_b64decode(pad(s[0])).decode('utf-8')
print(json.dumps(json.loads(h), indent=4))
print('Payload:')
p = base64.urlsafe_b64decode(pad(s[1])).decode('utf-8')
print(json.dumps(json.loads(p), indent=4))
if __name__ == '__main__':
credentials = google.oauth2.service_account.IDTokenCredentials.from_service_account_file(
json_filename,
target_audience=aud)
request = google.auth.transport.requests.Request()
credentials.refresh(request)
#print(dir(credentials))
# This is debug code to show how to decode Identity Token
print('Decoded Identity Token:')
print_jwt(credentials.token.encode())
# This is the actual Identity Token
print()
print('Identity Token:')
print(credentials.token)

最新更新