אימות הזהות של מכונה וירטואלית

לפני שאפליקציה שולחת מידע רגיש למכונה וירטואלית (VM), היא יכולה לאמת את הזהות של המכונה באמצעות אסימוני זהות של מכונה וירטואלית שנחתמו על ידי Google. לכל מופע יש JSON Web Token ‏ (JWT) ייחודי שכולל פרטים על המופע וגם חתימת RS256 של Google. האפליקציות שלכם יכולות לאמת את החתימה מול האישורים הציבוריים של Oauth2 של Google כדי לוודא את הזהות של המופע שאליו הן התחברו.

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

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

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

לשיטות האימות של מופעים ב-Google יש את היתרונות הבאים:

  • ‫Compute Engine יוצר טוקן ייחודי בכל פעם שמכונה מבקשת אותו, ותוקף כל טוקן פג תוך שעה. אתם יכולים להגדיר את האפליקציות כך שיקבלו את טוקן הזהות של מופע רק פעם אחת, וכך להקטין את הסיכון שמערכת לא מורשית תוכל לעשות שימוש חוזר בטוקן.
  • אסימוני מטא-נתונים חתומים מבוססים על תקן התעשייה הפתוח RFC 7519 ועל שכבת הזהות OpenID Connect 1.0, כך שכלי וספריות קיימים יפעלו בצורה חלקה עם אסימוני הזהות.

לפני שמתחילים

אימות הזהות של מופע

בתרחישים מסוימים, האפליקציות צריכות לאמת את הזהות של מופע שפועל ב-Compute Engine לפני שהן מעבירות מידע אישי רגיש למופע הזה. בדוגמה אופיינית אחת, יש מערכת אחת שפועלת מחוץ ל-Compute Engine שנקראת Host1 ומכונה ב-Compute Engine שנקראת VM1. מכונה וירטואלית VM1 יכולה להתחבר למארח Host1 ולאמת את הזהות של המופע הזה באמצעות התהליך הבא:

  1. המכונה הווירטואלית VM1 יוצרת חיבור מאובטח למארח Host1 באמצעות פרוטוקול חיבור מאובטח לפי בחירתכם, כמו HTTPS.

  2. מכונת VM1 מבקשת את אסימון הזהות הייחודי שלה משרת המטא-נתונים ומציינת את הקהל של האסימון. בדוגמה הזו, ערך הקהל הוא ה-URI של Host1. הבקשה לשרת המטא-נתונים כוללת את ה-URI של קהל היעד, כדי שמארח 1 יוכל לבדוק את הערך מאוחר יותר במהלך שלב אימות הטוקן.

  3. ‫Google יוצרת טוקן חדש וייחודי של זהות המופע בפורמט JWT ומספקת אותו למכונה וירטואלית 1. המטען הייעודי (Payload) של הטוקן כולל כמה פרטים על המופע וגם את ה-URI של קהל היעד. תיאור מלא של תוכן האסימון מופיע במאמר תוכן האסימון.

  4. המכונה הווירטואלית VM1 שולחת את אסימון הזהות למארח Host1 דרך החיבור המאובטח הקיים.

  5. המארח Host1 מפענח את אסימון הזהות כדי לקבל את כותרת האסימון ואת ערכי המטען הייעודי (payload).

  6. מארח 1 מאמת שהאסימון חתום על ידי Google על ידי בדיקת ערך הקהל ואימות חתימת האישור מול האישור הציבורי של Google.

  7. אם האסימון תקף, המארח ממשיך בהעברה וסוגר את החיבור כשהוא מסתיים. מערכת Host1 ומערכות אחרות צריכות לבקש טוקן חדש לכל חיבור עתידי ל-VM1.

קבלת אסימון זהות של המכונה

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

cURL

