Autentica servicio a servicio

Organiza tus páginas con colecciones Guarda y categoriza el contenido según tus preferencias.

Si en tu arquitectura usas varios servicios, es probable que estos deban comunicarse entre sí mediante medios asíncronos o síncronos. Muchos de estos servicios pueden ser privados y requieren credenciales para acceder.

Para la comunicación asíncrona, puedes usar los siguientes servicios de Google Cloud:

  • Cloud Tasks para la comunicación asíncrona uno a uno
  • Pub/Sub para una comunicación asíncrona uno a varios, uno a uno y varios a uno
  • Cloud Scheduler para la comunicación asíncrona programada con regularidad
  • Eventarc para la comunicación basada en eventos

En todos estos casos, el servicio que se usa administra la interacción con el servicio de recepción, en función de la configuración que estableciste.

Sin embargo, para la comunicación síncrona, tu servicio llama a otro servicio directamente, a través de HTTP, con su URL de extremo. Para este caso práctico, debes asegurarte de que cada servicio solo pueda realizar solicitudes a servicios específicos. Por ejemplo, si tienes un servicio login, debería poder acceder al servicio user-profiles, pero no al servicio search.

En esta situación, Google recomienda que uses IAM y una identidad de servicio basada en una cuenta de servicio administrada por el usuario de servicio a la que se le otorgó el conjunto mínimo de permisos necesarios para realizar su trabajo.

Además, la solicitud debe presentar un comprobante de la identidad del servicio de llamadas. Para ello, configura el servicio de llamadas a fin de que agregue un token de ID de OpenID Connect firmado por Google como parte de la solicitud.

Configure la cuenta de servicio

A fin de configurar una cuenta de servicio, configura el servicio de recepción para que acepte solicitudes del servicio de llamadas. Para ello, haz que la cuenta de servicio de llamada del miembro sea una principal en el servicio receptor. Luego, debes otorgar a esa cuenta de servicio la función de Invocador de Cloud Run (roles/run.invoker). Para realizar ambas tareas, sigue las instrucciones en la pestaña correspondiente:

IU de Console

  1. Ve a Google Cloud Console:

    Ir a Google Cloud Console

  2. Selecciona el servicio de recepción.

  3. Haz clic en Mostrar panel de información en la esquina superior derecha para que aparezca la pestaña Permisos.

  4. Haz clic en Agregar principal.

    1. Ingresa la identidad del servicio emisor. Por lo general, es una dirección de correo electrónico predeterminada PROJECT_NUMBER-compute@developer.gserviceaccount.com.

    2. Selecciona la función Cloud Run Invoker en el menú desplegable Selecciona una función.

    3. Haz clic en Guardar.

gcloud

Usa el 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'

donde RECEIVING_SERVICE es el nombre del servicio de recepción y CALLING_SERVICE_IDENTITY es la dirección de correo electrónico de la cuenta de servicio, según la configuración predeterminada PROJECT_NUMBER-compute@developer.gserviceaccount.com.

Terraform

El siguiente código de Terraform crea un servicio inicial de Cloud Run diseñado para ser público.

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

  template {
    spec {
      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_service.private.status[0].url
        }
      }

      # Give the "public" Cloud Run service
      # a service account's identity
      service_account_name = google_service_account.default.email
    }
  }
}

Reemplaza us-docker.pkg.dev/cloudrun/container/hello por una referencia a tu imagen de contenedor.

El siguiente código de Terraform hace que el servicio inicial sea público.

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

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

  policy_data = data.google_iam_policy.public.policy_data
}

El siguiente código de Terraform crea un segundo servicio de Cloud Run diseñado para ser privado.

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

  template {
    spec {
      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"
      }
    }
  }
}

Reemplaza us-docker.pkg.dev/cloudrun/container/hello por una referencia a tu imagen de contenedor.

El siguiente código de Terraform hace que el segundo servicio sea privado.

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_service.private.location
  project  = google_cloud_run_service.private.project
  service  = google_cloud_run_service.private.name

  policy_data = data.google_iam_policy.private.policy_data
}

Mediante el siguiente código de Terraform, se crea una cuenta de servicio.

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

El siguiente código de Terraform permite que los servicios adjuntos a la cuenta de servicio invoquen el servicio inicial privado de Cloud Run.

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_service.private.location
  project  = google_cloud_run_service.private.project
  service  = google_cloud_run_service.private.name

  policy_data = data.google_iam_policy.private.policy_data
}

Adquiere y configura el token de ID

Una vez que se le haya otorgado la función adecuada a la cuenta de servicio de llamadas, necesitarás hacer lo siguiente:

  1. Obtener un token de ID firmado por Google con la reclamación del público (aud) configurada como la URL del servicio receptor. El valor de aud debe seguir siendo la URL del servicio, incluso cuando se realizan solicitudes a una etiqueta de tráfico específica.

  2. Incluye el token de ID en un encabezado Authorization: Bearer ID_TOKEN en la solicitud al servicio.

