הגדרת אימות משתמשים ב-Cloud Service Mesh

אימות המשתמשים ב-Cloud Service Mesh הוא פתרון משולב לאימות משתמשי קצה בדפדפן ולבקרת גישה לעומסי העבודה שנפרסו. הוא מאפשר לכם לשלב ספקי זהויות (IDP) קיימים לאימות משתמשים, ומשתמש בממשקי API ובמדיניות הרשאות של Istio לניהול הרשאות גישה. זוהי חלופה ידידותית למשתמש לאימות של אסימון אינטרנט מסוג JSON‏ (JWT) של Istio.

תרחיש לדוגמה: ארגון משתמש ב-Cloud Service Mesh כדי לארח אפליקציית אינטרנט, והעובדים ניגשים אליה דרך דפדפן אינטרנט. בנוסף, הארגון צריך להשתמש בספק הזהויות הקיים שלו כדי לנהל את זהויות המשתמשים. אימות המשתמשים ב-Cloud Service Mesh מאפשר למשתמשים לבצע אימות בקלות באמצעות תהליך סטנדרטי של התחברות והסכמה מבוסס-אינטרנט של OpenID Connect ‏ (OIDC). כשהמשתמש מבצע אימות, Cloud Service Mesh אוכף את מדיניות ההרשאות של Istio, ואחרי שההרשאה מאושרת, הוא מעביר את הזהות לעומסי העבודה בפורמט מאובטח של פרטי כניסה.

איך זה עובד

אימות המשתמשים ב-Cloud Service Mesh כולל רכיב חדש, authservice. הרכיב הזה משתלב עם ה-ingress שמבוסס על Envoy כשירות הרשאה חיצוני, שמיירט את כל הבקשות הנכנסות לאימות. ‫authservice מטמיע את צד הלקוח של פרוטוקול OIDC ומאפשר למשתמשים לגשת לאפליקציות דרך דפדפן, שבו המשתמשים משלימים תהליך אימות והסכמה אינטראקטיבי כדי ליצור סשן קצר. ‫authservice מטמיע פרוטוקולים מקובלים בתחום כדי לשלב כל ספק זהויות שיכול לפעול כשרת הרשאות OIDC. כשהמשתמש מאומת, פרטי המשתמש נכללים ב-RCToken בפורמט JWT, שנחתם על ידי authservice ומועבר לשכבת ההרשאות של Istio ב-ingress. המודל הזה מספק בקרת גישה היקפית לתנועה שנכנסת לרשת. אם למשתמש יש הרשאה לגשת למשאב, ה-RCToken הזה מועבר גם למיקרו-שירותים כדי לקבל מידע על חשבון המשתמש ולאכוף בקרת גישה מפורטת (FGAC).

בתרשים הבא מוצג המיקום של authservice ברשת, והקשר שלו לחלקים האחרים ברשת, כמו ה-ingress, עומסי העבודה, דפדפן המשתמש וכל ספק זהויות (IDP) קיים.

אימות משתמש קצה

אדמינים יכולים להתקין את authservice כתוסף על גבי התקנה של Cloud Service Mesh. אחרי ההתקנה, authservice קורא את הגדרת נקודת הקצה של OIDC והגדרות משויכות אחרות שמוגדרות במשאב בהתאמה אישית UserAuth. האדמין יכול להשתמש בממשקי ה-API של Cloud Service Mesh ExternalAuthorization כדי להגדיר את auth_server כמסנן ב-Ingress.

התקנת שירות אימות המשתמשים

בשלבים הבאים מוסבר איך להגדיר את authservice.

דרישות מוקדמות

פועלים לפי השלבים שמפורטים בקטע התקנת כלים תלויים ואימות האשכול כדי:
  • אם אתם משתמשים ב-Cloud Service Mesh מנוהל באשכול פרטי, ודאו שהאשכול יכול לשלוח תנועה יוצאת ל-IDP.

בנוסף, צריך לוודא שאתם עומדים בדרישות המוקדמות, לפי השלבים הבאים.

התאמה אישית של שכבת-העל לאימות המשתמש בהתקנה

כדי להתקין את שירות אימות המשתמשים, צריך להתאים אישית את ההתקנה של Cloud Service Mesh ולהוסיף ספק הרשאות חיצוני ברמת הרשת. השלבים הנדרשים תלויים בשאלה אם אתם משתמשים ב-Cloud Service Mesh מנוהל או בתוך האשכול.

