Authentification pour l'appel

Pour appeler une fonction Cloud Run authentifiée, le compte principal sous-jacent doit satisfaire les exigences suivantes :

  • Être autorisé à appeler la fonction.
  • Fournir un jeton d'ID au moment d'appeler la fonction.

Qu'est-ce qu'un compte principal ? Comme décrit dans Sécuriser vos fonctions Cloud Run, Cloud Run Functions accepte deux types d'identités différents, également appelés comptes principaux :

  • Comptes de service : il s'agit de comptes spéciaux qui constituent l'identité d'un utilisateur non-humain, par exemple une fonction, une application ou une VM. Ils vous permettent d'authentifier ces entités non humaines.
  • Comptes utilisateur : ces comptes représentent de véritables utilisateurs, qu'il s'agisse de titulaires d'un compte Google individuel ou de membres d'une entité contrôlée par Google, comme un groupe Google.

Consultez la page de présentation d'IAM pour en savoir plus sur les concepts de base d'IAM.

Pour appeler une fonction Cloud Run authentifiée, le compte principal doit disposer de l'autorisation IAM "Demandeur" :

  • run.routes.invokeElle est généralement fournie via le rôle Demandeur Cloud Run. Cette autorisation doit être attribuée sur la ressource de service Cloud Run.

Pour accorder ces autorisations, exécutez la commande add-invoker-policy-binding, comme indiqué dans la section Authentifier les appels de fonction à fonction.

Pour obtenir l'autorisation de créer, mettre à jour ou effectuer d'autres actions d'administration sur une fonction, le compte principal doit disposer d'un rôle approprié. Les rôles incluent des autorisations qui définissent les actions que le compte principal est autorisé à effectuer. Pour en savoir plus, consultez la page Utiliser IAM pour autoriser l'accès.

L'appel d'une fonction peut impliquer des complexités supplémentaires. Les fonctions basées sur des événements ne peuvent être appelées que par la source de l'événement auquel elles sont abonnées. Toutefois, les fonctions HTTP peuvent être appelées par différents types d'identités, provenant de sources différentes. L'appelant peut être un développeur qui teste la fonction, ou bien une autre fonction ou un service qui souhaite utiliser la fonction. Par défaut, ces identités doivent fournir un jeton d'ID avec la requête pour s'authentifier. De plus, le compte utilisé doit également disposer des autorisations appropriées.

En savoir plus sur la génération et l'utilisation des jetons d'ID.

Exemples concernant l'authentification

Cette section présente quelques exemples différents d'authentification pour l'appel.

Exemple 1 : Authentifier les tests des développeurs

En tant que développeur, vous avez besoin d'un accès pour créer, mettre à jour et supprimer des fonctions. Cet accès est accordé à l'aide du processus normal (IAM).

Toutefois, en tant que développeur, vous devrez aussi probablement appeler vos fonctions à des fins de test. Pour appeler une fonction à l'aide de curl ou d'outils similaires, tenez compte des points suivants :

  • Attribuez à votre compte utilisateur Cloud Run Functions un rôle qui contient l'autorisation d'appel.

  • Si vous travaillez sur votre machine locale, configurez l'accès via la ligne de commande en initialisant Google Cloud CLI.

  • Dans votre requête, fournissez des identifiants d'authentification en tant que jeton d'ID généré par Google, stocké dans un en-tête Authorization. Par exemple, obtenez un jeton d'ID à l'aide de gcloud en exécutant la commande suivante :

    curl  -H "Authorization: Bearer $(gcloud auth print-identity-token)" \
      https://FUNCTION_URL

    FUNCTION_URL est l'URL de la fonction. Récupérez cette URL sur la page Cloud Run Functions de la console Google Cloud ou à l'aide de la commande gcloud functions describe, comme indiqué à la première étape de l'exemple de commande de déploiement dans Google Cloud CLI

