העברת זהות האפליקציה לאסימונים מזהים של OIDC

כששולחים בקשה מאפליקציה שפועלת בסביבת זמן הריצה של Python 2 לאפליקציה אחרת של App Engine, אפשר להשתמש ב-App Identity API של App Engine כדי לאמת את הזהות שלה. האפליקציה שמקבלת את הבקשה יכולה להשתמש בזהות הזו כדי לקבוע אם לעבד את הבקשה.

אם אפליקציות Python 3 צריכות לאמת את הזהות שלהן כששולחים בקשות לאפליקציות אחרות של App Engine, אפשר להשתמש באסימוני מזהה של OpenID Connect ‏(OIDC) שמונפקים ומפוענחים על ידי ממשקי ה-API של Google OAuth 2.0.

הנה סקירה כללית על שימוש באסימונים מזהים מסוג OIDC כדי לאמת את הזהות:

  1. אפליקציית App Engine בשם App A מאחזרת טוקן מזהה מ Google Cloud סביבת זמן הריצה.
  2. אפליקציה א' מוסיפה את האסימון הזה לכותרת הבקשה ממש לפני שליחת הבקשה לאפליקציה ב', שהיא אפליקציית App Engine אחרת.
  3. אפליקציה ב' משתמשת בממשקי ה-API של Google OAuth 2.0 כדי לאמת את מטען הייעודי (payload) של הטוקן. המטען הייעודי (payload) שפוענח מכיל את הזהות המאומתת של אפליקציה א' בתבנית של כתובת האימייל של חשבון השירות שמוגדר כברירת מחדל באפליקציה א'.
  4. אפליקציה ב' משווה את הזהות במטען הייעודי (payload) לרשימת זהויות שמותר לה להגיב להן. אם הבקשה הגיעה מאפליקציה מורשית, אפליקציה ב' מעבדת את הבקשה ומגיבה.

תהליך OAuth 2.0

במדריך הזה מוסבר איך לעדכן את אפליקציות App Engine כדי להשתמש באסימוני מזהה של OpenID Connect ‏ (OIDC) כדי להצהיר על זהות, ואיך לעדכן את אפליקציות App Engine אחרות כדי להשתמש באסימוני מזהה כדי לאמת זהות לפני עיבוד בקשה.

ההבדלים העיקריים בין App Identity API לבין OIDC API

  • באפליקציות בסביבת זמן הריצה של Python 2, אין צורך להצהיר במפורש על הזהות. כשמשתמשים בספריות Python‏ httplib, urllib או urllib2 או בשירות אחזור כתובות ה-URL של App Engine כדי לשלוח בקשות יוצאות, סביבת זמן הריצה משתמשת בשירות אחזור כתובות ה-URL של App Engine כדי לבצע את הבקשה. אם הבקשה נשלחת לדומיין appspot.com, הפונקציה URL Fetch מאמתת באופן אוטומטי את הזהות של האפליקציה ששולחת את הבקשה על ידי הוספת הכותרת X-Appengine-Inbound-Appid לבקשה. הכותרת הזו מכילה את מזהה האפליקציה (שנקרא גם מזהה הפרויקט).

    באפליקציות בסביבת זמן הריצה של Python 3 צריך לאמת את הזהות באופן מפורש על ידי אחזור אסימון מזהה OIDC מ Google Cloud סביבת זמן הריצה והוספתו לכותרת הבקשה. תצטרכו לעדכן את כל הקוד ששולח בקשות לאפליקציות אחרות של App Engine, כך שהבקשות יכללו אסימון מזהה OIDC.

  • הכותרת X-Appengine-Inbound-Appid בבקשה מכילה את מזהה הפרויקט של האפליקציה ששלחה את הבקשה.

    מטען הייעודי (payload) של טוקן מזהה OIDC של Google לא מזהה ישירות את מזהה הפרויקט של האפליקציה עצמה. במקום זאת, האסימון מזהה את חשבון השירות שהאפליקציה פועלת תחתיו, על ידי ציון כתובת האימייל של חשבון השירות הזה. תצטרכו להוסיף קוד כדי לחלץ את שם המשתמש ממטען הייעודי (payload) של הטוקן.

    אם חשבון השירות הזה הוא חשבון השירות שמשמש כברירת המחדל של App Engine ברמת האפליקציה בפרויקט, אפשר למצוא את מזהה הפרויקט בכתובת האימייל של חשבון השירות. החלק של שם המשתמש בכתובת זהה למזהה הפרויקט. במקרה כזה, הקוד של האפליקציה המקבלת יכול לחפש את המזהה ברשימת מזהי הפרויקטים שהיא תאפשר בקשות מהם.

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

  • המכסות של קריאות ל-URL Fetch API שונות מהמכסות של Google OAuth 2.0 APIs למתן טוקנים. אתם יכולים לראות את המספר המקסימלי של אסימונים שאתם יכולים להעניק בכל יום במסך ההסכמה ל-OAuth במסוףGoogle Cloud . לא נחייב אתכם על שימוש ב-URL Fetch, ב-App Identity API או בממשקי Google OAuth 2.0 API.

