Como autenticar entre serviços

Se sua arquitetura estiver usando vários serviços, esses serviços provavelmente precisam se comunicar uns com os outros, usando meios síncronos ou assíncronos. Muitos desses serviços podem ser privados e exigir credenciais para acesso.

Para a comunicação assíncrona, é possível usar os seguintes serviços do Google Cloud:

  • Cloud Tasks para comunicação assíncrona de um para um
  • Pub/Sub para comunicação assíncrona de um para muitos, de um para um e de muitos para um
  • Cloud Scheduler para comunicação assíncrona programada regularmente
  • Eventarc para comunicação baseada em eventos

Em todos esses casos, o serviço usado gerencia a interação com o serviço de recebimento, com base na configuração definida.

Mas para a comunicação síncrona, o serviço chama outro serviço diretamente, por HTTP, usando o URL do endpoint. Para este caso de uso, verifique se cada serviço só pode fazer solicitações para serviços específicos. Por exemplo, se você tiver um serviço login, ele poderá acessar o serviço user-profiles, mas não o serviço search.

Nessa situação, o Google recomenda usar o IAM e uma identidade de serviço baseada em uma conta de serviço gerenciada pelo usuário por serviço que tenha recebido o conjunto mínimo de permissões necessárias para fazer o trabalho.

Além disso, a solicitação precisa apresentar um comprovante da identidade do serviço de chamada. Para fazer isso, configure o serviço de chamada para adicionar um token de ID do OpenID Connect assinado pelo Google como parte da solicitação.

Configurar a conta de serviço

Para configurar uma conta de serviço, configure o serviço de recebimento para aceitar solicitações do serviço de chamada tornando a conta de serviço do serviço de chamada um principal no serviço de recebimento. Em seguida, você concede a essa conta de serviço o papel de Chamador (roles/run.invoker) do Cloud Run. Para executar as duas tarefas, siga as instruções na guia apropriada:

IU do Console

  1. Acesse o Console do Google Cloud:

    Acessar o Console do Google Cloud

  2. Selecione o serviço de recebimento.

  3. Clique em Mostrar painel de informações no canto superior direito para mostrar a guia Permissões.

  4. Clique em Adicionar principal.

    1. Digite a identidade do serviço de chamada. Este é geralmente um endereço de e-mail, por padrão PROJECT_NUMBER-compute@developer.gserviceaccount.com.

    2. Selecione o papel Cloud Run Invoker no menu suspenso Selecionar um papel.

    3. Clique em Save.

gcloud

Use o 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'

em que RECEIVING_SERVICE é o nome do serviço de recebimento e CALLING_SERVICE_IDENTITY é o endereço de e-mail da conta de serviço, por padrão PROJECT_NUMBER-compute@developer.gserviceaccount.com.

Terraform

Para saber como aplicar ou remover uma configuração do Terraform, consulte Comandos básicos do Terraform.

O código do Terraform a seguir cria um serviço inicial do Cloud Run destinado a ser público.

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

Substitua us-docker.pkg.dev/cloudrun/container/hello por uma referência à imagem do seu contêiner.

O código do Terraform a seguir torna o serviço inicial 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_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
}

O código do Terraform a seguir cria um segundo serviço do Cloud Run destinado a ser particular.

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

Substitua us-docker.pkg.dev/cloudrun/container/hello por uma referência à imagem do seu contêiner.

O código do Terraform a seguir torna o segundo serviço particular.

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
}

O código do Terraform a seguir cria uma conta de serviço.

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

O código do Terraform a seguir permite que os serviços anexados à conta de serviço invoquem o serviço inicial particular do 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_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
}

Adquirir e configurar o token de ID

Depois de conceder o papel adequado à conta de serviço de chamada, siga estas etapas:

  1. Busque um token de ID assinado pelo Google usando um dos métodos descritos na seção a seguir. Defina a declaração de público-alvo (aud) como o URL do serviço que recebe ou um público-alvo personalizado configurado. Se você não estiver usando um público-alvo personalizado, o valor aud precisará permanecer como o URL do serviço, mesmo ao fazer solicitações para uma tag de tráfego específica.

  2. Adicione o token de ID buscado na etapa anterior a um dos seguintes cabeçalhos na solicitação para o serviço de recebimento:

    • Um cabeçalho Authorization: Bearer ID_TOKEN.
    • Um cabeçalho X-Serverless-Authorization: Bearer ID_TOKEN. É possível usar esse cabeçalho se o aplicativo já usar o cabeçalho Authorization para autorização personalizada. Isso remove a assinatura antes de transmitir o token para o contêiner do usuário.

Para ver outras formas de conseguir um token de ID que não estejam descritas nesta página, consulte Métodos para receber um token de ID.

Usar as bibliotecas de autenticação

