Mengautentikasi antarlayanan

Jika arsitektur Anda menggunakan banyak layanan, mungkin diperlukan komunikasi antarlayanan, menggunakan cara asinkron atau sinkron. Banyak dari layanan ini mungkin bersifat pribadi sehingga memerlukan kredensial untuk mengaksesnya.

Untuk komunikasi asinkron, Anda dapat menggunakan layanan Google Cloud berikut:

  • Cloud Tasks untuk komunikasi asinkron one-to-one
  • Pub/Sub untuk komunikasi asinkron one-to-many, one-to-one, dan many-to-one
  • Cloud Scheduler untuk komunikasi asinkron yang terjadwal secara berkala
  • Eventarc untuk komunikasi berbasis peristiwa

Pada semua kasus ini, layanan yang digunakan bertindak untuk mengelola interaksi dengan layanan penerima berdasarkan konfigurasi yang Anda siapkan.

Namun, untuk komunikasi sinkron, layanan Anda akan memanggil layanan lain secara langsung, melalui HTTP, menggunakan URL endpoint-nya. Untuk kasus penggunaan ini, Anda harus memastikan bahwa setiap layanan hanya dapat membuat permintaan ke layanan tertentu. Misalnya, jika Anda memiliki layanan login, layanan tersebut harus dapat mengakses layanan user-profiles, tetapi tidak dapat mengakses layanan search.

Dalam situasi ini, Google merekomendasikan agar Anda menggunakan IAM dan identitas layanan berdasarkan akun layanan yang dikelola pengguna per layanan yang telah diberi kumpulan izin minimum yang diperlukan untuk melakukan pekerjaannya.

Selain itu, permintaan harus menunjukkan bukti identitas layanan panggilan. Untuk melakukannya, konfigurasikan layanan panggilan Anda agar menambahkan token ID OpenID Connect yang ditandatangani Google sebagai bagian dari permintaan.

Menyiapkan akun layanan

Untuk menyiapkan akun layanan, konfigurasikan layanan penerima untuk menerima permintaan dari layanan panggilan dengan menjadikan akun layanan dari layanan panggilan sebagai akun utama pada layanan penerima. Kemudian, Anda memberikan peran Cloud Run Invoker (roles/run.invoker) untuk akun layanan tersebut. Untuk melakukan kedua tugas tersebut, ikuti petunjuk di tab yang sesuai:

UI Konsol

  1. Buka Konsol Google Cloud:

    Buka Konsol Google Cloud

  2. Pilih layanan penerima.

  3. Klik Tampilkan Panel Info di pojok kanan atas untuk menampilkan tab Izin.

  4. Klik Tambahkan akun utama.

    1. Masukkan identitas layanan panggilan. Biasanya berupa alamat email, secara default PROJECT_NUMBER-compute@developer.gserviceaccount.com.

    2. Pilih peran Cloud Run Invoker dari menu drop-down Pilih peran.

    3. Klik Save.

gcloud

Gunakan perintah 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'

dengan RECEIVING_SERVICE sebagai nama layanan penerima, dan CALLING_SERVICE_IDENTITY adalah alamat email akun layanan, secara default PROJECT_NUMBER-compute@developer.gserviceaccount.com.

Terraform

Untuk mempelajari cara menerapkan atau menghapus konfigurasi Terraform, lihat Perintah dasar Terraform.

Kode Terraform berikut membuat layanan Cloud Run awal yang ditujukan untuk publik.

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

Ganti us-docker.pkg.dev/cloudrun/container/hello dengan referensi ke image container Anda.

Kode Terraform berikut menjadikan layanan awal bersifat publik.

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
}

Kode Terraform berikut membuat layanan Cloud Run kedua yang ditujukan untuk pribadi.

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

Ganti us-docker.pkg.dev/cloudrun/container/hello dengan referensi ke image container Anda.

Kode Terraform berikut membuat layanan kedua menjadi pribadi.

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
}

Kode Terraform berikut digunakan untuk membuat akun layanan.

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

Kode Terraform berikut memungkinkan layanan yang tercantum ke layanan akun memanggil layanan Cloud Run pribadi yang awal.

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
}

Memperoleh dan mengonfigurasi token ID

Setelah Anda memberikan peran yang sesuai ke akun layanan panggilan, ikuti langkah-langkah berikut:

  1. Ambil Token ID yang ditandatangani Google menggunakan salah satu metode yang dijelaskan di bagian berikut. Tetapkan klaim audiens (aud) ke URL layanan penerima atau audiens kustom yang dikonfigurasi. Jika Anda tidak menggunakan audiens kustom, nilai aud harus tetap menjadi URL layanan, bahkan saat membuat permintaan ke tag traffic tertentu.

  2. Tambahkan token ID yang Anda ambil dari langkah sebelumnya ke salah satu header berikut dalam permintaan ke layanan penerima:

    • Header Authorization: Bearer ID_TOKEN.
    • Header X-Serverless-Authorization: Bearer ID_TOKEN. Anda dapat menggunakan header ini jika aplikasi sudah menggunakan header Authorization untuk otorisasi kustom. Tindakan ini akan menghapus tanda tangan sebelum meneruskan token ke container pengguna.

Untuk mengetahui cara lain mendapatkan token ID yang tidak dijelaskan di halaman ini, baca Metode untuk mendapatkan token ID.

Menggunakan library autentikasi

