אימות באמצעות JSON Web Token

התכונה 'חיפוש וקטורי' תומכת בנקודות קצה מאומתות של אינדקס באמצעות אסימוני JWT (‏JSON Web Tokens) בחתימה עצמית. כדי לשלוט בגישה לנקודת הקצה של האינדקס, היא מוגדרת לקבל רק אסימוני JWT חתומים שהונפקו על ידי חשבונות שירות ספציפיים של Google עם הרשאה. המשמעות היא שרק לקוחות שמשתמשים בחשבונות המיועדים האלה יכולים ליצור אינטראקציה עם נקודת הקצה.

בדף הזה מפורטים השלבים הנדרשים להגדרת נקודת קצה של אינדקס עם אימות באמצעות JSON Web Token‏ (JWT) ולהרצת שאילתות מול נקודת הקצה הזו.

מגבלות

יצירה של JWT ושימוש בו כדי לשלוח שאילתה לאינדקס

כדי ליצור נקודת קצה של אינדקס ולשאול אותה באמצעות JWT בחתימה עצמית, מבצעים את השלבים הבאים.

יצירת אינדקס

יוצרים אינדקס חיפוש וקטורי לפי ההוראות במאמר יצירת אינדקס.

יצירת נקודת קצה פרטית

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

יצירה של חשבון שירות

יוצרים חשבון שירות ומקצים לו את תפקיד ה-IAM יצירת אסימונים בחשבון שירות.

  1. מפעילים את IAM Service Account Credentials API ויוצרים חשבון שירות:

    gcloud services enable iamcredentials.googleapis.com --project="PROJECT_ID"
    gcloud iam service-accounts create SERVICE_ACCOUNT_ID --project="PROJECT_ID"
    

    מחליפים את הערכים הבאים:

    • ‫PROJECT_ID: הפרויקט שבו רוצים ליצור את חשבון השירות.
    • ‫SERVICE_ACCOUNT_ID: המזהה של חשבון השירות.

    מידע נוסף על יצירת חשבון שירות

  2. משתמשים באחת מהפקודות הבאות כדי להקצות את התפקיד iam.serviceAccountTokenCreator ב-IAM לחשבון השירות:

    • הפקודה הבאה מאפשרת ליצור אסימוני JWT באמצעות חשבון השירות ממכונה וירטואלית (VM) של Compute Engine שחשבון השירות מצורף אליה:

      gcloud iam service-accounts add-iam-policy-binding \
         "SERVICE_ACCOUNT_ID@PROJECT_ID.iam.gserviceaccount.com" \
         --role "roles/iam.serviceAccountTokenCreator" \
         --member "serviceAccount:SERVICE_ACCOUNT_ID@PROJECT_ID.iam.gserviceaccount.com" \
         --project "PROJECT_ID"
      

      מחליפים את הערכים הבאים:

      • ‫SERVICE_ACCOUNT_ID: המזהה של חשבון השירות.
      • ‫PROJECT_ID: הפרויקט שבו רוצים ליצור את חשבון השירות.
    • הפקודה הבאה מעניקה הרשאה ליצור אסימוני JWT באמצעות חשבון השירות מחשבון Google שלכם (בתחנת העבודה):

      gcloud iam service-accounts add-iam-policy-binding \
         "SERVICE_ACCOUNT_ID@PROJECT_ID.iam.gserviceaccount.com" \
         --role "roles/iam.serviceAccountTokenCreator" \
         --member "user:EMAIL_ADDRESS" \
         --project PROJECT_ID
      

      מחליפים את הערכים הבאים:

      • ‫SERVICE_ACCOUNT_ID: המזהה של חשבון השירות.
      • ‫PROJECT_ID: הפרויקט שבו רוצים ליצור את חשבון השירות.
      • EMAIL_ADDRESS: כתובת האימייל שלכם.