Vous pouvez utiliser des jetons créés par gcloud pour appeler des fonctions HTTP dans n'importe quel projet, à condition que votre compte dispose de l'autorisation cloudfunctions.functions.invoke sur la fonction appelée. À des fins de développement, utilisez les jetons d'ID générés par gcloud. Notez toutefois que ces jetons ne sont pas revendiqués, ce qui les rend sensibles aux attaques par relais. Dans les environnements de production, utilisez des jetons d'ID émis pour un compte de service avec l'audience appropriée spécifiée. Cette approche améliore la sécurité en limitant l'utilisation des jetons au service prévu uniquement.

Comme toujours, nous vous recommandons d'allouer l'ensemble minimal d'autorisations requises pour développer et utiliser vos fonctions. Assurez-vous que les stratégies IAM associées à vos fonctions sont limitées au nombre minimal d'utilisateurs et de comptes de service.

Exemple 2 : Authentifier les appels de fonction à fonction

Lorsque vous créez des services qui connectent plusieurs fonctions, il est recommandé de s'assurer que chaque fonction ne peut envoyer des requêtes qu'à un sous-ensemble spécifique de vos autres fonctions. Par exemple, si vous avez une fonction login, elle doit pouvoir accéder à la fonction user profiles, mais probablement pas à la fonction search.

Pour configurer la fonction de réception afin d'accepter les requêtes d'une fonction d'appel spécifique, vous devez attribuer le rôle de demandeur approprié pour le compte de service de la fonction d'appel sur la fonction de réception. Le rôle de demandeur est celui intitulé Demandeur Cloud Run (roles/run.invoker), qui doit être attribué au service sous-jacent :

Console

Utilisez le demandeur Cloud Run :

  1. Accédez à Google Cloud Console :

    Accéder à Google Cloud Console

  2. Dans la liste des services Cloud Run, cochez la case en regard de la fonction de réception. (Ne cliquez pas directement sur la fonction.)

    Le panneau Autorisations s'affiche.

  3. Cliquez sur Ajouter un compte principal.

  4. Saisissez l'identité du service appelant. Il s'agit généralement d'une adresse e-mail (PROJECT_NUMBER-compute@developer.gserviceaccount.com par défaut).

    Notez que le numéro de projet est différent de l'ID du projet et du nom du projet. Le numéro de projet se trouve sur la page du tableau de bord de la console Google Cloud.

  5. Sélectionnez le rôle Cloud Run Invoker dans le menu déroulant Rôle.

  6. Cliquez sur Enregistrer.

gcloud

Exécutez la commande gcloud functions add-invoker-policy-binding :

gcloud functions add-invoker-policy-binding RECEIVING_FUNCTION \
  --member='serviceAccount:CALLING_FUNCTION_IDENTITY'

La commande add-invoker-policy-binding ajoute une liaison de stratégie IAM de rôle de demandeur qui permet au membre spécifié (compte principal) d'appeler la fonction spécifiée. Google Cloud CLI détecte automatiquement la génération de la fonction et ajoute le rôle Demandeur approprié (run.invoker pour Cloud Run Functions).

Remplacez les éléments suivants :

  • RECEIVING_FUNCTION : le nom de la fonction de réception.
  • CALLING_FUNCTION_IDENTITY : l'identité de la fonction d'appel, une adresse e-mail de compte de service.

Étant donné qu'elle va appeler la fonction de réception, la fonction appelante doit également fournir un jeton d'ID signé par Google pour s'authentifier. Il s'agit d'un processus en deux étapes :

  1. Créez un jeton d'ID signé par Google avec le champ d'audience (aud) défini sur l'URL de la fonction de réception.

  2. inclure le jeton d'ID dans un en-tête Authorization: Bearer ID_TOKEN de la requête destinée à la fonction.

Le moyen le plus simple et le plus fiable de gérer ce processus consiste à utiliser les bibliothèques d'authentification, comme indiqué ci-dessous, pour générer et utiliser ce jeton.

