호출 인증(1세대)

인증된 Cloud Run 함수를 호출하려면 기본 주 구성원이 다음 요구사항을 충족해야 합니다.

  • 함수를 호출할 수 있는 권한이 있음
  • 함수를 호출할 때 ID 토큰을 제공함

주 구성원이란 무엇인가요? Cloud Run Functions 보호에 설명된 대로 Cloud Run Functions는 주 구성원이라고도 하는 두 가지 종류의 ID를 지원합니다.

  • 서비스 계정: 함수, 애플리케이션, VM과 같이 비인간 사용자의 ID로 사용되는 특수 계정입니다. 이런 계정은 비인간 사용자를 인증하는 방법을 제공합니다.
  • 사용자 계정: 개인 Google 계정 소유자 또는 Google 그룹처럼 Google이 관리하는 항목에 속하는 사용자 등 인간이 사용하는 계정입니다.

IAM 개요에서 기본 IAM 개념에 대해 자세히 알아보세요.

인증된 Cloud Run 함수를 호출하려면 주 구성원에게 호출자 IAM 권한이 있어야 합니다.

  • cloudfunctions.functions.invoke. 일반적으로 호출자 역할을 통해 부여됩니다.

이러한 권한을 부여하려면 함수 호출에 함수 인증에 나온 대로 add-invoker-policy-binding 명령어를 사용합니다.

함수에 대한 다른 관리 작업을 생성, 업데이트, 수행할 권한을 위해서는 주 구성원에게 적절한 역할이 부여되어야 합니다. 역할에는 주 구성원이 수행할 수 있는 작업을 정의하는 권한이 포함됩니다. 자세한 내용은 IAM을 사용하여 액세스 승인을 참고하세요.

함수를 호출하는 것은 더 복잡할 수 있습니다. 이벤트 기반 함수는 구독 중인 이벤트 소스로만 호출될 수 있지만 HTTP 함수는 다른 위치에서 시작되는 다양한 종류의 ID로 호출될 수 있습니다. 호출자는 함수를 테스트 중인 개발자이거나 함수를 사용하려는 다른 함수 또는 서비스가 될 수 있습니다. 기본적으로 이러한 ID에서는 자신을 인증하는 요청과 함께 ID 토큰을 제공해야 합니다. 또한 사용 중인 계정에 적절한 권한이 부여되어 있어야 합니다.

ID 토큰 생성 및 사용에 대해 자세히 알아보세요.

인증 예시

이 섹션에서는 호출을 위해 인증하는 몇 가지 예를 보여줍니다.

예 1: 개발자 테스트 인증

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

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

  • 호출 권한이 포함된 Cloud Run Functions 사용자 계정에 역할을 할당합니다.

    • cloudfunctions.functions.invoke. 일반적으로 호출자 역할을 통해 부여됩니다. 기본적으로 Cloud Run Functions 관리자Cloud Run Functions 개발자 역할에는 이 권한이 있습니다. 역할과 관련 권한의 전체 목록은 Cloud Run Functions IAM 역할을 참조하세요.
  • 로컬 머신에서 작업하는 경우 Google Cloud CLI를 초기화하여 명령줄 액세스를 설정합니다.

  • 사용자 인증 정보가 있는 요청을 Authorization 헤더에 저장된 Google에서 생성한 ID 토큰으로 제공합니다. 예를 들어 다음 명령어를 실행하여 gcloud를 사용해 ID 토큰을 가져옵니다.

    curl  -H "Authorization: Bearer $(gcloud auth print-identity-token)" \
      https://FUNCTION_URL

    여기서 FUNCTION_URL은 함수 URL입니다. Google Cloud 콘솔의 Cloud Run Functions 페이지에서 또는 Google Cloud CLI 배포 명령어 예시의 1단계와 같이 gcloud functions describe 명령어를 실행하여 이 URL을 검색합니다.

