서비스 간 인증

아키텍처에서 여러 서비스를 사용하는 경우 이러한 서비스는 비동기 또는 동기 수단을 사용하여 서로 통신해야 할 수 있습니다. 이러한 서비스 대부분은 비공개일 수 있으며 액세스에 사용자 인증 정보가 필요합니다.

비동기 통신에는 다음 Google Cloud 서비스를 사용할 수 있습니다.

위 모든 경우에 사용되는 서비스는 설정한 구성에 따라 수신 서비스와의 상호작용을 관리합니다.

하지만 동기 통신의 경우에는 서비스가 HTTP를 통해 엔드포인트 URL을 사용하여 다른 서비스를 직접 호출합니다. 이 사용 사례에서는 각 서비스가 특정 서비스에 대해서만 요청을 수행할 수 있는지 확인해야 합니다. 예를 들어 login 서비스가 있으면 user-profiles 서비스에 액세스할 수 있지만 search 서비스에는 액세스할 수 없습니다.

이 경우 IAM서비스별 사용자 관리형 서비스 계정에 따라 부여된 서비스 ID를 사용하여 작업을 수행하는 데 필요한 최소 권한 모음입니다.

또한 요청은 호출 서비스의 ID를 증명해야 합니다. 이렇게 하려면 호출 서비스가 Google 서명 OpenID Connect ID 토큰을 요청의 일부로 추가하도록 구성합니다.

서비스 계정 설정

서비스 계정을 설정하려면 수신 서비스에서 호출 서비스의 서비스 계정 구성원을 만들어 수신 서비스에서 호출 서비스의 요청을 수락하도록 구성합니다. 그런 다음 해당 서비스 계정에 Cloud Run 호출자(roles/run.invoker) 역할을 부여합니다. 이 두 태스크를 모두 수행하려면 적절한 탭의 안내를 따르세요.

Console UI

  1. Google Cloud Console로 이동합니다.

    Google Cloud Console로 이동

  2. 수신 서비스를 선택합니다.

  3. 오른쪽 상단 모서리에 있는 정보 패널 표시를 클릭하여 권한 탭을 표시합니다.

  4. 구성원 추가 필드에 호출 서비스의 ID를 입력합니다. 일반적으로 이메일 주소이며 기본적으로 PROJECT_NUMBER-compute@developer.gserviceaccount.com입니다.

  5. 역할 선택 드롭다운 메뉴에서 Cloud Run Invoker 역할을 선택합니다.

  6. 추가를 클릭합니다.

gcloud

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는 수신 서비스의 이름이고 CALLING_SERVICE_IDENTITY는 기본적으로 서비스 계정의 이메일 주소입니다(PROJECT_NUMBER-compute@developer.gserviceaccount.com).

ID 토큰 획득 및 구성

호출 서비스 계정에 적절한 역할이 부여되면 다음을 수행해야 합니다.

  1. 대상 클레임(aud)이 수신 서비스의 URL로 설정된 Google 서명 ID 토큰을 가져옵니다.

  2. ID 토큰을 수신 서비스에 대한 요청의 Authorization: Bearer ID_TOKEN 헤더에 포함합니다.

이 프로세스를 관리하는 가장 쉽고 안정적인 방법은 다음에 표시된 것처럼 인증 라이브러리를 사용하여 이 토큰을 생성 및 사용하는 것입니다. 이 코드는 라이브러리가 로컬 애플리케이션 기본 사용자 인증 정보를 지원하는 환경을 포함하여 사용자 인증 정보를 가져올 수 있는 모든 환경에서(Google Cloud 외부에서도) 작동합니다.

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: https://my-cloud-run-service.run.app/
// const targetAudience = 'https://TARGET_HOSTNAME/';
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(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
}

자바

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

메타데이터 서버 사용

어떤 이유로든 인증 라이브러리를 사용할 수 없는 경우 컨테이너가 Cloud Run에서 실행되는 동안 Compute 메타데이터 서버에서 ID 토큰을 가져올 수 있습니다. 이 방법은 로컬 머신을 포함하여 Google Cloud 외부에서 작동하지 않습니다.

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

여기서 AUDIENCE는 호출할 서비스의 URL입니다.

이 서비스 간 인증 기술을 사용하는 애플리케이션에 대한 엔드 투 엔드 연습을 위해서는 Cloud Run 서비스 보안 가이드를 참조하세요.

GCP 외부에서 호출

Google Cloud 외부에서 비공개 서비스를 호출하는 가장 좋은 방법은 위 설명대로 애플리케이션 기본 사용자 인증 정보를 설정하여 인증 라이브러리를 사용하는 것입니다.

자체 서명 JWT를 사용하여 Google 서명 ID 토큰을 가져올 수 있지만 이 방법은 매우 복잡하며 오류가 발생하기 쉽습니다. 기본 단계는 다음과 같습니다.

  1. target_audience 클레임이 수신 서비스의 URL로 설정된 서비스 계정 JWT에 자체 서명합니다.

  2. Google이 서명한 ID 토큰과 자체 서명한 JWT를 교환합니다. 이 토큰에는 위 URL로 설정된 aud 클레임이 있습니다.

  3. ID 토큰을 서비스에 대한 요청의 Authorization: Bearer ID_TOKEN 헤더에 포함합니다.

Cloud Functions 예시를 통해 위 단계의 샘플을 검사할 수 있습니다.