אימות בין שירותים

אתם יכולים להטמיע אימות בין שירותים באמצעות חשבון שירות בשירות gRPC. בדף הזה מוצג אימות בין שירותים באמצעות דוגמה מלאה, כולל הסבר על אופן ההגדרה של Extensible Service Proxy‏ (ESP) בשירות gRPC כדי לתמוך בבקשות מאומתות, ועל אופן הקריאה לשירות מלקוח gRPC.

כדי ששירות יוכל לבצע קריאות מאומתות ל-Cloud Endpoints API, לשירות הקורא צריך להיות חשבון שירות, והוא צריך לשלוח אסימון אימות בקריאה. הגורם הקורא צריך להשתמש באסימון מזהה של Google או באסימון JWT‏ (JSON Web Token) מותאם אישית שחתום רק על ידי חשבון השירות של הגורם הקורא. ‫ESP מאמת שההצהרה iss ב-JWT תואמת להגדרה issuer בהגדרת השירות. פלטפורמת ה-ESP לא בודקת הרשאות של ניהול זהויות והרשאות גישה שניתנו בחשבון השירות.

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

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

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

  1. משכפלים את מאגר ה-Git שבו מתארח קוד הדוגמה של gRPC:

    git clone https://github.com/GoogleCloudPlatform/python-docs-samples.git
    
  2. כדי לשנות את ספריית העבודה:

    cd python-docs-samples/endpoints/bookstore-grpc/
    
  3. אם עדיין אין לכם פרויקט, פועלים לפי ההוראות שבקטע הדרכות כדי להגדיר פרויקט.

בדוגמה הזו נעשה שימוש בפריסה ל-Google Kubernetes Engine, אבל הגדרת האימות זהה גם ל-Compute Engine.

בדוגמה, יש שני פרויקטים ב-Google Cloud Platform שמופיעים בהפניה:

  • פרויקט לשירות מנוהל, שהוא הפרויקט שבבעלותו שירות Cloud Endpoints for gRPC.
  • הפרויקט של צרכן השירות, שהוא הפרויקט שבבעלותו נמצא לקוח ה-gRPC.

יצירת חשבון שירות ומפתח לצרכן

כדי ליצור את חשבון השירות והמפתח עבור פרויקט הצרכן:

  1. במסוף Google Cloud , נכנסים אל APIs & services (ממשקי API ושירותים).

    ממשקי API ושירותים

    מוודאים שאתם נמצאים בפרויקט הצרכן.
  2. בדף Credentials, בתפריט הנפתח Create Credentials, בוחרים באפשרות Service Account Key.
  3. בדף Create service account key, אם יש לכם חשבון שירות קיים שבו אתם רוצים להשתמש, בוחרים אותו. אם לא, בתפריט הנפתח Service account בוחרים באפשרות New service account ומקלידים שם לחשבון.

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

    service-account-name@YOUR_PROJECT_ID.iam.gserviceaccount.com
    
  4. לוחצים על הרשימה הנפתחת תפקיד ובוחרים את התפקידים הבאים:

    • Service Accounts > Service Account User
    • Service Accounts > Service Account Token Creator
  5. מוודאים שסוג המפתח שנבחר הוא JSON.

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

הגדרת אימות לשירות

צריך להשתמש בפרויקט ההפקה לכל השלבים בקטע הזה.

הגדרת אימות בתצורת gRPC API

האימות ל-ESP מוגדר בקטע authentication בקובץ ה-YAML של הגדרת gRPC API. ההגדרה עם אימות לשירות לדוגמה הזה נמצאת ב-api_config_auth.yaml.

authentication:
  providers:
  - id: google_service_account
    # Replace SERVICE-ACCOUNT-ID with your service account's email address.
    issuer: SERVICE-ACCOUNT-ID
    jwks_uri: https://www.googleapis.com/robot/v1/metadata/x509/SERVICE-ACCOUNT-ID
  rules:
  # This auth rule will apply to all methods.
  - selector: "*"
    requirements:
      - provider_id: google_service_account

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

בעותק של הקובץ הזה ממאגר הנתונים המשוכפל:

  • מחליפים את MY_PROJECT_ID במזהה פרויקט המפיק.
  • משנים את SERVICE-ACCOUNT-ID בקטע authentication (גם בערכים issuer וגם בערכים jwks_uri) למזהה של חשבון השירות לצרכן שרשמתם בקטע הקודם. ההגדרה הזו מציינת ל-ESP שאתם רוצים להעניק גישה לשירות שלכם למשתמשים שמספקים טוקנים תקפים מחשבון השירות הספציפי הזה.
  • אפשר גם להוסיף את התג jwt_locations מתחת לרכיב providers. אתם יכולים להשתמש בערך הזה כדי להגדיר מיקום מותאם אישית של JWT. מיקומי ברירת המחדל של ה-JWT הם המטא-נתונים Authorization (עם הקידומת Bearer ) והמטא-נתונים X-Goog-Iap-Jwt-Assertion.

שומרים את הקובץ לשלב הבא.