יוצרים בקשת curl וכוללים ערך בפרמטר audience. אפשר גם לכלול את הפרמטר format כדי לציין אם רוצים לכלול את פרטי הפרויקט והמופע במטען הייעודי (payload). אם משתמשים בפורמט full, אפשר לכלול את הפרמטר licenses כדי לציין אם רוצים לכלול קודי רישיון במטען הייעודי למטרה מסוימת.

curl -H "Metadata-Flavor: Google" \
'http://metadata/computeMetadata/v1/instance/service-accounts/default/identity?audience=AUDIENCE&format=FORMAT&licenses=LICENSES'

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

  • AUDIENCE: ה-URI הייחודי שמוסכם על ידי המופע ועל ידי המערכת שמאמתת את הזהות של המופע. לדוגמה, הקהל יכול להיות כתובת URL לחיבור בין שתי המערכות.
  • FORMAT: הפרמטר האופציונלי שמציין אם פרטי הפרויקט והמופע נכללים במטען הייעודי (payload). מציינים full כדי לכלול את המידע הזה במטען הייעודי (payload) או standard כדי להשמיט את המידע מהמטען הייעודי. ערך ברירת המחדל הוא standard. מידע נוסף זמין במאמר פורמט של אסימון זהות.
  • LICENSES: פרמטר אופציונלי שמציין אם קודי הרישיון של התמונות שמשויכות למופע הזה כלולים במטען הייעודי. מציינים TRUE כדי לכלול את המידע הזה או FALSE כדי להשמיט את המידע הזה ממטען הייעודי. ערך ברירת המחדל הוא FALSE. אין השפעה אלא אם format הוא full

שרת המטא-נתונים מגיב לבקשה הזו עם אסימון אינטרנט בפורמט JSON (JWT) שחתום באמצעות האלגוריתם RS256. האסימון כולל חתימה של Google ומידע נוסף במטען הייעודי (payload). אפשר לשלוח את האסימון הזה למערכות ולאפליקציות אחרות כדי שיוכלו לאמת את האסימון ולאשר את הזהות של המופע.

Python

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

import requests

AUDIENCE_URL = "http://www.example.com"
METADATA_HEADERS = {"Metadata-Flavor": "Google"}
METADATA_VM_IDENTITY_URL = (
    "http://metadata.google.internal/computeMetadata/v1/"
    "instance/service-accounts/default/identity?"
    "audience={audience}&format={format}&licenses={licenses}"
)
FORMAT = "full"
LICENSES = "TRUE"


def acquire_token(
    audience: str = AUDIENCE_URL, format: str = "standard", licenses: bool = True
) -> str:
    """
    Requests identity information from the metadata server.

    Args:
        audience: the unique URI agreed upon by both the instance and the
            system verifying the instance's identity. For example, the audience
            could be a URL for the connection between the two systems.
        format: the optional parameter that specifies whether the project and
            instance details are included in the payload. Specify `full` to
            include this information in the payload or standard to omit the
            information from the payload. The default value is `standard`.
        licenses: an optional parameter that specifies whether license
            codes for images associated with this instance are included in the
            payload. Specify TRUE to include this information or FALSE to omit
            this information from the payload. The default value is FALSE.
            Has no effect unless format is `full`.

    Returns:
        A JSON Web Token signed using the RS256 algorithm. The token includes a
        Google signature and additional information in the payload. You can send
        this token to other systems and applications so that they can verify the
        token and confirm that the identity of your instance.
    """
    # Construct a URL with the audience and format.
    url = METADATA_VM_IDENTITY_URL.format(
        audience=audience, format=format, licenses=licenses
    )

    # Request a token from the metadata server.
    r = requests.get(url, headers=METADATA_HEADERS)
    # Extract and return the token from the response.
    r.raise_for_status()
    return r.text

שרת המטא-נתונים מגיב לבקשה הזו עם אסימון אינטרנט בפורמט JSON (JWT) שחתום באמצעות האלגוריתם RS256. האסימון כולל חתימה של Google ומידע נוסף במטען הייעודי (payload). אפשר לשלוח את האסימון הזה למערכות ולאפליקציות אחרות כדי שיוכלו לאמת את האסימון ולאשר את הזהות של המופע.