A maneira mais fácil e confiável de gerenciar esse processo é usar as bibliotecas de autenticação, conforme mostrado abaixo, para gerar e empregar esse token. Esse código funciona em qualquer ambiente, mesmo fora do Google Cloud, em que as bibliotecas podem conseguir credenciais de autenticação para uma conta de serviço, incluindo ambientes compatíveis com aplicativos locais Credenciais padrão. No entanto, o código não funciona para conseguir credenciais de autenticação para uma conta de usuário.

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

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: %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();
  }
}

Usar o servidor de metadados

Se, por algum motivo, não for possível usar as bibliotecas de autenticação, será possível buscar um token de ID no servidor de metadados do Compute enquanto o contêiner estiver em execução no Cloud Run. Esse método não funciona fora do Google Cloud, inclusive na máquina local.

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

Em que AUDIENCE é o URL do serviço que você está invocando ou um público-alvo personalizado configurado.

A tabela a seguir resume as principais partes de uma solicitação de consulta de metadados.

Componentes Descrição
URL raiz

Todos os valores de metadados são definidos como subcaminhos abaixo do seguinte URL raiz:


http://metadata.google.internal/computeMetadata/v1
Cabeçalho da solicitação

O seguinte cabeçalho precisa estar em cada solicitação:


Metadata-Flavor: Google

Esse cabeçalho indica que a solicitação foi enviada com a intenção de recuperar valores de metadados, em vez de recuperar involuntariamente de uma fonte insegura e permite que o servidor de metadados retorne os dados solicitados. Se você não fornecer esse cabeçalho, o servidor de metadados negará sua solicitação.

Para ver instruções completas de um aplicativo usando essa técnica de autenticação de serviço a serviço, siga o tutorial sobre como proteger os serviços do Cloud Run.

Usar a federação de identidade da carga de trabalho de fora do Google Cloud

Se o ambiente usar um provedor de identidade com suporte da federação de identidade da carga de trabalho, será possível usar o método a seguir para autenticar com segurança o serviço do Cloud Run fora do Google Cloud:

  1. Configure sua conta de serviço conforme descrito em Configurar a conta de serviço nesta página.

  2. Configure a federação de identidade da carga de trabalho para seu provedor de identidade, conforme descrito em Como configurar a federação de identidade da carga de trabalho.

  3. Siga as instruções em Como conceder permissões de identidade externas para personificar uma conta de serviço.

  4. Use a API REST para adquirir um token de curta duração, mas, em vez de chamar generateAccessToken para receber um token de acesso, chame generateIdToken para receber um token de ID.

    Por exemplo, usando 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
    

    Em que SERVICE_ACCOUNT é o endereço de e-mail da conta de serviço que o pool de identidades da carga de trabalho está configurado para acessar, e SERVICE_URL é o URL do serviço do Cloud Run. que você está chamando. Esse valor precisa permanecer como o URL do serviço, mesmo ao fazer solicitações a uma tag de tráfego específica. $STS_TOKEN é o token do serviço de token de segurança que você recebeu na etapa anterior nas instruções de federação da identidade da carga de trabalho.

É possível incluir o token de ID da etapa anterior na solicitação ao serviço usando um cabeçalho Authorization: Bearer ID_TOKEN ou um cabeçalho X-Serverless-Authorization: Bearer ID_TOKEN. Se os dois cabeçalhos forem fornecidos, somente o cabeçalho X-Serverless-Authorization será verificado.

Usar uma chave de conta de serviço salva fora do Google Cloud

Se a federação de identidade da carga de trabalho não for adequada para seu ambiente, será possível usar uma chave de conta de serviço salva para autenticar de fora do Google Cloud. Atualize o código de cliente para usar as bibliotecas de autenticação como descrito anteriormente com o Application Default Credentials.

Para adquirir um token de ID assinado pelo Google, use um JWT autoassinado, mas isso é bastante complicado e propenso a erros. As etapas básicas são as seguintes:

  1. Autoassine um JWT da conta de serviço com a declaração target_audience definida como o URL do serviço que recebe ou um público-alvo personalizado configurado. Se você não estiver usando domínios personalizados, o valor target_audience permanecerá como o URL do serviço, mesmo ao fazer solicitações para uma tag de tráfego específica.

  2. Troque o JWT autoassinado por um token de ID assinado pelo Google, que precisa ter a reivindicação aud definida como a URL mostrada anteriormente.

  3. Inclua o token de ID na solicitação ao serviço usando um cabeçalho Authorization: Bearer ID_TOKEN ou um cabeçalho X-Serverless-Authorization: Bearer ID_TOKEN. Se ambos os cabeçalhos forem fornecidos, somente o cabeçalho X-Serverless-Authorization será verificado.

É possível examinar este exemplo do Cloud Functions para uma amostra das etapas anteriores.

Receber solicitações autenticadas

No serviço particular de recebimento, é possível analisar o cabeçalho de autorização para receber as informações enviadas pelo token do portador.

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"