אימות עומסי עבודה באמצעות ספריות אימות של Google Cloud

במסמך הזה מוסבר איך להשתמש באיחוד שירותי אימות הזהות של עומסי עבודה עם ספריות אימות, שנקראות ספריות auth, כדי לאמת עומסי עבודה מספקי זהויות של צד שלישי כמו AWS,‏ Microsoft Azure וספקים שתומכים ב-OpenID Connect ‏ (OIDC) או ב-SAML 2.0. Google Cloud

איחוד שירותי אימות הזהות של עומסי עבודה מאפשר לאפליקציות שפועלות מחוץ ל- Google CloudGoogle Cloud לגשת למשאבים בלי להשתמש במפתחות של חשבונות שירות. ספריות האימות של Google מאפשרות זאת על ידי המרה של פרטי כניסה חיצוניים באסימוני גישה Google Cloud לטווח קצר.

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

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

  1. Enable the required 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

  2. מגדירים את איחוד שירותי אימות הזהות של עומסי עבודה באמצעות ספק הזהויות.

אימות באמצעות מנגנון האישורים הרגיל

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

קובץ התצורה של פרטי הכניסה, שבדרך כלל נטען באמצעות משתנה הסביבה GOOGLE_APPLICATION_CREDENTIALS, יכול להנחות את ספריות האימות לקבל את טוקן הנושא של הצד השלישי באחת מהשיטות הבאות:

  • מקור הקובץ: הספרייה קוראת את אסימון הנושא מקובץ מקומי. תהליך נפרד צריך לוודא שהקובץ הזה מכיל טוקן תקין שלא פג תוקפו.
  • מכתובת URL: הספרייה מאחזרת את אסימון הנושא על ידי שליחת בקשה לנקודת קצה (endpoint) של כתובת URL מקומית שצוינה.
  • מקור קובץ הפעלה: הספריות מריצות פקודת קובץ הפעלה מוגדרת. הפלט הרגיל של קובץ ההפעלה אמור להכיל את אסימון הנושא.
  1. יוצרים את קובץ התצורה של פרטי הכניסה לספק הספציפי שלכם:

  2. משתמשים בקובץ התצורה של פרטי הכניסה כדי לבצע אימות.

    כדי לאפשר לספריות הלקוח Google Cloud לאתר ולהשתמש באופן אוטומטי בקובץ התצורה של פרטי הכניסה, צריך להגדיר את משתנה הסביבה GOOGLE_APPLICATION_CREDENTIALS לנתיב של קובץ ה-JSON שנוצר.

    מייצאים את משתנה הסביבה במעטפת: bash export GOOGLE_APPLICATION_CREDENTIALS=/path/to/your/config.json

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

בדוגמת הקוד הבאה אפשר לראות איך מבצעים קריאה מאומתת ל- Google Cloud API:

Node.js

במאמר ספריות הלקוח של IAM מוסבר איך להתקין את ספריית הלקוח ולהשתמש בה ב-IAM. למידע נוסף, קראו את מאמרי העזרה של ‎IAM Node.js API‎.

כדי לבצע אימות ב-IAM, צריך להגדיר את Application Default Credentials. מידע נוסף זמין במאמר הגדרת אימות לסביבת פיתוח מקומית.

// Imports the Google Cloud client library.
const {Storage} = require('@google-cloud/storage');

// Instantiates a client. If you don't specify credentials when constructing
// the client, the client library will look for credentials in the
// environment.
const storage = new Storage();
// Makes an authenticated API request.
async function listBuckets() {
  try {
    const results = await storage.getBuckets();

    const [buckets] = results;

    console.log('Buckets:');
    buckets.forEach(bucket => {
      console.log(bucket.name);
    });
  } catch (err) {
    console.error('ERROR:', err);
  }
}
listBuckets();

Python

במאמר ספריות הלקוח של IAM מוסבר איך להתקין את ספריית הלקוח ולהשתמש בה ב-IAM. למידע נוסף, קראו את מאמרי העזרה של ‎IAM Python API‎.

כדי לבצע אימות ב-IAM, צריך להגדיר את Application Default Credentials. מידע נוסף זמין במאמר הגדרת אימות לסביבת פיתוח מקומית.

