Créer un identifiant éphémère à champ d'application limité

Cette page explique comment utiliser les limites d'accès aux identifiants pour créer un jeton d'accès OAuth 2.0 avec des autorisations Cloud Storage à champ d'application limité.

Pour créer un jeton avec des autorisations à champ d'application limité, procédez comme suit :

  1. Attribuez les rôles IAM appropriés à un utilisateur ou à un compte de service.
  2. Définissez une limite d'accès aux identifiants qui définit la limite supérieure des autorisations disponibles pour l'utilisateur ou le compte de service.
  3. Créez un jeton d'accès OAuth 2.0 pour l'utilisateur ou le compte de service.
  4. Échangez le jeton d'accès OAuth 2.0 contre un nouveau jeton respectant la limite d'accès aux identifiants.

Vous pouvez ensuite utiliser le nouveau jeton d'accès OAuth 2.0 à champ d'application limité pour authentifier les requêtes dans Cloud Storage.

Avant de commencer

Avant d'utiliser les limites d'accès aux identifiants, assurez-vous de remplir les conditions suivantes :

  • Vous devez réduire le champ d'application des autorisations pour Cloud Storage uniquement, et non pour les autres servicesGoogle Cloud .

    Si vous devez réduire le champ d'application des autorisations associées à d'autres services Google Cloud, vous pouvez créer plusieurs comptes de service et attribuer un ensemble de rôles différent à chaque compte de service.

  • Vous pouvez utiliser les jetons d'accès OAuth 2.0 pour l'authentification. Les autres types d'identifiants éphémères ne sont pas compatibles avec les limites d'accès aux identifiants.

En outre, vous devez activer les API nécessaires :

  • Enable the IAM and Security Token Service APIs.

    Roles required to enable APIs

    To enable APIs, you need the Service Usage Admin IAM role (roles/serviceusage.serviceUsageAdmin), which contains the serviceusage.services.enable permission. Learn how to grant roles.

    Enable the APIs

Attribuer des rôles IAM

Une limite d'accès aux identifiants définit la limite supérieure des autorisations disponibles pour une ressource. Cette limite peut retirer des autorisations à un compte principal, mais ne peut pas ajouter d'autorisations que le compte principal ne possède pas déjà.

Par conséquent, vous devez attribuer au compte principal des rôles qui fournissent les autorisations nécessaires dans un bucket Cloud Storage ou sur une ressource de niveau supérieur, comme le projet.

Supposons, par exemple, que vous ayez besoin de créer des identifiants éphémères à champ d'application limité, permettant à un compte de service de créer des objets dans un bucket :

  • Vous devez au moins octroyer au compte de service un rôle comprenant l'autorisation storage.objects.create, par exemple le rôle Créateur d'objets Storage (roles/storage.objectCreator). La limite d'accès aux identifiants doit également inclure cette autorisation.
  • Vous pouvez également attribuer un rôle comprenant davantage d'autorisations, tel que le rôle Administrateur d'objets Storage (roles/storage.objectAdmin). Le compte de service ne peut utiliser que les autorisations qui apparaissent à la fois dans le rôle attribué et dans la limite d'accès aux identifiants.

Pour en savoir plus sur les rôles prédéfinis pour Cloud Storage, consultez la page Rôles Cloud Storage.

Définir la limite d'accès aux identifiants

Une limite d'accès aux identifiants est un objet qui contient une liste de règles de limite d'accès. Les règles sont constituées de paramètres qui spécifient la limite supérieure des autorisations disponibles pour l'utilisateur ou le compte de service. Pour définir une limite d'accès aux identifiants, créez un objet JSON qui liste les règles de limite d'accès et leurs paramètres.

Voici un exemple de limite d'accès aux identifiants :