מנוהל

  1. מעדכנים את ConfigMap כך שיכלול את MeshConfig לאימות משתמשים. בפקודה הבאה, משתמשים באותו REVISION_LABEL שבו השתמשתם כשהקציתם את Cloud Service Mesh מנוהל (למשל, asm-managed,‏ asm-managed-rapid או asm-managed-stable):

    kubectl edit configmap istio-REVISION_LABEL -n istio-system
    
  2. מוסיפים את הטקסט הבא בשדה mesh ב-MeshConfig:

    mesh: |-
    ...
      extensionProviders:
      - name: "asm-userauth-grpc"
        envoyExtAuthzGrpc:
          service: "authservice.asm-user-auth.svc.cluster.local"
          port: "10003"
    
  3. יוצרים מרחב שמות של asm-user-auth.

    kubectl create namespace asm-user-auth
    
  4. מפעילים את מרחב השמות להזרקה. השלבים תלויים בהטמעה של מישור הבקרה.

    מנוהל (TD)

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

    kubectl label namespace asm-user-auth \
        istio.io/rev- istio-injection=enabled --overwrite
    

    מנוהל (Istiod)

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

    ```sh
    kubectl label namespace asm-user-auth \
        istio.io/rev- istio-injection=enabled --overwrite
    ```
    

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

    1. מריצים את הפקודה הבאה כדי לאתר את ערוצי ההפצה הזמינים:
    kubectl -n istio-system get controlplanerevision
    

    הפלט אמור להיראות כך:

    NAME                AGE
    asm-managed-rapid   6d7h
    

    בפלט, הערך בעמודה NAME הוא תווית הגרסה שתואמת לערוץ ההפצה הזמין של גרסת Cloud Service Mesh.

    1. מחילים את תווית הגרסה על מרחב השמות:

      kubectl label namespace asm-user-auth \
          istio-injection- istio.io/rev=REVISION_LABEL --overwrite
      
  5. מתקינים את שער Istio במרחב השמות asm-user-auth.

    kubectl apply -n asm-user-auth -f DIR_PATH/samples/gateways/istio-ingressgateway
    

בתוך האשכול

  1. מורידים את שכבת-העל לדוגמה לאימות משתמשים ומעדכנים אותה אם יש התאמות אישיות ברשת. מומלץ לשמור את קובץ שכבת-העל הזה בבקרת המקור.

    curl https://raw.githubusercontent.com/GoogleCloudPlatform/asm-user-auth/v1.2.5/overlay/user-auth-overlay.yaml > user-auth-overlay.yaml
    
  2. פועלים לפי ההוראות במאמר התקנת Cloud Service Mesh עם שכבת-על כדי להשתמש בסקריפט ש-Google מספקת להתקנת Cloud Service Mesh עם שכבת-העל של אימות המשתמשים. לדוגמה:

    ./asmcli install \
      --project_id PROJECT_ID \
      --cluster_name CLUSTER_NAME \
      --cluster_location CLUSTER_LOCATION \
      --fleet_id FLEET_PROJECT_ID \
      --output_dir DIR_PATH \
      --enable_all \
      --custom_overlay user-auth-overlay.yaml
    

    החבילות של אימות המשתמשים kpt יוצרות AuthorizationPolicy כדי להפנות לספק ההרשאות החיצוני שצוין על ידי pkg/ext-authz.yaml.

  3. יוצרים מרחב שמות של asm-user-auth.

    kubectl create namespace asm-user-auth
    
  4. מפעילים את מרחב השמות להזרקה.

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

      kubectl label namespace asm-user-auth \
          istio.io/rev- istio-injection=enabled --overwrite
    

    מומלץ להשתמש בהזרקה כברירת מחדל, אבל יש תמיכה בהזרקה מבוססת-גרסה: כך עושים את זה:

    1. כדי לאתר את תווית הגרסה ב-istiod, משתמשים בפקודה הבאה:

      kubectl get deploy -n istio-system -l app=istiod -o \
        jsonpath={.items[*].metadata.labels.'istio\.io\/rev'}'{"\n"}'
      
    2. מחילים את תווית הגרסה על מרחב השמות. בפקודה הבאה, REVISION_LABEL הוא הערך של תווית הגרסה istiod שציינתם בשלב הקודם.

      kubectl label namespace asm-user-auth \
          istio-injection- istio.io/rev=REVISION_LABEL --overwrite
      
  5. מתקינים את שער Istio במרחב השמות asm-user-auth.

    kubectl apply -n asm-user-auth -f DIR_PATH/samples/gateways/istio-ingressgateway
    

הכנת ההגדרות של לקוח ה-OIDC

