Autenticazione tra servizi

Se la tua architettura usa più servizi, questi che probabilmente devono comunicare tra loro, utilizzando asincroni o sincroni. Molti di questi servizi potrebbero essere quindi richiedono le credenziali di accesso.

Per la comunicazione asincrona, puoi utilizzare i seguenti servizi Google Cloud:

  • Cloud Tasks per una comunicazione asincrona
  • Pub/Sub per uno a molti, uno a uno e da molti a uno comunicazione asincrona
  • Cloud Scheduler per la gestione comunicazione asincrona pianificata
  • Eventarc per la comunicazione basata su eventi

In tutti questi casi, il servizio utilizzato gestisce l'interazione con il servizio ricevente, in base alla configurazione che hai impostato.

Ma per la comunicazione sincrona, il servizio chiama un altro servizio direttamente tramite HTTP, utilizzando l'URL dell'endpoint. Per questo caso d'uso, dovresti apportare assicurati che ogni servizio sia in grado di inviare richieste solo a specifici i servizi di machine learning. Ad esempio, se hai un servizio login, questo dovrebbe essere in grado per accedere al servizio user-profiles, ma non al servizio search.

In questo caso, Google consiglia di utilizzare IAM. e un'identità del servizio basata su un account di servizio gestito dall'utente per ogni servizio a cui è stato concesso il set minimo delle autorizzazioni necessarie per svolgere il suo lavoro.

Inoltre, la richiesta deve presentare una prova dell'identità del servizio chiamante. Per farlo, configura il servizio di chiamata per aggiungere un OpenID Connect firmato da Google ID token di sicurezza nell'ambito della richiesta.

Configurare l'account di servizio

Per impostare un account di servizio, devi configurare il servizio ricevente in modo che accetti le richieste da rendendo l'account di servizio del servizio chiamante come entità sul servizio ricevente. Quindi concedi a quell'account di servizio l'Invoker di Cloud Run. (roles/run.invoker). Per eseguire entrambe le operazioni, segui le istruzioni riportate nella scheda appropriata:

Interfaccia utente della console

  1. Vai alla console Google Cloud:

    Vai alla console Google Cloud

  2. Seleziona il servizio di destinazione.

  3. Fai clic su Mostra riquadro informazioni nell'angolo in alto a destra per visualizzare le Scheda Autorizzazioni.

  4. Fai clic su Aggiungi entità.

    1. Inserisci l'identità del servizio chiamante. Di solito è un'email per impostazione predefinita PROJECT_NUMBER-compute@developer.gserviceaccount.com.

    2. Seleziona il ruolo Cloud Run Invoker da Seleziona un ruolo menu a discesa.

    3. Fai clic su Salva.

gcloud

Usa il comando gcloud run services add-iam-policy-binding:

gcloud run services add-iam-policy-binding RECEIVING_SERVICE \
  --member='serviceAccount:CALLING_SERVICE_IDENTITY' \
  --role='roles/run.invoker'

dove RECEIVING_SERVICE è il nome dell'emittente e CALLING_SERVICE_IDENTITY è l'indirizzo email dell'account di servizio, per impostazione predefinita PROJECT_NUMBER-compute@developer.gserviceaccount.com.

Terraform

Per scoprire come applicare o rimuovere una configurazione Terraform, consulta: Comandi Terraform di base.

Il seguente codice Terraform crea un servizio Cloud Run iniziale che dovrebbe essere pubblico.

resource "google_cloud_run_v2_service" "public" {
  name     = "public-service"
  location = "us-central1"

  template {
    containers {
      # TODO<developer>: replace this with a public service container
      # (This service can be invoked by anyone on the internet)
      image = "us-docker.pkg.dev/cloudrun/container/hello"

      # Include a reference to the private Cloud Run
      # service's URL as an environment variable.
      env {
        name  = "URL"
        value = google_cloud_run_v2_service.private.uri
      }
    }
    # Give the "public" Cloud Run service
    # a service account's identity
    service_account = google_service_account.default.email
  }
}

Sostituisci us-docker.pkg.dev/cloudrun/container/hello con un riferimento all'immagine container.

Il codice Terraform seguente rende pubblico il servizio iniziale.

data "google_iam_policy" "public" {
  binding {
    role = "roles/run.invoker"
    members = [
      "allUsers",
    ]
  }
}

resource "google_cloud_run_service_iam_policy" "public" {
  location = google_cloud_run_v2_service.public.location
  project  = google_cloud_run_v2_service.public.project
  service  = google_cloud_run_v2_service.public.name

  policy_data = data.google_iam_policy.public.policy_data
}

Il seguente codice Terraform crea un secondo servizio Cloud Run destinato a essere privato.

resource "google_cloud_run_v2_service" "private" {
  name     = "private-service"
  location = "us-central1"

  template {
    containers {
      // TODO<developer>: replace this with a private service container
      // (This service should only be invocable by the public service)
      image = "us-docker.pkg.dev/cloudrun/container/hello"
    }
  }
}

Sostituisci us-docker.pkg.dev/cloudrun/container/hello con un riferimento all'immagine container.

Il seguente codice Terraform rende privato il secondo servizio.