Générer des jetons d'ID

Cette section décrit différentes façons de générer le jeton d'ID dont un compte principal a besoin pour appeler une fonction.

L'accès non authentifié sans jeton d'ID est possible, mais doit être activé. Pour en savoir plus, consultez la page Utiliser IAM pour autoriser l'accès.

Générer des jetons de manière programmatique

Une fois que le code suivant a généré un jeton d'ID, il appelle votre fonction Cloud avec ce jeton en votre nom. Ce code fonctionne dans tous les environnements dans lesquels les bibliothèques peuvent obtenir des identifiants d'authentification, y compris les environnements compatibles avec les identifiants par défaut de l'application locaux.

Node.js

/**
 * TODO(developer): Uncomment these variables before running the sample.
 */

// Cloud Functions uses your function's url as the `targetAudience` value
// const targetAudience = 'https://project-region-projectid.cloudfunctions.net/myFunction';
// For Cloud Functions, endpoint (`url`) and `targetAudience` should be equal
// const url = targetAudience;


const {GoogleAuth} = require('google-auth-library');
const auth = new GoogleAuth();

async function request() {
  console.info(`request ${url} with target audience ${targetAudience}`);
  const client = await auth.getIdTokenClient(targetAudience);

  // Alternatively, one can use `client.idTokenProvider.fetchIdToken`
  // to return the ID Token.
  const res = await client.request({url});
  console.info(res.data);
}

request().catch(err => {
  console.error(err.message);
  process.exitCode = 1;
});

Python

import urllib

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


def make_authorized_get_request(endpoint, audience):
    """
    make_authorized_get_request makes a GET request to the specified HTTP endpoint
    by authenticating with the ID token obtained from the google-auth client library
    using the specified audience value.
    """

    # Cloud Functions uses your function's URL as the `audience` value
    # audience = https://project-region-projectid.cloudfunctions.net/myFunction
    # For Cloud Functions, `endpoint` and `audience` should be equal

    req = urllib.request.Request(endpoint)

    auth_req = google.auth.transport.requests.Request()
    id_token = google.oauth2.id_token.fetch_id_token(auth_req, audience)

    req.add_header("Authorization", f"Bearer {id_token}")
    response = urllib.request.urlopen(req)

    return response.read()

Go


import (
	"context"
	"fmt"
	"io"

	"google.golang.org/api/idtoken"
)

// `makeGetRequest` makes a request to the provided `targetURL`
// with an authenticated client using audience `audience`.
func makeGetRequest(w io.Writer, targetURL string, audience string) error {
	// For Cloud Functions, endpoint (`serviceUrl`) and `audience` are the same.
	// Example `audience` value (Cloud Functions): https://<PROJECT>-<REGION>-<PROJECT_ID>.cloudfunctions.net/myFunction
	// (`targetURL` and `audience` will differ for GET parameters)
	ctx := context.Background()

	// client is a http.Client that automatically adds an "Authorization" header
	// to any requests made.
	client, err := idtoken.NewClient(ctx, audience)
	if err != nil {
		return fmt.Errorf("idtoken.NewClient: %w", err)
	}

	resp, err := client.Get(targetURL)
	if err != nil {
		return fmt.Errorf("client.Get: %w", err)
	}
	defer resp.Body.Close()
	if _, err := io.Copy(w, resp.Body); err != nil {
		return fmt.Errorf("io.Copy: %w", err)
	}

	return nil
}

Java

import com.google.api.client.http.GenericUrl;
import com.google.api.client.http.HttpRequest;
import com.google.api.client.http.HttpResponse;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.auth.http.HttpCredentialsAdapter;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.auth.oauth2.IdTokenCredentials;
import com.google.auth.oauth2.IdTokenProvider;
import java.io.IOException;

public class Authentication {

