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

לפני שאפליקציה שולחת מידע רגיש למכונה וירטואלית (VM), היא יכולה לאמת את הזהות של המכונה באמצעות אסימוני זהות של מכונות שנחתמו על ידי Google. לכל מכונה יש אסימון JWT (‏JSON Web Token) ייחודי שכולל פרטים על המכונה וגם את חתימת 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 ממשיך בהעברה וסוגר את החיבור כשהוא מסתיים. ‫Host1 ומערכות אחרות צריכות לבקש אסימון חדש לכל החיבורים הבאים ל-VM1.

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

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

cURL

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

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: פרמטר אופציונלי שמציין אם קודי הרישיון של התמונות שמשויכות למופע הזה כלולים במטען הייעודי (payload). מציינים TRUE כדי לכלול את המידע הזה או FALSE כדי להשמיט את המידע הזה ממטען הייעודי. ערך ברירת המחדל הוא FALSE. אין השפעה אלא אם format הוא full

שרת המטא-נתונים מגיב לבקשה הזו עם JSON Web Token (אסימון אינטרנט בפורמט JSON) שחתום באמצעות האלגוריתם 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 Web Token (אסימון אינטרנט בפורמט JSON) שחתום באמצעות האלגוריתם RS256. האסימון כולל חתימה של Google ומידע נוסף במטען הייעודי (payload). אפשר לשלוח את האסימון הזה למערכות ולאפליקציות אחרות כדי שהן יוכלו לאמת את האסימון ולאשר את הזהות של המופע שלכם.

אימות הטוקן

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

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

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

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

אפשר לפענח ולאמת את הטוקן באמצעות כל כלי שרוצים, אבל שיטה נפוצה היא להשתמש בספריות של השפה שבחרתם. לדוגמה, אתם יכולים להשתמש ב-method‏ 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]: הצד שאליו הונפק טוקן של מזהה, שהוא המזהה הייחודי של חשבון השירות ששייכתם למכונה.
  • [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 אם המכונה היא מכונה וירטואלית (VM) חסויה.
  • ‫[LICENSE_1] עד [LICENSE_N]: קודי הרישיון של התמונות שמשויכות למכונה הזו.

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

{
  "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.

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