Autentica servicio a servicio

La autenticación de servicio a servicio es la capacidad de un servicio, que puede ser un servicio de Cloud Run, para invocar un servicio de Cloud Run.

Si en tu arquitectura usas varios servicios, es probable que estos deban comunicarse entre sí.

Puedes usar la comunicación de servicio a servicio síncrona o asíncrona:

Para la comunicación asíncrona, usa lo siguiente:

  • 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

Para la comunicación síncrona, un servicio invoca a otro a través de HTTP mediante la URL de extremo. En este caso práctico, te conviene 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, tal vez, no debería poder acceder al servicio search.

Primero, deberás configurar el servicio de recepción para que acepte solicitudes del servicio de emisión:

  1. Otorga la función de invocador de Cloud Run (roles/run.invoker) a la identidad del servicio de emisión en el servicio de recepción. De forma predeterminada, esa identidad es PROJECT_NUMBER-compute@developer.gserviceaccount.com.

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.

  5. Selecciona la función de Cloud Run Invoker en el menú desplegable Seleccionar 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'

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.

En el servicio de emisión, deberás hacer lo siguiente:

  1. Crea un token de ID de OAuth firmado por Google con el público (aud) configurado en la URL del servicio de recepción. Este valor debe contener el prefijo de esquema (http:// o https://); por el momento, los dominios personalizados no son compatibles con el valor aud.

  2. Incluye el token de ID en un encabezado Authorization: Bearer ID_TOKEN. Puedes obtener este token desde el servidor de metadatos, mientras el contenedor se ejecuta en Cloud Run. Si la aplicación se ejecuta fuera de Google Cloud, puedes generar un token de ID desde el archivo de claves de una cuenta de servicio.

Node.js
// Make sure to `npm install --save request-promise` or add the dependency to your package.json
const request = require('request-promise');

const receivingServiceURL = ...

// Set up metadata server request
// See https://cloud.google.com/compute/docs/instances/verifying-instance-identity#request_signature
const metadataServerTokenURL = 'http://metadata/computeMetadata/v1/instance/service-accounts/default/identity?audience=';
const tokenRequestOptions = {
    uri: metadataServerTokenURL + receivingServiceURL,
    headers: {
        'Metadata-Flavor': 'Google'
    }
};

// Fetch the token, then provide the token in the request to the receiving service
request(tokenRequestOptions)
  .then((token) => {
    return request(receivingServiceURL).auth(null, null, true, token)
  })
  .then((response) => {
    res.status(200).send(response);
  })
  .catch((error) => {
    res.status(400).send(error);
  });
    
Python
# Requests is already installed, no need to add it to requirements.txt
import requests

receiving_service_url = ...

# Set up metadata server request
# See https://cloud.google.com/compute/docs/instances/verifying-instance-identity#request_signature
metadata_server_token_url = 'http://metadata/computeMetadata/v1/instance/service-accounts/default/identity?audience='

token_request_url = metadata_server_token_url + receiving_service_url
token_request_headers = {'Metadata-Flavor': 'Google'}

# Fetch the token
token_response = requests.get(token_request_url, headers=token_request_headers)
jwt = token_response.content.decode("utf-8")

# Provide the token in the request to the receiving service
receiving_service_headers = {'Authorization': f'Bearer {jwt}'}
service_response = requests.get(receiving_service_url, headers=receiving_service_headers)

print(service_response.content)
    
Go
import (
	"fmt"
	"net/http"

	"cloud.google.com/go/compute/metadata"
)

// makeGetRequest makes a GET request to the specified Cloud Run endpoint in
// serviceURL (must be a complete URL) by authenticating with the ID token
// obtained from the Metadata API.
func makeGetRequest(serviceURL string) (*http.Response, error) {
	// query the id_token with ?audience as the serviceURL
	tokenURL := fmt.Sprintf("/instance/service-accounts/default/identity?audience=%s", serviceURL)
	idToken, err := metadata.Get(tokenURL)
	if err != nil {
		return nil, fmt.Errorf("metadata.Get: failed to query id_token: %+v", err)
	}
	req, err := http.NewRequest("GET", serviceURL, nil)
	if err != nil {
		return nil, err
	}
	req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", idToken))
	return http.DefaultClient.Do(req)
}
Java
En esta muestra, se usa la biblioteca de autenticación de Google para recuperar las credenciales predeterminadas de la aplicación de tu entorno local de desarrollo o de Cloud Run. Estas credenciales se usan para crear un token de ID a fin de anexar a la solicitud HTTP.
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();
  }
}

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

Si invocas un servicio desde una instancia de procesamiento que no tenga acceso a metadatos de procesamiento (p. ej., tu propio servidor), deberás generar el token adecuado de forma manual:

  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 el código de muestra de Identity-Aware Proxy para ver muestras de código de los pasos anteriores.