호출 인증

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

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

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

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

개발자 테스트 인증

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

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

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

    • cloudfunctions.functions.invoke 권한 1세대 함수용 cloudfunctions.functions.invoke 일반적으로 Cloud Functions 호출자 역할을 통해 이루어집니다. 기본적으로 Cloud Functions 관리자Cloud Functions 개발자 역할에는 이 권한이 있습니다. 전체 역할 및 관련 권한 목록은 Cloud Functions IAM 역할을 참조하세요.
    • 2세대 함수용 run.routes.invoke 이는 일반적으로 Cloud Run 호출자 역할을 통해 이루어집니다.
  • 로컬 머신에서 작업하는 경우 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 Functions 페이지에서 또는 Google Cloud CLI 배포 명령어 예시의 1단계와 같이 gcloud functions describe 명령어를 실행하여 이 URL을 검색합니다.

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

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

함수-함수 호출 인증

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

특정 호출 함수의 요청을 수락하도록 수신 함수를 구성하려면 수신 함수에서 호출 함수의 서비스 계정에 적절한 호출자 역할을 부여해야 합니다. 1세대 함수의 경우 호출자 역할은 Cloud Functions 호출자(roles/cloudfunctions.invoker)입니다. 2세대 함수의 경우 호출자 역할은 Cloud Run 호출자(roles/run.invoker)이며 기본 서비스에서 부여되어야 합니다.

Cloud Functions(1세대):

콘솔

1세대 함수의 경우 Cloud Functions 호출자를 사용합니다.

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

    Google Cloud 콘솔로 이동

  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인 서비스 계정 이메일입니다.

Cloud Functions(2세대):

콘솔

2세대 함수의 경우 Cloud Run 호출자를 사용합니다.

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

    Google Cloud 콘솔로 이동

  2. Cloud Run 서비스 목록에서 수신 함수 옆에 있는 체크박스를 클릭합니다. 함수 자체를 클릭하지 마세요.

    권한 패널이 열립니다.

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

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

    프로젝트 번호는 프로젝트 ID 및 프로젝트 이름과 다릅니다. Google Cloud 콘솔 대시보드 페이지에서 프로젝트 번호를 찾을 수 있습니다.

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

  6. 저장을 클릭합니다.

gcloud

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

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

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

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

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

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

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

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

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

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
}

자바

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

다음 단계