def implicit():
    from google.cloud import storage

    # If you don't specify credentials when constructing the client, the
    # client library will look for credentials in the environment.
    storage_client = storage.Client()

    # Make an authenticated API request
    buckets = list(storage_client.list_buckets())
    print(buckets)

Java

במאמר ספריות הלקוח של IAM מוסבר איך להתקין את ספריית הלקוח ולהשתמש בה ב-IAM. למידע נוסף, קראו את מאמרי העזרה של ‎IAM Java API‎.

כדי לבצע אימות ב-IAM, צריך להגדיר את Application Default Credentials. מידע נוסף זמין במאמר הגדרת אימות לסביבת פיתוח מקומית.

static void authImplicit() {
  // If you don't specify credentials when constructing the client, the client library will
  // look for credentials via the environment variable GOOGLE_APPLICATION_CREDENTIALS.
  Storage storage = StorageOptions.getDefaultInstance().getService();

  System.out.println("Buckets:");
  Page<Bucket> buckets = storage.list();
  for (Bucket bucket : buckets.iterateAll()) {
    System.out.println(bucket.toString());
  }
}

Go

במאמר ספריות הלקוח של IAM מוסבר איך להתקין את ספריית הלקוח ולהשתמש בה ב-IAM. למידע נוסף, קראו את מאמרי העזרה של ‎IAM Go API‎.

כדי לבצע אימות ב-IAM, צריך להגדיר את Application Default Credentials. מידע נוסף זמין במאמר הגדרת אימות לסביבת פיתוח מקומית.

import (
	"context"
	"fmt"
	"io"

	"cloud.google.com/go/storage"
	"google.golang.org/api/iterator"
)

// authenticateImplicitWithAdc uses Application Default Credentials
// to automatically find credentials and authenticate.
func authenticateImplicitWithAdc(w io.Writer, projectId string) error {
	// projectId := "your_project_id"

	ctx := context.Background()

	// NOTE: Replace the client created below with the client required for your application.
	// Note that the credentials are not specified when constructing the client.
	// The client library finds your credentials using ADC.
	client, err := storage.NewClient(ctx)
	if err != nil {
		return fmt.Errorf("NewClient: %w", err)
	}
	defer client.Close()

	it := client.Buckets(ctx, projectId)
	for {
		bucketAttrs, err := it.Next()
		if err == iterator.Done {
			break
		}
		if err != nil {
			return err
		}
		fmt.Fprintf(w, "Bucket: %v\n", bucketAttrs.Name)
	}

	fmt.Fprintf(w, "Listed all storage buckets.\n")

	return nil
}

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

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

גישה למשאבים מ-AWS

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

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

בדוגמאות הקוד הבאות אפשר לראות איך מגדירים גישה למשאבים של AWS באמצעות ספק אישורים בהתאמה אישית. Google Cloud

Node.js

במאמר ספריות הלקוח של IAM מוסבר איך להתקין את ספריית הלקוח ולהשתמש בה ב-IAM. למידע נוסף, קראו את מאמרי העזרה של ‎IAM Node.js API‎.

כדי לבצע אימות ב-IAM, צריך להגדיר את Application Default Credentials. מידע נוסף זמין במאמר הגדרת אימות לסביבת פיתוח מקומית.

const {AwsClient} = require('google-auth-library');
const {fromNodeProviderChain} = require('@aws-sdk/credential-providers');
const fs = require('fs');
const path = require('path');
const {STSClient} = require('@aws-sdk/client-sts');
const {Storage} = require('@google-cloud/storage');

/**
 * Custom AWS Security Credentials Supplier.
 *
 * This implementation resolves AWS credentials using the default Node provider
 * chain from the AWS SDK. This allows fetching credentials from environment
 * variables, shared credential files (~/.aws/credentials), or IAM roles
 * for service accounts (IRSA) in EKS, etc.
 */
class CustomAwsSupplier {
  constructor() {
    this.region = null;

    this.awsCredentialsProvider = fromNodeProviderChain();
  }

