Autentica servicio a servicio

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
  • Cloud Scheduler para la comunicación asíncrona programada con regularidad

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 un miembro 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. En el campo Agregar miembros, ingresa la identidad del servicio de emisión. Por lo general, es una dirección de correo electrónico predeterminada PROJECT_NUMBER-compute@developer.gserviceaccount.com.

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

  6. Haz clic en Agregar.

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.

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 en la URL del servicio de recepción.

  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.
 */
// const url = 'https://TARGET_URL';
// let targetAudience = null;
const {GoogleAuth} = require('google-auth-library');
const auth = new GoogleAuth();

async function request() {
  if (!targetAudience) {
    // Use the request URL hostname as the target audience for requests.
    const {URL} = require('url');
    targetAudience = new URL(url);
  }
  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(service_url):
    """
    make_authorized_get_request makes a GET request to the specified HTTP endpoint
    in service_url (must be a complete URL) by authenticating with the
    ID token obtained from the google-auth client library.
    """

    req = urllib.request.Request(service_url)

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

    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.
func makeGetRequest(w io.Writer, targetURL string) error {
	// functionURL := "https://TARGET_URL"
	ctx := context.Background()

	// client is a http.Client that automatically adds an "Authorization" header
	// to any requests made.
	client, err := idtoken.NewClient(ctx, targetURL)
	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.
  public static HttpResponse makeGetRequest(String serviceUrl) 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(serviceUrl)
            .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.

Llama desde afuera de GCP

La mejor manera de llamar a un servicio privado desde fuera de Google Cloud es usar las bibliotecas de autenticación como se describió con anterioridad, una vez que hayas configurado 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 en la URL del servicio receptor.

  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.