סקירה כללית של תהליך המיגרציה

כדי להעביר את אפליקציות Python לשימוש בממשקי OIDC API כדי לאמת את הזהות:

  1. באפליקציות שצריכות לאמת את הזהות שלהן כששולחות בקשות לאפליקציות אחרות של App Engine:

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

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

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

  2. באפליקציות שבהן נדרש אימות זהות לפני עיבוד בקשה:

    1. כדי להתחיל, צריך לשדרג את אפליקציות Python 2 כדי לתמוך גם באסימוני מזהה וגם בזהויות של App Identity API. כך תוכלו לאפשר לאפליקציות שלכם לאמת ולעבד בקשות מאפליקציות Python 2 שמשתמשות ב-App Identity API או מאפליקציות Python 3 שמשתמשות באסימוני זהות.

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

    3. אם אין יותר צורך לעבד בקשות מאפליקציות App Engine מדור קודם, צריך להסיר את הקוד שמאמת את הזהויות של App Identity API.

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

הצהרת זהות

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

  1. מתקינים את ספריית הלקוח google-auth.

  2. מוסיפים קוד כדי לבקש אסימון מזהה מ-Google OAuth 2.0 APIs ולהוסיף את האסימון לכותרת הבקשה לפני שליחת הבקשה.

  3. בודקים את העדכונים.

התקנה של ספריית הלקוח google-auth לאפליקציות Python 3

כדי להפוך את ספריית הלקוח google-auth לזמינה לאפליקציית Python3, יוצרים קובץ requirements.txt באותה תיקייה שבה נמצא הקובץ app.yaml ומוסיפים את השורה הבאה:

     google-auth

כשפורסים את האפליקציה, App Engine מוריד את כל יחסי התלות שמוגדרים בקובץ requirements.txt.

לפיתוח מקומי, מומלץ להתקין תלות בסביבה וירטואלית כמו venv.

הוספת קוד לאימות הזהות

מחפשים בקוד את כל המקרים של שליחת בקשות לאפליקציות אחרות של App Engine. צריך לעדכן את המקרים האלה כך שיבצעו את הפעולות הבאות לפני שליחת הבקשה:

  1. מוסיפים את ההצהרות הבאות של ייבוא:

    from google.auth.transport import requests as reqs
    from google.oauth2 import id_token
    
  2. משתמשים ב-google.oauth2.id_token.fetch_id_token(request, audience) כדי לאחזר אסימון מזהה. כוללים את הפרמטרים הבאים בהפעלת method:

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

      לשם הבהרה וספציפיות, מומלץ להעביר את appspot.com כתובת ה-URL שנוצרה על ידי App Engine עבור השירות הספציפי שמקבל את הבקשה, גם אם משתמשים בדומיין מותאם אישית לאפליקציה.

  3. באובייקט הבקשה, מגדירים את הכותרת הבאה:

    'Authorization': 'ID {}'.format(token)
    

לדוגמה:

# Copyright 2020 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from flask import Flask, render_template, request
from google.auth.transport import requests as reqs
from google.oauth2 import id_token
import requests

app = Flask(__name__)


@app.route("/", methods=["GET"])
def index():
    return render_template("index.html")


@app.route("/", methods=["POST"])
def make_request():
    url = request.form["url"]
    token = id_token.fetch_id_token(reqs.Request(), url)

    resp = requests.get(url, headers={"Authorization": f"Bearer {token}"})

    message = f"Response when calling {url}:\n\n"
    message += resp.text

    return message, 200, {"Content-type": "text/plain"}

עדכוני בדיקות לאימות הזהות

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

  1. כדי להפוך את פרטי הכניסה של חשבון השירות שמוגדר כברירת מחדל ב-App Engine לזמינים בסביבה המקומית (Google OAuth APIs דורשים את פרטי הכניסה האלה כדי ליצור אסימון מזהה):

    1. מזינים את הפקודה gcloud הבאה כדי לאחזר את מפתח חשבון השירות של חשבון App Engine שמוגדר כברירת מחדל בפרויקט:

      gcloud iam service-accounts keys create ~/key.json --iam-account project-ID@appspot.gserviceaccount.com

      מחליפים את project-ID במזההGoogle Cloud הפרויקט.

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

    2. מזינים את הפקודה הבאה:

      <code>export GOOGLE_APPLICATION_CREDENTIALS=<var>service-account-key</var></code>
      

    מחליפים את service-account-key בנתיב השם המוחלט של הקובץ שמכיל את המפתח של חשבון השירות שהורדתם.

  2. באותו מעטפת שבה ייצאתם את משתנה הסביבה GOOGLE_APPLICATION_CREDENTIALS, מפעילים את אפליקציית Python.

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

    1. מורידים את האפליקציה לדוגמה 'נכנסות'.
    2. בקובץ main.py של הדוגמה, מוסיפים את המזהה של פרויקט Google Cloud לקובץ allowed_app_ids. לדוגמה:

       allowed_app_ids = [
          '<APP_ID_1>',
          '<APP_ID_2>',
          'my-project-id'
        ]
      
    3. מריצים את הדוגמה המעודכנת בשרת הפיתוח המקומי של Python 2.