כדי להגדיר את לקוח ה-OIDC: במדריך הזה Google משמשת כ-IDP, אבל אתם יכולים להשתמש בכל IDP שתומך באימות OIDC.

  1. במסוף Google Cloud , נכנסים לדף API & Services ‏ > Credentials.

    כניסה לדף Credentials

  2. עוברים אל Create Credentials (יצירת פרטי כניסה) ובוחרים באפשרות OAuth client ID (מזהה לקוח OAuth). אם צריך, מגדירים את האפשרויות של מסך ההסכמה ל-OAuth ואז מגדירים את האפשרויות הבאות:

    • מגדירים את Application type ל-אפליקציית אינטרנט.
    • מגדירים את Authorized redirect URI לערך https://REDIRECT_HOST/REDIRECT_PATH. לדוגמה, עבור localhost אפשר להגדיר את https://localhost:8443/_gcp_asm_authenticate.

    לאחר מכן, לוחצים על שמירה.

  3. בנוסף, שומרים את הגדרות הלקוח של OIDC לשימוש מאוחר יותר.

    export OIDC_CLIENT_ID=CLIENT_ID
    export OIDC_CLIENT_SECRET=CLIENT_SECRET
    export OIDC_ISSUER_URI=ISSUER_URI
    export OIDC_REDIRECT_HOST=REDIRECT_HOST
    export OIDC_REDIRECT_PATH=REDIRECT_PATH
    

הורדת החבילות של kpt

כדי להתקין את ההגדרות המומלצות של authservice מהמאגר הציבורי: הפקודות האלה מאחזרות את הקונטיינר האחרון של authservice ומפעילות אותו כ-Pod במרחב השמות asm-user-auth. היא גם מגדירה את ה-ingress ליירט את כל הבקשות.

מורידים את חבילת kpt:

kpt pkg get https://github.com/GoogleCloudPlatform/asm-user-auth.git/@v1.2.5 .
cd asm-user-auth/

הגדרת כתובת ה-URL להפניה אוטומטית והסוד לשער הכניסה

כדי להשתמש ב-OAuth2, צריך כתובת URL להפניה אוטומטית שמתארחת בנקודת קצה שמוגנת באמצעות HTTPS. הפקודות האלה הן למטרות הדגמה, והן מפשטות את ההגדרה על ידי יצירת אישור בחתימה עצמית לשער הכניסה של Istio.

  1. יוצרים אישור בחתימה עצמית:

    openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem \
     -days 365 -nodes -subj '/CN=localhost'
    
  2. יוצרים סוד לשער הכניסה כדי לארח תנועת HTTPS:

    kubectl create -n asm-user-auth secret tls userauth-tls-cert --key=key.pem \
    --cert=cert.pem
    

החלת מפתחות ההצפנה והחתימה

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

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

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

  1. מכינים את מפתח ההצפנה של הסשן בפורמט הבא או משתמשים בדוגמה מהחבילה, שאפשר לראות באמצעות cat ./samples/cookie_encryption_key.json.

    {
      "keys":[
         {
            "kty":"oct",
            "kid":"key-0",
            "K":"YOUR_KEY",
            "useAfter": 1612813735
         }
      ]
    }
    

    כדי ליצור מפתח AES לבדיקה, אפשר להשתמש בפקודה הבאה:

    openssl enc -aes-256-cbc -k mycustomkey -P -md sha1 | grep key
    
  2. מכינים את מפתח החתימה של RCToken בפורמט הבא, או משתמשים בדוגמה מהחבילה, שאפשר לראות באמצעות cat ./samples/rctoken_signing_key.json.

    {
      "keys":[
         {
            "kty":"RSA",
            "kid":"rsa-signing-key",
            "K":"YOUR_KEY",  # k contains a Base64 encoded PEM format RSA signing key.
            "useAfter": 1612813735  # unix timestamp
         }
      ]
    }
    

    כדי ליצור מפתח פרטי של RSA לבדיקה של 512 ביטים, אפשר להשתמש בפקודה הבאה:

    openssl genpkey -algorithm RSA -out rsa_private.pem -pkeyopt rsa_keygen_bits:512
    
  3. יוצרים את הסוד של Kubernetes, ש-authservice יטען למערכת הקבצים שלו.

    kubectl create secret generic secret-key  \
        --from-file="session_cookie.key"="./samples/cookie_encryption_key.json" \
        --from-file="rctoken.key"="./samples/rctoken_signing_key.json"  \
        --namespace=asm-user-auth
    

פריסת שירות אימות המשתמשים

הפקודות הבאות יוצרות את שירות אימות המשתמשים ואת הפריסה במרחב השמות asm-user-auth.

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

kpt fn eval pkg --image gcr.io/kpt-fn/apply-setters:v0.2 --truncate-output=false -- \
  client-id="$(echo -n ${OIDC_CLIENT_ID} | base64 -w0)" \
  client-secret="$(echo -n ${OIDC_CLIENT_SECRET} | base64 -w0)" \
  issuer-uri="${OIDC_ISSUER_URI}" \
  redirect-host="${OIDC_REDIRECT_HOST}" \
  redirect-path="${OIDC_REDIRECT_PATH}"

מחילים את החבילה kpt:

# Remove the potential alpha version CRD if exists.
kubectl delete crd userauthconfigs.security.anthos.io
kubectl apply -f ./pkg/asm_user_auth_config_v1beta1.yaml
kubectl apply -f ./pkg

