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 uma comunicação assíncrona de um para muitos
  • Cloud Scheduler para comunicação assíncrona programada regularmente.

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 membro 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:

    Ir para o Google Cloud Console

  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. No campo Adicionar membros, insira a identidade do serviço de chamada. Este é geralmente um endereço de e-mail, por padrão PROJECT_NUMBER-compute@developer.gserviceaccount.com.

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

  6. Clique em Adicionar.

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.

Adquirir e configurar o token de ID

Depois que a conta de serviço de chamada tiver o papel adequado, você precisará:

  1. Buscar um token de ID assinado pelo Google com a declaração de público-alvo (aud) definida como o URL do serviço de recebimento.

  2. incluir o token do código em um cabeçalho Authorization: Bearer ID_TOKEN na solicitação para o serviço recebido.

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 receber credenciais de autenticação, incluindo ambientes compatíveis com Application Default Credentials locais.

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

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á chamando.

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.

Como fazer chamadas de fora do GCP

A melhor maneira de chamar um serviço particular estando fora do Google Cloud é usar as bibliotecas de autenticação conforme descrito acima, após configurar o Application Default Credentials.

É possível adquirir um token de ID assinado pelo Google usando um JWT autoassinado, mas isso é bastante complicado e propenso a erros. Estas são as etapas básicas:

  1. Assine um JWT de conta de serviço com a declaração target_audience definida como o URL do serviço de recebimento.

  2. Troque o JWT autoassinado por um token de código assinado pelo Google, que deve ter a reivindicação aud definida como a URL acima.

  3. Inclua o token de ID em um cabeçalho Authorization: Bearer ID_TOKEN na solicitação para o serviço.

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