{
  "accessBoundary": {
    "accessBoundaryRules": [
      {
        "availablePermissions": [
          "inRole:ROLE_ID"
        ],
        "availableResource": "//storage.googleapis.com/projects/_/buckets/BUCKET_NAME"
         "availabilityCondition": {
            "expression" : "CONDITION"
      }
    ]
  }
}

Remplacez les éléments suivants :

  • ROLE_ID : ID d'un rôle prédéfini ou personnalisé qui définit la limite supérieure des autorisations disponibles pour la ressource. Exemple :roles/storage.objectViewer Pour spécifier plusieurs rôles, ajoutez une ligne avec un préfixe inRole: suivi de l'ID du rôle. Seules les autorisations associées aux rôles spécifiés sont disponibles.
  • BUCKET_NAME : nom du bucket Cloud Storage auquel s'applique la règle.
  • CONDITION : facultatif. Expression de condition qui spécifie les objets Cloud Storage pour lesquels des autorisations sont disponibles. Par exemple, la condition suivante rend disponibles les autorisations pour les objets dont le nom commence par customer-a :

    resource.name.startsWith('projects/_/buckets/example-bucket/objects/customer-a')
    

Pour savoir comment créer et personnaliser des limites d'accès aux identifiants, consultez Composants d'une limite d'accès aux identifiants.

Pour obtenir des exemples de cas d'utilisation potentiels des limites d'accès aux identifiants, consultez Exemples de limites d'accès aux identifiants.

Créer un jeton d'accès OAuth 2.0

Avant de créer un identifiant éphémère à champ d'application limité, vous devez créer un jeton d'accès OAuth 2.0 standard. Vous pouvez ensuite échanger l'identifiant standard contre un identifiant à champ d'application limité. Lorsque vous créez le jeton d'accès, utilisez le champ d'application OAuth 2.0 https://www.googleapis.com/auth/cloud-platform.

Pour créer un jeton d'accès pour un compte de service, vous pouvez mettre en œuvre le flux OAuth 2.0 de serveur à serveur ou utiliser l'API Service Account Credentials pour générer un jeton d'accès OAuth 2.0.

Pour créer un jeton d'accès pour un utilisateur, consultez la page Obtenir des jetons d'accès OAuth 2.0. Vous pouvez également utiliser OAuth 2.0 Playground pour créer un jeton d'accès pour votre propre compte Google.

Échanger le jeton d'accès OAuth 2.0

Une fois le jeton d'accès OAuth 2.0 créé, vous pouvez l'échanger contre un nouveau jeton à champ d'application limité respectant la limite d'accès aux identifiants. Ce processus implique généralement un agent de service de jetons et un consommateur de jetons:

  • L'agent de service de jetons est chargé de définir les limites d'accès aux identifiants et d'échanger un jeton d'accès contre un jeton à champ d'application limité.

    L'agent de service de jetons peut utiliser une bibliothèque d'authentification compatible pour échanger automatiquement des jetons d'accès ou appeler le service de jetons de sécurité pour échanger des jetons manuellement.

  • Le consommateur de jetons demande un jeton d'accès à champ d'application limité à l'agent de service de jetons, puis utilise ce jeton pour effectuer une autre action.

    Le consommateur de jetons peut utiliser une bibliothèque d'authentification compatible pour actualiser automatiquement les jetons d'accès avant leur expiration. Il peut également actualiser les jetons manuellement, ou encore autoriser l'expiration des jetons sans les actualiser.

Il existe deux façons d'échanger le jeton d'accès contre un jeton à champ d'application limité :

  • Échange de jetons côté client (recommandé) : les clients obtiennent des éléments cryptographiques à partir du serveur de l'API Security Token Service. Les éléments cryptographiques permettent aux clients de générer des jetons à champ d'application limité avec différentes règles de limites d'accès aux identifiants de manière indépendante côté client pendant une durée définie (par exemple, une heure). Cette approche réduit la latence et améliore l'efficacité, en particulier pour les clients qui doivent mettre à jour fréquemment les règles de limites d'accès aux identifiants. Il est également plus efficace pour les applications qui doivent générer de nombreux jetons uniques à portée limitée. Il s'agit de l'approche recommandée, car elle offre de meilleures performances, une meilleure évolutivité et une meilleure compatibilité avec les futures fonctionnalités.

  • Échange de jetons côté serveur : les clients demandent un nouveau jeton à champ d'application limité au serveur de l'API Security Token Service chaque fois qu'une règle de périmètre d'accès aux identifiants change. Cette approche est simple, mais nécessite un aller-retour vers le serveur de l'API Security Token Service pour chaque demande de jeton à portée réduite. Cette approche n'est recommandée que pour les clients qui ont besoin d'une bibliothèque cliente qui ne prend pas en charge l'échange de jetons côté client, en raison de l'aller-retour vers l'API Security Token Service pour chaque demande de jeton à portée réduite.

Échange de jetons côté client

Si vous créez l'agent de service de jetons et le consommateur de jetons dans le langage suivant, vous pouvez utiliser la bibliothèque d'authentification de Google pour échanger et actualiser automatiquement les jetons à l'aide de l'approche côté client.

Java

Pour Java, vous pouvez échanger et actualiser automatiquement des jetons avec la version 1.32.1 ou une version ultérieure de l'artefact com.google.auth:google-auth-library-cab-token-generator.

Pour vérifier la version de votre artefact, exécutez la commande Maven suivante dans le répertoire de votre application :

mvn dependency:list -DincludeArtifactIds=google-auth-library-cab-token-generator

L'exemple suivant montre comment un agent de service de jetons peut générer des jetons à champ d'application limité :

import com.google.auth.credentialaccessboundary.ClientSideCredentialAccessBoundaryFactory;
import com.google.auth.oauth2.AccessToken;
import com.google.auth.oauth2.CredentialAccessBoundary;
import com.google.auth.oauth2.GoogleCredentials;
import dev.cel.common.CelValidationException;
import java.io.IOException;
import java.security.GeneralSecurityException;
  public static AccessToken getTokenFromBroker(String bucketName, String objectPrefix)
      throws IOException {
    // Retrieve the source credentials from ADC.
    GoogleCredentials sourceCredentials =
        GoogleCredentials.getApplicationDefault()
            .createScoped("https://www.googleapis.com/auth/cloud-platform");

    // Initialize the Credential Access Boundary rules.
    String availableResource = "//storage.googleapis.com/projects/_/buckets/" + bucketName;

    // Downscoped credentials will have readonly access to the resource.
    String availablePermission = "inRole:roles/storage.objectViewer";

    // Only objects starting with the specified prefix string in the object name will be allowed
    // read access.
    String expression =
        "resource.name.startsWith('projects/_/buckets/"
            + bucketName
            + "/objects/"
            + objectPrefix
            + "')";

    // Build the AvailabilityCondition.
    CredentialAccessBoundary.AccessBoundaryRule.AvailabilityCondition availabilityCondition =
        CredentialAccessBoundary.AccessBoundaryRule.AvailabilityCondition.newBuilder()
            .setExpression(expression)
            .build();

    // Define the single access boundary rule using the above properties.
    CredentialAccessBoundary.AccessBoundaryRule rule =
        CredentialAccessBoundary.AccessBoundaryRule.newBuilder()
            .setAvailableResource(availableResource)
            .addAvailablePermission(availablePermission)
            .setAvailabilityCondition(availabilityCondition)
            .build();

    // Define the Credential Access Boundary with all the relevant rules.
    CredentialAccessBoundary credentialAccessBoundary =
        CredentialAccessBoundary.newBuilder().addRule(rule).build();

    // Create an instance of ClientSideCredentialAccessBoundaryFactory.
    ClientSideCredentialAccessBoundaryFactory factory =
        ClientSideCredentialAccessBoundaryFactory.newBuilder()
            .setSourceCredential(sourceCredentials)
            .build();

    // Generate the token and pass it to the Token Consumer.
    try {
      return factory.generateToken(credentialAccessBoundary);
    } catch (GeneralSecurityException | CelValidationException e) {
      throw new IOException("Error generating downscoped token", e);
    }
  }

L'exemple suivant montre comment un consommateur de jetons peut utiliser un gestionnaire d'actualisation pour obtenir et actualiser automatiquement les jetons à champ d'application limité :

import com.google.auth.oauth2.AccessToken;
import com.google.auth.oauth2.OAuth2CredentialsWithRefresh;
import com.google.cloud.storage.Blob;
import com.google.cloud.storage.Storage;
import com.google.cloud.storage.StorageOptions;
import java.io.IOException;
  public static String retrieveBlobWithDownscopedToken(
      final String bucketName, final String objectName) throws IOException {
    // You can pass an `OAuth2RefreshHandler` to `OAuth2CredentialsWithRefresh` which will allow the
    // library to seamlessly handle downscoped token refreshes on expiration.
    OAuth2CredentialsWithRefresh.OAuth2RefreshHandler handler =
        new OAuth2CredentialsWithRefresh.OAuth2RefreshHandler() {
          @Override
          public AccessToken refreshAccessToken() throws IOException {
            // The common pattern of usage is to have a token broker pass the downscoped short-lived
            // access tokens to a token consumer via some secure authenticated channel.
            // For illustration purposes, we are generating the downscoped token locally.
            // We want to test the ability to limit access to objects with a certain prefix string
            // in the resource bucket. objectName.substring(0, 3) is the prefix here. This field is
            // not required if access to all bucket resources are allowed. If access to limited
            // resources in the bucket is needed, this mechanism can be used.
            return DownscopedAccessTokenGenerator
                .getTokenFromBroker(bucketName, objectName);
          }
        };

    AccessToken downscopedToken = handler.refreshAccessToken();

    OAuth2CredentialsWithRefresh credentials =
        OAuth2CredentialsWithRefresh.newBuilder()
            .setAccessToken(downscopedToken)
            .setRefreshHandler(handler)
            .build();

    StorageOptions options = StorageOptions.newBuilder().setCredentials(credentials).build();
    Storage storage = options.getService();

    Blob blob = storage.get(bucketName, objectName);
    if (blob == null) {
      return null;
    }
    return new String(blob.getContent());
  }

Échange de jetons côté serveur

Cette section décrit les méthodes suivantes que vous pouvez utiliser pour échanger des jetons via l'approche côté service :

Échanger et actualiser automatiquement le jeton d'accès à l'aide d'une approche côté serveur

Si vous créez l'agent de service de jetons et le consommateur de jetons dans l'un des langages suivants, vous pouvez utiliser la bibliothèque d'authentification de Google pour échanger et actualiser automatiquement les jetons à l'aide de l'approche de génération de jetons côté serveur :

Go

Pour Go, vous pouvez échanger et actualiser automatiquement des jetons avec la version v0.0.0-20210819190943-2bc19b11175f ou une version ultérieure du package golang.org/x/oauth2.

Pour vérifier la version de votre package, exécutez la commande suivante dans le répertoire de votre application :

go list -m golang.org/x/oauth2

L'exemple suivant montre comment un agent de service de jetons peut générer des jetons à champ d'application limité :


import (
	"context"
	"fmt"

	"golang.org/x/oauth2"
	"golang.org/x/oauth2/google"
	"golang.org/x/oauth2/google/downscope"
)

// createDownscopedToken would be run on the token broker in order to generate
// a downscoped access token that only grants access to objects whose name begins with prefix.
// The token broker would then pass the newly created token to the requesting token consumer for use.
func createDownscopedToken(bucketName string, prefix string) error {
	// bucketName := "foo"
	// prefix := "profile-picture-"

	ctx := context.Background()
	// A condition can optionally be provided to further restrict access permissions.
	condition := downscope.AvailabilityCondition{
		Expression:  "resource.name.startsWith('projects/_/buckets/" + bucketName + "/objects/" + prefix + "')",
		Title:       prefix + " Only",
		Description: "Restricts a token to only be able to access objects that start with `" + prefix + "`",
	}
	// Initializes an accessBoundary with one Rule which restricts the downscoped
	// token to only be able to access the bucket "bucketName" and only grants it the
	// permission "storage.objectViewer".
	accessBoundary := []downscope.AccessBoundaryRule{
		{
			AvailableResource:    "//storage.googleapis.com/projects/_/buckets/" + bucketName,
			AvailablePermissions: []string{"inRole:roles/storage.objectViewer"},
			Condition:            &condition, // Optional
		},
	}

	// This Source can be initialized in multiple ways; the following example uses
	// Application Default Credentials.
	var rootSource oauth2.TokenSource

	// You must provide the "https://www.googleapis.com/auth/cloud-platform" scope.
	rootSource, err := google.DefaultTokenSource(ctx, "https://www.googleapis.com/auth/cloud-platform")
	if err != nil {
		return fmt.Errorf("failed to generate rootSource: %w", err)
	}

	// downscope.NewTokenSource constructs the token source with the configuration provided.
	dts, err := downscope.NewTokenSource(ctx, downscope.DownscopingConfig{RootSource: rootSource, Rules: accessBoundary})
	if err != nil {
		return fmt.Errorf("failed to generate downscoped token source: %w", err)
	}
	// Token() uses the previously declared TokenSource to generate a downscoped token.
	tok, err := dts.Token()
	if err != nil {
		return fmt.Errorf("failed to generate token: %w", err)
	}
	// Pass this token back to the token consumer.
	_ = tok
	return nil
}

L'exemple suivant montre comment un consommateur de jetons peut utiliser un gestionnaire d'actualisation pour obtenir et actualiser automatiquement les jetons à champ d'application limité :


import (
	"context"
	"fmt"
	"io"

	"golang.org/x/oauth2/google"
	"golang.org/x/oauth2/google/downscope"

	"cloud.google.com/go/storage"
	"golang.org/x/oauth2"
	"google.golang.org/api/option"
)

// A token consumer should define their own tokenSource. In the Token() method,
// it should send a query to a token broker requesting a downscoped token.
// The token broker holds the root credential that is used to generate the
// downscoped token.
type localTokenSource struct {
	ctx        context.Context
	bucketName string
	brokerURL  string
}

func (lts localTokenSource) Token() (*oauth2.Token, error) {
	var remoteToken *oauth2.Token
	// Usually you would now retrieve remoteToken, an oauth2.Token, from token broker.
	// This snippet performs the same functionality locally.
	accessBoundary := []downscope.AccessBoundaryRule{
		{
			AvailableResource:    "//storage.googleapis.com/projects/_/buckets/" + lts.bucketName,
			AvailablePermissions: []string{"inRole:roles/storage.objectViewer"},
		},
	}
	rootSource, err := google.DefaultTokenSource(lts.ctx, "https://www.googleapis.com/auth/cloud-platform")
	if err != nil {
		return nil, fmt.Errorf("failed to generate rootSource: %w", err)
	}
	dts, err := downscope.NewTokenSource(lts.ctx, downscope.DownscopingConfig{RootSource: rootSource, Rules: accessBoundary})
	if err != nil {
		return nil, fmt.Errorf("failed to generate downscoped token source: %w", err)
	}
	// Token() uses the previously declared TokenSource to generate a downscoped token.
	remoteToken, err = dts.Token()
	if err != nil {
		return nil, fmt.Errorf("failed to generate token: %w", err)
	}

	return remoteToken, nil
}

// getObjectContents will read the contents of an object in Google Storage
// named objectName, contained in the bucket "bucketName".
func getObjectContents(output io.Writer, bucketName string, objectName string) error {
	// bucketName := "foo"
	// prefix := "profile-picture-"

	ctx := context.Background()

	thisTokenSource := localTokenSource{
		ctx:        ctx,
		bucketName: bucketName,
		brokerURL:  "yourURL.com/internal/broker",
	}

	// Wrap the TokenSource in an oauth2.ReuseTokenSource to enable automatic refreshing.
	refreshableTS := oauth2.ReuseTokenSource(nil, thisTokenSource)
	// You can now use the token source to access Google Cloud Storage resources as follows.
	storageClient, err := storage.NewClient(ctx, option.WithTokenSource(refreshableTS))
	if err != nil {
		return fmt.Errorf("failed to create the storage client: %w", err)
	}
	defer storageClient.Close()
	bkt := storageClient.Bucket(bucketName)
	obj := bkt.Object(objectName)
	rc, err := obj.NewReader(ctx)
	if err != nil {
		return fmt.Errorf("failed to retrieve the object: %w", err)
	}
	defer rc.Close()
	data, err := io.ReadAll(rc)
	if err != nil {
		return fmt.Errorf("could not read the object's contents: %w", err)
	}
	// Data now contains the contents of the requested object.
	output.Write(data)
	return nil
}

Java

Pour Java, vous pouvez échanger et actualiser automatiquement des jetons avec la version 1.1.0 ou une version ultérieure de l'artefact com.google.auth:google-auth-library-oauth2-http.

Pour vérifier la version de votre artefact, exécutez la commande Maven suivante dans le répertoire de votre application :

mvn dependency:list -DincludeArtifactIds=google-auth-library-oauth2-http

L'exemple suivant montre comment un agent de service de jetons peut générer des jetons à champ d'application limité :

public static AccessToken getTokenFromBroker(String bucketName, String objectPrefix)
    throws IOException {
  // Retrieve the source credentials from ADC.
  GoogleCredentials sourceCredentials =
      GoogleCredentials.getApplicationDefault()
          .createScoped("https://www.googleapis.com/auth/cloud-platform");

  // Initialize the Credential Access Boundary rules.
  String availableResource = "//storage.googleapis.com/projects/_/buckets/" + bucketName;

  // Downscoped credentials will have readonly access to the resource.
  String availablePermission = "inRole:roles/storage.objectViewer";

  // Only objects starting with the specified prefix string in the object name will be allowed
  // read access.
  String expression =
      "resource.name.startsWith('projects/_/buckets/"
          + bucketName
          + "/objects/"
          + objectPrefix
          + "')";

  // Build the AvailabilityCondition.
  CredentialAccessBoundary.AccessBoundaryRule.AvailabilityCondition availabilityCondition =
      CredentialAccessBoundary.AccessBoundaryRule.AvailabilityCondition.newBuilder()
          .setExpression(expression)
          .build();

  // Define the single access boundary rule using the above properties.
  CredentialAccessBoundary.AccessBoundaryRule rule =
      CredentialAccessBoundary.AccessBoundaryRule.newBuilder()
          .setAvailableResource(availableResource)
          .addAvailablePermission(availablePermission)
          .setAvailabilityCondition(availabilityCondition)
          .build();

  // Define the Credential Access Boundary with all the relevant rules.
  CredentialAccessBoundary credentialAccessBoundary =
      CredentialAccessBoundary.newBuilder().addRule(rule).build();

  // Create the downscoped credentials.
  DownscopedCredentials downscopedCredentials =
      DownscopedCredentials.newBuilder()
          .setSourceCredential(sourceCredentials)
          .setCredentialAccessBoundary(credentialAccessBoundary)
          .build();

  // Retrieve the token.
  // This will need to be passed to the Token Consumer.
  AccessToken accessToken = downscopedCredentials.refreshAccessToken();
  return accessToken;
}

L'exemple suivant montre comment un consommateur de jetons peut utiliser un gestionnaire d'actualisation pour obtenir et actualiser automatiquement les jetons à champ d'application limité :

public static void tokenConsumer(final String bucketName, final String objectName)
    throws IOException {
  // You can pass an `OAuth2RefreshHandler` to `OAuth2CredentialsWithRefresh` which will allow the
  // library to seamlessly handle downscoped token refreshes on expiration.
  OAuth2CredentialsWithRefresh.OAuth2RefreshHandler handler =
      new OAuth2CredentialsWithRefresh.OAuth2RefreshHandler() {
        @Override
        public AccessToken refreshAccessToken() throws IOException {
          // The common pattern of usage is to have a token broker pass the downscoped short-lived
          // access tokens to a token consumer via some secure authenticated channel.
          // For illustration purposes, we are generating the downscoped token locally.
          // We want to test the ability to limit access to objects with a certain prefix string
          // in the resource bucket. objectName.substring(0, 3) is the prefix here. This field is
          // not required if access to all bucket resources are allowed. If access to limited
          // resources in the bucket is needed, this mechanism can be used.
          return getTokenFromBroker(bucketName, objectName.substring(0, 3));
        }
      };

  // Downscoped token retrieved from token broker.
  AccessToken downscopedToken = handler.refreshAccessToken();

  // Create the OAuth2CredentialsWithRefresh from the downscoped token and pass a refresh handler
  // which will handle token expiration.
  // This will allow the consumer to seamlessly obtain new downscoped tokens on demand every time
  // token expires.
  OAuth2CredentialsWithRefresh credentials =
      OAuth2CredentialsWithRefresh.newBuilder()
          .setAccessToken(downscopedToken)
          .setRefreshHandler(handler)
          .build();

  // Use the credentials with the Cloud Storage SDK.
  StorageOptions options = StorageOptions.newBuilder().setCredentials(credentials).build();
  Storage storage = options.getService();

  // Call Cloud Storage APIs.
  Blob blob = storage.get(bucketName, objectName);
  String content = new String(blob.getContent());
  System.out.println(
      "Retrieved object, "
          + objectName
          + ", from bucket,"
          + bucketName
          + ", with content: "
          + content);
}

Node.js

Pour Node.js, vous pouvez échanger et actualiser automatiquement des jetons avec la version 7.9.0 ou une version ultérieure du package google-auth-library.

Pour vérifier la version de votre package, exécutez la commande suivante dans le répertoire de votre application :

npm list google-auth-library

L'exemple suivant montre comment un agent de service de jetons peut générer des jetons à champ d'application limité :

// Imports the Google Auth libraries.
const {GoogleAuth, DownscopedClient} = require('google-auth-library');
/**
 * Simulates token broker generating downscoped tokens for specified bucket.
 *
 * @param bucketName The name of the Cloud Storage bucket.
 * @param objectPrefix The prefix string of the object name. This is used
 *        to ensure access is restricted to only objects starting with this
 *        prefix string.
 */
async function getTokenFromBroker(bucketName, objectPrefix) {
  const googleAuth = new GoogleAuth({
    scopes: 'https://www.googleapis.com/auth/cloud-platform',
  });

  // Define the Credential Access Boundary object.
  const cab = {
    // Define the access boundary.
    accessBoundary: {
      // Define the single access boundary rule.
      accessBoundaryRules: [
        {
          availableResource: `//storage.googleapis.com/projects/_/buckets/${bucketName}`,
          // Downscoped credentials will have readonly access to the resource.
          availablePermissions: ['inRole:roles/storage.objectViewer'],
          // Only objects starting with the specified prefix string in the object name
          // will be allowed read access.
          availabilityCondition: {
            expression:
              "resource.name.startsWith('projects/_/buckets/" +
              `${bucketName}/objects/${objectPrefix}')`,
          },
        },
      ],
    },
  };

  // Obtain an authenticated client via ADC.
  const client = await googleAuth.getClient();

  // Use the client to create a DownscopedClient.
  const cabClient = new DownscopedClient(client, cab);

  // Refresh the tokens.
  const refreshedAccessToken = await cabClient.getAccessToken();

  // This will need to be passed to the token consumer.
  return refreshedAccessToken;
}

L'exemple suivant montre comment un consommateur de jetons peut fournir un gestionnaire d'actualisation qui obtient et actualise automatiquement les jetons à champ d'application limité :

// Imports the Google Auth and Google Cloud libraries.
const {OAuth2Client} = require('google-auth-library');
const {Storage} = require('@google-cloud/storage');
/**
 * Simulates token consumer generating calling GCS APIs using generated
 * downscoped tokens for specified bucket.
 *
 * @param bucketName The name of the Cloud Storage bucket.
 * @param objectName The name of the object in the Cloud Storage bucket
 *        to read.
 */
async function tokenConsumer(bucketName, objectName) {
  // Create the OAuth credentials (the consumer).
  const oauth2Client = new OAuth2Client();
  // We are defining a refresh handler instead of a one-time access
  // token/expiry pair.
  // This will allow the consumer to obtain new downscoped tokens on
  // demand every time a token is expired, without any additional code
  // changes.
  oauth2Client.refreshHandler = async () => {
    // The common pattern of usage is to have a token broker pass the
    // downscoped short-lived access tokens to a token consumer via some
    // secure authenticated channel. For illustration purposes, we are
    // generating the downscoped token locally. We want to test the ability
    // to limit access to objects with a certain prefix string in the
    // resource bucket. objectName.substring(0, 3) is the prefix here. This
    // field is not required if access to all bucket resources are allowed.
    // If access to limited resources in the bucket is needed, this mechanism
    // can be used.
    const refreshedAccessToken = await getTokenFromBroker(
      bucketName,
      objectName.substring(0, 3)
    );
    return {
      access_token: refreshedAccessToken.token,
      expiry_date: refreshedAccessToken.expirationTime,
    };
  };

  const storageOptions = {
    projectId: process.env.GOOGLE_CLOUD_PROJECT,
    authClient: oauth2Client,
  };

  const storage = new Storage(storageOptions);
  const downloadFile = await storage
    .bucket(bucketName)
    .file(objectName)
    .download();
  console.log(downloadFile.toString('utf8'));
}

Python

Pour Python, vous pouvez échanger et actualiser automatiquement des jetons avec la version 2.0.0 ou une version ultérieure du package google-auth.

Pour vérifier la version de votre package, exécutez la commande suivante dans l'environnement où le package est installé :

pip show google-auth

L'exemple suivant montre comment un agent de service de jetons peut générer des jetons à champ d'application limité :

import google.auth

from google.auth import downscoped
from google.auth.transport import requests

def get_token_from_broker(bucket_name, object_prefix):
    """Simulates token broker generating downscoped tokens for specified bucket.

    Args:
        bucket_name (str): The name of the Cloud Storage bucket.
        object_prefix (str): The prefix string of the object name. This is used
            to ensure access is restricted to only objects starting with this
            prefix string.

    Returns:
        Tuple[str, datetime.datetime]: The downscoped access token and its expiry date.
    """
    # Initialize the Credential Access Boundary rules.
    available_resource = f"//storage.googleapis.com/projects/_/buckets/{bucket_name}"
    # Downscoped credentials will have readonly access to the resource.
    available_permissions = ["inRole:roles/storage.objectViewer"]
    # Only objects starting with the specified prefix string in the object name
    # will be allowed read access.
    availability_expression = (
        "resource.name.startsWith('projects/_/buckets/{}/objects/{}')".format(
            bucket_name, object_prefix
        )
    )
    availability_condition = downscoped.AvailabilityCondition(availability_expression)
    # Define the single access boundary rule using the above properties.
    rule = downscoped.AccessBoundaryRule(
        available_resource=available_resource,
        available_permissions=available_permissions,
        availability_condition=availability_condition,
    )
    # Define the Credential Access Boundary with all the relevant rules.
    credential_access_boundary = downscoped.CredentialAccessBoundary(rules=[rule])

    # Retrieve the source credentials via ADC.
    source_credentials, _ = google.auth.default()
    if source_credentials.requires_scopes:
        source_credentials = source_credentials.with_scopes(
            ["https://www.googleapis.com/auth/cloud-platform"]
        )

    # Create the downscoped credentials.
    downscoped_credentials = downscoped.Credentials(
        source_credentials=source_credentials,
        credential_access_boundary=credential_access_boundary,
    )

    # Refresh the tokens.
    downscoped_credentials.refresh(requests.Request())

    # These values will need to be passed to the token consumer.
    access_token = downscoped_credentials.token
    expiry = downscoped_credentials.expiry
    return (access_token, expiry)

L'exemple suivant montre comment un consommateur de jetons peut fournir un gestionnaire d'actualisation qui obtient et actualise automatiquement les jetons à champ d'application limité :

from google.cloud import storage
from google.oauth2 import credentials

def token_consumer(bucket_name, object_name):
    """Tests token consumer readonly access to the specified object.

    Args:
        bucket_name (str): The name of the Cloud Storage bucket.
        object_name (str): The name of the object in the Cloud Storage bucket
            to read.
    """

    # Create the OAuth credentials from the downscoped token and pass a
    # refresh handler to handle token expiration. We are passing a
    # refresh_handler instead of a one-time access token/expiry pair.
    # This will allow the consumer to obtain new downscoped tokens on
    # demand every time a token is expired, without any additional code
    # changes.
    def refresh_handler(request, scopes=None):
        # The common pattern of usage is to have a token broker pass the
        # downscoped short-lived access tokens to a token consumer via some
        # secure authenticated channel.
        # For illustration purposes, we are generating the downscoped token
        # locally.
        # We want to test the ability to limit access to objects with a certain
        # prefix string in the resource bucket. object_name[0:3] is the prefix
        # here. This field is not required if access to all bucket resources are
        # allowed. If access to limited resources in the bucket is needed, this
        # mechanism can be used.
        return get_token_from_broker(bucket_name, object_prefix=object_name[0:3])

    creds = credentials.Credentials(
        None,
        scopes=["https://www.googleapis.com/auth/cloud-platform"],
        refresh_handler=refresh_handler,
    )

    # Initialize a Cloud Storage client with the oauth2 credentials.
    storage_client = storage.Client(credentials=creds)
    # The token broker has readonly access to the specified bucket object.
    bucket = storage_client.bucket(bucket_name)
    blob = bucket.blob(object_name)
    print(blob.download_as_bytes().decode("utf-8"))

Échanger et actualiser manuellement le jeton d'accès

Un agent de service de jetons peut utiliser l'API Security Token Service pour échanger un jeton d'accès contre un jeton d'accès à champ d'application limité. Il peut ensuite fournir le jeton à champ d'application limité à un consommateur de jetons.

Pour échanger le jeton d'accès, utilisez la méthode HTTP et l'URL suivantes :

POST https://sts.googleapis.com/v1/token

Définissez l'en-tête Content-Type de la requête sur application/x-www-form-urlencoded. Spécifiez les champs suivants dans le corps de la requête :

Champs
grant_type

string

Utilisez la valeur urn:ietf:params:oauth:grant-type:token-exchange.

options

string

Limite d'accès aux identifiants au format JSON, encodée avec le encodage-pourcent.

requested_token_type

string

Utilisez la valeur urn:ietf:params:oauth:token-type:access_token.

subject_token

string

Jeton d'accès OAuth 2.0 que vous souhaitez échanger.

subject_token_type

string

Utilisez la valeur urn:ietf:params:oauth:token-type:access_token.

La réponse est un objet JSON qui contient les champs suivants :

Champs
access_token

string

Jeton d'accès OAuth 2.0 à champ d'application limité respectant la limite d'accès aux identifiants.

expires_in

number

 : délai avant expiration du nouveau jeton d'accès à champ d'application limité, exprimé en secondes.

Ce champ n'est inclus que si le jeton d'accès d'origine représente un compte de service. Lorsque ce champ n'est pas inclus, le délai avant expiration du jeton à champ d'application limité est le même que celui du jeton d'accès d'origine.

issued_token_type

string

Contient la valeur urn:ietf:params:oauth:token-type:access_token.

token_type

string

Contient la valeur Bearer.

Par exemple, si une limite d'accès aux identifiants au format JSON est stockée dans le fichier ./access-boundary.json, vous pouvez utiliser la commande curl suivante pour échanger le jeton d'accès. Remplacez original-token par le jeton d'accès d'origine :

curl -H "Content-Type:application/x-www-form-urlencoded" \
    -X POST \
    https://sts.googleapis.com/v1/token \
    -d "grant_type=urn:ietf:params:oauth:grant-type:token-exchange&subject_token_type=urn:ietf:params:oauth:token-type:access_token&requested_token_type=urn:ietf:params:oauth:token-type:access_token&subject_token=original-token" \
    --data-urlencode "options=$(cat ./access-boundary.json)"

La réponse est semblable à l'exemple suivant :

{
  "access_token": "ya29.dr.AbCDeFg-123456...",
  "issued_token_type": "urn:ietf:params:oauth:token-type:access_token",
  "token_type": "Bearer",
  "expires_in": 3600
}

Lorsqu'un consommateur de jetons demande un jeton à champ d'application limité, l'agent de service de jetons répond avec le jeton à champ d'application limité et le nombre de secondes avant expiration. Si le jeton a expiré, le serveur rejette la requête. Pour actualiser le jeton à champ d'application limité, le consommateur peut demander un jeton à champ d'application limité à l'agent avant l'expiration du jeton existant.

Étapes suivantes