data "google_iam_policy" "private" {
  binding {
    role = "roles/run.invoker"
    members = [
      "serviceAccount:${google_service_account.default.email}",
    ]
  }
}

resource "google_cloud_run_service_iam_policy" "private" {
  location = google_cloud_run_v2_service.private.location
  project  = google_cloud_run_v2_service.private.project
  service  = google_cloud_run_v2_service.private.name

  policy_data = data.google_iam_policy.private.policy_data
}

Il codice Terraform seguente crea un account di servizio.

resource "google_service_account" "default" {
  account_id   = "cloud-run-interservice-id"
  description  = "Identity used by a public Cloud Run service to call private Cloud Run services."
  display_name = "cloud-run-interservice-id"
}

Il seguente codice Terraform consente i servizi collegati all'account di servizio per richiamare il servizio Cloud Run privato iniziale.

data "google_iam_policy" "private" {
  binding {
    role = "roles/run.invoker"
    members = [
      "serviceAccount:${google_service_account.default.email}",
    ]
  }
}

resource "google_cloud_run_service_iam_policy" "private" {
  location = google_cloud_run_v2_service.private.location
  project  = google_cloud_run_v2_service.private.project
  service  = google_cloud_run_v2_service.private.name

  policy_data = data.google_iam_policy.private.policy_data
}

Acquisisci e configura il token ID

Dopo aver concesso il ruolo appropriato all'account di servizio chiamante, segui questi passaggi passaggi:

  1. Recupera un token ID firmato da Google utilizzando uno dei metodi descritti nella sezione che segue. Impostare la rivendicazione del pubblico (aud) all'URL del servizio ricevente o a un segmento di pubblico personalizzato configurato. Se non utilizzi un segmento di pubblico personalizzato, il valore aud deve rimanere invariato URL del servizio, anche quando si effettuano richieste a un traffico specifico del tag.

  2. Aggiungi il token ID recuperato dal passaggio precedente in uno dei seguenti nella richiesta al servizio ricevente:

    • Un'intestazione Authorization: Bearer ID_TOKEN.
    • Un'intestazione X-Serverless-Authorization: Bearer ID_TOKEN. Puoi utilizzare questo se la tua applicazione utilizza già l'intestazione Authorization per autorizzazione. In questo modo la firma viene rimossa prima di trasmetterlo all'utente containerizzato.

Per altri modi per ottenere un token ID non descritti in questa pagina, consulta Metodi per ottenere un token ID.

Utilizzare le librerie di autenticazione

Il modo più semplice e affidabile per acquisire e configurare il processo di token ID consiste nell'utilizzare le librerie di autenticazione. Questo codice funziona in qualsiasi ambiente, anche al di fuori di Google Cloud, in cui le librerie possono ottenere le credenziali di autenticazione per un servizio inclusi gli ambienti che supportano le credenziali predefinite dell'applicazione locali. Per impostare credenziali predefinite dell'applicazione, scaricare un file della chiave dell'account di servizio e imposta la variabile di ambiente GOOGLE_APPLICATION_CREDENTIALS sul percorso del e il file delle chiavi dell'account di servizio. Per ulteriori informazioni, vedi Come funzionano le Credenziali predefinite dell'applicazione.

Questo codice non funziona per ottenere le credenziali di autenticazione per un account utente.

Node.js

/**
 * TODO(developer): Uncomment these variables before running the sample.
 */
// Example: https://my-cloud-run-service.run.app/books/delete/12345
// const url = 'https://TARGET_HOSTNAME/TARGET_URL';

// Example (Cloud Run): https://my-cloud-run-service.run.app/
// const targetAudience = 'https://TARGET_AUDIENCE/';

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

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

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

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

Python

import urllib

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


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

    # Cloud Run uses your service's hostname as the `audience` value
    # audience = 'https://my-cloud-run-service.run.app/'
    # For Cloud Run, `endpoint` is the URL (hostname + path) receiving the request
    # endpoint = 'https://my-cloud-run-service.run.app/my/awesome/url'

    req = urllib.request.Request(endpoint)

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

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

    return response.read()

Vai


