在 GKE 中使用代理身份进行身份验证

具有代理身份的 Google Kubernetes Engine (GKE) 代理可以使用该身份向 Google Cloud API 以及外部工具和服务进行身份验证。代理可以使用自己的身份,也可以代表最终用户执行操作。本文档向代理应用开发者展示了如何配置其应用以向各种资源进行身份验证。您应当已经熟悉如何为 GKE 代理请求代理身份。

根据代理需要访问的资源,平台管理员可能需要配置身份验证管理器凭据保险库,以运行其他工作流。例如,如果代理要代表最终用户向 GitHub 进行身份验证,则身份验证管理器中的三方 OAuth 身份验证提供程序必须处理用户登录、授权和重定向。作为开发者,您需要修改代理,使其能够调用正确的身份验证提供方,并处理最终用户的对话恢复。

限制

  • 请参阅代理身份限制。
  • 您只能使用 Google 身份验证库获取绑定访问令牌和 ID 令牌(仅限 Python)。身份验证库可能无法获取其他语言的绑定令牌。如果您使用的是其他语言,请将 GOOGLE_API_ENABLE_RUNTIME_BOUND_TOKEN 环境变量设置为 false,以切换到无绑定令牌。

准备工作

在开始之前,请确保您已执行以下任务:

  • 启用 Google Kubernetes Engine API。
  • 启用 Google Kubernetes Engine API
  • 如果您要使用 Google Cloud CLI 执行此任务,请安装并初始化 gcloud CLI。如果您之前安装了 gcloud CLI,请通过运行 gcloud components update 命令来获取最新版本。较早版本的 gcloud CLI 可能不支持运行本文档中的命令。

所需的角色

如需获得在 GKE 集群中配置已部署的代理所需的权限,请让您的管理员为您授予项目的 Kubernetes Engine Developer (roles/container.developer) IAM 角色。 如需详细了解如何授予角色,请参阅管理对项目、文件夹和组织的访问权限。

您也可以通过自定义角色或其他预定义角色来获取所需的权限。

向 Google Cloud API 进行身份验证

如需以代理的自有身份向 Google Cloud API 进行身份验证,代理可以使用节点上元数据服务器中的代理身份访问令牌。您可能需要对代码进行的更改取决于您调用 Google Cloud API 的方式,如下所示。

使用 Cloud 客户端库

如果您使用的 Cloud 客户端库的版本包含 google-auth 库的 2.61.0 版或更高版本,则应用默认凭据 (ADC) 会自动获取代理身份访问令牌。您无需对代码进行其他更改。如果您通过设置 iam.gke.io/inject-podcertificates: "true" 注解为 Pod 启用证书注入,则访问令牌默认绑定到 X.509 证书,除非您停用绑定令牌。

使用 Cloud 客户端库时,如需获取无绑定访问令牌,请执行以下任一操作:

  • 在 Pod 中启用证书注入,并将 GOOGLE_API_ENABLE_RUNTIME_BOUND_TOKEN 环境变量设置为 false。
  • 请勿在 Pod 中启用证书注入。

直接调用 Google Cloud API 端点

如果您不使用 Cloud 客户端库与服务进行交互,则可以通过执行以下操作,使用代理的身份向 Google Cloud API 进行身份验证:

  1. 从节点上的元数据服务器获取访问令牌。您可以使用以下方法之一获取令牌:

    • 绑定访问令牌:使用 google-auth Python 库,该库会发现 Pod 的 X.509 证书,并默认自动获取绑定访问令牌。对于其他编程语言,请使用无界令牌。

    • 无绑定访问令牌:如果 Pod 没有代理身份凭据软件包,请使用适用于您的编程语言的 Google 身份验证库。身份验证库会自动获取未绑定的访问令牌,并为您刷新即将过期的令牌。对于具有凭据软件包的 Pod 中的 Python 应用,请在 Pod 规范中将 GOOGLE_API_ENABLE_RUNTIME_BOUND_TOKEN 环境变量设置为 false,如以下示例所示:

      # Multiple lines are omitted here.
      spec:
        containers:
        - name: example-agent
          image: example-image
          env:
          - name: GOOGLE_API_ENABLE_RUNTIME_BOUND_TOKEN
            value: "false"
      # Multiple lines are omitted here.
      

      此环境变量可防止库获取绑定访问令牌和 ID 令牌。

  2. 对于绑定访问令牌,请将请求发送到 API 的 mTLS 端点,并在 HTTP 传输中包含代理身份 X.509 证书链。如果您使用 Python 版 Google 身份验证库,则该库会为您处理 HTTP 传输配置。

以下示例演示了如何使用适用于 Python 的 Google 身份验证库获取绑定访问令牌,并向 Cloud Storage mTLS 端点发出请求:

import google.auth
from google.auth.transport.requests import AuthorizedSession