계정에 호출된 함수에 대한 cloudfunctions.functions.invoke 권한이 있으면 gcloud에서 생성된 토큰을 사용하여 모든 프로젝트에서 HTTP 함수를 호출할 수 있습니다. 개발 용도로는 gcloud에서 생성된 ID 토큰을 사용합니다. 그러나 이러한 토큰에는 대상 클레임이 없으므로 릴레이 공격에 취약합니다. 프로덕션 환경에서는 적절하게 지정된 대상과 함께 서비스 계정에 발급된 ID 토큰을 사용합니다. 이 방식은 토큰 사용을 의도한 서비스로만 제한하여 보안을 강화합니다.

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

예 2: 함수 호출에 대한 함수 인증

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

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

콘솔

Cloud Run Functions 호출자를 사용합니다.

  1. Google Cloud 콘솔로 이동합니다.

    Google Cloud 콘솔로 이동

  2. 수신 함수 옆의 체크박스를 클릭합니다. 함수 자체를 클릭하지 마세요.

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

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

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

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

  7. 저장을 클릭합니다.

gcloud

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

gcloud functions add-invoker-policy-binding RECEIVING_FUNCTION \
  --member='serviceAccount:CALLING_FUNCTION_IDENTITY'

add-invoker-policy-binding 명령어는 지정된 구성원(주 구성원)이 지정된 함수를 호출할 수 있도록 허용하는 호출자 역할 IAM 정책 바인딩을 추가합니다. Google Cloud CLI가 함수 생성을 자동으로 감지하고 올바른 호출자 역할(1세대의 경우 cloudfunctions.invoker)을 추가합니다.

다음을 바꿉니다.

  • RECEIVING_FUNCTION: 수신 함수 이름
  • CALLING_FUNCTION_IDENTITY: 호출 함수 ID인 서비스 계정 이메일

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

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

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

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

ID 토큰 생성

이 섹션에서는 주 구성원이 함수를 호출하는 데 필요한 ID 토큰을 생성하는 다양한 방법을 설명합니다.

ID 토큰 없이 인증되지 않은 액세스를 수행할 수 있지만 사용 설정해야 합니다. 자세한 내용은 IAM을 사용하여 액세스 승인을 참조하세요.

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

다음 코드가 ID 토큰을 생성한 후 해당 토큰으로 Cloud Run 함수를 호출합니다. 이 코드는 로컬 애플리케이션 기본 사용자 인증 정보를 지원하는 환경 등 라이브러리가 인증 사용자 인증 정보를 얻을 수 있는 모든 환경에서 작동합니다.

Node.js

/**
 * TODO(developer): Uncomment these variables before running the sample.
 */

// Cloud Functions uses your function's url as the `targetAudience` value
// const targetAudience = 'https://project-region-projectid.cloudfunctions.net/myFunction';
// For Cloud Functions, endpoint (`url`) and `targetAudience` should be equal
// const url = targetAudience;


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 Functions uses your function's URL as the `audience` value
    # audience = https://project-region-projectid.cloudfunctions.net/myFunction
    # For Cloud Functions, `endpoint` and `audience` should be equal

    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 {
	// For Cloud Functions, endpoint (`serviceUrl`) and `audience` are the same.
	// Example `audience` value (Cloud Functions): https://<PROJECT>-<REGION>-<PROJECT_ID>.cloudfunctions.net/myFunction
	// (`targetURL` and `audience` will differ for 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`.
  //
  // For Cloud Functions, endpoint (`serviceUrl`) and `audience` are the same.
  // Example `audience` value (Cloud Functions): https://project-region-projectid.cloudfunctions.net/myFunction
  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();
  }
}

수동으로 토큰 생성

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

메타데이터 서버 사용

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

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

AUDIENCE를 호출할 함수의 URL로 바꿉니다. 위에 있는 개발자 테스트 인증 섹션에 설명된 것처럼 이 URL을 검색할 수 있습니다.

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

  1. 수신 함수에서 호출 함수의 서비스 계정에 호출자(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 헤더에 포함합니다.

다음 단계