验证虚拟机身份


应用可以先使用 Google 签署的实例身份令牌来验证实例的身份,然后再将敏感信息发送给虚拟机 (VM) 实例。每个实例都有一个唯一的 JSON 网络令牌 (JWT),其中包含有关该实例的详情以及 Google 的 RS256 签名。您的应用可以根据 Google 的公共 Oauth2 证书验证签名,以确认已与其建立连接的实例的身份。

仅当实例通过实例元数据请求已签署的实例令牌时,Compute Engine 才会生成这些令牌。实例只能访问自己的唯一令牌,而不能访问任何其他实例的令牌。

在以下情况下,您可能需要验证实例的身份:

  • 当您首次启动实例时,您的应用可能需要确保其连接的实例拥有有效的身份,然后才会向该实例传输敏感信息。
  • 当您的政策要求您在 Compute Engine 环境外存储凭据,并且您需要定期将这些凭据发送给您的实例以便临时使用时。您的应用可以在每次需要传输凭据时确认实例的身份。

Google 的实例身份验证方法具有以下优点:

  • 每次实例请求令牌时,Compute Engine 都会创建一个唯一令牌,并且每个令牌会在一个小时内过期。您可以将应用配置为仅接受一次实例的身份令牌,这样可以降低令牌被未经授权的系统重新使用的风险。
  • 带有签名的元数据令牌采用的是 RFC 7519 开放式行业标准和 OpenID Connect 1.0 身份层,因此现有的工具和库将与身份令牌无缝搭配使用。

准备工作

  • 了解如何检索实例元数据值
  • 学习 JSON 网络令牌的基础知识,以便您了解如何在应用中使用此类令牌。
  • 了解如何在您的实例上创建和启用服务账号。您的实例必须拥有与其关联的服务账号,以便可以检索其身份令牌。请注意,该服务账号不需要任何 IAM 权限来检索这些身份令牌。
  • 设置身份验证(如果尚未设置)。身份验证是通过其进行身份验证以访问 Google Cloud 服务和 API 的过程。如需从本地开发环境运行代码或示例,您可以按如下方式向 Compute Engine 进行身份验证。

    如需从本地开发环境使用本页面上的 Python 示例,请安装并初始化 gcloud CLI,然后使用用户凭据设置应用默认凭据。

    1. 安装 Google Cloud CLI。
    2. 如需初始化 gcloud CLI,请运行以下命令:

      gcloud init
    3. 为您的 Google 账号创建本地身份验证凭据:

      gcloud auth application-default login

    如需了解详情,请参阅 为本地开发环境设置身份验证

验证实例的身份

在某些情况下,您的应用必须验证在 Compute Engine 上运行的实例的身份,然后才能向该实例传输敏感数据。在一个典型示例中,有一个在 Compute Engine 之外运行并且名为 “Host1” 的系统和一个名为 “VM1” 的 Compute Engine 实例。其中,VM1 可以连接到 Host1 并使用以下过程验证该实例的身份:

  1. VM1 通过您选择的安全连接协议(如 HTTPS)与 Host1 建立安全连接。

  2. VM1 向元数据服务器请求其唯一的身份令牌并指定令牌的受众群体。在本例中,对于 Host1,受众群体值是 URI。向元数据服务器发出的请求包含受众群体 URI,以便 Host1 稍后可以在令牌验证步骤中检查相应的值。

  3. Google 会生成一个新的唯一实例身份令牌(使用 JWT 格式),并将其提供给 VM1。令牌的载荷包含有关实例的多项详情,并且还包含目标对象 URI。如需查看令牌内容的完整说明,请参阅令牌内容

  4. VM1 通过现有的安全连接将身份令牌发送给 Host1。

  5. Host1 对身份令牌进行解码以获取令牌标头和有效负载值。

  6. Host1 检查目标对象值并根据公共 Google 证书验证证书签名,以确认该令牌是否由 Google 签署

  7. 如果令牌有效,Host1 将继续传输并在传输完成后关闭连接。如果随后还需要与 VM1 建立任何连接,Host1 和任何其他系统应请求一个新的令牌。

获取实例身份令牌

当您的虚拟机实例收到提供其身份令牌的请求时,该实例将使用获取实例元数据的常规流程向元数据服务器请求该令牌。例如,您可以使用以下某种方法:

cURL

创建curl请求,并在 audience 参数中添加一个值。或者,您可以添加 format 参数来指定是否要在载荷中包含项目和实例详细信息。如果使用 full 格式,您可以添加 licenses 参数来指定是否要在载荷中包含许可代码。