Cara termudah dan paling andal untuk mengelola proses ini adalah dengan menggunakan library autentikasi untuk membuat dan menggunakan token ini, seperti yang ditunjukkan di bawah. Kode ini dapat berfungsi di lingkungan apa pun, bahkan di luar Google Cloud, tempat library dapat memperoleh kredensial autentikasi untuk akun layanan, termasuk lingkungan yang mendukung Kredensial Default Aplikasi lokal. Namun, kode ini tidak berfungsi untuk mendapatkan kredensial autentikasi untuk akun pengguna.

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

Menggunakan server metadata

Jika karena alasan tertentu Anda tidak dapat menggunakan library autentikasi, Anda dapat mengambil token ID dari Server metadata Compute saat container Anda sedang berjalan di Cloud Run. Perlu diperhatikan bahwa metode ini tidak berfungsi di luar Google Cloud, termasuk dari komputer lokal Anda.

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

Dengan AUDIENCE adalah URL layanan yang Anda panggil atau audiens kustom yang dikonfigurasi.

Tabel berikut merangkum bagian-bagian utama dari permintaan kueri metadata:

Komponen Deskripsi
URL root

Semua nilai metadata didefinisikan sebagai sub-jalur di bawah URL root berikut:


http://metadata.google.internal/computeMetadata/v1
Header permintaan

Header berikut harus ada dalam setiap permintaan:


Metadata-Flavor: Google

Header ini menunjukkan bahwa permintaan dikirim dengan tujuan mengambil nilai metadata, bukan secara tidak sengaja mengambilnya dari sumber yang tidak aman, dan memungkinkan server metadata menampilkan data yang Anda minta. Jika Anda tidak memberikan header ini, server metadata akan menolak permintaan Anda.

Untuk mengetahui panduan menyeluruh terkait aplikasi yang menggunakan teknik autentikasi antarlayanan ini, ikuti tutorial mengamankan layanan Cloud Run.

Menggunakan workload identity federation dari luar Google Cloud

Jika lingkungan Anda menggunakan penyedia identitas yang didukung oleh workload identity federation, Anda dapat menggunakan metode berikut untuk melakukan autentikasi dengan aman ke layanan Cloud Run dari luar Google Cloud:

  1. Siapkan akun layanan seperti yang dijelaskan dalam Menyiapkan akun layanan di halaman ini.

  2. Konfigurasikan penggabungan workload identity untuk penyedia identitas seperti yang dijelaskan dalam Mengonfigurasi workload identity federation.

  3. Ikuti petunjuk di bagian Memberikan izin identitas eksternal untuk meniru identitas akun layanan.

  4. Gunakan REST API untuk mendapatkan token yang memiliki masa aktif singkat, tetapi alih-alih memanggil generateAccessToken untuk mendapatkan token akses, panggil generateIdToken untuk mendapatkan token ID.

    Misalnya, menggunakan 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
    

    Dengan SERVICE_ACCOUNT adalah alamat email akun layanan tempat workload identity pool dikonfigurasi untuk diakses, dan SERVICE_URL adalah URL layanan Cloud Run yang Anda panggil. Nilai ini harus tetap sebagai URL layanan, bahkan saat membuat permintaan ke tag traffic tertentu. $STS_TOKEN adalah token Layanan Token Keamanan yang Anda terima pada langkah sebelumnya di petunjuk workload identity federation.

Anda dapat menyertakan token ID dari langkah sebelumnya dalam permintaan ke layanan menggunakan header Authorization: Bearer ID_TOKEN atau header X-Serverless-Authorization: Bearer ID_TOKEN. Jika kedua header disediakan, hanya header X-Serverless-Authorization yang akan dicentang.

Menggunakan kunci akun layanan yang didownload dari luar Google Cloud

Jika workload identity federation tidak cocok dengan lingkungan Anda, Anda dapat menggunakan kunci akun layanan yang didownload untuk melakukan autentikasi dari luar Google Cloud. Perbarui kode klien Anda untuk menggunakan library autentikasi seperti yang dijelaskan sebelumnya dengan Kredensial Default Aplikasi.

Anda dapat memperoleh token ID yang ditandatangani Google menggunakan JWT yang ditandatangani sendiri, tetapi cara ini cukup rumit dan berpotensi rentan mengalami error. Langkah-langkah dasarnya adalah sebagai berikut:

  1. Tanda tangani sendiri JWT akun layanan dengan klaim target_audience ditetapkan ke URL layanan penerima atau audiens kustom yang dikonfigurasi. Jika tidak menggunakan domain kustom, nilai target_audience harus tetap sebagai URL layanan, meski saat membuat permintaan ke tag traffic khusus.

  2. Tukar JWT yang ditandatangani sendiri dengan token ID yang ditandatangani Google, yang harus memiliki klaim aud yang ditetapkan ke URL sebelumnya.

  3. Sertakan token ID dalam permintaan ke layanan menggunakan header Authorization: Bearer ID_TOKEN atau header X-Serverless-Authorization: Bearer ID_TOKEN. Jika kedua header disediakan, hanya header X-Serverless-Authorization yang akan dicentang.

Anda dapat lihat contoh Cloud Functions ini untuk melihat contoh langkah sebelumnya.

Menerima permintaan yang diautentikasi

Dalam layanan pribadi penerima, Anda dapat mengurai header otorisasi untuk menerima informasi yang dikirim oleh token Pemilik.

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"