  /**
   * Returns the AWS region. This is required for signing the AWS request.
   * It resolves the region automatically by using the default AWS region
   * provider chain, which searches for the region in the standard locations
   * (environment variables, AWS config file, etc.).
   */
  async getAwsRegion(_context) {
    if (this.region) {
      return this.region;
    }

    const client = new STSClient({});
    this.region = await client.config.region();

    if (!this.region) {
      throw new Error(
        'CustomAwsSupplier: Unable to resolve AWS region. Please set the AWS_REGION environment variable or configure it in your ~/.aws/config file.'
      );
    }

    return this.region;
  }

  /**
   * Retrieves AWS security credentials using the AWS SDK's default provider chain.
   */
  async getAwsSecurityCredentials(_context) {
    const awsCredentials = await this.awsCredentialsProvider();

    if (!awsCredentials.accessKeyId || !awsCredentials.secretAccessKey) {
      throw new Error(
        'Unable to resolve AWS credentials from the node provider chain. ' +
          'Ensure your AWS CLI is configured, or AWS environment variables (like AWS_ACCESS_KEY_ID) are set.'
      );
    }

    return {
      accessKeyId: awsCredentials.accessKeyId,
      secretAccessKey: awsCredentials.secretAccessKey,
      token: awsCredentials.sessionToken,
    };
  }
}

/**
 * Authenticates with Google Cloud using AWS credentials and retrieves bucket metadata.
 *
 * @param {string} bucketName The name of the bucket to retrieve.
 * @param {string} audience The Workload Identity Pool audience.
 * @param {string} [impersonationUrl] Optional Service Account impersonation URL.
 */
async function authenticateWithAwsCredentials(
  bucketName,
  audience,
  impersonationUrl
) {
  const customSupplier = new CustomAwsSupplier();

  const clientOptions = {
    audience: audience,
    subject_token_type: 'urn:ietf:params:aws:token-type:aws4_request',
    service_account_impersonation_url: impersonationUrl,
    aws_security_credentials_supplier: customSupplier,
  };

  const authClient = new AwsClient(clientOptions);

  const storage = new Storage({
    authClient: authClient,
  });

  const [metadata] = await storage.bucket(bucketName).getMetadata();
  return metadata;
}

Python

במאמר ספריות הלקוח של IAM מוסבר איך להתקין את ספריית הלקוח ולהשתמש בה ב-IAM. למידע נוסף, קראו את מאמרי העזרה של ‎IAM Python API‎.

כדי לבצע אימות ב-IAM, צריך להגדיר את Application Default Credentials. מידע נוסף זמין במאמר הגדרת אימות לסביבת פיתוח מקומית.

import json
import os
import sys

import boto3
from google.auth import aws
from google.auth import exceptions
from google.cloud import storage


class CustomAwsSupplier(aws.AwsSecurityCredentialsSupplier):
    """Custom AWS Security Credentials Supplier using Boto3."""

    def __init__(self):
        """Initializes the Boto3 session, prioritizing environment variables for region."""
        # Explicitly read the region from the environment first.
        region = os.getenv("AWS_REGION") or os.getenv("AWS_DEFAULT_REGION")

        # If region is None, Boto3's discovery chain will be used when needed.
        self.session = boto3.Session(region_name=region)
        self._cached_region = None

    def get_aws_region(self, context, request) -> str:
        """Returns the AWS region using Boto3's default provider chain."""
        if self._cached_region:
            return self._cached_region

        self._cached_region = self.session.region_name

        if not self._cached_region:
            raise exceptions.GoogleAuthError(
                "Boto3 was unable to resolve an AWS region."
            )

        return self._cached_region

    def get_aws_security_credentials(
        self, context, request=None
    ) -> aws.AwsSecurityCredentials:
        """Retrieves AWS security credentials using Boto3's default provider chain."""
        creds = self.session.get_credentials()
        if not creds:
            raise exceptions.GoogleAuthError(
                "Unable to resolve AWS credentials from Boto3."
            )

        return aws.AwsSecurityCredentials(
            access_key_id=creds.access_key,
            secret_access_key=creds.secret_key,
            session_token=creds.token,
        )