פריסת האינדקס לנקודת הקצה עם הגדרת אימות JWT

  1. פורסים את האינדקס לנקודת הקצה הפרטית כמו בדוגמה הבאה:

    gcloud ai index-endpoints deploy-index INDEX_ENDPOINT_ID \
       --index=INDEX_ID \
       --deployed-index-id=DEPLOYED_INDEX_ID \
       --display-name=DEPLOYED_INDEX_NAME \
       --audiences=AUDIENCES \
       --allowed-issuers="SERVICE_ACCOUNT_ID@PROJECT_ID.iam.gserviceaccount.com" \
       --project=PROJECT_ID \
       --region=LOCATION
    

    מחליפים את הערכים הבאים:

    • ‫INDEX_ENDPOINT_ID: המזהה של נקודת הקצה של האינדקס.
    • ‫INDEX_ID: מזהה האינדקס.
    • ‫DEPLOYED_INDEX_ID: מחרוזת שצוינה על ידי המשתמש כדי לזהות באופן ייחודי את האינדקס שנפרס. הוא צריך להתחיל באות ולהכיל רק אותיות, מספרים או קווים תחתונים. הנחיות לגבי הפורמט מופיעות במאמר DeployedIndex.id.
    • ‫DEPLOYED_INDEX_NAME: השם המוצג של האינדקס שנפרס.
    • ‫AUDIENCES: מחרוזת תיאורית שמזהה את קהל היעד הצפוי של השירות, עומס העבודה או האפליקציה, לדוגמה, "123456-my-app".
    • ‫SERVICE_ACCOUNT_ID: המזהה של חשבון השירות.
    • ‫PROJECT_ID: מזהה הפרויקט ב- Google Cloud .
    • ‫LOCATION: האזור שבו משתמשים ב-Agent Platform.

שליחת שאילתה לאינדקס באמצעות JWT בחתימה עצמית

באופן כללי, השלבים הנדרשים הם:

  1. יוצרים מטען ייעודי (payload) של JWT.
  2. חותמים על האסימון באמצעות חשבון השירות שנוצר קודם.
  3. שולחים שאילתה לאינדקס באמצעות קריאת gRPC, ומעבירים את הטוקן בכותרת ההרשאה.

Python

יצירה וחתימה של מטען ה-JWT

בדוגמה הזו נעשה שימוש ב-method ‏sign_jwt של ספריית פרטי הכניסה של IAM API ב-Python כדי לקבל אסימון חתום. מידע נוסף על התקנה ושימוש בספרייה הזו זמין במסמכי התיעוד של ספריות הלקוח של IAM API.

   from google.cloud import iam_credentials_v1
   from datetime import datetime, timezone
   import json

   def sign_jwt(issuer: str, audience: str):
      client = iam_credentials_v1.IAMCredentialsClient()
      payload = {
            'aud': audience,
            'sub': audience,
            'iss': issuer,
            'iat': int(datetime.now(timezone.utc).timestamp()),
            'exp': int(datetime.now(timezone.utc).timestamp()) + 600,
      }
      response = client.sign_jwt(name="projects/-/serviceAccounts/" + issuer,
                                 payload=json.dumps(payload))
      return response.signed_jwt

   sign_jwt("SERVICE_ACCOUNT_ID@PROJECT_ID.iam.gserviceaccount.com",
            "AUDIENCES")

שורת פקודה

יצירת מטען ייעודי (payload) של JWT

אימות של חיפוש וקטורי מקבל אסימוני JWT שחתומים על ידי חשבון שירות שאושר מראש, עבור קהל מוגדר מראש. כשפורסים אינדקס לנקודת קצה פרטית, מבצע הקריאה צריך לציין את חשבון השירות ואת הקהל. אחרי שמפעילים אינדקס עם ההגדרות האלה, כל בקשות ה-API של gRPC לנקודת הקצה הזו צריכות לכלול כותרת הרשאה שמכילה JWT שנחתם על ידי המנפיק (חשבון שירות) ומיועד לקהל שצוין. ה-JWT החתום מועבר כטוקן bearer בכותרת authorization של בקשת gRPC. בנוסף לחתימה של חשבון השירות, ה-JWT צריך לכלול את ההצהרות הבאות:

  • התביעה iss (הנפקן המורשה) צריכה להיות כתובת האימייל של חשבון השירות, לדוגמה:

    "iss": "SERVICE_ACCOUNT_ID@PROJECT_ID.iam.gserviceaccount.com"
    
  • הערכים של טענות aud (קהל) ו-sub (נושא) צריכים להיות זהים. זוהי מחרוזת תיאורית שמזהה את קהל היעד הצפוי של השירות, עומס העבודה או האפליקציה שלכם, למשל:

    "aud": "123456-my-app",
    "sub": "123456-my-app"
    

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

  • ההצהרה iat (הונפק בתאריך) צריכה להיות מוגדרת לזמן שבו האסימון מונפק. התוקף של ההצהרה exp (זמן התפוגה) צריך להיות בטווח של זמן קצר (בערך שעה). הערכים האלה מבוטאים בזמן Unix epoch, לדוגמה:

    "iat": 1698966927, // unix time since epoch eg via date +%s
    "exp": 1698967527 // iat + a few mins (eg 600 seconds)
    

