Vérifier l'identité des VM


Avant d'envoyer des informations sensibles à une instance de machine virtuelle (VM), l'application peut vérifier l'identité de l'instance à l'aide de jetons d'identité d'instance signés par Google. Chaque instance possède un jeton Web JSON (JWT) unique qui contient des informations sur l'instance ainsi que la signature RS256 de Google. Vos applications peuvent vérifier la signature par rapport aux certificats publics Oauth2 de Google pour confirmer l'identité de l'instance avec laquelle elles ont établi une connexion.

Compute Engine génère des jetons d'instance signés uniquement lorsqu'une instance les demande à partir de métadonnées d'instance. Les instances peuvent accéder à leur propre jeton unique, mais pas aux jetons des autres instances.

Il sera probablement nécessaire de vérifier l'identité de vos instances dans les cas suivants :

  • Lorsque vous démarrez une instance pour la première fois, vos applications peuvent avoir besoin de s'assurer que l'instance à laquelle elles se sont connectées possède une identité valide avant de lui transmettre des informations sensibles.
  • Lorsque vos règles vous obligent à stocker des informations d'identifiants à l'extérieur de l'environnement Compute Engine et que vous envoyez régulièrement ces identifiants à vos instances pour un usage provisoire, vos applications peuvent vérifier l'identité des instances à chaque fois qu'elles doivent transmettre des identifiants.

Les méthodes d'authentification des instances de Google présentent les avantages suivants :

  • Compute Engine crée un jeton unique à chaque fois qu'une instance le demande et chaque jeton expire dans l'heure qui suit. Vous pouvez configurer vos applications pour qu'elles n'acceptent qu'une seule fois le jeton d'identité d'une instance, ce qui réduit le risque de réutilisation du jeton par un système non autorisé.
  • Les jetons de métadonnées signés utilisent le standard ouvert du secteur RFC 7519 et la couche d'identité OpenID Connect 1.0, de sorte que les outils et les bibliothèques existants fonctionnent de manière transparente avec les jetons d'identité.

Avant de commencer

  • Découvrez comment récupérer les valeurs de métadonnées d'instance.
  • Découvrez les bases des jetons Web JSON afin de savoir comment les utiliser dans vos applications.
  • Découvrez comment créer et activer des comptes de service sur vos instances. Un compte de service doit être associé à vos instances afin qu'elles puissent récupérer leurs jetons d'identité. Le compte de service ne nécessite aucune autorisation IAM pour récupérer ces jetons d'identité.
  • Si ce n'est pas déjà fait, configurez l'authentification. L'authentification est le processus permettant de valider votre identité pour accéder aux services et aux API Google Cloud. Pour exécuter du code ou des exemples depuis un environnement de développement local, vous pouvez vous authentifier auprès de Compute Engine comme suit :

    Pour utiliser les exemples Python de cette page dans un environnement de développement local, installez et initialisez gcloud CLI, puis configurez le service Identifiants par défaut de l'application à l'aide de vos identifiants utilisateur.

    1. Installez Google Cloud CLI.
    2. Pour initialiser gcloudCLI, exécutez la commande suivante :

      gcloud init
    3. Créez des identifiants d'authentification locaux pour votre compte Google :

      gcloud auth application-default login

    Pour en savoir plus, consultez les sections sur Configurer l'authentification pour un environnement de développement local.

Vérifier l'identité d'une instance