אימות ועיבוד של בקשות

כדי לשדרג את אפליקציות Python 2 כך שישתמשו בזהויות של אסימונים מזהים או ב-API ‏App Identity לפני עיבוד הבקשות:

  1. מתקינים את ספריית הלקוח google-auth.

  2. מעדכנים את הקוד כך שיבצע את הפעולות הבאות:

    1. אם הבקשה מכילה את הכותרת X-Appengine-Inbound-Appid, צריך להשתמש בכותרת הזו כדי לאמת את הזהות. אפליקציות שפועלות בסביבת ריצה מדור קודם כמו Python 2 יכללו את הכותרת הזו.

    2. אם הבקשה לא מכילה את הכותרת X-Appengine-Inbound-Appid, צריך לבדוק אם יש אסימון OIDC ID. אם האסימון קיים, צריך לאמת את מטען האסימון ולבדוק את הזהות של השולח.

  3. בודקים את העדכונים.

התקנה של ספריית הלקוח google-auth לאפליקציות Python 2

כדי להפוך את ספריית הלקוח google-auth לזמינה לאפליקציית Python 2:

  1. יוצרים קובץ requirements.txt באותה תיקייה שבה נמצא קובץ app.yaml ומוסיפים את השורה הבאה:

     google-auth==1.19.2
    

    מומלץ להשתמש בגרסה 1.19.2 של ספריית הלקוח Cloud Logging כי היא תומכת באפליקציות Python 2.7.

  2. בקטע libraries בקובץ app.yaml של האפליקציה, מציינים את ספריית ה-SSL אם היא עדיין לא צוינה:

    libraries:
    - name: ssl
      version: latest
    
  3. יוצרים ספרייה לאחסון ספריות של צד שלישי, כמו lib/. לאחר מכן משתמשים ב-pip install כדי להתקין את הספריות בספרייה. לדוגמה:

    pip install -t lib -r requirements.txt
  4. יוצרים קובץ appengine_config.py באותה תיקייה שבה נמצא קובץ app.yaml. מוסיפים לקובץ appengine_config.py את הנתונים הבאים:

    # appengine_config.py
    import pkg_resources
    from google.appengine.ext import vendor
    
    # Set path to your libraries folder.
    path = 'lib'
    # Add libraries installed in the path folder.
    vendor.add(path)
    # Add libraries to pkg_resources working set to find the distribution.
    pkg_resources.working_set.add_entry(path)

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

    import os
    path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'lib')

לפיתוח מקומי, מומלץ להתקין את התלות בסביבה וירטואלית כמו virtualenv ל-Python 2.

עדכון הקוד לאימות בקשות

מחפשים בקוד את כל המקרים שבהם מתקבל הערך של הכותרת X-Appengine-Inbound-Appid. מעדכנים את המכונות האלה כדי לבצע את הפעולות הבאות:

  1. מוסיפים את ההצהרות הבאות של ייבוא:

    from google.auth.transport import requests as reqs
    from google.oauth2 import id_token
    
  2. אם הבקשה הנכנסת לא מכילה את הכותרת X-Appengine-Inbound-Appid, צריך לחפש את הכותרת Authorization ולאחזר את הערך שלה.

    הערך של הכותרת הוא בפורמט ID: token.

  3. אפשר להשתמש ב-google.oauth2.id_token.verify_oauth2_token(token, request, audience) כדי לאמת ולאחזר את המטען הייעודי (payload) של האסימון המפוענח. כוללים את הפרמטרים הבאים בהפעלת ה-method:

    • token: מעבירים את האסימון שחולץ מהבקשה הנכנסת.
    • request: מעבירים אובייקט google.auth.transport.Request חדש.

    • audience: מעבירים את כתובת ה-URL של האפליקציה הנוכחית (האפליקציה ששולחת את בקשת האימות). שרת ההרשאות של Google ישווה את כתובת ה-URL הזו לכתובת ה-URL שסופקה כשנוצר האסימון במקור. אם כתובות ה-URL לא תואמות, הטוקן לא יאומת ושרת ההרשאות יחזיר שגיאה.

  4. השיטה verify_oauth2_token מחזירה את מטען ייעודי (payload) של האסימון המפוענח, שמכיל כמה זוגות של שם/ערך, כולל כתובת האימייל של חשבון השירות שמוגדר כברירת מחדל לאפליקציה שיצרה את האסימון.

  5. מחפשים את שם המשתמש במטען הייעודי (payload) של הטוקן.

    שם המשתמש זהה למזהה הפרויקט של האפליקציה ששלחה את הבקשה. זהו אותו ערך שהוחזר קודם בכותרת X-Appengine-Inbound-Appid.

  6. אם שם המשתמש או מזהה הפרויקט מופיעים ברשימת מזהי הפרויקטים המורשים, הבקשה תעובד.

