如何从 GoogleCredentials 获取 projectId?

How do I get projectId from GoogleCredentials?

使用 Python 我想获取 Google 云上所有 Dataproc 集群的列表。

我将服务帐户凭据存储在 JSON 密钥文件中,其位置由环境变量 GOOGLE_APPLICATION_CREDENTIALS 引用。这是我到目前为止的代码:

import os
import googleapiclient.discovery
from oauth2client.client import GoogleCredentials


def build_dataproc_service(credentials):
    return googleapiclient.discovery.build("dataproc", "v1", credentials=credentials)


def list_clusters():
    credentials = GoogleCredentials.get_application_default()
    dataproc = build_dataproc_service(credentials)
    clusters = dataproc.projects().regions().clusters().list(projectId="my-project", region="REGION").execute()
    return clusters


if __name__ == "__main__":
    list_clusters()

如您所见,我已经对 projectId ("my-project") 进行了硬编码。鉴于 projectId 存在于 JSON 密钥文件中,我希望我可以通过询问 credentials 对象的 属性 来获得它,但不存在这样的 属性 。 projectId 确实 嵌入在 credentials._service_account_email 字符串 属性 中,但从那里提取它很笨重而且感觉不对。

我想一定有更好的方法。如何获取服务账号所在项目的projectId?

请注意,最初我打算将此代码 运行 放在 Google Compute Engine 实例上的 docker 容器中,但是将来有一天我可能想要 运行 在 GKE 上。不确定这是否会影响答案。

我认为客户端代码不应该从 GoogleCredentials 中提取项目 ID。请参阅 API doc 中的此代码片段。

from googleapiclient.discovery import build
from oauth2client.client import GoogleCredentials

credentials = GoogleCredentials.get_application_default()
service = build('compute', 'v1', credentials=credentials)

PROJECT = 'bamboo-machine-422'
ZONE = 'us-central1-a'
request = service.instances().list(project=PROJECT, zone=ZONE)
response = request.execute()

print(response)

您或许可以从 credentials._service_account_email 推断出项目 ID,但这并不可靠。此外,项目 A 中的服务帐户未绑定到该项目,它可能还具有对其他项目的权限。

一种正式的思考方式是,虽然 projectId 有时是 服务帐户 的 属性,但 projectId 通常不是 属性长期存在的 凭据 。例如,想想您在 gcloud CLI 中使用的离线安装的个人凭证(如果有)与您的 Google account/email 地址相关联。该电子邮件身份不在 任何 云项目中,但可用于派生 GoogleCredential 对象。

从技术上讲,如果您想这样做 "properly" 您需要一个主服务帐户,该帐户有权 GET 拥有您计划的实际服务帐户的所有项目中的服务帐户描述使用,然后在服务帐户电子邮件地址上调用 IAM API 的 projects.serviceAccounts.get,而不是在 "credential" 对象上。那里的响应可以识别服务帐户所在的项目 ID。这相当于 gcloud 命令:

gcloud iam service-accounts describe my-service-account@projectid.iam.gserviceaccount.com

然而,正如 Dagang 所说,在长期 运行 开始烘烤时,通常会适得其反,假设服务帐户将仅用于其所在项目的操作。特别是,虽然 service account 资源本身存在于项目中,但它们通常以跨项目的方式使用。一种常见的操作模式是使用单个 GCP 项目来管理大量服务帐户,然后授予这些帐户对其他 GCP 项目中资源的各种细粒度访问权限。

服务帐户凭据 .json 包含 project_id,因此您可以:

# If this is running in a cloud function, then GCP_PROJECT should be defined
if 'GCP_PROJECT' in os.environ:
    project_id = os.environ['GCP_PROJECT']

# else if this is running locally then GOOGLE_APPLICATION_CREDENTIALS should be defined
elif 'GOOGLE_APPLICATION_CREDENTIALS' in os.environ:
    with open(os.environ['GOOGLE_APPLICATION_CREDENTIALS'], 'r') as fp:
        credentials = json.load(fp)
    project_id = credentials['project_id']
else:
    raise Exception('Failed to determine project_id')

从 UnderlyingCredential 获取 ProjectId

var credential = GoogleCredential.FromFile(googleCredentialPath);
var projectId = ((Google.Apis.Auth.OAuth2.ServiceAccountCredential)credential.UnderlyingCredential).ProjectId;

在 C# 中

var credentials = GoogleCredential.GetApplicationDefault();
var projectId = (credentials.UnderlyingCredential as dynamic).ProjectId as string;