יצירת פרטי כניסה לטווח קצר עם הרשאות מצומצמות

בדף הזה מוסבר איך משתמשים בגבולות גישה לפרטי כניסה כדי ליצור אסימון גישה מסוג OAuth 2.0 עם הרשאות מצומצמות ל-Cloud Storage.

תהליך היצירה של אסימון עם הרשאות מצומצמות כולל את השלבים הבאים:

  1. מעניקים את התפקידים המתאימים ב-IAM לחשבון משתמש או לחשבון שירות.
  2. מגדירים גבולות גישה לפרטי הכניסה שקובעים את הרף העליון של ההרשאות שיהיו לחשבון המשתמש או לחשבון השירות.
  3. יוצרים אסימון גישה מסוג OAuth 2.0 לחשבון המשתמש או לחשבון השירות.
  4. מחליפים את אסימון הגישה מסוג OAuth 2.0 באסימון חדש שעונה על גבולות הגישה.

לאחר מכן תוכלו להשתמש באסימון הגישה החדש והמצומצם מסוג OAuth 2.0 כדי לאמת בקשות ל-Cloud Storage.

לפני שמתחילים

לפני שאתם משתמשים בגבולות גישה לפרטי הכניסה, חשוב לוודא שהמקרה שלכם עונה על הקריטריונים הבאים:

  • אתם צריכים לצמצם את ההרשאות רק ל-Cloud Storage, ולא לשירותים אחרים שלGoogle Cloud .

    אם צריך לצמצם את ההרשאות לשירותים נוספים של Google Cloud, אפשר ליצור מספר חשבונות שירות ולהקצות קבוצת תפקידים שונה לכל חשבון שירות.

  • אפשר להשתמש באסימוני גישה מסוג OAuth 2.0 לצורך אימות. אי אפשר להגדיר גבולות גישה לסוגים אחרים של פרטי כניסה עם תוקף קצר.

בנוסף, צריך להפעיל את ממשקי ה-API הנדרשים:

  • 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

מתן תפקידים ב-IAM

גבולות הגישה לפרטי הכניסה מגדירים רף עליון להרשאות שיהיו למשאב. הם יכולים להוריד מחשבון המשתמש הרשאות קיימות, אבל לא להוסיף הרשאות שאין לו.

לכן, צריך לתת לחשבון המשתמש תפקידים עם ההרשאות הנדרשות בקטגוריה של Cloud Storage או במשאב ברמה גבוהה יותר, למשל הפרויקט.

לדוגמה, נניח שאתם צריכים ליצור פרטי כניסה עם תוקף קצר והרשאות מצומצמות, שמאפשרים לחשבון שירות ליצור אובייקטים בקטגוריה:

  • לכל הפחות, אתם צריכים לתת לחשבון השירות תפקיד שכולל את ההרשאה storage.objects.create, למשל Storage Object Creator‏ (roles/storage.objectCreator). גבולות הגישה לפרטי הכניסה צריכים לכלול את ההרשאה הזו.
  • תוכלו לתת גם תפקיד שכולל הרשאות נוספות, כמו Storage Object Admin ‏(roles/storage.objectAdmin). חשבון השירות יוכל להשתמש רק בהרשאות שמופיעות גם בתפקיד שניתן לו וגם בגבולות הגישה.

למידע נוסף על תפקידים מוגדרים מראש ב-Cloud Storage

הגדרת גבולות הגישה לפרטי הכניסה

גבולות גישה לפרטי הכניסה הם אובייקט שמכיל רשימה של כללים להגבלת הגישה. הכללים מורכבים מפרמטרים שמציינים את הרף העליון של ההרשאות שיהיו לחשבון המשתמש או לחשבון השירות. כדי להגדיר גבולות גישה לפרטי הכניסה, יוצרים אובייקט JSON שמפרט את הכללים להגבלת הגישה ואת הפרמטרים שלהם.

זוהי דוגמה לגבול גישה לפרטי כניסה:

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

מחליפים את מה שכתוב בשדות הבאים:

  • ROLE_ID: המזהה של תפקיד מוגדר מראש או תפקיד בהתאמה אישית שמגדיר את הרף העליון של ההרשאות שיהיו למשאב. לדוגמה, roles/storage.objectViewer. כדי לציין כמה תפקידים, מוסיפים שורה חדשה עם הקידומת inRole: ואחריה מזהה התפקיד. רק ההרשאות שכלולות בתפקידים שצוינו יינתנו.
  • BUCKET_NAME: השם של קטגוריית Cloud Storage שעליה חל הכלל.
  • CONDITION: אופציונלי. ביטוי תנאי שמציין את האובייקטים של Cloud Storage שאליהם יינתנו ההרשאות. לדוגמה, התנאי הבא נותן הרשאות רק לאובייקטים שהשם שלהם מתחיל ב-customer-a:

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

מידע נוסף על יצירה והתאמה אישית של גבולות גישה לפרטי כניסה זמין במאמר רכיבים של גבול גישה לפרטי כניסה.

במאמר דוגמאות לגבולות גישה לפרטי כניסה מופיעות דוגמאות לתרחישי שימוש אפשריים בגבולות גישה לפרטי כניסה.

