אימות להפעלה (דור ראשון)

כדי להפעיל פונקציית Cloud Run מאומתת, הגורם המרכזי הבסיסי צריך לעמוד בדרישות הבאות:

  • יש לכם הרשאה להפעיל את הפונקציה.
  • מספק אסימון מזהה כשהוא מפעיל את הפונקציה.

מה זה פרינציפל? כפי שמתואר במאמר בנושא אבטחת פונקציות Cloud Run, פונקציות Cloud Run תומכות בשני סוגים שונים של זהויות, שנקראות גם גורמים ראשיים:

  • חשבונות שירות: אלה חשבונות מיוחדים שמשמשים כזהות של ישות שאינה אדם, כמו פונקציה, אפליקציה או מכונה וירטואלית. הם מאפשרים לכם לאמת את הזהות של ישויות שאינן אנשים.
  • חשבונות משתמשים: החשבונות האלה מייצגים אנשים, כבעלי חשבונות Google פרטיים או כחלק מישות בשליטת Google, כמו קבוצת Google.

בסקירה הכללית על IAM תוכלו לקרוא מידע נוסף על מושגי יסוד ב-IAM.

כדי להפעיל פונקציית Cloud Run מאומתת, לחשבון המשתמש צריכה להיות הרשאת ההפעלה של IAM:

כדי להעניק את ההרשאות האלה, משתמשים בפקודה add-invoker-policy-binding כמו שמוסבר במאמר בנושא אימות פונקציה לקריאות לפונקציה.

כדי לקבל הרשאה ליצור, לעדכן או לבצע פעולות ניהול אחרות בפונקציה, לחשבון המשתמש צריכים להיות תפקידים מתאימים. תפקידים כוללים הרשאות שמגדירות את הפעולות שהגורם המורשה יכול לבצע. מידע נוסף זמין במאמר שימוש ב-IAM כדי לאשר גישה.

יכול להיות שיהיו עוד מורכבויות בהפעלת פונקציה. פונקציות מבוססות-אירוע יכולות להיות מופעלות רק על ידי מקור האירוע שאליו הן רשומות, אבל פונקציות HTTP יכולות להיות מופעלות על ידי סוגים שונים של זהויות, שמקורן במקומות שונים. הגורם שמפעיל את הפונקציה יכול להיות מפתח שבודק את הפונקציה, או פונקציה או שירות אחרים שרוצים להשתמש בפונקציה. כברירת מחדל, כדי לאמת את הזהות שלהם, הגורמים האלה צריכים לספק טוקן מזהה עם הבקשה. בנוסף, לחשבון שבו משתמשים צריכות להיות ההרשאות המתאימות.

מידע נוסף על יצירה ושימוש באסימונים מזהים

דוגמאות לאימות

בקטע הזה מוצגות כמה דוגמאות שונות לאימות לצורך הפעלה.

דוגמה 1: אימות של בדיקות מפתחים

מפתחים צריכים גישה ליצירה, לעדכון ולמחיקה של פונקציות, והגישה הזו ניתנת באמצעות התהליך הרגיל (IAM).

אבל כמפתחים, יכול להיות שתצטרכו להפעיל את הפונקציות שלכם למטרות בדיקה. כדי להפעיל פונקציה באמצעות curl או כלים דומים, חשוב לשים לב לנקודות הבאות:

  • מקצים לחשבון המשתמש של פונקציות Cloud Run תפקיד שמכיל את הרשאת ההפעלה.

  • אם אתם עובדים מהמחשב המקומי, אתם צריכים לאתחל את Google Cloud CLI כדי להגדיר גישה לשורת הפקודה.

  • מצרפים לבקשה פרטי כניסה לאימות בתור אסימון מזהה שנוצר על ידי Google ומאוחסן בכותרת Authorization. לדוגמה, כדי לקבל אסימון מזהה באמצעות gcloud, מריצים את הפקודה הבאה:

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

    כאשר FUNCTION_URL הוא כתובת ה-URL של הפונקציה. אפשר לאחזר את כתובת ה-URL הזו מהדף פונקציות Cloud Run במסוףGoogle Cloud , או על ידי הרצת הפקודה gcloud functions describe כמו שמוצג בשלב הראשון בדוגמה של פקודת הפריסה של Google Cloud CLI.

אתם יכולים להשתמש באסימונים שנוצרו על ידי gcloud כדי להפעיל פונקציות HTTP בכל פרויקט, כל עוד לחשבון שלכם יש הרשאה cloudfunctions.functions.invoke בפונקציה שמופעלת. למטרות פיתוח, משתמשים באסימונים מזהים שנוצרו על ידי gcloud. עם זאת, חשוב לזכור שלאסימונים כאלה אין טענת קהל, ולכן הם חשופים למתקפות ממסר. בסביבות ייצור, משתמשים באסימונים מזהים שהונפקו לחשבון שירות עם קהל מתאים שצוין. הגישה הזו משפרת את האבטחה כי היא מגבילה את השימוש באסימונים לשירות שאליו הם מיועדים בלבד.

כמו תמיד, מומלץ להקצות את ההרשאות המינימליות שנדרשות לפיתוח ולשימוש בפונקציות. חשוב לוודא שמדיניות IAM בפונקציות מוגבלת למספר המינימלי של משתמשים וחשבונות שירות.

דוגמה 2: אימות פונקציה לקריאות לפונקציות