def authenticate_with_aws_credentials(bucket_name, audience, impersonation_url=None):
    """Authenticates using the custom AWS supplier and gets bucket metadata.

    Returns:
        dict: The bucket metadata response from the Google Cloud Storage API.
    """

    custom_supplier = CustomAwsSupplier()

    credentials = aws.Credentials(
        audience=audience,
        subject_token_type="urn:ietf:params:aws:token-type:aws4_request",
        service_account_impersonation_url=impersonation_url,
        aws_security_credentials_supplier=custom_supplier,
        scopes=["https://www.googleapis.com/auth/devstorage.read_only"],
    )

    storage_client = storage.Client(credentials=credentials)

    bucket = storage_client.get_bucket(bucket_name)

    return bucket._properties

Java

במאמר ספריות הלקוח של IAM מוסבר איך להתקין את ספריית הלקוח ולהשתמש בה ב-IAM. למידע נוסף, קראו את מאמרי העזרה של ‎IAM Java API‎.

כדי לבצע אימות ב-IAM, צריך להגדיר את Application Default Credentials. מידע נוסף זמין במאמר הגדרת אימות לסביבת פיתוח מקומית.

import com.google.auth.oauth2.AwsCredentials;
import com.google.auth.oauth2.AwsSecurityCredentials;
import com.google.auth.oauth2.AwsSecurityCredentialsSupplier;
import com.google.auth.oauth2.ExternalAccountSupplierContext;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.cloud.storage.Bucket;
import com.google.cloud.storage.Storage;
import com.google.cloud.storage.StorageOptions;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import java.io.IOException;
import java.io.Reader;
import java.lang.reflect.Type;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Map;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
import software.amazon.awssdk.auth.credentials.AwsSessionCredentials;
import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.regions.providers.DefaultAwsRegionProviderChain;

  public static Bucket authenticateWithAwsCredentials(
      String gcpWorkloadAudience, String saImpersonationUrl, String gcsBucketName)
      throws IOException {

    CustomAwsSupplier customSupplier = new CustomAwsSupplier();

    AwsCredentials.Builder credentialsBuilder =
        AwsCredentials.newBuilder()
            .setAudience(gcpWorkloadAudience)
            // This token type indicates that the subject token is an AWS Signature Version 4 signed
            // request. This is required for AWS Workload Identity Federation.
            .setSubjectTokenType("urn:ietf:params:aws:token-type:aws4_request")
            .setAwsSecurityCredentialsSupplier(customSupplier);

    if (saImpersonationUrl != null) {
      credentialsBuilder.setServiceAccountImpersonationUrl(saImpersonationUrl);
    }

    GoogleCredentials credentials = credentialsBuilder.build();

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

    return storage.get(gcsBucketName);
  }

  /**
   * Custom AWS Security Credentials Supplier.
   *
   * <p>This implementation resolves AWS credentials and regions using the default provider chains
   * from the AWS SDK (v2). This supports environment variables, ~/.aws/credentials, and EC2/EKS
   * metadata.
   */
  private static class CustomAwsSupplier implements AwsSecurityCredentialsSupplier {
    private final AwsCredentialsProvider awsCredentialsProvider;
    private String region;

    public CustomAwsSupplier() {
      // The AWS SDK handles caching internally.
      this.awsCredentialsProvider = DefaultCredentialsProvider.create();
    }

    @Override
    public String getRegion(ExternalAccountSupplierContext context) {
      if (this.region == null) {
        Region awsRegion = new DefaultAwsRegionProviderChain().getRegion();
        if (awsRegion == null) {
          throw new IllegalStateException(
              "Unable to resolve AWS region. Ensure AWS_REGION is set or configured.");
        }
        this.region = awsRegion.id();
      }
      return this.region;
    }

    @Override
    public AwsSecurityCredentials getCredentials(ExternalAccountSupplierContext context) {
      software.amazon.awssdk.auth.credentials.AwsCredentials credentials =
          this.awsCredentialsProvider.resolveCredentials();

      if (credentials == null) {
        throw new IllegalStateException("Unable to resolve AWS credentials.");
      }

      String sessionToken = null;
      if (credentials instanceof AwsSessionCredentials) {
        sessionToken = ((AwsSessionCredentials) credentials).sessionToken();
      }

      return new AwsSecurityCredentials(
          credentials.accessKeyId(), credentials.secretAccessKey(), sessionToken);
    }
  }

גישה למשאבים מ-OIDC ומ-SAML

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

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