curl -H "Metadata-Flavor: Google" \
'http://metadata/computeMetadata/v1/instance/service-accounts/default/identity?audience=AUDIENCE&format=FORMAT&licenses=LICENSES'

请替换以下内容:

  • AUDIENCE:由实例和验证实例身份的系统商定的唯一 URI。例如,受众群体可能是两个系统之间的连接的网址。
  • FORMAT:可选参数,用于指定项目和实例详情是否包含在载荷中。指定 full 可将此信息包含在载荷中,指定 standard 可在载荷中省略该信息。默认值为 standard。如需了解详情,请参阅身份令牌格式
  • LICENSES:可选参数,用于指定是否将与此实例关联的映像的许可代码包含在载荷中。指定 TRUE 可添加此信息;指定 FALSE 可在载荷中省略此信息。默认值为 FALSE。除非 formatfull,否则该值无效

元数据服务器通过使用 RS256 算法签署的 JSON 网络令牌响应此请求。该令牌包含 Google 签名和载荷中的其他信息。您可以将此令牌发送给其他系统和应用,以便其验证令牌并确认您的实例的身份。

Python

您可以使用 Python requests 库中的方法,将实例的简单请求提交到元数据服务器。以下示例向服务器请求了实例身份令牌,然后打印了该令牌。请注意,令牌对于发出此请求的实例是唯一的。

import requests

AUDIENCE_URL = "http://www.example.com"
METADATA_HEADERS = {"Metadata-Flavor": "Google"}
METADATA_VM_IDENTITY_URL = (
    "http://metadata.google.internal/computeMetadata/v1/"
    "instance/service-accounts/default/identity?"
    "audience={audience}&format={format}&licenses={licenses}"
)
FORMAT = "full"
LICENSES = "TRUE"

def acquire_token(
    audience: str = AUDIENCE_URL, format: str = "standard", licenses: bool = True
) -> str:
    """
    Requests identity information from the metadata server.

    Args:
        audience: the unique URI agreed upon by both the instance and the
            system verifying the instance's identity. For example, the audience
            could be a URL for the connection between the two systems.
        format: the optional parameter that specifies whether the project and
            instance details are included in the payload. Specify `full` to
            include this information in the payload or standard to omit the
            information from the payload. The default value is `standard`.
        licenses: an optional parameter that specifies whether license
            codes for images associated with this instance are included in the
            payload. Specify TRUE to include this information or FALSE to omit
            this information from the payload. The default value is FALSE.
            Has no effect unless format is `full`.

    Returns:
        A JSON Web Token signed using the RS256 algorithm. The token includes a
        Google signature and additional information in the payload. You can send
        this token to other systems and applications so that they can verify the
        token and confirm that the identity of your instance.
    """
    # Construct a URL with the audience and format.
    url = METADATA_VM_IDENTITY_URL.format(
        audience=audience, format=format, licenses=licenses
    )

    # Request a token from the metadata server.
    r = requests.get(url, headers=METADATA_HEADERS)
    # Extract and return the token from the response.
    r.raise_for_status()
    return r.text

元数据服务器通过使用 RS256 算法签署的 JSON 网络令牌响应此请求。该令牌包含 Google 签名和载荷中的其他信息。您可以将此令牌发送给其他系统和应用,以便其验证令牌并确认您的实例的身份。

验证令牌

应用从 Compute Engine 实例收到实例身份令牌后,可以使用以下过程验证令牌。

  1. 从虚拟机实例接收令牌,使用 RS256 JWT 解码器解码令牌,并读取标头内容以获取 kid 值。

  2. 根据公共 Google 证书来检查令牌,以验证其是否由 Google 签署。每个公共证书都有一个 kid 值,对应于令牌标头中的 kid 值。

  3. 如果令牌有效,请将载荷内容与期望值进行比较。如果令牌载荷包含有关实例和项目的详细信息,那么您的应用程序可以检查 instance_idproject_idzone值。这些值是一个全局唯一的元组,用于确认您的应用是否正在与所需的项目中的正确实例进行通信。

您可以使用自己喜欢的工具解码和验证令牌,但常见的方法是使用您选择的语言对应的库。例如,您可以使用 Python 版 Google OAuth 2.0 库中的 verify_token 方法。verify_token 方法可将 kid 值与相应证书匹配、验证签名、检查目标对象声明,以及从令牌返回载荷内容。

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