יצירת אסימון גישה מסוג OAuth 2.0

לפני שיוצרים פרטי כניסה עם תוקף קצר והרשאות מצומצמות, צריך ליצור אסימון גישה רגיל מסוג OAuth 2.0. אחר כך אפשר להחליף את פרטי הכניסה הרגילים בפרטי הגישה המצומצמים. כשיוצרים את אסימון הגישה, צריך להשתמש ב-OAuth 2.0 בהיקף https://www.googleapis.com/auth/cloud-platform.

כדי ליצור אסימון גישה לחשבון שירות, אפשר להשתמש בהרשאה באמצעות OAuth 2.0 משרת-לשרת או ב-Service Account Credentials API כדי ליצור אסימון גישה מסוג OAuth 2.0.

איך יוצרים אסימוני גישה מסוג OAuth 2.0? תוכלו גם להשתמש ב-OAuth 2.0 Playground כדי ליצור אסימון גישה לחשבון Google שלכם.

החלפת אסימון הגישה מסוג OAuth 2.0

אחרי שיצרתם אסימון גישה מסוג OAuth 2.0, תוכלו להחליף אותו באסימון בהיקף שעומד בגבולות הגישה לפרטי הכניסה. בדרך כלל התהליך הזה כולל ברוקר וצרכן של האסימון:

  • ברוקר האסימון אחראי להגדיר את גבולות הגישה לפרטי הכניסה ולהחליף את אסימון הגישה באחד עם היקף מצומצם.

    ברוקר האסימון יכול להשתמש בספריית אימות נתמכת כדי להחליף אסימוני גישה אוטומטית, או לקרוא לשירות Security Token כדי להחליף אסימונים ידנית.

  • צרכן האסימון מבקש מברוקר האסימון גישה עם היקף מצומצם ואז משתמש בו כדי לבצע פעולה נוספת.

    צרכן האסימון יכול להשתמש בספריית אימות נתמכת כדי לרענן את אסימוני הגישה באופן אוטומטי לפני שהתוקף שלהם פג. לחלופין, הוא יכול לרענן ידנית את האסימונים, או לאפשר לתוקף של האסימונים לפוג בלי לרענן אותם.

יש שתי דרכים להחליף את אסימון הגישה באסימון עם היקף מצומצם:

  • החלפת אסימונים בצד הלקוח (מומלץ): לקוחות מקבלים חומרים קריפטוגרפיים משרת Security Token Service API. החומרים הקריפטוגרפיים מאפשרים ללקוחות ליצור אסימונים עם היקף מצומצם עם כללים שונים של גבולות הגישה לפרטי הכניסה באופן עצמאי בצד הלקוח למשך זמן מוגדר (לדוגמה, שעה אחת). הגישה הזו מפחיתה את זמן האחזור ומשפרת את היעילות, במיוחד עבור לקוחות שנדרשים לעדכן לעיתים קרובות את כללי הגבלת הגישה לפרטי הכניסה. השיטה הזו גם יעילה יותר לאפליקציות שצריכות ליצור הרבה אסימונים ייחודיים עם הרשאות מוגבלות. זהו הגישה המומלצת כי היא מספקת ביצועים טובים יותר, יכולת הרחבה ותאימות לתכונות עתידיות.

  • החלפת אסימונים בצד השרת: לקוחות מבקשים אסימון חדש בהיקף מצומצם משרת Security Token Service API בכל פעם שמשתנה כלל של Credential Access Boundary. הגישה הזו פשוטה, אבל היא מחייבת שליחה של בקשה לשרת של Security Token Service API וקבלת תשובה ממנו עבור כל בקשה ליצירת אסימון עם הרשאות מוגבלות. הגישה הזו מומלצת רק ללקוחות שנדרשת להם ספריית לקוח שלא תומכת בהחלפת אסימונים בצד הלקוח, בגלל הטיפול בבקשות לאסימונים עם היקף מצומצם באמצעות ה-API של Security Token Service.

החלפת טוקנים בצד הלקוח

אם אתם יוצרים את הברוקר ואת הצרכן של האסימון בשפה הבאה, תוכלו להשתמש בספריית האימות של Google כדי להחליף ולרענן אסימונים אוטומטית באמצעות הגישה בצד הלקוח.

Java

ב-Java, אפשר להחליף או לרענן אסימונים אוטומטית באמצעות ארטיפקט של com.google.auth:google-auth-library-cab-token-generator החל מגרסה 1.32.1.

כדי לבדוק את גרסת הארטיפקט, מריצים את פקודת Maven הבאה בספריית האפליקציות:

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

כך הברוקר יכול ליצור אסימונים עם היקף מצומצם:

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);
    }
  }

כך צרכן האסימון יכול להשתמש ב-handler של רענון כדי לקבל ולרענן אוטומטית אסימונים עם היקף מצומצם:

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());
  }

החלפת טוקנים בצד השרת

בקטע הזה מתוארות השיטות הבאות שבהן אפשר להשתמש כדי להחליף אסימונים באמצעות הגישה בצד השרת:

