איך מוודאים תאימות של אישורי webhook לפני שמשדרגים לגרסה v1.23

החל מגרסה 1.23, ‏ Kubernetes לא תומך יותר באימות זהות השרת באמצעות השדה X.509 Common Name (CN) באישורים. במקום זאת, מערכת Kubernetes תסתמך רק על מידע בשדות של שם חלופי לנושא (SAN) מסוג X.509.

כדי למנוע השפעה על האשכולות, צריך להחליף אישורים לא תואמים ללא SAN עבור קצה עורפי של שרתי API מצטברים ו-webhook לפני שמשדרגים את האשכולות ל-Kubernetes גרסה 1.23.

למה Kubernetes לא תומך יותר באישור קצה עורפי ללא SAN

‫GKE מפעיל Kubernetes בקוד פתוח, שמשתמש ברכיב kube-apiserver כדי ליצור קשר עם קצה העורפי של שרת ה-API המצטבר ועם ה-webhook באמצעות Transport Layer Security ‏ (TLS). הרכיב kube-apiserver נכתב בשפת התכנות Go.

לפני גרסה Go 1.15, לקוחות TLS אימתו את הזהות של השרתים שהם התחברו אליהם בתהליך דו-שלבי:

  1. בודקים אם שם ה-DNS (או כתובת ה-IP) של השרת מופיע כאחד משמות ה-SAN באישור של השרת.
  2. כפתרון חלופי, בודקים אם שם ה-DNS (או כתובת ה-IP) של השרת שווה לשם הנפוץ (CN) באישור של השרת.

בשנת 2011, RFC 6125 הוציא משימוש את אימות זהות השרת שמבוסס על השדה CN. דפדפנים ואפליקציות אחרות שקריטיות לאבטחה כבר לא משתמשים בשדה הזה.

כדי להתאים למערכת האקולוגית הרחבה יותר של TLS,‏ Go 1.15 הסירה את שלב 2 מתהליך האימות שלה, אבל השאירה מתג לניפוי באגים (x509ignoreCN=0) כדי לאפשר את ההתנהגות הישנה כדי להקל על תהליך ההעברה. ‫Kubernetes version 1.19 הייתה הגרסה הראשונה שנבנתה באמצעות Go 1.15. באשכולות GKE בגרסאות 1.19 עד 1.22, מתג הניפוי באגים מופעל כברירת מחדל כדי לתת ללקוחות יותר זמן להחליף את האישורים עבור ה-webhook המושפע והקצה העורפי המצטבר של שרת ה-API.

‫Kubernetes version 1.23 מבוססת על Go 1.17, שבה הוסר מתג הניפוי באגים. אחרי ש-GKE ישדרג את האשכולות לגרסה 1.23, לא תהיה אפשרות להתחבר מהמישור של האשכול לשירותי API מצטברים או ל-webhook שלא מספקים אישור X.509 תקף עם SAN מתאים.

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

לצבירי תגים שמופעלות בהם גרסאות תיקון לפחות 1.21.9 או 1.22.3

באשכולות בגרסאות 1.21.9 ו-1.22.3 ואילך עם Cloud Logging מופעל,‏ GKE מספק יומן של Cloud Audit Logs כדי לזהות קריאות לשרתי קצה מושפעים מהאשכול. כדי לחפש את היומנים, אפשר להשתמש במסנן הבא:

logName =~ "projects/.*/logs/cloudaudit.googleapis.com%2Factivity"
resource.type = "k8s_cluster"
operation.producer = "k8s.io"
"invalid-cert.webhook.gke.io"

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

בדוגמה הבאה מוצגת רשומה ביומן עבור קצה עורפי של webhook שאותו מארח שירות בשם example-webhook במרחב השמות default:

{
  ...
  resource {
    type: "k8s_cluster",
    "labels": {
      "location": "us-central1-c",
      "cluster_name": "example-cluster",
      "project_id": "example-project"
    }
  },
  labels: {
    invalid-cert.webhook.gke.io/example-webhook.default.svc: "No subjectAltNames returned from example-webhook.default.svc:8443",
    ...
  },
  logName: "projects/example-project/logs/cloudaudit.googleapis.com%2Factivity",
  operation: {
    ...
    producer: "k8s.io",
    ...
  },
  ...
}

שמות המארחים של השירותים המושפעים (למשל example-webhook.default.svc) נכללים כסיומות בשמות התוויות שמתחילות ב-invalid-cert.webhook.gke.io/. אפשר גם לקבל את שם האשכול שביצע את השיחה מהתווית resource.labels.cluster_name, שהערך שלה בדוגמה הזו הוא example-cluster.

תובנות לגבי הוצאה משימוש

אפשר לראות באילו אשכולות נעשה שימוש באישורים לא תואמים בתובנות לגבי הוצאה משימוש. התובנות זמינות עבור אשכולות שפועלת בהם גרסה 1.22.6-gke.1000 ואילך.