כשיוצרים שירותים שמקשרים בין כמה פונקציות, מומלץ לוודא שכל פונקציה יכולה לשלוח בקשות רק לקבוצת משנה ספציפית של הפונקציות האחרות. לדוגמה, אם יש לכם פונקציה login, היא צריכה להיות מסוגלת לגשת לפונקציה user profiles, אבל כנראה שלא תהיה לה גישה לפונקציה search.

כדי להגדיר את פונקציית הקבלה כך שתקבל בקשות מפונקציית הפעלה ספציפית, צריך להקצות את תפקיד ההפעלה המתאים לחשבון השירות של פונקציית ההפעלה בפונקציית הקבלה. תפקיד ההפעלה הוא Cloud Run functions Invoker ‏ (roles/cloudfunctions.invoker):

המסוף

משתמשים ב-Invoker של פונקציות Cloud Run:

  1. נכנסים למסוף Google Cloud :

    כניסה למסוף Google Cloud

  2. לוחצים על תיבת הסימון לצד הפונקציה המקבלת. (לא לוחצים על הפונקציה עצמה).

  3. לוחצים על הרשאות בחלק העליון של המסך. נפתחת החלונית הרשאות.

  4. לוחצים על Add principal.

  5. בשדה New principals, מזינים את הזהות של הפונקציה שקוראת. זו צריכה להיות כתובת אימייל של חשבון שירות.

  6. בתפריט הנפתח Select a role, בוחרים את התפקיד Cloud Functions > Cloud Functions Invoker.

  7. לוחצים על Save.

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 מזהה באופן אוטומטי את יצירת הפונקציה ומוסיף את התפקיד הנכון של Invoker (cloudfunctions.invoker לדור הראשון).

מחליפים את מה שכתוב בשדות הבאים:

  • RECEIVING_FUNCTION: השם של הפונקציה המקבלת.
  • CALLING_FUNCTION_IDENTITY: זהות הפונקציה שקוראת, כתובת אימייל של חשבון שירות.

מכיוון שהיא תפעיל את הפונקציה המקבלת, הפונקציה הקוראת צריכה לספק גם אסימון מזהה חתום על ידי Google לצורך אימות. התהליך כולל שני שלבים:

  1. יוצרים אסימון מזהה חתום על ידי Google עם שדה הקהל (aud) שמוגדר לכתובת ה-URL של הפונקציה המקבלת.

  2. כוללים את האסימון המזהה בכותרת Authorization: Bearer ID_TOKEN בבקשה לפונקציה.

הדרך הכי קלה ומהימנה לנהל את התהליך הזה היא להשתמש בספריות האימות, כמו שמוצג בהמשך, כדי ליצור את האסימון הזה ולהשתמש בו.

יצירת אסימונים מזהים

בקטע הזה מתוארות דרכים שונות ליצירת טוקן מזהה שנדרש לישות כדי להפעיל פונקציה.

אפשר לקבל גישה לא מאומתת ללא אסימון מזהה, אבל צריך להפעיל את האפשרות הזו. מידע נוסף זמין במאמר שימוש ב-IAM כדי לאשר גישה.

יצירת טוקנים באופן פרוגרמטי

אחרי שהקוד הבא יוצר אסימון מזהה, הוא מפעיל את פונקציית Cloud Run עם האסימון הזה בשמכם. הקוד הזה פועל בכל סביבה שבה הספריות יכולות לקבל פרטי כניסה לאימות, כולל סביבות שתומכות ב-Application Default Credentials מקומיים.

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

יצירת טוקנים באופן ידני

אם אתם מפעילים פונקציה ומסיבה כלשהי אתם לא יכולים להשתמש בספריות האימות, יש שתי דרכים לקבל את אסימון המזהה באופן ידני: באמצעות שרת המטא-נתונים של Compute או על ידי יצירת אסימון JWT בחתימה עצמית והחלפתו באסימון מזהה בחתימת Google.

שימוש בשרת מטא-נתונים

אתם יכולים להשתמש בשרת המטא-נתונים של Compute כדי לאחזר אסימוני מזהה עם קהל ספציפי, באופן הבא:

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

מחליפים את AUDIENCE בכתובת ה-URL של הפונקציה שרוצים להפעיל. אפשר לאחזר את כתובת ה-URL הזו כמו שמתואר בקטע אימות בדיקות של מפתחים למעלה.

החלפת JWT בחתימה עצמית באסימון מזהה עם חתימה של Google

  1. מקצים את התפקיד Invoker (מפעיל) ‏(roles/cloudfunctions.invoker) לחשבון השירות של הפונקציה הקוראת בפונקציה המקבלת.

  2. יוצרים חשבון שירות ומפתח ומורידים את הקובץ עם המפתח הפרטי (בפורמט JSON) למארח שממנו הפונקציה או השירות שקוראים מבצעים את הבקשות.

  3. יוצרים JWT עם הכותרת {"alg":"RS256","typ":"JWT"}. המטען הייעודי (payload) צריך לכלול את התביעה target_audience שמוגדרת לכתובת ה-URL של הפונקציה המקבלת, ואת התביעות iss ו-sub שמוגדרות לכתובת האימייל של חשבון השירות שצוין למעלה. היא צריכה לכלול גם את exp ואת הטענות iat. הערך של טענת 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 שיצרתם.

    הפעולה הזו מחזירה אסימון JWT נוסף שכולל id_token בחתימת Google.

שולחים את בקשת ה-GET/POST לפונקציית הקבלה. כוללים את האסימון המזהה שחתום על ידי Google בכותרת Authorization: Bearer ID_TOKEN_JWT בבקשה.

המאמרים הבאים