Verificar la identidad de las VMs


Antes de que una aplicación envíe información sensible a una instancia de máquina virtual (VM), la aplicación puede verificar la identidad de la instancia mediante tokens de identidad de instancia que firma Google. Cada instancia tiene un JSON Web Token (JWT) único que incluye detalles sobre la instancia y la firma RS256 de Google. Tus aplicaciones pueden verificar la firma con los certificados públicos de Oauth2 de Google para confirmar la identidad de la instancia con la que establecieron una conexión.

Compute Engine genera tokens de instancia firmados solo cuando una instancia los solicita a los metadatos de la instancia. Las instancias solo pueden acceder a su propio token único y no a los tokens para ninguna otra instancia.

Es posible que desees verificar las identidades de tus instancias en las siguientes situaciones:

  • Cuando inicias una instancia por primera vez, es posible que tus aplicaciones necesiten asegurarse de que la instancia a la que se conectaron tenga una identidad válida antes de transmitir información sensible a la instancia.
  • Cuando tus políticas requieren que almacenes credenciales fuera del entorno de Compute Engine y envías esas credenciales regularmente a tus instancias para uso temporal. Tus aplicaciones pueden confirmar las identidades de las instancias cada vez que necesitan transmitir credenciales.

Los métodos de autenticación de instancias de Google tienen los siguientes beneficios:

  • Compute Engine crea un token único cada vez que una instancia lo solicita, y cada token vence en una hora. Puedes configurar tus aplicaciones para que acepten el token de identidad de una instancia solo una vez, lo que reduce el riesgo de que el token pueda ser reutilizado por un sistema no autorizado.
  • Los tokens de metadatos firmados usan el estándar abierto de la industria RFC 7519 y la capa de identidad OpenID Connect 1.0, por lo que las herramientas y bibliotecas existentes funcionarán a la perfección con los tokens de identidad.

Antes de comenzar

  • Comprende cómo recuperar los valores de metadatos de la instancia.
  • Comprende los conceptos básicos de JSON Web Tokens para saber cómo usarlos en tus aplicaciones.
  • Comprende cómo crear y habilitar cuentas de servicio en tus instancias. Tus instancias deben tener una cuenta de servicio asociada a ellas para que puedan recuperar sus tokens de identidad. La cuenta de servicio no requiere ningún permiso de IAM para recuperar estos tokens de identidad.
  • Si aún no lo hiciste, configura la autenticación. La autenticación es el proceso mediante el cual se verifica tu identidad para acceder a los servicios y las API de Google Cloud. Para ejecutar código o muestras desde un entorno de desarrollo local, puedes autenticarte en Compute Engine de la siguiente manera.

    Para usar las muestras de Python de esta página desde un entorno de desarrollo local, instala e inicializa la CLI de gcloud y, luego, configura las credenciales predeterminadas de la aplicación con tus credenciales de usuario.

    1. Instala Google Cloud CLI.
    2. Para inicializar la CLI de gcloud, ejecuta el siguiente comando:

      gcloud init
    3. Crea credenciales de autenticación locales para tu Cuenta de Google:

      gcloud auth application-default login

    Para obtener más información, consulta Configura la autenticación para un entorno de desarrollo local.

Verifica la identidad de una instancia