איך מחליפים ומרעננים אוטומטית את אסימון הגישה באמצעות גישה בצד השרת

אם אתם יוצרים את הברוקר ואת הצרכן של האסימון באחת מהשפות הבאות, תוכלו להשתמש בספריית האימות של Google כדי להחליף ולרענן אסימונים אוטומטית באמצעות הגישה של יצירת אסימונים בצד השרת:

Go

ב-Go, אפשר להחליף או לרענן אסימונים אוטומטית באמצעות חבילת golang.org/x/oauth2 החל מגרסה v0.0.0-20210819190943-2bc19b11175f.

כדי לבדוק את גרסת החבילה, מריצים את הפקודה הבאה בספריית האפליקציות:

go list -m golang.org/x/oauth2

כך הברוקר יכול ליצור אסימונים עם היקף מצומצם:


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
}

כך צרכן האסימון יכול להשתמש ב-handler של רענון כדי לקבל ולרענן אוטומטית אסימונים עם היקף מצומצם:


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

ב-Java, אפשר להחליף או לרענן אסימונים אוטומטית באמצעות ארטיפקט של com.google.auth:google-auth-library-oauth2-http החל מגרסה 1.1.0.

כדי לבדוק את גרסת הארטיפקט, מריצים את פקודת Maven הבאה בספריית האפליקציות:

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

כך הברוקר יכול ליצור אסימונים עם היקף מצומצם:

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;
}

כך צרכן האסימון יכול להשתמש ב-handler של רענון כדי לקבל ולרענן אוטומטית אסימונים עם היקף מצומצם:

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

ב-Node.js, אפשר להחליף או לרענן אסימונים אוטומטית באמצעות חבילת google-auth-library החל מגרסה 7.9.0.

כדי לבדוק את גרסת החבילה, מריצים את הפקודה הבאה בספריית האפליקציות:

npm list google-auth-library

כך הברוקר יכול ליצור אסימונים עם היקף מצומצם:

// 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;
}

כך צרכן האסימון יכול לספק handler של רענון כדי לקבל ולרענן אוטומטית אסימונים עם היקף מצומצם:

// 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

ב-Python, אפשר להחליף או לרענן אסימונים אוטומטית באמצעות חבילת google-auth החל מגרסה 2.0.0.

כדי לבדוק את גרסת החבילה, מריצים את הפקודה הבאה בסביבה שבה החבילה מותקנת:

pip show google-auth

כך הברוקר יכול ליצור אסימונים עם היקף מצומצם:

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)

כך צרכן האסימון יכול לספק handler של רענון כדי לקבל ולרענן אוטומטית אסימונים עם היקף מצומצם:

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"))

איך מחליפים ומרעננים ידנית את אסימון הגישה

הברוקר של האסימון יכול להשתמש ב-API של Security Token Service כדי להחליף את אסימון הגישה באחד אחר עם היקף מצומצם ולתת את האסימון המצומצם לצרכן.

כדי להחליף את אסימון הגישה, משתמשים ב-method הבא ב-HTTP ובכתובת ה-URL:

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

מגדירים את הכותרת Content-Type בבקשה ל-application/x-www-form-urlencoded. בגוף הבקשה, כוללים את השדות הבאים:

שדות
grant_type

string

משתמשים בערך urn:ietf:params:oauth:grant-type:token-exchange.

options

string

גבולות הגישה לפרטי הכניסה בפורמט JSON, עם קידוד באחוזים.

requested_token_type

string

משתמשים בערך urn:ietf:params:oauth:token-type:access_token.

subject_token

string

אסימון הגישה מסוג OAuth 2.0 שאותו רוצים להחליף.

subject_token_type

string

משתמשים בערך urn:ietf:params:oauth:token-type:access_token.

התגובה היא אובייקט JSON שמכיל את השדות הבאים:

שדות
access_token

string

אסימון גישה מצומצם מסוג OAuth 2.0, שעומד בדרישות של גבולות הגישה לפרטי הכניסה.

expires_in

number

תוך כמה שניות יפוג התוקף של האסימון המצומצם.

השדה הזה נכלל רק אם אסימון הגישה המקורי מייצג חשבון שירות. אם השדה הזה לא נכלל, לאסימון המצומצם יש את אותו זמן תפוגה כמו אסימון הגישה המקורי.

issued_token_type

string

הוא מכיל את הערך urn:ietf:params:oauth:token-type:access_token.

token_type

string

הוא מכיל את הערך Bearer.

לדוגמה, אם גבולות הגישה בפורמט JSON שמורים בקובץ ./access-boundary.json, אפשר להשתמש בפקודה הבאה ב-curl כדי להחליף את אסימון הגישה. מחליפים את original-token באסימון הגישה המקורי:

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)"

התגובה אמורה להיראות כך:

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

כשצרכן דורש אסימון עם היקף מצומצם, הברוקר מגיב גם עם האסימון המצומצם וגם עם מספר השניות עד לתפוגה שלו. אם פג התוקף של האסימון, השרת דוחה את הבקשה. כדי לרענן את האסימון המצומצם, הצרכן יכול לבקש אסימון חדש לפני שהתוקף יפוג.

המאמרים הבאים