גרסאות אחרות של אשכולות

אם יש לכם אשכול בגרסת תיקון שקודמת ל-1.22.3 בגרסה המשנית 1.22, או בגרסת תיקון שקודמת ל-1.21.9, יש לכם שתי אפשרויות לקבוע אם האשכול שלכם מושפע מהוצאה משימוש הזו:

אפשרות 1 (מומלצת): משדרגים את האשכול לגרסת תיקון שכוללת תמיכה בזיהוי של אישורים מושפעים באמצעות יומנים. מוודאים ש-Cloud Logging מופעל באשכול. אחרי השדרוג של האשכול, יומני Cloud Audit Logs שמזהים את הבעיה יופקו בכל פעם שהאשכול ינסה להתקשר לשירות שלא מספק אישור עם SAN מתאים. היומנים יופקו רק בניסיון לבצע שיחה, ולכן מומלץ להמתין 30 יום אחרי השדרוג כדי לאפשר מספיק זמן להפעלת כל נתיבי השיחות.

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

אפשרות 2: בודקים את האישורים שמשמשים את ה-Webhooks או את שרתי ה-API המצטברים באשכולות כדי לדעת אם הם מושפעים מכך שאין להם SAN:

  1. מקבלים את הרשימה של Webhooks ושל שרתי API מצטברים באשכול ומזהים את ה-backends שלהם (שירותים או כתובות URL).
  2. בודקים את האישורים שבהם נעשה שימוש בשירותי הקצה העורפי.

בשיטה הזו נדרשת עבודה ידנית כדי לבדוק את כל האישורים, ולכן כדאי להשתמש בה רק אם אתם צריכים להעריך את ההשפעה של הוצאת השימוש משימוש ב-Kubernetes גרסה 1.23 לפני שאתם משדרגים את האשכול ל-גרסה 1.21. אם אפשר לשדרג את האשכול לגרסה 1.21, כדאי לשדרג אותו קודם ואז לפעול לפי ההוראות באפשרות 1 כדי להימנע ממאמץ ידני.

זיהוי שירותים לקצה העורפי לבדיקה

כדי לזהות את הקצה העורפי שעשוי להיות מושפע מהוצאה משימוש, צריך לקבל את רשימת ה-Webhooks ושירותי ה-API המצטברים ואת הקצה העורפי המשויך שלהם באשכול.

כדי לראות רשימה של כל ה-webhook הרלוונטיים באשכול, משתמשים בפקודות kubectl הבאות:

kubectl get mutatingwebhookconfigurations -A   # mutating admission webhooks

kubectl get validatingwebhookconfigurations -A # validating admission webhooks

כדי לקבל שירות או כתובת URL משויכים של קצה עורפי עבור webhook נתון, צריך לבדוק את clientConfig.service השדה או את webhooks.clientConfig.url השדה בהגדרות של ה-webhook:

kubectl get mutatingwebhookconfigurations example-webhook -o yaml

הפלט של הפקודה הזו אמור להיראות כך:

apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
webhooks:
- admissionReviewVersions:
  clientConfig:
    service:
        name: example-service
        namespace: default
        port: 443

שימו לב שאפשר לציין את הקצה העורפי של clientConfig כשירות Kubernetes ‏(clientConfig.service) או ככתובת URL ‏ (clientConfig.url).

כדי לפרסם את כל שירותי ה-API הרלוונטיים המצטברים באשכול, משתמשים בפקודה kubectl הבאה:

kubectl get apiservices -A |grep -v Local      # aggregated API services

הפלט של הפקודה הזו אמור להיראות כך:

NAME                     SERVICE                      AVAILABLE   AGE
v1beta1.metrics.k8s.io   kube-system/metrics-server   True        237d

בדוגמה הזו, הפונקציה מחזירה את השירות metric-server ממרחב השמות kube-system.

כדי לקבל שירות משויך ל-API מצטבר נתון, צריך לבדוק את השדה spec.service:

kubectl get apiservices v1beta1.metrics.k8s.io -o yaml

הפלט של הפקודה הזו אמור להיראות כך:

...
apiVersion: apiregistration.k8s.io/v1
kind: APIService
spec:
  service:
    name: metrics-server
    namespace: kube-system
    port: 443

בדיקת האישור של שירות

