호출 인증

함수에 대한 다른 관리 작업을 생성, 업데이트 또는 수행하려면 적절한 역할이 부여된 주 구성원이 있어야 합니다. 자세한 내용은 IAM을 통한 액세스 승인을 참조하세요.

하지만 함수 호출은 좀 더 복잡한 이벤트일 수 있습니다. 이벤트 기반 함수는 구독 중인 이벤트 소스로만 호출될 수 있지만 HTTP 함수는 다른 위치에서 시작되는 다양한 종류의 ID로 호출될 수 있습니다. 호출자는 함수를 테스트 중인 개발자이거나 함수를 사용하려는 다른 함수 또는 서비스가 될 수 있습니다. 기본적으로 이러한 ID는 자체 인증해야 하며(ID 증명 제공) ID에 적절한 권한이 있어야 합니다. 인증되지 않은 액세스는 가능하지만 사용 설정해야 합니다. 자세한 내용은 IAM을 통한 액세스 관리를 참조하세요.

개발자 테스트 인증

개발자에게는 함수를 생성, 업데이트, 삭제할 수 있는 액세스 권한이 필요하며 권한은 일반(IAM) 프로세스를 사용하여 부여됩니다.

그러나 개발자는 테스트 목적으로 함수를 호출해야 할 수 있습니다. curl 또는 유사한 도구를 사용하여 함수를 호출하려면 다음을 수행해야 합니다.

  • Cloud Functions에 액세스하는 데 사용하는 계정에 cloudfunctions.functions.invoke 권한이 있는 역할을 할당합니다. 기본적으로 Cloud Functions 관리자Cloud Functions 개발자 역할에는 이 권한이 있습니다. 역할과 관련 권한의 전체 목록은 Cloud Functions IAM 역할을 참조하세요.

  • 로컬 머신에서 작업하는 경우 Google Cloud SDK를 초기화하여 명령줄 액세스를 설정합니다. 서비스 계정 키를 다운로드하고 GOOGLE_APPLICATION_CREDENTIALS 환경 변수를 설정했는지 확인합니다.

  • Authorization 헤더에 저장된 Google에서 생성한 ID 토큰으로 요청에 인증 사용자 인증 정보를 제공합니다. 예를 들어 다음과 같이 gcloud를 통해 토큰을 가져올 수 있습니다.

    curl https://REGION-PROJECT_ID.cloudfunctions.net/FUNCTION_NAME \
      -H "Authorization: bearer $(gcloud auth print-identity-token)"
    

항상 그렇듯이 함수를 개발하고 사용하는 데 필요한 최소 권한 집합을 할당하는 것이 좋습니다. 함수에 대한 IAM 정책이 최소 사용자 수와 서비스 계정 수로 제한되어 있는지 확인합니다.

함수-함수 호출 인증

여러 함수를 연결하는 서비스를 빌드할 경우 각 함수가 다른 함수의 특정 하위 집합에만 요청을 보낼 수 있도록 하는 것이 좋습니다. 예를 들어 login 함수가 있는 경우 user profiles 함수에는 액세스할 수 있지만 search 함수에는 액세스할 수 없어야 합니다.

특정 호출 함수의 요청을 수락하도록 수신 함수를 구성하려면 수신 함수에서 호출 함수의 서비스 계정에 Cloud Functions 호출자(roles/cloudfunctions.invoker) 역할을 부여해야 합니다.

콘솔

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

    Google Cloud Console로 이동

  2. 수신 함수 옆의 체크박스를 클릭합니다.

  3. 화면 상단의 권한을 클릭합니다. 권한 패널이 열립니다.

  4. 주 구성원 추가를 클릭합니다.

  5. 새 주 구성원 필드에 호출 함수의 ID를 입력합니다. 서비스 계정 이메일이어야 합니다.

  6. 역할 선택 드롭다운 메뉴에서 Cloud Functions > Cloud Functions 호출자 역할을 선택합니다.

  7. 저장을 클릭합니다.