בדוגמה הבאה מוצגות ההצהרות האלה במטען ייעודי (payload) יחיד של JWT:

{
   "iss": "SERVICE_ACCOUNT_ID@PROJECT_ID.iam.gserviceaccount.com",
   "aud": "123456-my-app",
   "sub": "123456-my-app",
   "iat": 1698956084,
   "exp": 1698960084
}

המטען הייעודי (payload) של ה-JWT חתום באמצעות חשבון השירות שצוין בטענה iss.

יצירת ה-JWT

  1. מוודאים שיש לכם (מבצע הקריאה) את התפקיד roles/iam.serviceAccountTokenCreator בחשבון השירות.

  2. יוצרים קובץ JSON בשם jwt_in.json שמכיל את ה-JWT הגולמי:

    SA="serviceAccount:SERVICE_ACCOUNT_ID@PROJECT_ID.iam.gserviceaccount.com"
    cat << EOF > jwt_in.json
    {
      "aud": "AUDIENCES",
      "sub": "AUDIENCES",
      "iss": "${SA}",
      "iat": $(date +%s),
      "exp": $(expr $(date +%s) + 600)
    }
    EOF
    

    מחליפים את הערכים הבאים:

    • ‫SERVICE_ACCOUNT_ID: המזהה של חשבון השירות.
    • ‫PROJECT_ID: מזהה הפרויקט ב- Google Cloud .
    • ‫AUDIENCES: מחרוזת תיאורית שמזהה את קהל היעד הצפוי של השירות, עומס העבודה או האפליקציה, לדוגמה, "123456-my-app".

חתימה על ה-JWT (API בארכיטקטורת REST)

  1. בעזרת jq, יוצרים את ה-payload של הבקשה curl על ידי קידוד ה-JWT למחרוזת:

    cat jwt_in.json | jq -Rsa >request.json
    
  2. חותמים על האסימון על ידי העברת מטען הייעודי (payload) של הבקשה אל ה-method signJwt ב-API בארכיטקטורת REST.

    SA="serviceAccount:SERVICE_ACCOUNT_ID@PROJECT_ID.iam.gserviceaccount.com"
    curl -X POST \
       -H "Authorization: Bearer $(gcloud auth print-access-token)" \
       -H "Content-Type: application/json; charset=utf-8" \
       -d @request.json \
       "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/$SA:signJwt"
    

    מחליפים את הערכים הבאים:

    • ‫SERVICE_ACCOUNT_ID: המזהה של חשבון השירות.
    • ‫PROJECT_ID: מזהה הפרויקט ב- Google Cloud .

    מאחסנים את הערך signedJwt שמוחזר במשתנה סביבה שנקרא signedJwt.

חתימה על ה-JWT (CLI של gcloud)

לחלופין, אפשר לחתום על ה-JWT על ידי העברת הקובץ jwt_in.json ישירות לשיטה sign-jwt של ה-CLI של gcloud.

gcloud iam service-accounts sign-jwt jwt_in.json jwt_out \
   --iam-account=SERVICE_ACCOUNT_ID@PROJECT_ID.iam.gserviceaccount.com

מחליפים את הערכים הבאים:

  • ‫SERVICE_ACCOUNT_ID: המזהה של חשבון השירות.
  • ‫PROJECT_ID: מזהה הפרויקט ב- Google Cloud .