אחרי שמזהים את שירותי ה-Backend הרלוונטיים לבדיקה, אפשר לבדוק את האישור של כל שירות ספציפי, כמו example-service:

  1. מוצאים את הסלקטור ואת יעד ההעברה של השירות:

    kubectl describe service example-service
    

    הפלט של הפקודה הזו אמור להיראות כך:

    Name: example-service
    Namespace: default
    Labels: run=nginx
    Selector: run=nginx
    Type: ClusterIP
    IP: 172.21.xxx.xxx
    Port: 443
    TargetPort: 444
    

    בדוגמה הזו, ל-example-service יש את הסלקטור run=nginx ואת יציאת היעד 444.

  2. חיפוש פוד שתואם לסלקטור:

    kubectl get pods --selector=run=nginx
    

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

    NAME          READY   STATUS    RESTARTS   AGE
    example-pod   1/1     Running   0          21m
    
  3. הגדרת העברה ליציאה אחרת

    מ-kubectllocalhost אל ה-Pod.

    kubectl port-forward pods/example-pod LOCALHOST_PORT:TARGET_PORT # port forwarding in background
    

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

    • LOCALHOST_PORT: הכתובת להאזנה.
    • TARGET_PORT TargetPort משלב 1.
  4. משתמשים ב-openssl כדי להדפיס את האישור שבו השירות משתמש:

    openssl s_client -connect localhost:LOCALHOST_PORT </dev/null | openssl x509 -noout -text
    

    בדוגמה הזו של פלט מוצג אישור תקין (עם רשומות SAN):

    Subject: CN = example-service.default.svc
    X509v3 extensions:
      X509v3 Subject Alternative Name:
        DNS:example-service.default.svc
    

    בדוגמה הזו של פלט מוצג אישור שחסר בו SAN:

    Subject: CN = example-service.default.svc
      X509v3 extensions:
          X509v3 Key Usage: critical
              Digital Signature, Key Encipherment
          X509v3 Extended Key Usage:
              TLS Web Server Authentication
          X509v3 Authority Key Identifier:
              keyid:1A:5F:29:D8:E9:3C:54:3C:35:CC:D8:AB:D1:21:FD:C3:56:25:C0:74
    
  5. כדי להפסיק את העברת היציאה ברקע, מריצים את הפקודות הבאות:

    $ jobs
    [1]+  Running                 kubectl port-forward pods/example-pod 8888:444 &
    $ kill %1
    [1]+  Terminated              kubectl port-forward pods/example 8888:444
    

בדיקת האישור של קצה עורפי של כתובת URL

אם ה-webhook משתמש ב-url backend, הוא מתחבר ישירות לשם המארח שצוין בכתובת ה-URL. לדוגמה, אם כתובת ה-URL היא https://example.com:123/foo/bar, משתמשים בפקודה openssl הבאה כדי להדפיס את האישור שבו נעשה שימוש בשרת העורפי:

  openssl s_client -connect example.com:123 </dev/null | openssl x509 -noout -text

צמצום הסיכון בשדרוג לגרסה 1.23

אחרי שמזהים את האשכולות המושפעים ואת שירותי ה-Backend שלהם באמצעות אישורים ללא SAN, צריך לעדכן את ה-Webhooks ואת שרתי ה-API המצטברים של ה-Backend כדי להשתמש באישורים עם SAN מתאים לפני שמשדרגים את האשכולות לגרסה 1.23.

מערכת GKE לא תשדרג אוטומטית אשכולות בגרסאות 1.22.6-gke.1000 ואילך עם קצה עורפי שמשתמשים באישורים לא תואמים, עד שתחליפו את האישורים או עד שגרסה 1.22 תגיע לסוף התמיכה הרגילה.

אם האשכול שלכם הוא בגרסת GKE קודמת ל-1.22.6-gke.1000, אתם יכולים למנוע באופן זמני שדרוגים אוטומטיים על ידי הגדרה של החרגה מתחזוקה כדי למנוע שדרוגים משניים.

משאבים

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

  • הערות לגבי הגרסה Kubernetes 1.23
    • ‫Kubernetes מבוסס על Go 1.17. בגרסה הזו של Go, אי אפשר להשתמש בהגדרת הסביבה GODEBUG=x509ignoreCN=0 כדי להפעיל מחדש התנהגות מדור קודם שהוצאה משימוש, שלפיה ה-CN של אישורי שרת X.509 נחשב כשם מארח.
  • הערות לגבי הגרסאות של Kubernetes 1.19 ושל Kubernetes 1.20
    • התנהגות מדור קודם שהוצאה משימוש, שבה השדה CN באישורי שרת מסוג X.509 נחשב כשם מארח כשלא קיימים שמות SAN, מושבתת עכשיו כברירת מחדל.
  • נתוני הגרסה של Go 1.17
    • הסימון הזמני GODEBUG=x509ignoreCN=0 הוסר.
  • נתוני הגרסה של Go 1.15
    • התנהגות מדור קודם שהוצאה משימוש, שלפיה השדה CN באישורי X.509 נחשב למארח כשאין SAN, מושבתת עכשיו כברירת מחדל.
  • RFC 6125 (page 46)
    • השימוש בערך CN הוא נוהג קיים, אבל הוא הוצא משימוש. רצוי שרשויות אישורים יספקו ערכים של subjectAltName במקום זאת.
  • Admission webhooks