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

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

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

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

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

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

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

  • cloudfunctions.functions.invoke. בדרך כלל זה נעשה באמצעות התפקיד Invoker.

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

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

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

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

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

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

דוגמה 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 functions במסוף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):

המסוף

משתמשים ב-Cloud Run functions Invoker:

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

המשך


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 בבקשה.

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