Como autenticar entre serviços

Mantenha tudo organizado com as coleções Salve e categorize o conteúdo com base nas suas preferências.

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

O código do Terraform a seguir cria um serviço inicial do Cloud Run destinado a 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
    }
  }
}

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

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

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

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

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

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. O valor aud precisa permanecer como o URL do serviço, mesmo ao fazer solicitações a uma tag de tráfego específica.

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

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

Chamada de fora do Google Cloud

É possível chamar um serviço particular de fora do Google Cloud usando a federação de identidade da carga de trabalho ou usando uma chave de conta de serviço salva. Os dois métodos são descritos nas seções a seguir.

Usar federação de identidade da carga de trabalho

Se o ambiente usar um provedor de identidade compatível com a federação de identidade da carga de trabalho, você poderá usar esse método para autenticar com segurança no serviço do Cloud Run:

  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.

Depois de receber o token de ID, é possível incluí-lo em um cabeçalho Authorization: Bearer ID_TOKEN na solicitação para o serviço de recebimento.

Usar uma chave da conta de serviço salva

Se a federação da identidade da carga de trabalho não for apropriada para seu ambiente, será possível usar uma chave de conta de serviço transferida para autenticar. Atualize seu código de cliente para usar as bibliotecas de autenticação como descrito acima com 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. O valor target_audience precisa permanecer como o URL do serviço, mesmo ao fazer solicitações a uma tag de tráfego específica.

  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.