ה-authservice צורך את ה-CRD של UserAuthConfig כדי לספק אימות של משתמשי קצה. אפשר להגדיר את UserAuthConfig בזמן הריצה, ולעדכן אותו כדי לשנות את ההתנהגות של authservice ולהגדיר אותו עם נקודות קצה (endpoints) לכל שרת הרשאות OIDC.

אפשר להציג את הקובץ לפי cat pkg/user_auth_config.yaml, והוא מכיל את השדות הבאים:

apiVersion: security.anthos.io/v1beta1
kind: UserAuthConfig
metadata:
  name: user-auth-config
  namespace: asm-user-auth
spec:
  authentication:
    oidc:
      certificateAuthorityData: ""  # kpt-set: ${ca-cert}
      issuerURI: "<your issuer uri>"  # kpt-set: ${issuer-uri}
      proxy: ""  # kpt-set: ${proxy}
      oauthCredentialsSecret:
        name: "oauth-secret"  # kpt-set: ${secret-name}
        namespace: "asm-user-auth"  # kpt-set: ${secret-namespace}
      redirectURIHost: ""  # kpt-set: ${redirect-host}
      redirectURIPath: "/_gcp_asm_authenticate"  # kpt-set: ${redirect-path}
      scopes: ""  # kpt-set: ${scopes}
      groupsClaim: ""  # kpt-set: ${groups}
  outputJWTAudience: "test_audience"  # kpt-set: ${jwt-audience}

במאמר פרטי ההגדרה של אימות משתמשים מופיעים תיאורים מפורטים של השדות של user_auth_config.yaml.

הגדרת מזהים של בקשות API

מזהי בקשות API הם קבוצה של ביטויי Common Expression Language‏ (CEL) שמשמשים לאימות משתמשים ב-Cloud Service Mesh כדי לזהות בקשות API. הגדרת המזהים האלה מאפשרת לאימות המשתמשים ב-Cloud Service Mesh לטפל באופן סמנטי בכשלים באימות של בקשות API.

באופן ספציפי, כשאתם מספקים ביטויי CEL שמזהים את בקשות ה-API, אתם מאפשרים לאימות המשתמשים של Cloud Service Mesh לדחות בקשות API לא מאומתות עם קוד הסטטוס 401-Unauthorized. זה קריטי ללקוחות כמו ממשקי שורת פקודה (CLI) וחשבונות שירות, שמצפים לקוד הסטטוס הזה בבקשות API לא מאומתות.

בלי מזהי בקשות API, אימות המשתמשים ב-Cloud Service Mesh יפנה את כל הבקשות שלא אומתו לספק OpenID Connect ‏ (OIDC) שהוגדר, ויחזיר קוד סטטוס 302-Found. ההתנהגות הזו של הפניה אוטומטית בדרך כלל לא רצויה לבקשות API, שבהן צפויה שגיאה 401-Unauthorized במקרה של כישלון.

כדי להגדיר את המזהים של בקשות ה-API, מוסיפים קטע apiRequestIdentifier לקובץ UserAuthConfig CR. כך אפשר להוסיף כמה ביטויי CEL. אם מציינים כמה ביטויי CEL, בקשה נחשבת לבקשת API אם היא תואמת לפחות לאחד מהם.

בדוגמה הבאה מוסבר איך להגדיר כבקשת API כל בקשה שמכילה את הכותרת example-api-header, כל בקשה שמופנית למארח api.example.com או כל בקשה שמטרגטת נתיב ב-/v1/api/.

apiVersion: security.anthos.io/v1beta1
kind: UserAuthConfig
metadata:
  name: user-auth-config
  namespace: asm-user-auth
spec:
  authentication:
    oidc:
      certificateAuthorityData: ""  # kpt-set: ${ca-cert}
      issuerURI: "<your issuer uri>"  # kpt-set: ${issuer-uri}
      proxy: ""  # kpt-set: ${proxy}
      oauthCredentialsSecret:
        name: "oauth-secret"  # kpt-set: ${secret-name}
        namespace: "asm-user-auth"  # kpt-set: ${secret-namespace}
      redirectURIHost: ""  # kpt-set: ${redirect-host}
      redirectURIPath: "/_gcp_asm_authenticate"  # kpt-set: ${redirect-path}
      scopes: ""  # kpt-set: ${scopes}
      groupsClaim: ""  # kpt-set: ${groups}
  outputJWTAudience: "test_audience"  # kpt-set: ${jwt-audience}
  apiRequestIdentifier: 
    name: "api-request-identifiers"
    expressions: 
    - "'example-api-header' in http_request.headers"
    - "http_request.host == 'api.example.com'"
    - "matches(http_request.path, '/v1/api/(.*)')"
    # other expressions go here