def verify_token(token: str, audience: str) -> dict:
    """
    Verify token signature and return the token payload.

    Args:
        token: the JSON Web Token received from the metadata server to
            be verified.
        audience: the unique URI agreed upon by both the instance and the
            system verifying the instance's identity.

    Returns:
        Dictionary containing the token payload.
    """
    request = google.auth.transport.requests.Request()
    payload = id_token.verify_token(token, request=request, audience=audience)
    return payload

应用验证令牌及其内容后,可以继续通过安全连接与该实例通信,然后在通信完成后关闭连接。对于后续连接,请向实例请求新令牌并重新验证实例的身份。

令牌内容

实例身份令牌包含以下三个主要部分:

标头中包含 kid,用于标识您在验证签名时必须使用的公共 Oauth2 证书。标头中还包含 alg 值,用于确认使用 RS256 算法生成的签名。

{
  "alg": "RS256",
  "kid": "511a3e85d2452aee960ed557e2666a8c5cedd8ae",
}

载荷

载荷中包含 aud 目标对象声明。如果实例在请求令牌时指定了 format=full,则载荷还会包含有关虚拟机实例及其项目的声明。请求完整格式令牌时,如果指定 licenses=TRUE,则载荷还将包含有关与实例关联的许可的声明。

{
   "iss": "[TOKEN_ISSUER]",
   "iat": [ISSUED_TIME],
   "exp": [EXPIRED_TIME],
   "aud": "[AUDIENCE]",
   "sub": "[SUBJECT]",
   "azp": "[AUTHORIZED_PARTY]",
   "google": {
    "compute_engine": {
      "project_id": "[PROJECT_ID]",
      "project_number": [PROJECT_NUMBER],
      "zone": "[ZONE]",
      "instance_id": "[INSTANCE_ID]",
      "instance_name": "[INSTANCE_NAME]",
      "instance_creation_timestamp": [CREATION_TIMESTAMP],
      "instance_confidentiality": [INSTANCE_CONFIDENTIALITY],
      "license_id": [
        "[LICENSE_1]",
          ...
        "[LICENSE_N]"
      ]
    }
  }
}

其中:

  • [TOKEN_ISSUER]:用于标识令牌颁发者的网址。对于 Compute Engine,该值为 https://accounts.google.com
  • [ISSUED_TIME]:指示令牌颁发时间的 unix 时间戳。每当实例向元数据服务器请求令牌时,此值都会更新。
  • [EXPIRED_TIME]:指示令牌到期时间的 unix 时间戳。
  • [AUDIENCE]:由实例和验证实例身份的系统商定的唯一 URI。例如,受众群体可能是两个系统之间的连接的网址。
  • [SUBJECT]:令牌的主体,即您用来关联实例的服务账号的唯一 ID。
  • [AUTHORIZED_PARTY]:ID 令牌的接收方,即您用来关联实例的服务账号的唯一 ID。
  • [PROJECT_ID]:您在其中创建了实例的项目的 ID。
  • [PROJECT_NUMBER]:您在其中创建了实例的项目的唯一编号。
  • [ZONE]:实例所在的区域。
  • [INSTANCE_ID]:此令牌所属实例的唯一 ID。此 ID 在项目和可用区中是唯一的。
  • [INSTANCE_NAME]:此令牌所属实例的名称。如果您的项目使用可用区级 DNS,则该名称可以跨可用区重复使用,因此请结合使用 project_idzoneinstance_id值,以标识唯一实例 ID 的值。启用了全球级 DNS 的项目在整个项目中具有唯一的实例名称。
  • [CREATION_TIMESTAMP]:一个 Unix 时间戳,指示您创建实例的时间。
  • [INSTANCE_CONFIDENTIALITY]1(如果实例为机密虚拟机)。
  • [LICENSE_1][LICENSE_N]:与此实例关联的映像的许可代码

您的载荷可能类似如下示例:

{
  "iss": "https://accounts.google.com",
  "iat": 1496953245,
  "exp": 1496956845,
  "aud": "https://www.example.com",
  "sub": "107517467455664443765",
  "azp": "107517467455664443765",
  "google": {
    "compute_engine": {
      "project_id": "my-project",
      "project_number": 739419398126,
      "zone": "us-west1-a",
      "instance_id": "152986662232938449",
      "instance_name": "example",
      "instance_creation_timestamp": 1496952205,
      "instance_confidentiality": 1,
      "license_id": [
        "1000204"
      ]
    }
  }
}

签名

Google 通过对标头和有效负载进行 base64url 编码并连接这两个值来生成签名。您可以根据公共 Oauth2 证书检查此值以验证令牌。

后续步骤