La forma más fácil y confiable de administrar este proceso es usar las bibliotecas de autenticación como se muestra a continuación para generar y usar este token. Este código funciona en cualquier entorno, incluso fuera de Google Cloud, en el que las bibliotecas pueden obtener credenciales de autenticación, incluidos los entornos que admiten Application Default Credentials.

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

Go


import (
	"context"
	"fmt"
	"io"

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

// `makeGetRequest` makes a request to the provided `targetURL`
// with an authenticated client using audience `audience`.
func makeGetRequest(w io.Writer, targetURL string, audience string) error {
	// 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: %v", err)
	}

	resp, err := client.Get(targetURL)
	if err != nil {
		return fmt.Errorf("client.Get: %v", err)
	}
	defer resp.Body.Close()
	if _, err := io.Copy(w, resp.Body); err != nil {
		return fmt.Errorf("io.Copy: %v", 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();
  }
}

Usa el servidor de metadatos

Si, por algún motivo, no puedes usar las bibliotecas de autenticación, puedes recuperar un token de ID del servidor de metadatos de Compute mientras tu contenedor se ejecuta en Cloud Run. . Ten en cuenta que este método no funciona fuera de Google Cloud, incluso desde la máquina local.

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

Donde AUDIENCE es la URL del servicio que estás invocando.

Para obtener una explicación completa de una aplicación que use esta técnica de autenticación de servicio a servicio, sigue el instructivo sobre cómo proteger los servicios de Cloud Run.

Recibe solicitudes autenticadas

Dentro del servicio privado receptor, puedes analizar el encabezado de autorización para recibir la información que envía el token del portador.

Python


from google.auth import jwt

def receive_authorized_get_request(request):
    """
    receive_authorized_get_request takes the "Authorization" header from a
    request, decodes it using the google-auth client library, and returns
    back the email from the header to the caller.
    """
    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 = jwt.decode(creds, verify=False)
            return f"Hello, {claims['email']}!\n"

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

Llama desde fuera de Google Cloud

Puedes llamar a un servicio privado desde fuera de Google Cloud mediante la federación de Workload Identity o mediante la descarga de una clave de cuenta de servicio. Ambos métodos se describen en las siguientes secciones.

Usa la federación de Workload Identity

Si tu entorno usa un proveedor de identidad compatible con la federación de Workload Identity, puedes usar este método para autenticarte de forma segura en el servicio de Cloud Run:

  1. Configura tu cuenta de servicio como se describe en Configura la cuenta de servicio en esta página.

  2. Configura la federación de Workload Identity para tu proveedor de identidad como se describe en Configura la federación de Workload Identity.

  3. Sigue las instrucciones en Otorga permisos a identidades externas para actuar en nombre de una cuenta de servicio.

  4. Usa la API de REST para adquirir un token de corta duración, pero en lugar de llamar a generateAccessToken a fin de obtener un token de acceso, llama a generateIdToken para obtener un token de ID.

    Por ejemplo, con 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
    

    SERVICE_ACCOUNT es la dirección de correo electrónico de la cuenta de servicio a la que está configurado el grupo de identidades de carga de trabajo, y SERVICE_URL es la URL del servicio de Cloud Run que invocas. Este valor debe seguir siendo la URL del servicio, incluso cuando se realizan solicitudes a una etiqueta de tráfico específica. $STS_TOKEN es el token de servicio del token de seguridad que recibiste en el paso anterior en las instrucciones de federación de Workload Identity.

Una vez que tengas el token de ID, puedes incluirlo en un encabezado Authorization: Bearer ID_TOKEN en la solicitud al servicio de recepción.

Usa una clave de cuenta de servicio descargada

Si la federación de Workload Identity no es adecuada para tu entorno, puedes usar una clave de cuenta de servicio descargada a fin de autenticarte. Actualiza el código de cliente para usar las bibliotecas de autenticación como se describió anteriormente con las credenciales predeterminadas de la aplicación.

Puedes adquirir un token de ID firmado por Google con un JWT autofirmado, pero esto es bastante complicado y potencialmente propenso a errores. Los pasos básicos son los siguientes:

  1. Autofirma un JWT de cuenta de servicio con la reclamación target_audience configurada como la URL del servicio receptor. El valor de target_audience debe seguir siendo la URL del servicio, incluso cuando se realizan solicitudes a una etiqueta de tráfico específica.

  2. Intercambia el JWT autofirmado por un token de ID firmado por Google, que debería tener la reclamación aud configurada en la URL anterior.

  3. Incluye el token de ID en un encabezado Authorization: Bearer ID_TOKEN en la solicitud al servicio.

Puedes examinar este ejemplo de Cloud Functions para ver una muestra de los pasos anteriores.