Dans certains cas, vos applications doivent vérifier l'identité d'une instance exécutée sur Compute Engine avant de transmettre des données sensibles à cette instance. Voici un exemple typique avec un système exécuté à l'extérieur de Compute Engine appelé "Host1" et une instance de Compute Engine appelée "VM1". VM1 peut se connecter à Host1 et valider l'identité de cette instance en procédant comme suit :

  1. VM1 établit une connexion sécurisée avec Host1 via le protocole de connexion sécurisée de votre choix, tel que HTTPS.

  2. VM1 demande son jeton d'identité unique au serveur de métadonnées et spécifie l'audience du jeton. Dans cet exemple, la valeur de l'audience est l'URI de Host1. La demande adressée au serveur de métadonnées inclut l'URI d'audience afin que Host1 puisse vérifier la valeur lors de l'étape de vérification du jeton.

  3. Google génère un nouveau jeton d'identité d'instance unique au format JWT et le fournit à VM1. La charge utile du jeton contient plusieurs informations sur l'instance et inclut également l'URI d'audience. Consultez la section Contenu du jeton pour une description complète du contenu du jeton.

  4. VM1 envoie le jeton d'identité à Host1 via la connexion sécurisée existante.

  5. Host1 décode le jeton d'identité pour obtenir l'en-tête du jeton et les valeurs de la charge utile.

  6. Host1 s'assure que le jeton est signé par Google en vérifiant la valeur d'audience et la signature du certificat par rapport au certificat Google public.

  7. Si le jeton est valide, Host1 procède à la transmission et ferme la connexion lorsque celle-ci est terminée. Host1 et tous les autres systèmes doivent demander un nouveau jeton pour toute connexion ultérieure à VM1.

Obtenir le jeton d'identité de l'instance

Lorsque votre instance de machine virtuelle reçoit une demande de transmission de son jeton d'identité, l'instance demande ce jeton au serveur de métadonnées en utilisant le processus normal d'obtention des métadonnées d'instance. Vous pouvez, par exemple, utiliser l'une des méthodes suivantes :

cURL

Créez une requête curl et incluez une valeur dans le paramètre audience. Si vous le souhaitez, vous pouvez inclure le paramètre format pour indiquer si vous souhaitez ou non inclure les détails du projet et de l'instance dans la charge utile. Si vous utilisez le format full, vous pouvez inclure le paramètre licenses pour indiquer si vous souhaitez ou non inclure des codes de licence dans la charge utile.

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

Remplacez les éléments suivants :

  • AUDIENCE : URI unique convenu entre l'instance et le système pour vérifier l'identité de l'instance. Par exemple, l'audience pourrait être une URL permettant la connexion entre les deux systèmes.
  • FORMAT : paramètre facultatif qui spécifie si les informations du projet et de l'instance sont incluses ou non dans la charge utile. Indiquez full pour inclure ces informations dans la charge utile ou standard pour omettre les informations de la charge utile. La valeur par défaut est standard. Pour en savoir plus, consultez la section Format des jetons d'identité.
  • LICENSES : paramètre facultatif qui spécifie si les codes de licence des images associées à cette instance sont inclus dans la charge utile. Spécifiez TRUE pour inclure ces informations ou FALSE pour omettre les informations de la charge utile. La valeur par défaut est FALSE. N'a d'effet que si format est full

Le serveur de métadonnées répond à cette demande avec un jeton Web JSON signé à l'aide de l'algorithme RS256. Le jeton comprend une signature Google et des informations supplémentaires dans la charge utile. Vous pouvez envoyer ce jeton à d'autres systèmes et applications afin qu'ils puissent vérifier le jeton et l'identité de votre instance.

Python

Vous pouvez envoyer une demande simple de votre instance au serveur de métadonnées à l'aide des méthodes de la bibliothèque Python requests. Dans l'exemple suivant, le jeton d'identité de l'instance est demandé, puis imprimé. Le jeton est unique à l'instance qui effectue cette demande.

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

Le serveur de métadonnées répond à cette demande avec un jeton Web JSON signé à l'aide de l'algorithme RS256. Le jeton comprend une signature Google et des informations supplémentaires dans la charge utile. Vous pouvez envoyer ce jeton à d'autres systèmes et applications afin qu'ils puissent vérifier le jeton et l'identité de votre instance.

Vérifier le jeton

Après avoir reçu un jeton d'identité d'instance à partir d'une instance Compute Engine, votre application peut vérifier le jeton de la façon suivante :

  1. Recevoir le jeton de l'instance de machine virtuelle, décoder le jeton à l'aide d'un décodeur JWT RS256 et lire le contenu de l'en-tête pour obtenir la valeur kid.

  2. Vérifier que le jeton est signé en vérifiant le jeton par rapport au certificat Google public. Chaque certificat public possède une valeur kid qui correspond à la valeur kid dans l'en-tête du jeton.

  3. Si le jeton est valide, comparer le contenu de la charge utile aux valeurs attendues. Si la charge utile de jeton inclut des informations sur l'instance et le projet, votre application peut vérifier les valeurs instance_id, project_id et zone. Ces valeurs sont un tuple unique qui confirme que votre application communique avec la bonne instance dans le projet souhaité.