בדוגמאות הקוד הבאות אפשר לראות איך מגדירים גישה למשאבים מתוך ספקים שתומכים ב-OpenID Connect ‏ (OIDC) או ב-SAML 2.0 באמצעות ספק אישורים בהתאמה אישית. Google Cloud

Node.js

במאמר ספריות הלקוח של IAM מוסבר איך להתקין את ספריית הלקוח ולהשתמש בה ב-IAM. למידע נוסף, קראו את מאמרי העזרה של ‎IAM Node.js API‎.

כדי לבצע אימות ב-IAM, צריך להגדיר את Application Default Credentials. מידע נוסף זמין במאמר הגדרת אימות לסביבת פיתוח מקומית.

const {IdentityPoolClient} = require('google-auth-library');
const {Storage} = require('@google-cloud/storage');
const {Gaxios} = require('gaxios');
const fs = require('fs');
const path = require('path');

/**
 * A custom SubjectTokenSupplier that authenticates with Okta using the
 * Client Credentials grant flow.
 */
class OktaClientCredentialsSupplier {
  constructor(domain, clientId, clientSecret) {
    const cleanDomain = domain.endsWith('/') ? domain.slice(0, -1) : domain;
    this.oktaTokenUrl = `${cleanDomain}/oauth2/default/v1/token`;

    this.clientId = clientId;
    this.clientSecret = clientSecret;
    this.accessToken = null;
    this.expiryTime = 0;
    this.gaxios = new Gaxios();
  }

  /**
   * Main method called by the auth library. It will fetch a new token if one
   * is not already cached.
   * @returns {Promise<string>} A promise that resolves with the Okta Access token.
   */
  async getSubjectToken() {
    const isTokenValid =
      this.accessToken && Date.now() < this.expiryTime - 60 * 1000;

    if (isTokenValid) {
      return this.accessToken;
    }

    const {accessToken, expiresIn} = await this.fetchOktaAccessToken();
    this.accessToken = accessToken;
    this.expiryTime = Date.now() + expiresIn * 1000;
    return this.accessToken;
  }

  /**
   * Performs the Client Credentials grant flow with Okta.
   */
  async fetchOktaAccessToken() {
    const params = new URLSearchParams();
    params.append('grant_type', 'client_credentials');
    params.append('scope', 'gcp.test.read');

    const authHeader =
      'Basic ' +
      Buffer.from(`${this.clientId}:${this.clientSecret}`).toString('base64');

    try {
      const response = await this.gaxios.request({
        url: this.oktaTokenUrl,
        method: 'POST',
        headers: {
          Authorization: authHeader,
          'Content-Type': 'application/x-www-form-urlencoded',
        },
        data: params.toString(),
      });

      const {access_token, expires_in} = response.data;
      if (access_token && expires_in) {
        return {accessToken: access_token, expiresIn: expires_in};
      } else {
        throw new Error(
          'Access token or expires_in not found in Okta response.'
        );
      }
    } catch (error) {
      throw new Error(
        `Failed to authenticate with Okta: ${error.response?.data || error.message}`
      );
    }
  }
}

/**
 * Authenticates with Google Cloud using Okta credentials and retrieves bucket metadata.
 *
 * @param {string} bucketName The name of the bucket to retrieve.
 * @param {string} audience The Workload Identity Pool audience.
 * @param {string} domain The Okta domain.
 * @param {string} clientId The Okta client ID.
 * @param {string} clientSecret The Okta client secret.
 * @param {string} [impersonationUrl] Optional Service Account impersonation URL.
 */
async function authenticateWithOktaCredentials(
  bucketName,
  audience,
  domain,
  clientId,
  clientSecret,
  impersonationUrl
) {
  const oktaSupplier = new OktaClientCredentialsSupplier(
    domain,
    clientId,
    clientSecret
  );

  const authClient = new IdentityPoolClient({
    audience: audience,
    subject_token_type: 'urn:ietf:params:oauth:token-type:jwt',
    token_url: 'https://sts.googleapis.com/v1/token',
    subject_token_supplier: oktaSupplier,
    service_account_impersonation_url: impersonationUrl,
  });

  const storage = new Storage({
    authClient: authClient,
  });

  const [metadata] = await storage.bucket(bucketName).getMetadata();
  return metadata;
}

Python