gcloud

gcloud functions add-iam-policy-binding 명령어를 사용합니다.

gcloud functions add-iam-policy-binding RECEIVING_FUNCTION \
  --member='serviceAccount:CALLING_FUNCTION_IDENTITY' \
  --role='roles/cloudfunctions.invoker'

여기서 RECEIVING_FUNCTION은 수신 함수 이름이고 CALLING_FUNCTION_IDENTITY는 호출 함수 ID인 서비스 계정 이메일입니다.

수신 함수를 호출하므로 호출 함수는 인증을 위해 Google 서명 ID 토큰도 제공해야 합니다. 이 절차는 두 단계로 이루어져 있습니다.

  1. 대상 필드(aud)가 수신 함수의 URL로 설정된 Google에서 서명한 ID 토큰을 만듭니다.

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

이 프로세스를 관리하는 가장 쉽고 신뢰할 수 있는 방법은 아래와 같이 인증 라이브러리를 사용하여 이 토큰을 생성하고 사용하는 것입니다.

프로그래매틱 방식으로 토큰 생성

다음 코드를 사용하여 ID 토큰을 생성할 수 있습니다. 이 코드는 로컬 애플리케이션 기본 사용자 인증 정보를 지원하는 환경 등 라이브러리가 인증 사용자 인증 정보를 얻을 수 있는 모든 환경에서 작동합니다.

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

수동으로 토큰 생성

함수를 호출할 때 어떤 이유로 인증 라이브러리를 사용할 수 없는 경우 컴퓨팅 메타데이터 서버를 사용하거나 자체 서명 JWT를 만들어 Google 서명 ID 토큰과 교환하는 두 가지 방법으로 ID 토큰을 수동으로 가져올 수 있습니다.

메타데이터 서버 사용

다음과 같이 컴퓨팅 메타데이터 서버를 사용하여 특정 잠재고객이 있는 ID 토큰을 가져올 수 있습니다.

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

여기서 잠재고객은 호출하는 함수의 URL입니다(예: https://GCP_REGION-PROJECT_ID.cloudfunctions.net/my-function).

Google 서명 ID 토큰과 자체 서명 JWT 교환

  1. 수신 함수에서 호출 함수의 서비스 계정에 Cloud Functions 호출자(roles/cloudfunctions.invoker) 역할을 부여합니다.

  2. 서비스 계정 및 키를 만들고 비공개 키(JSON 형식)가 포함된 파일을 호출 함수나 서비스에서 요청하는 호스트에 다운로드합니다.

  3. 헤더가 {"alg":"RS256","typ":"JWT"}로 설정된 JWT를 만듭니다. 페이로드에는 수신 함수의 URL로 설정된 target_audience 클레임과 위에서 사용한 서비스 계정의 이메일 주소로 설정된 isssub 클레임이 포함되어야 합니다. expiat 클레임도 포함되어야 합니다. aud 클레임은 https://www.googleapis.com/oauth2/v4/token으로 설정되어야 합니다.

  4. 위에서 다운로드한 비공개 키를 사용하여 JWT에 서명합니다.

  5. 이 JWT를 사용하여 POST 요청을 https://www.googleapis.com/oauth2/v4/token으로 전송합니다. 인증 데이터는 요청의 헤더와 요청 본문에 포함되어야 합니다.

    헤더:

    Authorization: Bearer $JWT - where $JWT is the JWT you just created
    Content-Type: application/x-www-form-urlencoded
    

    본문:

    grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=$JWT
    

    $JWT를 방금 만든 JWT로 바꿉니다.

    그러면 Google에서 서명한 id_token이 포함된 또 다른 JWT가 반환됩니다.

GET/POST 요청을 수신 함수에 보냅니다. Google 서명 ID 토큰을 요청의 Authorization: Bearer ID_TOKEN_JWT 헤더에 포함합니다.