Vous pouvez décoder et vérifier le jeton à l'aide de n'importe quel outil, mais la méthode la plus courante consiste à utiliser les bibliothèques de la langue de votre choix. Par exemple, vous pouvez utiliser la méthode verify_token de la bibliothèque Google OAuth 2.0 pour Python. La méthode verify_token met en correspondance la valeur kid avec le certificat approprié, vérifie la signature, vérifie la déclaration d'audience et renvoie le contenu de la charge utile à partir du jeton.

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

Après avoir vérifié le jeton et son contenu, votre application peut continuer à communiquer avec cette instance via une connexion sécurisée, puis fermer la connexion une fois l'opération terminée. Pour établir des connexions plus tard, demandez un nouveau jeton à partir de l'instance et vérifiez à nouveau l'identité de l'instance.

Contenu du jeton

Le jeton d'identité de l'instance se compose de trois parties principales :

L'en-tête inclut la valeur kid pour identifier les certificats Oauth2 publics que vous devez utiliser pour vérifier la signature. L'en-tête comprend également la valeur alg pour confirmer que la signature est générée à l'aide de l'algorithme RS256.

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

Charge utile

La charge utile contient la déclaration d'audience aud. Si l'instance spécifie format=full lors de la demande de jeton, la charge utile inclut également des déclarations au sujet de l'instance de la machine virtuelle et de son projet. Lorsque vous demandez un jeton de format complet, le fait de spécifier licenses=TRUE inclut également les déclaration concernant les licences associées à l'instance.

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

Où :

  • [TOKEN_ISSUER] : URL identifiant l'émetteur du jeton. Pour Compute Engine, cette valeur est https://accounts.google.com.
  • [ISSUED_TIME] : horodatage unix indiquant le moment d'émission du jeton. Cette valeur est mise à jour à chaque fois que l'instance demande un jeton au serveur de métadonnées.
  • [EXPIRED_TIME] : horodatage unix indiquant la date d'expiration du jeton.
  • [AUDIENCE] : URI unique convenu entre l'instance et le système pour vérifier l'identité de l'instance. Par exemple, l'audience pourrait être une URL permettant la connexion entre les deux systèmes.
  • [SUBJECT] : objet du jeton, qui est l'ID unique du compte de service que vous avez associé à votre instance.
  • [AUTHORIZED_PARTY] : partie à laquelle le jeton d'ID a été émis. Il s'agit de l'ID unique du compte de service que vous avez associé à votre instance.
  • [PROJECT_ID] : ID du projet dans lequel vous avez créé l'instance.
  • [PROJECT_NUMBER] : numéro unique du projet dans lequel vous avez créé l'instance.
  • [ZONE] : zone où se trouve l'instance
  • [INSTANCE_ID] : ID unique de l'instance à laquelle appartient ce jeton. Cet ID est unique dans le projet et la zone.
  • [INSTANCE_NAME] : nom de l'instance à laquelle appartient ce jeton. Si votre projet utilise un DNS zonal, ce nom peut être réutilisé dans toutes les zones. Par conséquent, utilisez une combinaison des valeurs project_id, zone et instance_id pour identifier un ID d'instance unique. Les projets pour lesquels le DNS global est activé ont un nom d'instance unique pour l'ensemble du projet.
  • [CREATION_TIMESTAMP] : horodatage unix indiquant le moment de création de l'instance.
  • [INSTANCE_CONFIDENTIALITY] : 1 si l'instance est une VM confidentielle Confidential VM.
  • De [LICENSE_1] à [LICENSE_N] : codes de licence des images associées à cette instance.

Votre charge utile devrait ressembler à ceci :

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

Signature

Google génère la signature en encodant en base64url l'en-tête et la charge utile et en combinant les deux valeurs. Vous pouvez comparer cette valeur aux certificats Oauth2 publics pour vérifier le jeton.

Étapes suivantes