במאמר ספריות הלקוח של IAM מוסבר איך להתקין את ספריית הלקוח ולהשתמש בה ב-IAM. למידע נוסף, קראו את מאמרי העזרה של ‎IAM Python API‎.

כדי לבצע אימות ב-IAM, צריך להגדיר את Application Default Credentials. מידע נוסף זמין במאמר הגדרת אימות לסביבת פיתוח מקומית.

import json
import time
import urllib.parse

from google.auth import identity_pool
from google.cloud import storage
import requests


class OktaClientCredentialsSupplier:
    """A custom SubjectTokenSupplier that authenticates with Okta.

    This supplier uses the Client Credentials grant flow for machine-to-machine
    (M2M) authentication with Okta.
    """

    def __init__(self, domain, client_id, client_secret):
        self.okta_token_url = f"{domain.rstrip('/')}/oauth2/default/v1/token"
        self.client_id = client_id
        self.client_secret = client_secret
        self.access_token = None
        self.expiry_time = 0

    def get_subject_token(self, context, request=None) -> str:
        """Fetches a new token if the current one is expired or missing."""
        if self.access_token and time.time() < self.expiry_time - 60:
            return self.access_token
        self._fetch_okta_access_token()
        return self.access_token

    def _fetch_okta_access_token(self):
        """Performs the Client Credentials grant flow with Okta."""
        headers = {
            "Content-Type": "application/x-www-form-urlencoded",
            "Accept": "application/json",
        }
        data = {
            "grant_type": "client_credentials",
            "scope": "gcp.test.read",  # Set scope as per Okta app config.
        }

        response = requests.post(
            self.okta_token_url,
            headers=headers,
            data=urllib.parse.urlencode(data),
            auth=(self.client_id, self.client_secret),
        )
        response.raise_for_status()

        token_data = response.json()
        self.access_token = token_data["access_token"]
        self.expiry_time = time.time() + token_data["expires_in"]


def authenticate_with_okta_credentials(
    bucket_name, audience, domain, client_id, client_secret, impersonation_url=None
):
    """Authenticates using the custom Okta supplier and gets bucket metadata.

    Returns:
        dict: The bucket metadata response from the Google Cloud Storage API.
    """

    okta_supplier = OktaClientCredentialsSupplier(domain, client_id, client_secret)

    credentials = identity_pool.Credentials(
        audience=audience,
        subject_token_type="urn:ietf:params:oauth:token-type:jwt",
        token_url="https://sts.googleapis.com/v1/token",
        subject_token_supplier=okta_supplier,
        default_scopes=["https://www.googleapis.com/auth/devstorage.read_only"],
        service_account_impersonation_url=impersonation_url,
    )

    storage_client = storage.Client(credentials=credentials)

    bucket = storage_client.get_bucket(bucket_name)

    return bucket._properties

Java

במאמר ספריות הלקוח של IAM מוסבר איך להתקין את ספריית הלקוח ולהשתמש בה ב-IAM. למידע נוסף, קראו את מאמרי העזרה של ‎IAM Java API‎.

כדי לבצע אימות ב-IAM, צריך להגדיר את Application Default Credentials. מידע נוסף זמין במאמר הגדרת אימות לסביבת פיתוח מקומית.