אימות הטוקן

אחרי שהאפליקציה מקבלת טוקן של זהות מכונה ממכונה ב-Compute Engine, היא יכולה לאמת את הטוקן באמצעות התהליך הבא.

  1. מקבלים את האסימון מהמכונה הווירטואלית, מפענחים את האסימון באמצעות מפענח JWT מסוג RS256 וקוראים את תוכן הכותרת כדי לקבל את הערך kid.

  2. כדי לוודא שהאסימון חתום, בודקים אותו מול האישור הציבורי של Google. לכל אישור ציבורי יש ערך kid שתואם לערך kid בכותרת של האסימון.

  3. אם האסימון תקף, משווים את תוכן המטען הייעודי (payload) לערכים הצפויים. אם מטען הייעודי (payload) של האסימון כולל פרטים על המופע והפרויקט, האפליקציה יכולה לבדוק את הערכים instance_id,‏ project_id ו-zone. הערכים האלה הם טאפל ייחודי גלובלית שמאשר שהאפליקציה שלכם מתקשרת עם המופע הנכון בפרויקט הרצוי.

אפשר לפענח ולאמת את הטוקן באמצעות כל כלי שרוצים, אבל שיטה נפוצה היא להשתמש בספריות של השפה שבחרתם. לדוגמה, אפשר להשתמש בשיטה verify_token מתוך ספריית Google OAuth 2.0 ל-Python. השיטה verify_token מתאימה לערך kid לאישור המתאים, מאמתת את החתימה, בודקת את טענת הקהל ומחזירה את תוכן המטען הייעודי (payload) מהאסימון.

import google.auth.transport.requests
from google.oauth2 import id_token
def verify_token(token: str, audience: str) -> dict:
    """Verify token signature and return the token payload.

    Args:
        token: the JSON Web Token received from the metadata server to
            be verified.
        audience: the unique URI agreed upon by both the instance and the
            system verifying the instance's identity.

    Returns:
        Dictionary containing the token payload.
    """
    request = google.auth.transport.requests.Request()
    payload = id_token.verify_token(token, request=request, audience=audience)
    return payload

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

התוכן של הטוקן

אסימון הזהות של המופע מכיל שלושה חלקים עיקריים:

הכותרת כוללת את הערך kid כדי לזהות את האישורים הציבוריים של Oauth2 שבהם צריך להשתמש כדי לאמת את החתימה. הכותרת כוללת גם את הערך alg כדי לאשר שהחתימה נוצרה באמצעות אלגוריתם RS256.

{
  "alg": "RS256",
  "kid": "511a3e85d2452aee960ed557e2666a8c5cedd8ae",
}

Payload

המטען הייעודי (payload) מכיל את טענת הקהל aud. אם המופע צוין format=full כשנשלחה הבקשה לאסימון, מטען הייעודי (payload) כולל גם הצהרות לגבי מופע המכונה הווירטואלית והפרויקט שלו. כשמבקשים אסימון בפורמט מלא, ציון licenses=TRUE יכלול גם טענות לגבי הרישיונות שמשויכים למופע.

{
   "iss": "[TOKEN_ISSUER]",
   "iat": [ISSUED_TIME],
   "exp": [EXPIRED_TIME],
   "aud": "[AUDIENCE]",
   "sub": "[SUBJECT]",
   "azp": "[AUTHORIZED_PARTY]",
   "email": "[EMAIL]",
   "email_verified": "[EMAIL_VERIFIED]",
   "google": {
    "compute_engine": {
      "project_id": "[PROJECT_ID]",
      "project_number": [PROJECT_NUMBER],
      "zone": "[ZONE]",
      "instance_id": "[INSTANCE_ID]",
      "instance_name": "[INSTANCE_NAME]",
      "instance_creation_timestamp": [CREATION_TIMESTAMP],
      "instance_confidentiality": [INSTANCE_CONFIDENTIALITY],
      "license_id": [
        "[LICENSE_1]",
          ...
        "[LICENSE_N]"
      ]
    }
  }
}