def call_storage_api_mtls(bucket_name: str) -> None:
    # Discover the Pod's X.509 certificate chain by using the auth library
    credentials, project = google.auth.default(
        scopes=["https://www.googleapis.com/auth/cloud-platform"]
    )
    # Configure the mTLS session by using the Pod's certificate chain
    session = AuthorizedSession(credentials)
    session.configure_mtls_channel()
    # Call the Google Cloud mTLS endpoint
    mtls_url = f"https://storage.mtls.googleapis.com/storage/v1/b/{bucket_name}/o"
    response = session.get(mtls_url)
    response.raise_for_status()
    print(response.json())

向外部工具和服务进行身份验证

如需向外部工具和服务进行身份验证,您可以配置智能体以从 Agent Identity 身份验证管理器获取所需的凭据。平台管理员在身份验证管理器中配置各种身份验证提供程序,每个提供程序管理特定的身份验证工作流和凭据。您修改应用代码以调用特定的身份验证提供方,并根据身份验证工作流处理用户同意情况和对话恢复。您对代理进行的具体更改取决于您需要访问的内容,如下所示:

身份验证管理器会处理相应的身份验证工作流,并授予代理对加密凭据的访问权限,然后可以将这些凭据包含在对外部服务的请求中。如需详细了解平台管理员必须执行哪些操作才能配置这些身份验证提供方并向代理身份授予访问权限,请参阅代理的身份验证工作流。

向其他代理进行身份验证

在多智能体架构中,智能体通常通过直接调用对等智能体或下游服务来协作。您可以使用身份令牌在代理工作负载之间建立直接通信。您可以从 GKE 元数据服务器获取绑定或未绑定的 ID 令牌,并使用该令牌直接向其他代理进行身份验证。

如需获取 ID 令牌并在 HTTP 请求中使用该令牌,请使用 Python 版 Google 身份验证库。该库会自动处理证书发现和 ID 令牌获取。如果您使用其他编程语言,则 Google 身份验证库可能无法获取绑定 ID 令牌。请改为使用不绑定 ID 令牌。

获取 ID 令牌

如需在代理代码中请求 ID 令牌,请使用适用于您的编程语言的 Google 身份验证库。您可以使用该库请求绑定或未绑定的 ID 令牌,如下所示:

  • 绑定 ID 令牌:使用 iam.gke.io/inject-podcertificates: "true" 注解为您的 Pod 启用证书注入。适用于 Python 的身份验证库会自动从 GKE 元数据服务器请求与证书绑定的 ID 令牌。在通过 mTLS 在 Google Cloud 上运行的代理之间进行身份验证时,请使用绑定 ID 令牌。
  • 非绑定 ID 令牌:

    • 为您的 Pod 启用证书注入,然后执行以下操作之一:
      • 在应用代码的 id_token.fetch_id_token 函数中,将 bind_id_token 实参设置为值 False。此实参会导致身份验证库请求未绑定的 ID 令牌。访问令牌请求不受影响。
      • 在 Pod 规范中,将 GOOGLE_API_ENABLE_RUNTIME_BOUND_TOKEN 环境变量设置为 false。此环境变量可防止库请求绑定访问令牌和 ID 令牌。
    • 请勿为您的 Pod 启用证书注入。身份验证库会获取未绑定的 ID 令牌,因为 Pod 中没有凭据软件包。

    当您使用非 mTLS 连接向 Google Cloud API、外部服务或其他代理进行身份验证时,请使用非绑定 ID 令牌。

以下示例展示了如何为已启用凭据注入的代理请求绑定或未绑定的 ID 令牌:

  • 请求绑定 ID 令牌:

    import google.auth.transport.requests
    from google.oauth2 import id_token
    
    # Application Default Credentials automatically requests a certificate-bound
    # ID token.
    def get_bound_id_token(target_audience: str) -> str:
        auth_req = google.auth.transport.requests.Request()
        return id_token.fetch_id_token(auth_req, audience=target_audience)
    

    绑定身份令牌在 cnf.x5t#S256 参数中包含 Pod 的 X.509 证书链的 SHA-256 证书指纹。

  • 请求无绑定 ID 令牌:

    import google.auth.transport.requests
    from google.oauth2 import id_token
    
    def get_unbound_id_token(target_audience: str) -> str:
        auth_req = google.auth.transport.requests.Request()
        return id_token.fetch_id_token(
            auth_req,
            audience=target_audience,
            # Always get an unbound ID token, even if the Pod has a credential
            # bundle.
            bind_id_token=False,
        )
    

在向其他代理发出的请求中使用 ID 令牌