import com.google.api.client.json.GenericJson;
import com.google.api.client.json.gson.GsonFactory;
import com.google.auth.oauth2.ExternalAccountSupplierContext;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.auth.oauth2.IdentityPoolCredentials;
import com.google.auth.oauth2.IdentityPoolSubjectTokenSupplier;
import com.google.cloud.storage.Bucket;
import com.google.cloud.storage.Storage;
import com.google.cloud.storage.StorageOptions;
import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
import com.google.gson.reflect.TypeToken;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.lang.reflect.Type;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.time.Instant;
import java.util.Base64;
import java.util.Map;

  public static Bucket authenticateWithOktaCredentials(
      String gcpWorkloadAudience,
      String saImpersonationUrl,
      String gcsBucketName,
      String oktaDomain,
      String oktaClientId,
      String oktaClientSecret)
      throws IOException {

    OktaClientCredentialsSupplier oktaSupplier =
        new OktaClientCredentialsSupplier(oktaDomain, oktaClientId, oktaClientSecret);

    IdentityPoolCredentials.Builder credentialsBuilder =
        IdentityPoolCredentials.newBuilder()
            .setAudience(gcpWorkloadAudience)
            // This token type indicates that the subject token is a JSON Web Token (JWT).
            // This is required for Workload Identity Federation with an OIDC provider like Okta.
            .setSubjectTokenType("urn:ietf:params:oauth:token-type:jwt")
            .setTokenUrl("https://sts.googleapis.com/v1/token")
            .setSubjectTokenSupplier(oktaSupplier);

    if (saImpersonationUrl != null) {
      credentialsBuilder.setServiceAccountImpersonationUrl(saImpersonationUrl);
    }

    GoogleCredentials credentials = credentialsBuilder.build();

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

    return storage.get(gcsBucketName);
  }

  /**
   * A custom SubjectTokenSupplier that authenticates with Okta using the Client Credentials grant
   * flow.
   */
  private static class OktaClientCredentialsSupplier implements IdentityPoolSubjectTokenSupplier {

    private static final long TOKEN_REFRESH_BUFFER_SECONDS = 60;

    private final String oktaTokenUrl;
    private final String clientId;
    private final String clientSecret;
    private String accessToken;
    private Instant expiryTime;

    public OktaClientCredentialsSupplier(String domain, String clientId, String clientSecret) {
      // Ensure domain doesn't have a trailing slash for cleaner URL construction
      String cleanedDomain =
          domain.endsWith("/") ? domain.substring(0, domain.length() - 1) : domain;
      this.oktaTokenUrl = cleanedDomain + "/oauth2/default/v1/token";
      this.clientId = clientId;
      this.clientSecret = clientSecret;
    }

    /**
     * Main method called by the auth library. It will fetch a new token if one is not already
     * cached.
     */
    @Override
    public String getSubjectToken(ExternalAccountSupplierContext context) throws IOException {
      // Check if the current token is still valid (with a 60-second buffer).
      boolean isTokenValid =
          this.accessToken != null
              && this.expiryTime != null
              && Instant.now().isBefore(this.expiryTime.minusSeconds(TOKEN_REFRESH_BUFFER_SECONDS));

      if (isTokenValid) {
        return this.accessToken;
      }

      fetchOktaAccessToken();
      return this.accessToken;
    }

    /**
     * Performs the Client Credentials grant flow by making a POST request to Okta's token endpoint.
     */
    private void fetchOktaAccessToken() throws IOException {
      URL url = new URL(this.oktaTokenUrl);
      HttpURLConnection conn = (HttpURLConnection) url.openConnection();
      conn.setRequestMethod("POST");
      conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
      conn.setRequestProperty("Accept", "application/json");

      // The client_id and client_secret are sent in a Basic Auth header.
      String auth = this.clientId + ":" + this.clientSecret;
      String encodedAuth =
          Base64.getEncoder().encodeToString(auth.getBytes(StandardCharsets.UTF_8));
      conn.setRequestProperty("Authorization", "Basic " + encodedAuth);

      conn.setDoOutput(true);
      try (java.io.OutputStream out = conn.getOutputStream()) {
        // Scopes define the permissions the access token will have.
        // Update "gcp.test.read" to match your Okta configuration.
        String params = "grant_type=client_credentials&scope=gcp.test.read";
        out.write(params.getBytes(StandardCharsets.UTF_8));
        out.flush();
      }

      int responseCode = conn.getResponseCode();
      if (responseCode == HttpURLConnection.HTTP_OK) {
        try (BufferedReader in =
            new BufferedReader(
                new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8))) {

          GenericJson jsonObject =
              GsonFactory.getDefaultInstance().createJsonParser(in).parse(GenericJson.class);

          if (jsonObject.containsKey("access_token") && jsonObject.containsKey("expires_in")) {
            this.accessToken = (String) jsonObject.get("access_token");
            Number expiresInNumber = (Number) jsonObject.get("expires_in");
            this.expiryTime = Instant.now().plusSeconds(expiresInNumber.longValue());
          } else {
            throw new IOException("Access token or expires_in not found in Okta response.");
          }
        }
      } else {
        throw new IOException("Failed to authenticate with Okta. Response code: " + responseCode);
      }
    }
  }

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