כאשר:

  • [TOKEN_ISSUER]: כתובת URL שמזהה את הגורם שהנפיק את האסימון. ב-Compute Engine, הערך הזה הוא https://accounts.google.com.
  • [ISSUED_TIME]: חותמת זמן של מערכת Unix שמציינת מתי הונפק האסימון. הערך הזה מתעדכן בכל פעם שהמופע מבקש אסימון משרת המטא-נתונים.
  • [EXPIRED_TIME]: חותמת זמן של מערכת Unix שמציינת מתי יפוג תוקף האסימון.
  • [AUDIENCE]: מזהה משאבים אחיד (URI) ייחודי שמוסכם על ידי המופע ועל ידי המערכת שמאמתת את הזהות של המופע. לדוגמה, הקהל יכול להיות כתובת URL לחיבור בין שתי המערכות.
  • [SUBJECT]: הנושא של האסימון, שהוא המזהה הייחודי של חשבון השירות ששייכתם למופע.
  • [AUTHORIZED_PARTY]: הצד שאליו הונפק אסימון ה-ID, שהוא המזהה הייחודי של חשבון השירות ששייכתם למופע.
  • [EMAIL]: כתובת האימייל של חשבון השירות שמשויך למופע.
  • [EMAIL_VERIFIED]: true אם כתובת האימייל של חשבון השירות אומתה.
  • [PROJECT_ID]: המזהה של הפרויקט שבו יצרתם את המופע.
  • [PROJECT_NUMBER]: המספר הייחודי של הפרויקט שבו יצרתם את המופע.
  • [ZONE]: האזור שבו נמצאת המכונה.
  • [INSTANCE_ID]: המזהה הייחודי של המופע שאליו משויך האסימון הזה. המזהה הזה ייחודי בתוך הפרויקט והתחום.
  • [INSTANCE_NAME]: השם של המכונה שאליה שייך האסימון. אם הפרויקט שלכם משתמש ב-DNS אזורי, אפשר להשתמש בשם הזה שוב באזורים אחרים. לכן, כדי לזהות מזהה ייחודי של מופע, צריך להשתמש בשילוב של הערכים project_id, zone ו-instance_id. בפרויקטים שבהם מופעל DNS גלובלי, לכל מופע יש שם ייחודי בפרויקט.
  • [CREATION_TIMESTAMP]: חותמת זמן של מערכת Unix שמציינת מתי יצרתם את המופע.
  • [INSTANCE_CONFIDENTIALITY]: 1 אם המופע הוא מכונה וירטואלית חסויה.
  • [LICENSE_1] עד [LICENSE_N]: קודי הרישיון של התמונות שמשויכות למופע הזה.

המטען הייעודי שלכם יכול להיראות כמו הדוגמה הבאה:

{
  "iss": "https://accounts.google.com",
  "iat": 1496953245,
  "exp": 1496956845,
  "aud": "https://www.example.com",
  "sub": "107517467455664443765",
  "azp": "107517467455664443765",
  "email": "739419398126-compute@developer.gserviceaccount.com",
  "email_verified": true,
  "google": {
    "compute_engine": {
      "project_id": "my-project",
      "project_number": 739419398126,
      "zone": "us-west1-a",
      "instance_id": "152986662232938449",
      "instance_name": "example",
      "instance_creation_timestamp": 1496952205,
      "instance_confidentiality": 1,
      "license_id": [
        "1000204"
      ]
    }
  }
}

חתימה

‫Google יוצרת את החתימה על ידי קידוד base64url של הכותרת ושל המטען הייעודי (payload), ושרשור שני הערכים. כדי לאמת את האסימון, אפשר להשוות את הערך הזה לאישורים ציבוריים של Oauth2.

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