En algunas situaciones, tus aplicaciones deben verificar la identidad de una instancia que se ejecuta en Compute Engine antes de transmitir datos sensibles a esa instancia. En un ejemplo común, hay un sistema que se ejecuta fuera de Compute Engine denominado “Host1” y una instancia de Compute Engine denominada “VM1”. VM1 puede conectarse a Host1 y validar la identidad de esa instancia con el siguiente proceso:

  1. VM1 establece una conexión segura con Host1 a través de un protocolo de conexión segura de tu elección, como HTTPS.

  2. VM1 solicita el token único de identidad al servidor de metadatos y especifica el público del token. En este ejemplo, el valor del público es el URI para Host1. La solicitud al servidor de metadatos incluye el URI del público para que Host1 pueda verificar el valor más adelante durante el paso de verificación del token.

  3. Google genera un nuevo token único de identidad de instancia en formato JWT y se lo proporciona a VM1. La carga útil del token incluye varios detalles sobre la instancia y también incluye el URI del público. Consulta Contenido del token para obtener una descripción completa del contenido del token.

  4. VM1 envía el token de identidad a Host1 a través de la conexión segura existente.

  5. Host1 decodifica el token de identidad para obtener el encabezado del token y los valores de carga útil.

  6. Host1 verifica que Google haya firmado el token mediante la revisión del valor de la audiencia y la verificación de la firma del certificado con el certificado público de Google.

  7. Si el token es válido, Host1 continúa con la transmisión y finaliza la conexión cuando haya terminado. Host1 y cualquier otro sistema deben solicitar un token nuevo para cualquier conexión posterior a VM1.

Obtén el token de identidad de instancia

Cuando tu instancia de máquina virtual recibe una solicitud para proporcionar su token de identidad, la instancia solicita ese token al servidor de metadatos mediante el proceso normal para obtener metadatos de instancia. Por ejemplo, puedes usar uno de los siguientes métodos:

cURL

Crea una solicitud curl que incluya un valor en el parámetro audience. Si lo deseas, puedes incluir el parámetro format para especificar si quieres incluir o no los detalles del proyecto y la instancia en la carga útil. Si usas el formato full, puedes incluir el parámetro licenses para especificar si deseas o no incluir códigos de licencia en la carga útil.

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

Reemplaza lo siguiente:

  • AUDIENCE: el URI único acordado por la instancia y por el sistema que verifica la identidad de la instancia. Por ejemplo, el público podría ser una URL para la conexión entre los dos sistemas.
  • FORMAT: el parámetro opcional que especifica si los detalles del proyecto y la instancia se incluyen en la carga útil. Especifica full para incluir esta información en la carga útil o standard si deseas omitir la información de la carga útil. El valor predeterminado es standard. Para obtener más información, consulta la sección sobre el formato de token de identidad.
  • LICENSES: un parámetro opcional que especifica si los códigos de licencia de las imágenes asociadas con esta instancia se incluyen en la carga útil. Especifica TRUE con el fin de incluir esta información en la carga útil o FALSE para omitirla. El valor predeterminado es FALSE. No tiene efecto, a menos que format esté full

El servidor de metadatos responde a esta solicitud con un JSON Web Token firmado con el algoritmo RS256. El token incluye una firma de Google y, también, información adicional en la carga útil. Puedes enviar este token a otros sistemas y aplicaciones para que puedan verificar el token y confirmar la identidad de tu instancia.

Python

Puedes enviar una solicitud simple desde tu instancia al servidor de metadatos mediante los métodos en la biblioteca requests de Python. El siguiente ejemplo solicita y, luego, imprime un token de identidad de instancia. El token es exclusivo de la instancia que hace esta solicitud.

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

El servidor de metadatos responde a esta solicitud con un JSON Web Token firmado con el algoritmo RS256. El token incluye una firma de Google y, también, información adicional en la carga útil. Puedes enviar este token a otros sistemas y aplicaciones para que puedan verificar el token y confirmar la identidad de tu instancia.

Verifica el token

Una vez que tu aplicación reciba un token de identidad de instancia de una instancia de Compute Engine, puedes verificar el token mediante el siguiente proceso:

  1. Recibe el token de la instancia de la máquina virtual, decodifica el token con un decodificador RS256 JWT y lee el contenido del encabezado para obtener el valor kid.

  2. Verifica que el token esté firmado mediante la comparación del token con el certificado público de Google. Cada certificado público tiene un valor kid que corresponde al valor kid en el encabezado del token.

    .
  3. Si el token es válido, compara el contenido de la carga útil con los valores esperados. Si la carga útil del token incluye detalles sobre la instancia y el proyecto, tu aplicación puede verificar los valores de instance_id, project_id y zone. Esos valores son una tupla global única que confirma que tu aplicación se está comunicando con la instancia correcta en el proyecto deseado.