  // makeGetRequest makes a GET request to the specified Cloud Run or
  // Cloud Functions endpoint `serviceUrl` (must be a complete URL), by
  // authenticating with an ID token retrieved from Application Default
  // Credentials using the specified `audience`.
  //
  // For Cloud Functions, endpoint (`serviceUrl`) and `audience` are the same.
  // Example `audience` value (Cloud Functions): https://project-region-projectid.cloudfunctions.net/myFunction
  public static HttpResponse makeGetRequest(String serviceUrl, String audience) throws IOException {
    GoogleCredentials credentials = GoogleCredentials.getApplicationDefault();
    if (!(credentials instanceof IdTokenProvider)) {
      throw new IllegalArgumentException("Credentials are not an instance of IdTokenProvider.");
    }
    IdTokenCredentials tokenCredential =
        IdTokenCredentials.newBuilder()
            .setIdTokenProvider((IdTokenProvider) credentials)
            .setTargetAudience(audience)
            .build();

    GenericUrl genericUrl = new GenericUrl(serviceUrl);
    HttpCredentialsAdapter adapter = new HttpCredentialsAdapter(tokenCredential);
    HttpTransport transport = new NetHttpTransport();
    HttpRequest request = transport.createRequestFactory(adapter).buildGetRequest(genericUrl);
    return request.execute();
  }
}

Générer des jetons manuellement

Si vous appelez une fonction et que, pour une raison quelconque, vous ne pouvez pas utiliser les bibliothèques d'authentification, vous pouvez obtenir le jeton d'ID manuellement de deux manières : soit à l'aide du serveur de métadonnées Compute, soit en créant un jeton JWT autosigné et en le remplaçant par un jeton d'ID signé par Google.

Utiliser le serveur de métadonnées

Vous pouvez utiliser le serveur de métadonnées Compute pour extraire des jetons d'identification associés à une audience spécifique, comme suit :

curl "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity?audience=AUDIENCE" \
     -H "Metadata-Flavor: Google"

Remplacez AUDIENCE par l'URL de la fonction que vous appelez. Vous pouvez récupérer cette URL comme décrit dans la section Authentifier les tests des développeurs ci-dessus.

Remplacer un jeton JWT autosigné par un jeton d'ID signé par Google

  1. Accordez le rôle "Demandeur" (roles/cloudfunctions.invoker) au compte de service de la fonction d'appel sur la fonction de réception.

  2. Créez un compte de service et une clé, puis téléchargez le fichier avec la clé privée (au format JSON) sur l'hôte à partir duquel la fonction appelante ou le service effectue ses requêtes.

  3. Créez un jeton JWT avec l'en-tête défini sur {"alg":"RS256","typ":"JWT"}. La charge utile doit inclure une revendication target_audience définie sur l'URL de la fonction de réception, ainsi que les revendications iss et sub définies sur l'adresse e-mail du compte de service utilisé ci-dessus. Elle doit également inclure des revendications exp et iat. La revendication aud doit être définie sur https://www.googleapis.com/oauth2/v4/token.

  4. Utilisez la clé privée téléchargée ci-dessus pour signer le jeton JWT.

  5. À l'aide de ce jeton JWT, envoyez une requête POST à https://www.googleapis.com/oauth2/v4/token. Les données d'authentification doivent être incluses dans l'en-tête et dans le corps de la requête.

    Dans l'en-tête :

    Authorization: Bearer $JWT - where $JWT is the JWT you just created
    Content-Type: application/x-www-form-urlencoded
    

    Dans le corps :

    grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=$JWT
    

    Remplacez $JWT par le jeton JWT que vous venez de créer.

    Cette requête renvoie un autre jeton JWT, qui inclut un objet id_token signé par Google.

Envoyez votre requête GET/POST à la fonction de réception. Incluez le jeton d'ID signé par Google dans un en-tête Authorization: Bearer ID_TOKEN_JWT dans la requête.

Étapes suivantes