פריסת ההגדרה והשירות

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

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

    gcloud endpoints services deploy api_descriptor.pb api_config_auth.yaml --project PRODUCER_PROJECT
    
  2. אם עדיין לא עשיתם זאת, צרו אשכול מאגרי תגים ואמתו את kubectl באשכול.

  3. פריסת ה-API לדוגמה ו-ESP באשכול. אם אתם משתמשים בפרויקטים נפרדים של יצרן וצרכן, קודם צריך לוודא שהגדרתם את הפרויקט המתאים בכלי gcloud של שורת הפקודה:

    gcloud config set project PRODUCER_PROJECT
    

קריאה לשיטות מאומתות מלקוח gRPC

לבסוף, בצד הלקוח, אפשר להשתמש במפתח של חשבון השירות כדי ליצור אסימון JWT, ואז להשתמש באסימון כדי לקרוא לשיטה מאומתת של חנות הספרים. קודם כול, מתקינים את דרישות Python המתאימות כדי ליצור את האסימון ולהריץ את לקוח הדוגמה. מוודאים שאתם נמצאים בתיקייה python-docs-samples/endpoints/bookstore-grpc של הלקוח המשוכפל, ואז:

virtualenv bookstore-env
source bookstore-env/bin/activate
pip install -r requirements.txt

יצירת טוקן JWT

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

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

#!/usr/bin/env python

# Copyright 2016 Google Inc.
#
# 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.

"""Example of generating a JWT signed from a service account file."""

import argparse
import json
import time

import google.auth.crypt
import google.auth.jwt

"""Max lifetime of the token (one hour, in seconds)."""
MAX_TOKEN_LIFETIME_SECS = 3600


def generate_jwt(service_account_file, issuer, audiences):
    """Generates a signed JSON Web Token using a Google API Service Account."""
    with open(service_account_file) as fh:
        service_account_info = json.load(fh)

    signer = google.auth.crypt.RSASigner.from_string(
        service_account_info["private_key"], service_account_info["private_key_id"]
    )

    now = int(time.time())

    payload = {
        "iat": now,
        "exp": now + MAX_TOKEN_LIFETIME_SECS,
        # aud must match 'audience' in the security configuration in your
        # swagger spec. It can be any string.
        "aud": audiences,
        # iss must match 'issuer' in the security configuration in your
        # swagger spec. It can be any string.
        "iss": issuer,
        # sub and email are mapped to the user id and email respectively.
        "sub": issuer,
        "email": "user@example.com",
    }

    signed_jwt = google.auth.jwt.encode(signer, payload)
    return signed_jwt


if __name__ == "__main__":
    parser = argparse.ArgumentParser(
        description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter
    )
    parser.add_argument("--file", help="The path to your service account json file.")
    parser.add_argument("--issuer", default="", help="issuer")
    parser.add_argument("--audiences", default="", help="audiences")

    args = parser.parse_args()

    signed_jwt = generate_jwt(args.file, args.issuer, args.audiences)
    print(signed_jwt.decode("utf-8"))

כדי ליצור אסימון באמצעות הסקריפט:

  • יוצרים טוקן JWT ומקצים אותו למשתנה $JWT_TOKEN:

    JWT_TOKEN=$(python jwt_token_gen.py \
        --file=[SERVICE_ACCOUNT_FILE] \
        --audiences=[SERVICE_NAME] \
        --issuer=[SERVICE-ACCOUNT-ID])
    

    where:

    • [SERVICE_ACCOUNT_FILE] הוא קובץ מפתח JSON של חשבון שירות צרכני שהורדתם.
    • [SERVICE_NAME] הוא השם של שירות חנות הספרים שהוחזר כשפרסתם את הגדרת השירות המעודכנת שלו ב-Endpoints.
    • [SERVICE-ACCOUNT-ID] הוא המזהה המלא של חשבון השירות הפרטי כשיוצרים את חשבון השירות.

ביצוע קריאת gRPC מאומתת

בשלב האחרון הזה נעשה שימוש ב-bookstore_client.py, שהוא אותו לקוח שבו נעשה שימוש במדריכים. כדי לבצע קריאה מאומתת, הלקוח מעביר את ה-JWT כמטא-נתונים עם הפעלת ה-method.

def run(
    host, port, api_key, auth_token, timeout, use_tls, servername_override, ca_path

כדי להריץ את הדוגמה:

  1. משתמשים ב-kubectl get services כדי לקבל את כתובת ה-IP החיצונית של חנות הספרים שפרסתם:

    #kubectl get services
    NAME                 CLUSTER-IP      EXTERNAL-IP      PORT(S)           AGE
    echo                 10.11.246.240   104.196.186.92   80/TCP            10d
    endpoints            10.11.243.168   104.196.210.50   80/TCP,8090/TCP   10d
    esp-grpc-bookstore   10.11.254.34    104.196.60.37    80/TCP            1d
    kubernetes           10.11.240.1     <none>           443/TCP           10d
    

    במקרה הזה, השירות הוא esp-grpc-bookstore וכתובת ה-IP החיצונית שלו היא 104.196.60.37.

  2. מקצים את כתובת ה-IP למשתנה EXTERNAL_IP

    EXTERNAL_IP=104.196.60.37
    
  3. רשימה של כל המדפים משירות חנות הספרים:

    python bookstore_client.py --port=80 --host=$EXTERNAL_IP --auth_token=$JWT_TOKEN
    

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

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