לדוגמה:

# Copyright 2020 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""
Authenticate requests coming from other App Engine instances.
"""

import logging

from google.auth.transport import requests
from google.oauth2 import id_token
import webapp2


def get_app_id(request):
    # Requests from App Engine Standard for Python 2.7 will include a
    # trustworthy X-Appengine-Inbound-Appid. Other requests won't have
    # that header, as the App Engine runtime will strip it out
    incoming_app_id = request.headers.get("X-Appengine-Inbound-Appid", None)
    if incoming_app_id is not None:
        return incoming_app_id

    # Other App Engine apps can get an ID token for the App Engine default
    # service account, which will identify the application ID. They will
    # have to include at token in an Authorization header to be recognized
    # by this method.
    auth_header = request.headers.get("Authorization", None)
    if auth_header is None:
        return None

    # The auth_header must be in the form Authorization: Bearer token.
    bearer, token = auth_header.split()
    if bearer.lower() != "bearer":
        return None

    try:
        info = id_token.verify_oauth2_token(token, requests.Request())
        service_account_email = info["email"]
        incoming_app_id, domain = service_account_email.split("@")
        if domain != "appspot.gserviceaccount.com":  # Not App Engine svc acct
            return None
        else:
            return incoming_app_id
    except Exception as e:
        # report or log if desired, as here:
        logging.warning("Request has bad OAuth2 id token: {}".format(e))
        return None


class MainPage(webapp2.RequestHandler):
    allowed_app_ids = ["other-app-id", "other-app-id-2"]

    def get(self):
        incoming_app_id = get_app_id(self.request)

        if incoming_app_id is None:
            self.abort(403)

        if incoming_app_id not in self.allowed_app_ids:
            self.abort(403)

        self.response.write("This is a protected page.")


app = webapp2.WSGIApplication([("/", MainPage)], debug=True)

עדכוני בדיקות לאימות הזהות

כדי לבדוק שהאפליקציה יכולה להשתמש בטוקן מזהה או בכותרת X-Appengine-Inbound-Appid כדי לאמת בקשות, מריצים את האפליקציה בשרת הפיתוח המקומי של Python 2 ושולחים בקשות מאפליקציות Python 2 (שישתמשו ב-App Identity API) ומאפליקציות Python 3 ששולחות טוקנים מזהים.

אם לא עדכנתם את האפליקציות כך שישלחו טוקנים של מזהים:

  1. מורידים את האפליקציה לדוגמה "בקשה".

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

  3. משתמשים בפקודות סטנדרטיות של Python 3 כדי להפעיל את אפליקציית Python 3 לדוגמה.

  4. שולחים בקשה מהאפליקציה לדוגמה ומוודאים שהיא מצליחה.

פריסת האפליקציות

כשמוכנים לפרוס את האפליקציות, צריך:

  1. בדיקת האפליקציות ב-App Engine.

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

שימוש בחשבון שירות אחר כדי לאשר את הזהות

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

לחשבון השירות שמוגדר כברירת מחדל ב-App Engine יש רמת הרשאות גבוהה מאוד כברירת מחדל. הוא יכול להציג ולערוך את כל הפרויקטGoogle Cloud , ולכן ברוב המקרים לא מומלץ להשתמש בחשבון הזה כשהאפליקציה צריכה לבצע אימות בשירותי Cloud.

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

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

  1. מגדירים משתנה סביבה בשם GOOGLE_APPLICATION_CREDENTIALS לנתיב של קובץ JSON שמכיל את פרטי הכניסה של חשבון השירות. כדאי לעיין בהמלצות שלנו בנושא אחסון בטוח של פרטי הכניסה האלה.

  2. משתמשים ב-google.oauth2.id_token.fetch_id_token(request, audience) כדי לאחזר אסימון מזהה.

  3. כשמאמתים את הטוקן הזה, מטען הייעודי (payload) של הטוקן יכיל את כתובת האימייל של חשבון השירות החדש.