איסוף יומנים של שרת Proxy של OAuth2
במאמר הזה מוסבר איך להטמיע יומנים של OAuth2 Proxy ב-Google Security Operations באמצעות Google Cloud Storage V2.
OAuth2 Proxy הוא שרת proxy הפוך של CNCF Sandbox שמספק אימות באמצעות ספקי OAuth2/OIDC (Google, GitHub, Keycloak, Azure AD ואחרים) כדי לאמת חשבונות לפי אימייל, דומיין או קבוצה. הוא יוצר יומני אימות (כניסה מוצלחת או כניסה שנכשלה), יומני בקשות (בקשות HTTP שעברו דרך פרוקסי עם זהות המשתמש) ויומני אפליקציה רגילים. מכיוון ש-OAuth2 Proxy פועל כקונטיינר ב-Kubernetes וכותב את כל היומנים ל-stdout, נעשה שימוש באוסף יומנים מקורי של Kubernetes (Fluentd) כדי להעביר יומנים לקטגוריה של GCS לצורך הטמעה ב-Google SecOps.
לפני שמתחילים
חשוב לוודא שמתקיימות הדרישות המוקדמות הבאות:
- מופע של Google SecOps
- פרויקט ב-GCP עם Cloud Storage API מופעל
- הרשאות ליצירה ולניהול של מאגרי GCS
- הרשאות לניהול מדיניות IAM בקטגוריות GCS
- אשכול Kubernetes פעיל עם שרת proxy של OAuth2 שפרוס (דרך תרשים Helm או פריסה ידנית)
kubectlגישה לאשכול Kubernetes עם הרשאות ליצירת DaemonSets, ConfigMaps, Secrets ו-Namespaces- מפתח JSON של חשבון שירות ב-GCP עם הרשאה
storage.objects.createבקטגוריית היעד ב-GCS
יצירת קטגוריה של Google Cloud Storage
- נכנסים אל מסוף Google Cloud.
- בוחרים פרויקט או יוצרים פרויקט חדש.
- בתפריט הניווט, עוברים אל Cloud Storage > Buckets.
- לוחצים על Create bucket.
מספקים את פרטי ההגדרה הבאים:
הגדרה ערך Name your bucket (שם הקטגוריה) מזינים שם ייחודי גלובלית (לדוגמה, oauth2-proxy-logs-bucket)סוג המיקום בוחרים לפי הצרכים (אזור, שני אזורים, מספר אזורים) Location בוחרים את המיקום (לדוגמה, us-central1).סוג אחסון (storage class) רגיל (מומלץ ליומנים שמתבצעת אליהם גישה לעיתים קרובות) בקרת גישה אחיד (מומלץ) כלים להגנה אופציונלי: הפעלת ניהול גרסאות של אובייקטים או מדיניות שמירת נתונים לוחצים על יצירה.
הגדרת רישום ביומן של שרת Proxy מסוג OAuth2
OAuth2 Proxy כותב שלושה סוגים של יומנים ל-stdout: יומנים רגילים, יומני אימות ויומני בקשות. כל שלושת הסוגים מופעלים כברירת מחדל. כדי לוודא שהיומנים מכילים את כל הפרטים שרלוונטיים לאבטחה, צריך להגדיר את OAuth2 Proxy עם דגלי הרישום ביומן הבאים.
אפשרות 1: הגדרה באמצעות ערכי תרשים Helm
אם OAuth2 Proxy נפרס באמצעות תרשים Helm הרשמי, צריך להוסיף את הקוד הבא לקובץ
values.yaml:config: configFile: |- standard_logging = true auth_logging = true request_logging = true silence_ping_logging = true standard_logging_format = "[{{.Timestamp}}] [{{.File}}] {{.Message}}" auth_logging_format = "{{.Client}} - {{.RequestID}} - {{.Username}} [{{.Timestamp}}] [{{.Status}}] {{.Message}}" request_logging_format = "{{.Client}} - {{.RequestID}} - {{.Username}} [{{.Timestamp}}] {{.Host}} {{.RequestMethod}} {{.Upstream}} {{.RequestURI}} {{.Protocol}} {{.UserAgent}} {{.StatusCode}} {{.ResponseSize}} {{.RequestDuration}}"מחילים את ערכי Helm המעודכנים:
helm upgrade oauth2-proxy oauth2-proxy/oauth2-proxy -f values.yaml -n <your-namespace>
אפשרות 2: הגדרה באמצעות תגי שורת פקודה
אם OAuth2 Proxy נפרס באמצעות מניפסט של פריסת Kubernetes, צריך להוסיף את הארגומנטים הבאים למפרט של הקונטיינר:
args: - --standard-logging=true - --auth-logging=true - --request-logging=true - --silence-ping-logging=true
אפשרות 3: הגדרה באמצעות משתני סביבה
מגדירים את משתני הסביבה הבאים בקונטיינר של OAuth2 Proxy:
env: - name: OAUTH2_PROXY_STANDARD_LOGGING value: "true" - name: OAUTH2_PROXY_AUTH_LOGGING value: "true" - name: OAUTH2_PROXY_REQUEST_LOGGING value: "true" - name: OAUTH2_PROXY_SILENCE_PING_LOGGING value: "true"
אימות הרישום ביומן של OAuth2 Proxy
אחרי שמחילים את ההגדרה, מוודאים ש-OAuth2 Proxy יוצר יומנים:
kubectl logs -l app=oauth2-proxy -n <your-namespace> --tail=20הפלט כולל שורות שדומות לאלה:
10.0.0.1 - abc123 - user@example.com [2024/01/15 10:30:00] [AuthSuccess] Authenticated via OAuth2 10.0.0.1 - abc123 - user@example.com [2024/01/15 10:30:01] example.com GET 10.0.0.5:8080 "/dashboard" HTTP/1.1 "Mozilla/5.0" 200 1234 0.005
יצירת חשבון שירות ב-GCP עבור Fluentd
Fluentd דורש חשבון שירות ב-GCP עם גישת כתיבה לקטגוריה של GCS.
- במסוף GCP, עוברים אל IAM & Admin > Service Accounts.
- לוחצים על יצירת חשבון שירות.
- מספקים את פרטי ההגדרה הבאים:
- שם חשבון השירות: מזינים
fluentd-gcs-writer - תיאור חשבון השירות: מזינים
Service account for Fluentd to write OAuth2 Proxy logs to GCS
- שם חשבון השירות: מזינים
- לוחצים על יצירה והמשך.
- בקטע Grant this service account access to project (מתן גישה של חשבון השירות הזה לפרויקט):
- לוחצים על בחירת תפקיד.
- מחפשים את Storage Object Admin ולוחצים עליו.
- לוחצים על Continue.
- לוחצים על סיום.
יצירה והורדה של מפתח לחשבון שירות
- ברשימה Service Accounts, לוחצים על חשבון השירות
fluentd-gcs-writer. - עוברים לכרטיסייה Keys.
- לוחצים על Add Key > Create new key.
- בוחרים באפשרות JSON בתור סוג המפתח.
- לוחצים על יצירה.
- שומרים את קובץ מפתח ה-JSON שהורדתם במקום בטוח. הקובץ הזה ישמש בשלב הבא.
יצירת סוד של Kubernetes בשביל המפתח של חשבון השירות
יוצרים סוד של Kubernetes שמכיל את המפתח של חשבון השירות ב-GCP במרחב השמות שבו יופעל Fluentd:
kubectl create namespace logging kubectl create secret generic fluentd-gcs-key \ --from-file=service-account-key.json=<path-to-downloaded-key>.json \ -n logging
פריסת Fluentd DaemonSet לאיסוף יומנים של OAuth2 Proxy
פורסים את Fluentd כ-DaemonSet באשכול Kubernetes כדי לאסוף יומני קונטיינרים מתרמילי OAuth2 Proxy ולהעביר אותם לדלי GCS.
יצירת ConfigMap של Fluentd
יוצרים קובץ בשם
fluentd-configmap.yamlעם התוכן הבא:apiVersion: v1 kind: ConfigMap metadata: name: fluentd-gcs-config namespace: logging data: fluent.conf: | <source> @type tail read_from_head true tag kubernetes.* path /var/log/containers/*oauth2-proxy*.log pos_file /var/log/fluentd-oauth2-proxy.log.pos <parse> @type regexp expression /^(?<time>[^ ]+) (?<stream>stdout|stderr) [^ ]* (?<log>.*)$/ time_format %Y-%m-%dT%H:%M:%S.%N%z </parse> </source> <filter kubernetes.**> @type kubernetes_metadata @id filter_kube_metadata </filter> <match kubernetes.**> @type gcs project YOUR_GCP_PROJECT_ID keyfile /etc/secrets/service-account-key.json bucket oauth2-proxy-logs-bucket path oauth2-proxy-logs/%Y/%m/%d/ object_key_format %{path}%{time_slice}_%{hostname}_%{index}.%{file_extension} <buffer tag,time> @type file path /var/log/fluentd/gcs timekey 300 timekey_wait 60 timekey_use_utc true chunk_limit_size 10MB </buffer> <format> @type json </format> </match>
מחליפים את הערכים הבאים:
-
YOUR_GCP_PROJECT_ID: מזהה הפרויקט ב-GCP (לדוגמה,my-project-123456)
oauth2-proxy-logs-bucket: השם של קטגוריית GCS שנוצרה קודםמחילים את ה-ConfigMap:
kubectl apply -f fluentd-configmap.yaml
יצירת Fluentd DaemonSet
יוצרים קובץ בשם
fluentd-daemonset.yamlעם התוכן הבא:apiVersion: apps/v1 kind: DaemonSet metadata: name: fluentd-gcs namespace: logging labels: k8s-app: fluentd-gcs spec: selector: matchLabels: k8s-app: fluentd-gcs template: metadata: labels: k8s-app: fluentd-gcs spec: tolerations: - key: node-role.kubernetes.io/control-plane effect: NoSchedule - key: node-role.kubernetes.io/master effect: NoSchedule containers: - name: fluentd-gcs image: fluent/fluentd-kubernetes-daemonset:v1-debian-gcs resources: limits: memory: 512Mi requests: cpu: 100m memory: 200Mi volumeMounts: - name: fluentd-gcs-config-volume mountPath: /fluentd/etc/fluent.conf subPath: fluent.conf readOnly: true - name: fluentd-gcs-secrets-volume mountPath: /etc/secrets/service-account-key.json subPath: service-account-key.json readOnly: true - name: varlog mountPath: /var/log - name: dockercontainerlogdirectory mountPath: /var/log/pods readOnly: true - name: fluentd-buffer mountPath: /var/log/fluentd terminationGracePeriodSeconds: 30 volumes: - name: fluentd-gcs-config-volume configMap: name: fluentd-gcs-config - name: fluentd-gcs-secrets-volume secret: secretName: fluentd-gcs-key - name: varlog hostPath: path: /var/log - name: dockercontainerlogdirectory hostPath: path: /var/log/pods - name: fluentd-buffer emptyDir: {}מחילים את DaemonSet:
kubectl apply -f fluentd-daemonset.yaml
אימות הפריסה של Fluentd
מוודאים שרכיבי ה-pod של Fluentd פועלים בכל צומת:
kubectl get pods -n logging -l k8s-app=fluentd-gcsבודקים ביומנים של Fluentd אם הכתיבה ל-GCS הצליחה:
kubectl logs -l k8s-app=fluentd-gcs -n logging --tail=20מוודאים שקבצי היומן מופיעים בקטגוריה GCS:
- נכנסים אל Cloud Storage > Buckets במסוף GCP.
- לוחצים על ה-bucket (לדוגמה,
oauth2-proxy-logs-bucket). - עוברים לתיקייה
oauth2-proxy-logs/. - בודקים שקיימים קובצי
.jsonעם חותמות זמן עדכניות.
אחזור חשבון השירות של Google SecOps
Google SecOps משתמש בחשבון שירות ייחודי כדי לקרוא נתונים מהקטגוריה של GCS. צריך להעניק לחשבון השירות הזה גישה לדלי.
קבלת כתובת האימייל בחשבון השירות
- עוברים אל SIEM Settings > Feeds (הגדרות SIEM > פידים).
- לוחצים על הוספת פיד חדש.
- לוחצים על Configure a single feed (הגדרת פיד יחיד).
- בשדה שם הפיד, מזינים שם לפיד (לדוגמה,
OAuth2 Proxy Logs). - בוחרים באפשרות Google Cloud Storage V2 בתור סוג המקור.
- בוחרים באפשרות Kubernetes Auth Proxy בתור סוג היומן.
לוחצים על קבלת חשבון שירות. תוצג כתובת האימייל בחשבון שירות ייחודי, לדוגמה:
chronicle-12345678@chronicle-gcp-prod.iam.gserviceaccount.comמעתיקים את כתובת האימייל הזו לשימוש בשלב הבא.
לוחצים על הבא.
מציינים ערכים לפרמטרים הבאים של הקלט:
כתובת ה-URL של קטגוריית האחסון: מזינים את ה-URI של קטגוריית GCS עם נתיב הקידומת:
gs://oauth2-proxy-logs-bucket/oauth2-proxy-logs/מחליפים את
oauth2-proxy-logs-bucketבשם של קטגוריית GCS.
אפשרות למחיקת המקור: בוחרים את אפשרות המחיקה לפי ההעדפה שלכם:
- אף פעם: לא נמחקים קבצים אחרי ההעברה (מומלץ לבדיקות).
- מחיקת קבצים שהועברו: הקבצים נמחקים אחרי שההעברה מסתיימת.
מחיקת קבצים שהועברו וספריות ריקות: מחיקת קבצים וספריות ריקות אחרי שההעברה מסתיימת בהצלחה.
הגיל המקסימלי של הקובץ: כולל קבצים ששונו במספר הימים האחרון (ברירת המחדל היא 180 ימים)
מרחב השמות של הנכס: מרחב השמות של הנכס
תוויות להוספה: התווית שתתווסף לאירועים מהפיד הזה
לוחצים על הבא.
בודקים את ההגדרות של הפיד החדש במסך סיום ולוחצים על שליחה.
הענקת הרשאות IAM לחשבון השירות של Google SecOps
לחשבון השירות של Google SecOps צריך להיות התפקיד צפייה באובייקט אחסון בקטגוריה של GCS.
- עוברים אל Cloud Storage > Buckets (קטגוריות).
- לוחצים על שם הקטגוריה (לדוגמה,
oauth2-proxy-logs-bucket). - עוברים לכרטיסייה Permissions.
- לוחצים על הענקת גישה.
- מספקים את פרטי ההגדרה הבאים:
- הוספת ישויות מורשות: מדביקים את כתובת האימייל בחשבון השירות של Google SecOps (לדוגמה,
chronicle-12345678@chronicle-gcp-prod.iam.gserviceaccount.com) - הקצאת תפקידים: בוחרים באפשרות צפייה באובייקטים באחסון.
- הוספת ישויות מורשות: מדביקים את כתובת האימייל בחשבון השירות של Google SecOps (לדוגמה,
- לוחצים על Save.
הפניה ליומן של OAuth2 Proxy
בטבלה הבאה מתוארים שדות היומן שנוצרים על ידי OAuth2 Proxy והרלוונטיות שלהם לאבטחה:
שדות ביומן האימות
| שדה | דוגמה | תיאור |
|---|---|---|
| לקוח | 10.0.0.1 |
כתובת ה-IP של הלקוח או של המחשב המרוחק (משתמש בכותרת X-Real-IP אם --reverse-proxy=true) |
| RequestID | 00010203-0405-4607-8809-0a0b0c0d0e0f |
מזהה הבקשה מהכותרת X-Request-Id (מזהה ייחודי אוניברסלי (UUID) אקראי אם הכותרת ריקה) |
| שם משתמש | user@example.com |
כתובת האימייל או שם המשתמש של בקשת האימות |
| חותמת הזמן | 2024/01/15 10:30:00 |
התאריך והשעה של אירוע האימות |
| סטטוס | AuthSuccess |
תוצאת האימות: AuthSuccess, AuthFailure או AuthError |
| הודעה | Authenticated via OAuth2 |
פרטים על ניסיון האימות |
שדות ביומן הבקשות
| שדה | דוגמה | תיאור |
|---|---|---|
| לקוח | 10.0.0.1 |
כתובת ה-IP של הלקוח או של המחשב המרוחק |
| RequestID | 00010203-0405-4607-8809-0a0b0c0d0e0f |
מזהה בקשה |
| שם משתמש | user@example.com |
כתובת האימייל של המשתמש המאומת |
| חותמת הזמן | 2024/01/15 10:30:01 |
התאריך והשעה של שליחת הבקשה |
| מארח | app.example.com |
הערך של כותרת המארח |
| RequestMethod | GET |
שיטת בקשת HTTP |
| Upstream | 10.0.0.5:8080 |
השרת במעלה הזרם שטיפל בבקשה |
| RequestURI | /dashboard |
נתיב ה-URI של הבקשה |
| פרוטוקול | HTTP/1.1 |
פרוטוקול הבקשה |
| UserAgent | Mozilla/5.0 |
תיאור מלא של סוכן המשתמש |
| StatusCode | 200 |
קוד הסטטוס של תגובת HTTP |
| ResponseSize | 1234 |
גודל התשובה בבייטים |
| RequestDuration | 0.005 |
זמן עיבוד הבקשה בשניות |
טבלת מיפוי UDM
| שדה ביומן | מיפוי UDM | היגיון |
|---|---|---|
| מידע כללי | מידע כללי | מידע על האירוע |
| http_req_id_field | additional.fields | שדות נוספים שלא נכללים בסכימת UDM רגילה |
| http_req_path_field | additional.fields | |
| k8s_pod_app_field | additional.fields | |
| k8s_pod_template_hash_field | additional.fields | |
| k8s_pod_tls_mode_field | additional.fields | |
| k8s_pod_canonical_revision_field | additional.fields | |
| k8s_pod_canonical_name_field | additional.fields | |
| pod_name | additional.fields | |
| cntnr_name | additional.fields | |
| destination_canonical_revision | additional.fields | |
| requested_server | additional.fields | |
| nodename_label | additional.fields | |
| componentName_label | additional.fields | |
| componentVersion_label | additional.fields | |
| azureResourceID_label | additional.fields | |
| producer_label | additional.fields | |
| first_label | additional.fields | |
| last_label | additional.fields | |
| meta_name | additional.fields | |
| resource_version_label | additional.fields | |
| request_apiVersion | additional.fields | |
| request_kind_label | additional.fields | |
| request_type_label | additional.fields | |
| response_apiVersion | additional.fields | |
| response_kind_label | additional.fields | |
| response_type_label | additional.fields | |
| jsonPayload.message | metadata.description | תיאור האירוע |
| event_type | metadata.event_type | סוג האירוע |
| labels.request_id | metadata.product_log_id | מזהה יומן ספציפי למוצר |
| insertId | metadata.product_log_id | |
| jsonPayload.chartVersion | metadata.product_version | גרסת המוצר |
| httpRequest.protocol | network.application_protocol | פרוטוקול האפליקציה שנעשה בו שימוש בחיבור לרשת |
| network.direction | network.direction | כיוון התנועה ברשת |
| httpRequest.requestMethod | network.http.method | שיטת HTTP |
| http_method | network.http.method | |
| httpRequest.status | network.http.response_code | קוד תגובת HTTP |
| httpRequest.userAgent | network.http.user_agent | סוכן משתמש ב-HTTP |
| requestMetadata.callerSuppliedUserAgent | network.http.user_agent | |
| labels.protocol | network.ip_protocol | פרוטוקול IP |
| httpRequest.responseSize | network.received_bytes | מספר הבייטים שהתקבלו |
| labels.total_received_bytes | network.received_bytes | |
| httpRequest.requestSize | network.sent_bytes | מספר הבייטים שנשלחו |
| labels.total_sent_bytes | network.sent_bytes | |
| jsonPayload.session | network.session_id | מזהה הסשן |
| labels.service_authentication_policy | network.tls.cipher | סט אלגוריתמים להצפנה (cipher suite) ב-TLS |
| משתמש (principal) | משתמש (principal) | הישות העיקרית שמעורבת באירוע |
| principal_hostname | principal.hostname | שם המארח של הישות המורשית |
| prin_userid | principal.user.userid | מזהה המשתמש של הישות המורשית |
| security_result | security_result | תוצאה של הערכת אבטחה |
| target | target | ישות היעד שמעורבת באירוע |
| target_hostname | target.hostname | שם המארח של היעד |
| resource_sub_type | target.resource.resource_subtype | סוג המשנה של משאב היעד |
| target_userid | target.user.userid | מזהה המשתמש של היעד |
| metadata.product_name | metadata.product_name | שם המוצר |
| metadata.vendor_name | metadata.vendor_name | שם הספק |
יומן שינויים
צפייה ביומן השינויים של מנתח התוכן הזה
הבעיה עדיין לא נפתרה? קבלת תשובות מחברי הקהילה וממומחי Google SecOps.