אפשר ליצור ביטויי CEL לאימות משתמשים ב-Cloud Service Mesh מהשדות הבאים:

  1. ‫http_request.headers: מפה של כותרות בקשות ה-HTTP, שבה המפתחות הם מחרוזות באותיות קטנות והערכים הם וקטורים של מחרוזות.

    אתם יכולים להשתמש בשדה הזה בביטוי ה-CEL כדי להגדיר בקשות עם כותרות ספציפיות כבקשות API.

  2. ‫http_request.host: המארח של כתובת ה-URL מסוג HTTP שביקשתם (למשל api.example.com).

    אתם יכולים להשתמש בשדה הזה בביטוי ה-CEL כדי להגדיר בקשות למארחים ספציפיים כבקשות API.

  3. ‫http_request.path: הנתיב המבוקש של כתובת ה-URL מסוג HTTP (למשל /example-api-path). הוא תמיד מתחיל ב-'/' ולא מכיל את רכיב השאילתה של כתובת ה-URL של הבקשה.

    אתם יכולים להשתמש בשדה הזה בביטוי ה-CEL כדי להגדיר בקשות עם נתיבים ספציפיים כבקשות API.

בקטע הבא מוצגות דוגמאות לביטויי CEL שבודקים ומתאימים חלקים מהשדות האלה, כדי להנחות את אימות המשתמשים ב-Cloud Service Mesh איך לקבוע אילו בקשות הן בקשות API.

ביטויים לדוגמה

התאמה לכל הבקשות שמכילות את הכותרת X-API-Key:

"'x-api-key' in http_request.headers"

התאמה לכל הבקשות שבהן הכותרת X-API-Key מוגדרת לערך מסוים:

"'x-api-key' in http_request.headers && http_request.headers['x-api-key'].values == ['example-value ']"

התאמת כל הבקשות למארח api.example.com:

"http_request.host == api.example.com"

התאמת כל הבקשות לנתיב /api/query:

"http_request.path == /api/query"

התאמה של כל הבקשות לכל נתיב ב-/api/:

"matches(http_request.path, '/api/(.*)')"

ביצוע משימות אחרי ההתקנה

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

הפעלת אימות משתמשים באפליקציות

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

כדי להפעיל את תהליך ה-OIDC, אימות המשתמשים ב-Cloud Service Mesh משתמש במדיניות הרשאה מסוג CUSTOM.

אחרי שמתקינים את שער Istio, מגדירים אותו להצגת תנועת HTTPS באמצעות אישור ה-TLS‏ userauth-tls-cert שיצרתם קודם. בהמשך מוצגת ההגדרה של pkg/gateway.yaml שהתקנתם.

apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
  name: userauth
  namespace: asm-user-auth
spec:
  selector:
    istio: ingressgateway
  servers:
  - hosts:
    - '*'
    port:
      name: https
      number: 443
      protocol: HTTPS
    tls:
      mode: SIMPLE
      credentialName: userauth-tls-cert
---
# This ensures the OIDC endpoint has at least some route defined.
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: userauth-oidc
  namespace: asm-user-auth
spec:
  gateways:
  - userauth
  hosts:
  - '*'
  http:
  - match:
    - uri:
        prefix: /status
    - uri:
        prefix: "your-oidc-redirect-path"
    name: user-auth-route
    route:
    - destination:
        host: authservice
        port:
          number: 10004
  1. מוסיפים תווית למרחב השמות default כדי להפעיל הזרקה אוטומטית של istio-proxy לפריסות.

    kubectl label namespace default istio.io/rev=REVISION --overwrite
    
  2. פריסת httpbin במרחב השמות default.

    kubectl apply -f https://raw.githubusercontent.com/istio/istio/master/samples/httpbin/httpbin.yaml -n default
    
  3. מעדכנים את httpbin כדי להשתמש בשער הזה להצגת תנועת HTTPS, ומשתמשים בהעברה ליציאה אחרת כדי לגשת לאפליקציה באופן מקומי:

    kubectl apply -f./samples/httpbin-route.yaml -n default
    kubectl port-forward service/istio-ingressgateway 8443:443 -n asm-user-auth
    
    apiVersion: networking.istio.io/v1alpha3
    kind: VirtualService
    metadata:
      name: httpbin
      namespace: default
    spec:
      gateways:
      - asm-user-auth/userauth
      hosts:
      - '*'
      http:
      - match:
        - uri:
            prefix: /ip
        - uri:
            prefix: /headers
        name: httpbin-routes
        route:
        - destination:
            host: httpbin.default.svc.cluster.local
            port:
              number: 8000
    

    שער הכניסה ביציאה 8443 יועבר אל localhost כדי לאפשר גישה לאפליקציה באופן מקומי.

  4. פורסים את samples/rctoken-authz.yaml כדי לאפשר ל-RequestAuthentication ול-AuthorizationPolicy לאמת את ה-RCToken של הבקשות.

    kubectl apply -f ./samples/rctoken-authz.yaml -n asm-user-auth
    

    דוגמה samples/rctoken-authz.yaml:

    apiVersion: security.istio.io/v1beta1
    kind: RequestAuthentication
    metadata:
      name: require-rc-token
    spec:
      selector:
        matchLabels:
          istio: ingressgateway
      jwtRules:
      - issuer: "authservice.asm-user-auth.svc.cluster.local"
        audiences:
        - "test_audience"
        jwksUri: "http://authservice.asm-user-auth.svc.cluster.local:10004/_gcp_user_auth/jwks"
        fromHeaders:
        - name: X-ASM-RCTOKEN
        forwardOriginalToken: true
    ---
    apiVersion: security.istio.io/v1beta1
    kind: AuthorizationPolicy
    metadata:
      name: require-rc-token
    spec:
      selector:
        matchLabels:
          istio: ingressgateway
      action: ALLOW
      rules:
      - when:
        - key: request.auth.claims[iss]
          values:
          - authservice.asm-user-auth.svc.cluster.local
        - key: request.auth.claims[aud]
          values:
          - test_audience
    