import (
	"context"
	"fmt"
	"io"

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

// `makeGetRequest` makes a request to the provided `targetURL`
// with an authenticated client using audience `audience`.
func makeGetRequest(w io.Writer, targetURL string, audience string) error {
	// Example `audience` value (Cloud Run): https://my-cloud-run-service.run.app/
	// (`targetURL` and `audience` will differ for non-root URLs and GET parameters)
	ctx := context.Background()

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

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

	return nil
}

Java

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

public class Authentication {

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

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

Utilizzare il server dei metadati

Se per qualche motivo non puoi utilizzare le librerie di autenticazione, puoi recupera un token ID dal server di metadati Compute mentre il container è in esecuzione su Cloud Run. Tieni presente che questo metodo non funziona al di fuori di Google Cloud, nemmeno dalla tua macchina locale.

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

dove AUDIENCE è l'URL del servizio che stai richiamando o un segmento di pubblico personalizzato configurato.

La seguente tabella riassume le parti principali di una richiesta di query sui metadati:

Componenti Descrizione
URL di base

Tutti i valori dei metadati sono definiti come percorsi secondari il seguente URL principale:

http://metadata.google.internal/computeMetadata/v1
Intestazione della richiesta

In ogni richiesta deve essere presente la seguente intestazione:

Metadata-Flavor: Google

Questa intestazione indica che la richiesta è stata inviata con l'intento di recuperare anziché provenienti involontariamente da un'origine non sicura, il server dei metadati restituisce i dati richiesti. Se non fornisci questo , il server dei metadati rifiuta la tua richiesta.

Per una procedura dettagliata end-to-end di un'applicazione che utilizza questo servizio di autenticazione, segui le tutorial sulla protezione dei servizi Cloud Run.

Utilizza la federazione delle identità per i carichi di lavoro dall'esterno di Google Cloud

Se il tuo ambiente utilizza un provider di identità supportato da federazione delle identità per i carichi di lavoro, usare il seguente metodo per eseguire l'autenticazione sicura sul tuo Servizio Cloud Run esterno a Google Cloud:

  1. Configura l'account di servizio come descritto in Configura l'account di servizio in questa pagina.

  2. Configura la federazione delle identità per i carichi di lavoro per il provider di identità come descritti in Configurazione della federazione delle identità per i carichi di lavoro.

  3. Segui le istruzioni in Concessione dell'autorizzazione a identità esterne per rappresentare un account di servizio.

  4. Utilizzare l'API REST per acquisire un token di breve durata, ma invece di chiamare generateAccessToken per ottenere un token di accesso, richiama generateIdToken per ottenere un token ID.

    Ad esempio, utilizzando cURL:

    ID_TOKEN=$(curl -0 -X POST https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/SERVICE_ACCOUNT:generateIdToken \
      -H "Content-Type: text/json; charset=utf-8" \
      -H "Authorization: Bearer $STS_TOKEN" \
      -d @- <<EOF | jq -r .token
      {
          "audience": "SERVICE_URL"
      }
    EOF
    )
    echo $ID_TOKEN
    

    Dove SERVICE_ACCOUNT è l'indirizzo email del servizio account a cui è configurato il pool di identità per i carichi di lavoro, e SERVICE_URL è l'URL di Cloud Run servizio che stai richiamando. Questo valore deve rimanere come URL del servizio, anche quando invii richieste a un tag di traffico specifico. $STS_TOKEN è il token di servizio del token di sicurezza che nel passaggio precedente nelle istruzioni di federazione delle identità per i carichi di lavoro.

Puoi includere il token ID del passaggio precedente nella richiesta all'elemento un servizio tramite un Authorization: Bearer ID_TOKEN un'intestazione X-Serverless-Authorization: Bearer ID_TOKEN. Se sono fornite entrambe le intestazioni, solo X-Serverless-Authorization sia selezionata.

Usa una chiave dell'account di servizio scaricata dall'esterno di Google Cloud

Se la federazione delle identità per i carichi di lavoro non è appropriata per il tuo ambiente, puoi utilizzare una chiave dell'account di servizio scaricata per eseguire l'autenticazione dall'esterno in Google Cloud. Aggiorna il codice client per utilizzare le librerie di autenticazione come descritto in precedenza con Credenziali predefinite dell'applicazione.

Puoi acquisire un token ID firmato da Google utilizzando un JWT autofirmato, ma si tratta di piuttosto complicata e potenzialmente soggetta a errori. I passaggi di base sono i seguenti:

  1. Firma autonomamente il JWT di un account di servizio con l'attestazione target_audience impostata su URL del servizio ricevente o di un segmento di pubblico personalizzato configurato. Se non utilizzi domini personalizzati, il valore target_audience deve rimanere come URL del servizio, anche quando si effettuano richieste a un traffico specifico del tag.

  2. Scambia il JWT autofirmato con un token ID firmato da Google, che dovrebbe la rivendicazione aud è impostata sull'URL precedente.

  3. Includi il token ID nella richiesta al servizio utilizzando un Authorization: Bearer ID_TOKEN un'intestazione X-Serverless-Authorization: Bearer ID_TOKEN. Se vengono fornite entrambe le intestazioni, solo X-Serverless-Authorization sia selezionata.

Puoi esaminare questo esempio di Cloud Functions per un esempio dei passaggi precedenti.

Ricevi richieste autenticate

All'interno del servizio privato ricevente, puoi analizzare l'intestazione dell'autorizzazione riceveranno le informazioni inviate dal token di connessione.

Python


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


def receive_authorized_get_request(request):
    """Parse the authorization header and decode the information
    being sent by the Bearer token.

    Args:
        request: Flask request object

    Returns:
        The email from the request's Authorization header.
    """
    auth_header = request.headers.get("Authorization")
    if auth_header:
        # split the auth type and value from the header.
        auth_type, creds = auth_header.split(" ", 1)

        if auth_type.lower() == "bearer":
            claims = id_token.verify_token(creds, requests.Request())
            return f"Hello, {claims['email']}!\n"

        else:
            return f"Unhandled header format ({auth_type}).\n"
    return "Hello, anonymous user.\n"