Puedes decodificar y verificar el token con cualquier herramienta que desees, pero un método común es utilizar las bibliotecas para el lenguaje que elijas. Por ejemplo, puedes usar el método verify_token desde la biblioteca OAuth 2.0 de Google para Python. El método verify_token hace que el valor kid coincida con el certificado apropiado, verifica la firma, comprueba la reclamación del público y muestra el contenido de la carga útil del token.

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

Después de que tu aplicación verifica el token y su contenido, puede comunicarse con esa instancia a través de una conexión segura y, luego, finalizar la conexión cuando haya terminado. Para conexiones posteriores, solicita un nuevo token a la instancia y vuelve a verificar su identidad.

Contenido del token

El token de identidad de instancia contiene tres partes principales:

El encabezado incluye el valor kid para identificar qué certificados públicos de Oauth2 debes usar con el fin de verificar la firma. El encabezado también incluye el valor alg para confirmar que la firma se genera con el algoritmo RS256.

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

Carga útil

La carga útil contiene la reclamación de aud. Si la instancia especificó format=full cuando solicitó el token, la carga útil también incluye reclamaciones sobre la instancia de máquina virtual y su proyecto. Cuando se solicita un token de formato completo, también se incluyen reclamaciones sobre las licencias asociadas con la instancia mediante la especificación de 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]"
      ]
    }
  }
}

Aquí:

  • [TOKEN_ISSUER]: una URL que identifica quién emitió el token. Para Compute Engine, este valor es https://accounts.google.com.
  • [ISSUED_TIME]: una marca de tiempo de UNIX que indica cuándo se emitió el token. Este valor se actualiza cada vez que la instancia solicita un token al servidor de metadatos.
  • [EXPIRED_TIME]: una marca de tiempo de UNIX que indica cuándo vence el token.
  • [AUDIENCE]: el URI único acordado por la instancia y por el sistema que verifica la identidad de la instancia. Por ejemplo, el público podría ser una URL para la conexión entre los dos sistemas.
  • [SUBJECT]: el asunto del token, que es el ID único de la cuenta de servicio que asociaste a tu instancia.
  • [AUTHORIZED_PARTY]: la parte a la que se emitió el token de ID, que es el ID único de la cuenta de servicio que asociaste a tu instancia.
  • [PROJECT_ID]: el ID del proyecto en el que creaste la instancia.
  • [PROJECT_NUMBER]: el número único del proyecto en el que creaste la instancia.
  • [ZONE]: Es la zona en la que se encuentra la instancia.
  • [INSTANCE_ID]: el ID único para la instancia a la que pertenece este token. Este ID es único dentro del proyecto y la zona.
  • [INSTANCE_NAME]: el nombre de la instancia a la que pertenece este token. Si tu proyecto usa un DNS zonal, este nombre se puede volver a usar en las zonas, así que usa una combinación con los valores project_id, zone y instance_id para identificar un ID de instancia único. Los proyectos con DNS global habilitado tienen un nombre de instancia único en todo el proyecto.
  • [CREATION_TIMESTAMP]: una marca de tiempo de UNIX que indica cuándo creaste la instancia.
  • [INSTANCE_CONFIDENTIALITY]: 1 si la instancia es una VM confidencial.
  • De [LICENSE_1] a [LICENSE_N]: los códigos de licencia para las imágenes asociadas con esta instancia.

Tu carga útil puede ser similar al siguiente ejemplo:

{
  "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"
      ]
    }
  }
}

Firma

Google genera la firma mediante base64url, lo que codifica el encabezado y la carga útil, y concatena los dos valores. Puedes verificar este valor con los certificados públicos de Oauth2 para comprobar el token.

¿Qué sigue?