אימות המשתמשים

ה-httpbin משרת שני נתיבים, /ip נגיש לציבור ו-/headers דורש ממשתמש הקצה להתחבר דרך ה-IDP שהוגדר.

  1. כדי לוודא שיש לכם גישה ישירה אל /ip, נכנסים לכתובת https://localhost:8443/ip.

  2. נכנסים לכתובת https://localhost:8443/headers כדי לוודא שדף ההתחברות של OIDC מופיע.

  3. אחרי שמתחברים, לוחצים על Next ומוודאים שמועברים לדף /headers.

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

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

  1. מוסיפים כללי מדיניות הרשאה של Istio ב-ingress כדי לוודא שמתבצעת בדיקת הרשאה לכל משתמש מאומת:

    kubectl apply -f ./samples/httpbin-authz.yaml -n asm-user-auth
    
  2. הקובץ httpbin-authz.yaml מגדיר את שער תעבורת הנתונים הנכנסת (ingress) לאמת את אסימון ה-RC שהונפק על ידי authservice, ולאשר רק כש-JWT מכיל את השדות הרצויים, כמו קהלים ומנפיקים.

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

    apiVersion: security.istio.io/v1beta1
    kind: AuthorizationPolicy
    metadata:
      name: require-rc-token
    spec:
      selector:
        matchLabels:
          istio: ingressgateway
      action: ALLOW
      rules:
      - to:
        - operation:
            paths: ["/ip"]
      - to:
        when:
        - key: request.auth.claims[iss]
          values:
          - authservice.asm-user-auth.svc.cluster.local
        - key: request.auth.claims[aud]
          values:
          - test_audience
        - key: request.auth.claims[sub]
          values:
          - allowed_user_sub_1  # Change this with the "sub" claim in the RC token. Wildcard '*' will match everything.
    

קביעת הגדרות ספציפיות לסביבה

בשלבים הקודמים השתמשנו ב-localhost ובאישור HTTPS בחתימה עצמית כדי להגדיר במהירות. לשימוש בסביבת ייצור אמיתית, צריך להשתמש בדומיין משלכם, כמו example.com.

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

ניהול ורוטציה של מפתחות

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

שני המפתחות הם בפורמט JSON. השדה useAfter מציין את חותמת הזמן שממנה המפתח ייחשב לשימוש. במהלך רוטציית מפתחות, צריך לכלול בקובץ ה-JSON גם את המפתחות הישנים וגם את המפתחות החדשים. לדוגמה, בדוגמה הבאה, המערכת תשתמש ב-new-key רק אחרי חותמת הזמן 1712813735.

{
   "keys":[
      {
         "kty":"RSA",
         "kid":"old-key",
         "K":"...", # k contains a Base64 encoded PEM format RSA signing key.
         "useAfter": 1612813735, # unix timestamp
      }
      {
      "kty":"RSA",
         "kid":"new-key",
         "K":"...", # k contains a Base64 encoded PEM format RSA signing key.
         "useAfter": 1712813735, # unix timestamp
      }
   ]
}

ב-Cloud Service Mesh נעשה שימוש במפתח הסימטרי כדי להצפין את נתוני הסשן שמאוחסנים בקובצי ה-cookie של הדפדפן. כדי לוודא שהסשנים הקיימים תקינים, authservice מנסה לפענח את ההצפנה באמצעות כל המפתחות בקבוצת המפתחות. במהלך הרוטציה, authservice ישתמש במפתח החדש כדי להצפין סשנים חדשים, וימשיך לנסות לפענח את ההצפנה באמצעות המפתחות הישנים.

זוג המפתחות הציבורי/הפרטי משמש לחתימה על RCToken. המפתח הציבורי מועבר ל-sidecars על ידי istiod לאימות JWT. חשוב מאוד שה-sidecars יקבלו את המפתח הציבורי החדש לפני ש-authservice יתחיל להשתמש במפתח הפרטי החדש כדי לחתום על ה-RCToken. לכן, המערכת של authservice מתחילה לפרסם את המפתח הציבורי מיד אחרי שהמפתח נוסף, אבל ממתינה פרק זמן משמעותי לפני שהיא מתחילה להשתמש בו כדי לחתום על RCToken.