ה-JWT החתום מוחזר בקובץ הפלט jwt_out. מאחסנים אותו במשתנה סביבה שנקרא signedJwt.

שליחת ה-JWT החתום לנקודת הקצה של האינדקס

Python

במאמר התקנת Vertex AI SDK ל-Python מוסבר איך להתקין או לעדכן את Vertex AI SDK ל-Python. מידע נוסף מופיע ב מאמרי העזרה של Python API.

def vector_search_match_jwt(
    project: str,
    location: str,
    index_endpoint_name: str,
    deployed_index_id: str,
    queries: List[List[float]],
    num_neighbors: int,
    signed_jwt: str,
) -> List[List[aiplatform.matching_engine.matching_engine_index_endpoint.MatchNeighbor]]:
    """Query the vector search index.

    Args:
        project (str): Required. Project ID
        location (str): Required. The region name
        index_endpoint_name (str): Required. Index endpoint to run the query
        against. The endpoint must be a private endpoint.
        deployed_index_id (str): Required. The ID of the DeployedIndex to run
        the queries against.
        queries (List[List[float]]): Required. A list of queries. Each query is
        a list of floats, representing a single embedding.
        num_neighbors (int): Required. The number of neighbors to return.
        signed_jwt (str): Required. The signed JWT token for the private
        endpoint. The endpoint must be configured to accept tokens from JWT's
        issuer and encoded audience.

    Returns:
        List[List[aiplatform.matching_engine.matching_engine_index_endpoint.MatchNeighbor]] - A list of nearest neighbors for each query.
    """
    # Initialize the Vertex AI client
    aiplatform.init(project=project, location=location)

    # Create the index endpoint instance from an existing endpoint.
    my_index_endpoint = aiplatform.MatchingEngineIndexEndpoint(
        index_endpoint_name=index_endpoint_name
    )

    # Query the index endpoint for matches.
    resp = my_index_endpoint.match(
        deployed_index_id=deployed_index_id,
        queries=queries,
        num_neighbors=num_neighbors,
        signed_jwt=signed_jwt,
    )
    return resp

שורת פקודה

ממכונה וירטואלית ב-Compute Engine באותה רשת VPC, קוראים לנקודת הקצה MatchService gRPC, ומעבירים את אסימון signedJwt בכותרת authorization, כמו בדוגמה הבאה:

./grpc_cli call ${TARGET_IP}:10000 google.cloud.aiplatform.container.v1.MatchService.Match \
   '{deployed_index_id: "${DEPLOYED_INDEX_ID}", float_val: [-0.1,..]}' \
   --metadata "authorization: Bearer $signedJwt"

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

  • ‫TARGET_IP היא כתובת ה-IP של שרת האינדקס שפרסתם. הוראות לאחזור הערך הזה מפורטות במאמר בנושא שאילתות על אינדקסים כדי לקבל את השכנים הקרובים ביותר.
  • ‫DEPLOYED_INDEX_ID: מחרוזת שצוינה על ידי המשתמש כדי לזהות באופן ייחודי את האינדקס שנפרס. הוא צריך להתחיל באות ולהכיל רק אותיות, מספרים או קווים תחתונים. הנחיות לגבי הפורמט מופיעות במאמר DeployedIndex.id.

‫signedJwt הוא משתנה הסביבה שמכיל את ה-JWT החתום.

פתרון בעיות

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

הודעת שגיאה של gRPC סיבה
לא נמצאה כותרת הרשאה לאינדקס 'INDEX_ID' המטא-נתונים של gRPC לא מכילים כותרת הרשאה
הפורמט של ה-JWT לא תקין הפורמט של הטוקן שגוי ואי אפשר לנתח אותו בצורה נכונה
האימות של JWT נכשל התוקף של האסימון פג או שהוא לא חתום על ידי חשבון השירות הנכון
הנפקן של ה-JWT צריך להיות ברשימת הנפקנים המותרים האסימון iss לא נמצא ברשימת המנפיקים המורשים של auth_config
בדיקת ההרשאה נכשלה עבור האינדקס 'INDEX_ID' האסימון aud או הטענה sub לא נמצאים בקהלים auth_config

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