为代理获取 ID 令牌后,您可以使用该令牌直接向另一个代理进行身份验证。您如何对连接进行身份验证取决于您是否使用绑定 ID 令牌,如下所示:

  • 对于绑定 ID 令牌,请与接收代理建立 mTLS 连接,并使用 Pod 中 /var/run/secrets/workload-spiffe-credentials/ 目录中的以下两种凭据对连接进行身份验证:
    • x509.credential-bundle.private-key.pem 文件中的代理身份凭据软件包,其中包含 Pod 的叶证书链。
    • TRUST_DOMAIN.spiffe-trust-bundle.pem 文件中的集群信任软件包。 此文件包含接收代理的根 CA 证书,用于在 mTLS 握手期间验证接收代理的证书链。发起方代理和接收方代理必须位于同一代理身份池中。
  • 对于未绑定的 ID 令牌,与接收代理建立非 mTLS 连接。

以下示例展示了如何使用绑定或未绑定的 ID 令牌向其他代理发送请求:

  • 绑定 ID 令牌:将绑定身份令牌添加到您发送给接收方代理的 mTLS 端点的请求的 Authorization: Bearer 标头中。使用 Pod 的 X.509 证书和私钥对 TLS 连接进行身份验证:

    import ssl
    import urllib3
    
    BUNDLE_PATH = "/var/run/secrets/workload-spiffe-credentials/x509.credential-bundle.private-key.pem"
    TRUST_BUNDLE_PATH = "/var/run/secrets/workload-spiffe-credentials/TRUST_DOMAIN.spiffe-trust-bundle.pem"
    
    def call_peer_agent_bound_mtls(target_mtls_url: str, target_audience: str) -> None:
    
        # Configure the mTLS context by using the certificate chain and trust
        # bundle from the Pod.
        ctx = ssl.create_default_context(cafile=TRUST_BUNDLE_PATH)
        ctx.load_cert_chain(BUNDLE_PATH)
        http = urllib3.PoolManager(ssl_context=ctx, assert_hostname=False)
    
        # Call a peer agent's mTLS endpoint by using the bound ID token and Pod
        # certificate chain.
        bound_id_token = get_bound_id_token(target_audience)
        response = http.request(
            "POST",
            target_mtls_url,
            headers={"Authorization": f"Bearer {bound_id_token}"},
            json={"task": "analyze_data"},
            timeout=10,
        )
        print(response.json())
    

    将 TRUST_DOMAIN 替换为代理身份池的信任域。

  • 未绑定 ID 令牌:将令牌添加到对对等代理的请求的 Authorization: Bearer 标头中:

    import requests
    
    def call_peer_agent_unbound(target_url: str, target_audience: str) -> None:
        unbound_id_token = get_unbound_id_token(target_audience)
        # Send the request by using a standard TLS connection or plain HTTP.
        response = requests.post(
            target_url,
            headers={"Authorization": f"Bearer {unbound_id_token}"},
            json={"task": "analyze_data"},
            timeout=10,
        )
        response.raise_for_status()
        print(response.json())
    

验证接收代理中的请求

在接收代理中,通过执行以下操作来验证传入请求中的 ID 令牌。您可以使用 Tink 等加密库来执行这些验证步骤,而无需编写自定义代码。

  1. 从请求 Authorization: Bearer 标头中提取身份令牌。
  2. 验证 ID 令牌中的 iss(颁发者)声明是否为调用代理的代理身份池。签发者是以下其中一项,具体取决于调用代理是否位于组织中的项目中:
    • 组织中的项目:https://sts.googleapis.com/v1/organizations/ORGANIZATION_ID/locations/global/workloadIdentityPools/agents.global.org-ORGANIZATION_ID.system.id.goog,其中 ORGANIZATION_ID 是包含调用代理的项目所属组织的组织 ID。
    • 不属于组织的项目:https://sts.googleapis.com/v1/projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/agents.global.proj-PROJECT_NUMBER.system.id.goog,其中 PROJECT_NUMBER 是调用代理的 GKE 集群的项目编号。
  3. 发现颁发者的 JSON Web 密钥集 (JWKS) 的 URI,并缓存公共 JSON Web 密钥 (JWK)。JWKS 的端点采用 ISSUER_URL/openid/jwks 格式,其中 ISSUER_URL 是提供方网址。
  4. 使用 ID 令牌的 JOSE 标头中的以下信息验证令牌签名:
    • 与 kid 标头参数匹配的公开 JWK。
    • 与 alg 标头参数匹配的加密算法,例如 RS256。
  5. 验证 cnf.x5t#S256 参数中的 SHA-256 证书指纹是否与调用代理用于对 mTLS 连接进行身份验证的 X.509 证书的指纹一致。
  6. 验证 ID 令牌中的以下声明:
    • exp 声明中的失效时间是未来的时间。
    • aud 声明中的受众群体是接收代理。
  7. 根据令牌的 sub(主题)声明中的 SPIFFE ID 授权请求。

后续步骤