לסיכום, כשמבצעים רוטציית מפתחות, מומלץ:

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

פריסה בכמה אשכולות

אימות המשתמשים ב-Cloud Service Mesh תומך בפריסה בכמה אשכולות. צריך לפרוס את אימות המשתמשים בכל אשכול, כמו שמתואר למעלה. צריך לשכפל בכל אשכול את הגדרת אימות המשתמש, כמו המשאב המותאם אישית UserAuth, סוד הלקוח ב-OIDC ומפתחות ההצפנה.

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

apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: authservice-fail-over
  namespace: asm-user-auth
spec:
  host: authservice.asm-user-auth.svc.cluster.local
  trafficPolicy:
    loadBalancer:
      localityLbSetting:
        enabled: true
        failover:
        - from:  us-east
          to: us-west
        - from: us-west
          to: us-east

כמו הגדרות אחרות, צריך להגדיר את זה בכל אשכול.

מיפוי הצהרות בהתאמה אישית

כדי להגדיר מיפוי הצהרות בהתאמה אישית, מגדירים את spec.authentication.oidc.attributeMapping כדי להגדיר מיפויים מ-IDToken של ספק הזהויות המקורי. המפתח יהיה שם ההצהרה ב-RCToken והערך יהיה ביטוי CEL שמציין איך לנתח את ההצהרה מ-IDToken. צריך להשתמש ב-assertion כדי להפנות ל-IDToken.

דוגמה:

spec:
  authentication:
    oidc:
      attributeMapping:
        aud_copy: assertion.aud
        decision: 'assertion.sub.startsWith("123") ? "success" : "fail"'

ב-RCToken, שדה מקונן attributes מכיל את השדות שהוגדרו:

"attributes": {
    "aud_copy": "foo.googleusercontent.com",
    "decision": "success"
}

אם ביטוי ה-CEL לא מצליח לנתח את הערך מ-IDToken, הוא יתעלם מהתביעה בלי להכשיל את תהליך האימות.

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

  1. מתקינים שוב את חבילות user-auth כי הן מכילות את הקובץ הבינארי המעודכן לגרסה החדשה של user-auth:

    kpt pkg get https://github.com/GoogleCloudPlatform/asm-user-auth.git/@v1.2.5 .
    cd asm-user-auth/
    
  2. שומרים את הגדרות הלקוח של OIDC:

    export OIDC_CLIENT_ID=CLIENT_ID
    export OIDC_CLIENT_SECRET=CLIENT_SECRET
    export OIDC_ISSUER_URI=ISSUER_URI
    export OIDC_REDIRECT_HOST=REDIRECT_HOST
    export OIDC_REDIRECT_PATH=REDIRECT_PATH
    
  3. פורסים את שירות אימות המשתמשים כדי לשדרג לגרסה חדשה.

פרטי ההגדרות של אימות המשתמשים

בטבלה הבאה מתוארים כל השדות ב-CRD:

שם השדה תיאור
authentication.oidc בקטע הזה מופיעים ההגדרות של נקודת הקצה של OIDC והפרמטרים שמשמשים בתהליך של OIDC.
authentication.oidc.certificateAuthorityData זהו אישור הבסיס של SSL של הדומיין של שרת ההרשאות של OIDC או של שרת ה-proxy ל-HTTPS, אם יש כזה.
authentication.oidc.oauthCredentialsSecret הפניות סודיות לסוד מסוג Kubernetes Opaque שמכיל את מזהה הלקוח וסוד הלקוח של OAuth2 OIDC במטען הייעודי (payload) בפורמט JSON.
authentication.oidc.issuerURI ה-URI שבו צריך להשתמש כמנפיק ב-RCToken של הפלט.
authentication.oidc.proxy שרת proxy ל-IdP שתומך ב-OIDC, אם רלוונטי. בפורמט http://user:password@10.10.10.10:8888.
authentication.oidc.redirectURIHost המארח שישמש ל-URI של סיום OAuth. אם משאירים את השדה הזה ריק, המערכת תשתמש במארח מכתובת ה-URL המטורגטת ותבנה את ה-URI להפניה אוטומטית באופן דינמי.
אפשר להשתמש בערך הזה כשרוצים סשן SSO של אימות משתמש בדומיין ברמה גבוהה יותר. לדוגמה, כדי להפעיל כניסה יחידה בין profile.example.com/ ‎ לבין admin.example.com/‎, אפשר להגדיר את הערך הזה ל-example.com. כך אפשר יהיה ליצור סשן אימות משתמש ב-example.com שישותף בין כל תת-הדומיינים. הערה: אם כמה דומיינים מנוהלים מאותה רשת, למשל example1.com ו-example2.com, אי אפשר להשתמש בתכונה ומומלץ להשאיר את השדה ריק.
authentication.oidc.redirectURIPath הנתיב של נקודת הקצה שבו authservice יסיים את תהליך ה-OAuth. צריך לרשום את נתיב ה-URI הזה ואת המארח ככתובת URI מורשית להפניה אוטומטית בשרת ההרשאות של authentication.oidc.clientID.
בנוסף, ה-URI הזה צריך להיות מוגש מאותו Service mesh ו-Ingress שבהם מופעל authservice.
authentication.oidc.scopes היקף ההרשאות של OAuth שצריך לבקש בבקשת האימות. רשימה מופרדת בפסיקים של מזהים שמשמשים כדי לציין אילו הרשאות גישה מתבקשות בנוסף להיקף "openid", למשל: "groups,allatclaim".
authentication.oidc.groupsClaim אם ה-idtoken מכיל תביעת בעלות של קבוצות, צריך להשתמש בשדה הזה כדי לציין את השם שלה. אם השדה הזה צוין, השירות יעביר את הנתונים שבו לשדה groups ב-RCToken שנוצר. הטענה הזו צריכה להכיל רשימה מופרדת בפסיקים של מחרוזות, למשל: ["group1", "group2"].
authentication.oidc.attributeMapping מכיל מיפוי הצהרות אחד או יותר מביטויי CEL של idtoken. כל ההצהרות צריכות להיות מקושרות ל-assertion.X, ו-assertion מקושר ל-IDToken המקורי, לדוגמה aud_copy: assertion.aud.
authentication.outputJWTAudience הקהל של ה-RCToken שנוצר על ידי authservice. ה-sidecars יכולים לאמת את ה-RCToken הנכנס מול ערך הקהל הזה.

פתרון בעיות

  1. נגישות הרשת ל-IDP.

    יומן אפשרי: error: TLS handshake failed..

    כדי לאמת, מריצים את curl מהקונטיינר הזמני שמצורף ל-istio-proxy כדי לקרוא ל-URI של מנפיק ה-IDP. לדוגמה, ראו איסוף יומנים של Cloud Service Mesh.

    אם לא הצלחתם להתחבר, בדקו את הכללים של חומת האש או הגדרות רשת אחרות של האשכול.

  2. אישור CA בסיסי.

    יומן אפשרי: error: The server's TLS certificate did not match expectations. או error: TLS handshake failed..

    מוודאים שב-certificateAuthorityData יש את אישור ה-CA הבסיסי הנכון. אם אין שרת proxy ל-HTTPS שמסיים את תנועת ה-HTTPS, צריך להזין כאן את אישור ה-CA הבסיסי של ספק הזהויות. אם יש כזה, הוא צריך להכיל את כתובת ה-IP של שרת ה-proxy.

  3. הגדרת נתיב ההפניה האוטומטית.

    תצפית אפשרית: קבלת דף שגיאה 404 במהלך תהליך האימות של OIDC.

    אימות המשתמש מחזיר את הכותרת Set-Cookie בלי להשתמש במאפיין הנתיב. כברירת מחדל, הדפדפן משתמש בספרייה של כתובת ה-URL של הבקשה כנתיב של קובץ ה-Cookie (היקף קובץ ה-Cookie שקשור לנתיב). לכן מומלץ לא לכלול את התו "/" בנתיב ההפניה האוטומטית, אלא אם זו הכוונה.

  4. ה-sidecar לא יכול לאחזר את jwksUri.

    בתרחישים מסוימים, הגבלה של sidecar עלולה לגרום לכך שלא תצליחו לאחזר את jwksUri. אם מרחב השמות לא מופיע באמצעות תו כללי לחיפוש (לדוגמה, ./* או istio-system/*), זה לא יעבוד. צריך להוסיף באופן ידני את מרחב השמות שלהם ב-sidecar של תעבורת הנתונים היוצאת.

שאלות נפוצות

  1. איך משדרגים את Cloud Service Mesh כשהאימות של המשתמשים מופעל?

    פועלים לפי תהליך השדרוג של Cloud Service Mesh ומציינים את קובץ שכבת-העל על ידי הוספת --custom_overlay user-auth-overlay.yaml בשורת הפקודה ל-asmcli install.

  2. כמה משאבים צריך להקצות ל-authservice? ובכמה בקשות בשנייה הוא יכול לטפל?

    כברירת מחדל, authservice מוגדר עם 2.0 vCPU וזיכרון של 256Mi. בהגדרה כזו, authservice יכול לטפל ב-500 בקשות לשנייה. כדי לטפל בכמויות גדולות יותר של בקשות, צריך להקצות יותר מעבד (CPU), באופן יחסי לקיבולת הטיפול בבקשות. אפשר גם להגדיר כמה רפליקות של authservice כדי להגדיל את יכולת ההתאמה האופקית לעומס (horizontal scalability).