איסוף יומנים של BeyondTrust Endpoint Privilege Management (EPM)
במאמר הזה מוסבר איך להטמיע יומנים של BeyondTrust Endpoint Privilege Management (EPM) ב-Google Security Operations באמצעות Cloud Storage. הכלי לניתוח מתמקד בהמרת נתוני יומן גולמיים בפורמט JSON מ-BeyondTrust Endpoint לפורמט מובנה שתואם ל-UDM של Google SecOps. הוא מאתחל קודם ערכי ברירת מחדל לשדות שונים, ואז מנתח את מטען הנתונים (payload) בפורמט JSON, ולאחר מכן ממפה שדות ספציפיים מהיומן הגולמי לשדות UDM תואמים באובייקט event.idm.read_only_udm.
לפני שמתחילים
חשוב לוודא שמתקיימות הדרישות המוקדמות הבאות:
- מופע של Google SecOps
- פרויקט ב- Google Cloud עם Cloud Storage API מופעל
- הרשאות ליצירה ולניהול של קטגוריות ב-Cloud Storage
- הרשאות לניהול כללי מדיניות של ניהול זהויות והרשאות גישה (IAM) בדליים של Cloud Storage
- הרשאות ליצירת שירותי פונקציות של Cloud Run, נושאי Pub/Sub ומשימות של Cloud Scheduler
- גישת אדמין לדייר או ל-API של BeyondTrust Endpoint Privilege Management
יצירת קטגוריה של Cloud Storage
- עוברים אל Google Cloud המסוף.
- בוחרים פרויקט או יוצרים פרויקט חדש.
- בתפריט הניווט, עוברים אל Cloud Storage > Buckets.
- לוחצים על Create bucket.
מספקים את פרטי ההגדרה הבאים:
הגדרה ערך Name your bucket (שם הקטגוריה) מזינים שם ייחודי גלובלית (לדוגמה, beyondtrust-epm-logs)סוג המיקום בוחרים לפי הצרכים (אזור, שני אזורים, מספר אזורים) Location בוחרים את המיקום (לדוגמה, us-central1).סוג אחסון (storage class) רגיל (מומלץ ליומנים שמתבצעת אליהם גישה לעיתים קרובות) בקרת גישה אחיד (מומלץ) כלים להגנה אופציונלי: הפעלת ניהול גרסאות של אובייקטים או מדיניות שמירת נתונים לוחצים על יצירה.
איסוף פרטי הכניסה ל-API של BeyondTrust EPM
- נכנסים למסוף האינטרנט של BeyondTrust Privilege Management כאדמינים.
- עוברים אל הגדרות > הגדרות > הגדרות API.
- לוחצים על Create an API Account (יצירת חשבון API).
- מספקים את פרטי ההגדרה הבאים:
- שם: מזינים
Google SecOps Collector. - גישה ל-API: מפעילים את האפשרות דיווח (קריאה בלבד).
/management-api/v3/Events/FromStartDateנקודת הקצה שבה נעשה שימוש על ידי כלי האיסוף הזה שייכת לקטגוריית ההרשאות דיווח. מפעילים גם את ביקורת (קריאה בלבד) רק אם רוצים לאסוף גם נתוני ביקורת על פעילות במסוף האינטרנט.
- שם: מזינים
- מעתיקים את מזהה הלקוח ואת הסוד של הלקוח ושומרים אותם.
מעתיקים את כתובת הבסיס של ה-API שמופיעה בחלק העליון של הדף API Settings (הגדרות API). בדרך כלל זה הנתיב
https://<your-tenant>-services.pm.beyondtrustcloud.com. השימוש שלך יהיה כ-BPT_API_URL.
יצירת חשבון שירות לפונקציית Cloud Run
פונקציית Cloud Run צריכה חשבון שירות עם הרשאות כתיבה לקטגוריה של Cloud Storage והרשאות הפעלה על ידי Pub/Sub.
יצירת חשבון השירות
- במסוף Google Cloud , נכנסים אל IAM & Admin > Service Accounts.
- לוחצים על יצירת חשבון שירות.
- מספקים את פרטי ההגדרה הבאים:
- שם חשבון השירות: מזינים
beyondtrust-epm-collector-sa. - תיאור חשבון השירות: מזינים
Service account for Cloud Run function to collect BeyondTrust EPM logs.
- שם חשבון השירות: מזינים
- לוחצים על יצירה והמשך.
- בקטע Grant this service account access to project (מתן גישה של חשבון השירות הזה לפרויקט), מוסיפים את התפקידים הבאים:
- לוחצים על בחירת תפקיד.
- מחפשים את Storage Object Admin ולוחצים עליו.
- לוחצים על + הוספת תפקיד נוסף.
- מחפשים את האפשרות Cloud Run Invoker (הפעלת שירותים ב-Cloud Run) ולוחצים עליה.
- לוחצים על + הוספת תפקיד נוסף.
- מחפשים את האפשרות Cloud Functions Invoker (הפעלת פונקציות של Cloud Functions) ולוחצים עליה.
- לוחצים על Continue.
- לוחצים על סיום.
התפקידים האלה נדרשים כדי:
- Storage Object Admin: כתיבת יומנים לקטגוריה של Cloud Storage וניהול קובצי מצב
- Cloud Run Invoker: מאפשר ל-Pub/Sub להפעיל את הפונקציה
- Cloud Functions Invoker: מאפשר הפעלה של פונקציות
הענקת הרשאות IAM בקטגוריה של Cloud Storage
נותנים לחשבון השירות הרשאות כתיבה בקטגוריה של Cloud Storage:
- עוברים אל Cloud Storage > Buckets (קטגוריות).
- לוחצים על שם הקטגוריה.
- עוברים לכרטיסייה Permissions.
- לוחצים על הענקת גישה.
- מספקים את פרטי ההגדרה הבאים:
- Add principals (הוספת ישויות מורשות): מזינים את כתובת האימייל בחשבון השירות. לדוגמה,
beyondtrust-epm-collector-sa@PROJECT_ID.iam.gserviceaccount.com. - הקצאת תפקידים: בוחרים באפשרות אדמין של אובייקטים באחסון.
- Add principals (הוספת ישויות מורשות): מזינים את כתובת האימייל בחשבון השירות. לדוגמה,
- לוחצים על Save.
יוצרים נושא Pub/Sub
יוצרים נושא Pub/Sub ש-Cloud Scheduler יפרסם בו ופונקציית Cloud Run תירשם אליו.
- בGoogle Cloud מסוף, עוברים אל Pub/Sub > Topics.
- לוחצים על יצירת נושא.
- מספקים את פרטי ההגדרה הבאים:
- מזהה הנושא: מזינים
beyondtrust-epm-trigger. - משאירים את שאר ההגדרות כברירת מחדל.
- מזהה הנושא: מזינים
- לוחצים על יצירה.
יצירת פונקציית Cloud Run לאיסוף יומנים
הפונקציה של Cloud Run מופעלת על ידי הודעות Pub/Sub מ-Cloud Scheduler כדי לאחזר יומנים מ-BeyondTrust EPM API ולכתוב אותם ב-Cloud Storage.
- במסוף Google Cloud , עוברים אל Cloud Run.
- לוחצים על יצירת שירות.
- בוחרים באפשרות פונקציה. משתמשים בעורך הישיר כדי ליצור פונקציה.
בקטע Configure (הגדרה), מזינים את פרטי ההגדרה הבאים:
הגדרה ערך שם השירות beyondtrust-epm-collectorאזור בוחרים את האזור שתואם לקטגוריה של Cloud Storage (לדוגמה, us-central1)זמן ריצה בוחרים באפשרות Python 3.12 ואילך. בקטע טריגר (אופציונלי):
- לוחצים על + הוספת טריגר.
- בוחרים באפשרות Cloud Pub/Sub.
- בקטע Select a Cloud Pub/Sub topic, בוחרים את הנושא
beyondtrust-epm-trigger. - לוחצים על Save.
בקטע Authentication:
- בוחרים באפשרות דרישת אימות.
- בודקים את ניהול זהויות והרשאות גישה (IAM).
עוברים אל Containers, Networking, Security (מאגרי מידע, רשת, אבטחה) ומרחיבים אותו.
עוברים לכרטיסייה אבטחה:
- חשבון שירות: בוחרים את חשבון השירות
beyondtrust-epm-collector-sa.
- חשבון שירות: בוחרים את חשבון השירות
עוברים לכרטיסייה מאגרי תגים:
- לוחצים על Variables & Secrets (משתנים וסודות).
- לוחצים על + הוספת משתנה לכל משתנה סביבתי:
שם המשתנה ערך לדוגמה GCS_BUCKETbeyondtrust-epm-logsGCS_PREFIXbeyondtrust-epm/STATE_KEYbeyondtrust-epm-state.jsonBPT_API_URLhttps://yourtenant-services.pm.beyondtrustcloud.comCLIENT_IDyour-client-idCLIENT_SECRETyour-client-secretRECORD_SIZE1000MAX_BATCHES50LOOKBACK_HOURS24-
RECORD_SIZE: רשומות לכל בקשה. /Events/FromStartDateמקבל ערכים מ-1 עד 1,000. הפונקציה מגבילה ערכים גבוהים יותר. -
MAX_BATCHES: מגבלת בטיחות על בקשות בכל הרצה. אם הפעלה מגיעה לשם, הסמן עדיין מתקדם למה שנכתב, כך שההפעלה הבאה ממשיכה משם ולא חוזרת על העבודה. -
LOOKBACK_HOURS: כמה זמן אחורה מגיעה ההרצה הראשונה. BeyondTrust שומרת אירועים פסיביים (קודים 106, 107, 603, 706) למשך 30 ימים, ואת כל שאר האירועים למשך 90 ימים, כך שחיפוש עם חלון מבט לאחור ארוך יותר לא יחזיר תוצאות ישנות יותר. -
LOOKBACK_HOURS: כמה זמן אחורה צריך להגיע בהרצה הראשונה, לפני שנוצר סמן.
בכרטיסייה Variables & Secrets (משתנים וסודות), עוברים אל Requests (בקשות):
- זמן קצוב לתפוגת בקשה: מזינים
600שניות (10 דקות).
- זמן קצוב לתפוגת בקשה: מזינים
עוברים לכרטיסייה הגדרות במאגרי תגים:
- בקטע Resources (מקורות מידע):
- זיכרון: בוחרים באפשרות 512 MiB או יותר.
- מעבד: בוחרים באפשרות 1.
- לוחצים על סיום.
- בקטע Resources (מקורות מידע):
גוללים אל סביבת הפעלה:
- בוחרים באפשרות ברירת מחדל (מומלץ).
בקטע שינוי קנה מידה של העריכה:
- מספר המינימום של מופעים: מזינים
0. - מספר מקסימלי של מופעים: מזינים
100(או משנים בהתאם לעומס הצפוי).
- מספר המינימום של מופעים: מזינים
לוחצים על יצירה.
ממתינים ליצירת השירות (1-2 דקות).
אחרי שיוצרים את השירות, עורך הקוד המוטבע נפתח באופן אוטומטי.
הוספת קוד פונקציה
- מזינים main בFunction entry point
בעורך הקוד המוטבע, יוצרים שני קבצים:
- הקובץ הראשון – main.py:
import hashlib import json import os import re import time import urllib3 import uuid from datetime import datetime, timedelta, timezone from urllib.parse import urlencode import functions_framework from google.cloud import storage from google.cloud.exceptions import NotFound # Initialize HTTP client with timeouts http = urllib3.PoolManager( timeout=urllib3.Timeout(connect=5.0, read=60.0), retries=False, ) # Initialize Storage client storage_client = storage.Client() TOKEN_PATH = '/oauth/connect/token' # Events/FromStartDate is the endpoint BeyondTrust documents for bulk extraction: # "A new API is exposed to extract the events in bulk." It takes only StartDate and # RecordSize. Events/search is not usable here: it requires an OperatingSystem value, # it is a single string with no documented "all" value, so it can only ever return one # operating system per call. EVENTS_PATH = '/management-api/v3/Events/FromStartDate' OAUTH_SCOPE = 'urn:management:api' # RecordSize accepts 1 to 1000. MAX_RECORD_SIZE = 1000 class FetchError(Exception): """Raised when the BeyondTrust API call fails. The cursor must never advance on a failed fetch, otherwise the events in the failed window are skipped permanently. """ def rfc3339(dt: datetime) -> str: """Render a datetime the way StartDate accepts it: milliseconds and a literal Z.""" return dt.astimezone(timezone.utc).strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3] + 'Z' def parse_iso(stamp: str) -> datetime: """Parse any ISO 8601 timestamp shape the API emits. event.ingested comes back with a +00:00 offset and up to seven fractional digits, e.g. 2026-08-03T13:29:55.1109163+00:00. fromisoformat's tolerance for long fractions varies across Python versions, so the fraction is trimmed to microseconds first. """ text = stamp.strip() if text.endswith('Z'): text = text[:-1] + '+00:00' text = re.sub(r'\.(\d{1,6})\d*', r'.\1', text, count=1) parsed = datetime.fromisoformat(text) if parsed.tzinfo is None: parsed = parsed.replace(tzinfo=timezone.utc) return parsed def canonical(stamp: str) -> str: """Re-render an API timestamp into the one form StartDate accepts. The API returns event.ingested as +00:00-offset ISO 8601 but rejects that same shape as a StartDate value with 400 "Invalid Start date format": it accepts only Z-suffixed values. Every timestamp that came from the API must pass through here before being sent back or persisted. """ return rfc3339(parse_iso(stamp)) def event_id(evt: dict) -> str: """Return the event's identity for deduplication. Falls back to a content hash when event.id is absent, so an id-less event still deduplicates instead of being re-ingested on every boundary re-read. """ explicit = str((evt.get('event') or {}).get('id') or '') if explicit: return explicit digest = hashlib.sha256( json.dumps(evt, sort_keys=True, ensure_ascii=False).encode('utf-8') ).hexdigest() return f'sha256:{digest}' def event_ingested(evt: dict) -> str: """Return the Elastic ingestion timestamp, which is what StartDate filters on.""" return str((evt.get('event') or {}).get('ingested') or '') def next_millisecond(stamp: str) -> str: """Return the canonical timestamp one millisecond later. Used only when a full batch fits inside a single ingestion millisecond. Without this the cursor cannot move and the collector stalls on that timestamp forever. """ return rfc3339(parse_iso(stamp) + timedelta(milliseconds=1)) @functions_framework.cloud_event def main(cloud_event): """Fetch BeyondTrust EPM events and write them to Cloud Storage as NDJSON. Args: cloud_event: CloudEvent object containing the Pub/Sub message. """ bucket_name = os.environ.get('GCS_BUCKET') prefix = os.environ.get('GCS_PREFIX', 'beyondtrust-epm/') state_key = os.environ.get('STATE_KEY', 'beyondtrust-epm-state.json') api_url = (os.environ.get('BPT_API_URL') or '').rstrip('/') client_id = os.environ.get('CLIENT_ID') client_secret = os.environ.get('CLIENT_SECRET') # Clamp both ends: the API rejects RecordSize outside 1 to 1000, and a # misconfigured 0 would otherwise crash-loop on a 400 every run. record_size = min(max(int(os.environ.get('RECORD_SIZE', '1000')), 1), MAX_RECORD_SIZE) max_batches = int(os.environ.get('MAX_BATCHES', '50')) lookback_hours = int(os.environ.get('LOOKBACK_HOURS', '24')) if not all([bucket_name, api_url, client_id, client_secret]): raise RuntimeError( 'Missing required environment variables: ' 'GCS_BUCKET, BPT_API_URL, CLIENT_ID, CLIENT_SECRET' ) bucket = storage_client.bucket(bucket_name) state = load_state(bucket, state_key) # StartDate is a rising cursor on the Elastic ingestion timestamp, not a closed # window. There is no EndDate on this endpoint. if state.get('last_ingested'): # canonical() also repairs state written by the previous script revision, # which persisted the API's raw +00:00 form that StartDate rejects. try: start_date = canonical(state['last_ingested']) except (ValueError, TypeError, AttributeError) as e: raise RuntimeError( f'Unparseable last_ingested in gs://{bucket_name}/{state_key}: ' f'{state["last_ingested"]!r}. Fix or delete that object; deleting ' f'restarts collection from the lookback window.' ) from e else: start_date = rfc3339(datetime.now(timezone.utc) - timedelta(hours=lookback_hours)) seen_ids = set(state.get('seen_ids', [])) print(f'Collecting events ingested from {start_date}') token = get_oauth_token(api_url, client_id, client_secret) fresh, cursor, drained = fetch_events( api_url, token, start_date, record_size, max_batches, seen_ids ) if not fresh: print('No new events. Cursor left unchanged.') return # Everything that can fail is computed before the upload: a crash between the # upload and save_state replays the batch on the next run, so the window where # side effects exist without recorded state must stay minimal. The retained ids # share the cursor millisecond, because those are exactly the ones the # inclusive StartDate will return again. retained = { event_id(e) for e in fresh if event_ingested(e) and canonical(event_ingested(e)) == cursor } if cursor == start_date: # The cursor millisecond did not advance, so ids retained by earlier runs # are still on the boundary; dropping them would re-ingest their events. retained |= seen_ids timestamp = datetime.now(timezone.utc).strftime('%Y%m%dT%H%M%SZ') # The random suffix keeps concurrent executions (Pub/Sub delivers at least # once) from overwriting each other's object within the same second. filename = ( f'{prefix}beyondtrust-epm-events-{timestamp}-' f'{uuid.uuid4().hex[:8]}.ndjson' ) ndjson = '\n'.join(json.dumps(e, ensure_ascii=False) for e in fresh) + '\n' bucket.blob(filename).upload_from_string( ndjson, content_type='application/x-ndjson' ) print(f'Wrote {len(fresh)} events to gs://{bucket_name}/{filename}') save_state(bucket, state_key, { 'last_ingested': cursor, 'seen_ids': sorted(retained), 'updated_at': rfc3339(datetime.now(timezone.utc)), }) if not drained: print( f'Stopped after {max_batches} batches with more events available. ' 'The cursor advanced, so the next run continues from here.' ) def fetch_events(api_url, token, start_date, record_size, max_batches, seen_ids): """Read forward from start_date until the API returns a short batch. The response envelope has no page count and no next-page token, so a batch shorter than record_size is the only documented end-of-data signal. StartDate is inclusive and the cursor lands on the newest event's timestamp, so every batch re-returns the events sharing it. Deduplication by event id therefore has to happen as batches arrive, not only between runs. Returns: Tuple of (new events, cursor to persist, whether the stream was drained). Raises: FetchError: on any API or transport failure, so the caller cannot mistake a failed fetch for an empty result and advance the cursor. """ headers = { 'Authorization': f'Bearer {token}', 'Accept': 'application/json', } all_events = [] seen = set(seen_ids) cursor = start_date for batch in range(1, max_batches + 1): query = urlencode({'StartDate': cursor, 'RecordSize': record_size}) body = request_with_retry(f'{api_url}{EVENTS_PATH}?{query}', headers) events = body.get('events') or [] new = [e for e in events if event_id(e) not in seen] seen.update(event_id(e) for e in events if event_id(e)) all_events.extend(new) print(f'Batch {batch}: {len(events)} events, {len(new)} new') # Termination is judged on what the API returned, not on what survived # deduplication: a batch can be full and still be entirely duplicates. # The newest stamp in the batch, not events[-1]: ordering is a client-side # convention, not a documented guarantee, and a misordered tail would # regress the cursor and re-ingest events already written. stamps = [canonical(s) for s in (event_ingested(e) for e in events) if s] if len(events) < record_size: # Short batch: the stream is drained. if events and not stamps: print( 'Warning: no event in the final batch carries event.ingested; ' 'cursor left unchanged.' ) if stamps: cursor = max(max(stamps), cursor) return all_events, cursor, True if not stamps: raise FetchError( 'No event in a full batch carries event.ingested, ' 'so the cursor cannot advance' ) batch_max = max(stamps) if batch_max <= cursor: # A full batch fits inside one ingestion millisecond. Stepping past it is the only # way to make progress; holding the cursor here stalls collection forever. # Events beyond record_size at that exact millisecond are unreachable, which # needs more than 1000 events in one millisecond. print( f'Warning: a full batch shares ingestion timestamp {batch_max}; ' 'stepping past it. Events beyond RecordSize at that timestamp are skipped.' ) cursor = next_millisecond(cursor) else: cursor = batch_max return all_events, cursor, False def request_with_retry(url, headers, attempts=4): """GET with backoff on 429 and 5xx. The documented rate limit is 1000 requests per 100 seconds. """ backoff = 1.0 for attempt in range(1, attempts + 1): try: response = http.request('GET', url, headers=headers) except Exception as e: raise FetchError(f'Request to {url} failed: {e}') from e if response.status in (429, 500, 502, 503, 504) and attempt < attempts: retry_after = response.headers.get('Retry-After') try: delay = int(retry_after) if retry_after else backoff except (TypeError, ValueError): delay = backoff # Retry-After is server-controlled input: bound it so a bogus value # cannot sleep past the function timeout or crash time.sleep. delay = min(max(delay, 1.0), 60.0) print(f'HTTP {response.status}. Retrying in {delay}s...') time.sleep(delay) backoff = min(backoff * 2, 30.0) continue if response.status != 200: raise FetchError( f'Request failed: {response.status} {response.data.decode("utf-8")}' ) text = response.data.decode('utf-8') # BeyondTrust's sample folds "Owner" into "owner" before parsing, but that # workaround exists for PowerShell's case-insensitive ConvertFrom-Json. # Python parses case-sensitively, and the SecOps parser maps file.Owner.* # and file.owner to different UDM fields, so both keys must survive. try: return json.loads(text) except json.JSONDecodeError as e: raise FetchError(f'Malformed JSON response: {e}') from e raise FetchError(f'Giving up on {url} after {attempts} attempts') def get_oauth_token(api_url, client_id, client_secret): """Get an access token using the OAuth client credentials flow. The token is valid for one hour. """ body = urlencode({ 'grant_type': 'client_credentials', 'client_id': client_id, 'client_secret': client_secret, 'scope': OAUTH_SCOPE, }) response = http.request( 'POST', f'{api_url}{TOKEN_PATH}', body=body, headers={'Content-Type': 'application/x-www-form-urlencoded'}, ) if response.status != 200: raise FetchError( f'Token request failed: {response.status} ' f'{response.data.decode("utf-8")}' ) return json.loads(response.data.decode('utf-8'))['access_token'] def load_state(bucket, key): """Read the collector state from Cloud Storage. Only a missing object is a cold start. Any other error is raised: swallowing it would reset collection to the lookback window and re-ingest that period. """ blob = bucket.blob(key) try: return json.loads(blob.download_as_text()) except NotFound: print('No state file found. Starting from the lookback window.') return {} def save_state(bucket, key, state): """Write the collector state to Cloud Storage. Failures are raised, not logged. A run that cannot record its cursor must fail, otherwise the next run repeats the same window. """ bucket.blob(key).upload_from_string( json.dumps(state, indent=2), content_type='application/json' ) print(f"Saved state: last_ingested={state.get('last_ingested')}")- הקובץ השני: requirements.txt:
functions-framework==3.* google-cloud-storage==2.* urllib3>=2.0.0לוחצים על Deploy כדי לשמור ולפרוס את הפונקציה.
ממתינים לסיום הפריסה (2-3 דקות).
יצירת משימה ב-Cloud Scheduler
Cloud Scheduler מפרסם הודעות בנושא Pub/Sub במרווחי זמן קבועים, וכך מפעיל את הפונקציה של Cloud Run.
- נכנסים אל Cloud Scheduler במסוף Google Cloud .
- לוחצים על Create Job (יצירת משימה).
מספקים את פרטי ההגדרה הבאים:
הגדרה ערך שם beyondtrust-epm-collector-hourlyאזור בוחרים את אותו אזור כמו פונקציית Cloud Run תדירות 0 * * * *(כל שעה, בדיוק בשעה)אזור זמן בחירת אזור זמן (מומלץ לבחור UTC) סוג הטירגוט Pub/Sub נושא בחירת הנושא beyondtrust-epm-triggerגוף ההודעה {}(אובייקט JSON ריק)לוחצים על יצירה.
אפשרויות לתדירות התזמון
בחירת התדירות בהתאם לנפח היומן ולדרישות זמן האחזור:
| תדירות | ביטוי Cron | תרחיש לדוגמה |
|---|---|---|
| כל 5 דקות | */5 * * * * |
נפח גבוה, זמן אחזור נמוך |
| כל 15 דקות | */15 * * * * |
נפח חיפושים בינוני |
| כל שעה | 0 * * * * |
רגיל (מומלץ) |
| כל 6 שעות | 0 */6 * * * |
נפח נמוך, עיבוד באצווה |
| יומי | 0 0 * * * |
איסוף נתונים היסטוריים |
בדיקת משימת המתזמן
- במסוף Cloud Scheduler, מוצאים את העבודה.
- כדי להפעיל את הטריגר באופן ידני, לוחצים על הפעלה מאולצת.
- מחכים כמה שניות ועוברים אל Cloud Run > Services > beyondtrust-epm-collector > Logs.
- מוודאים שהפונקציה בוצעה בהצלחה.
- בודקים את קטגוריה של Cloud Storage כדי לוודא שהיומנים נכתבו.
אחזור חשבון השירות של Google SecOps
Google SecOps משתמש בחשבון שירות ייחודי כדי לקרוא נתונים מהקטגוריה שלכם ב-Cloud Storage. צריך להעניק לחשבון השירות הזה גישה לדלי.
קבלת כתובת האימייל בחשבון השירות
- עוברים אל SIEM Settings > Feeds (הגדרות SIEM > פידים).
- לוחצים על הוספת פיד חדש.
- לוחצים על Configure a single feed (הגדרת פיד יחיד).
- בשדה שם הפיד, מזינים שם לפיד (לדוגמה,
BeyondTrust EPM logs). - בוחרים באפשרות Google Cloud Storage V2 בתור סוג המקור.
- בוחרים באפשרות BeyondTrust Endpoint Privilege Management בתור סוג היומן.
לוחצים על קבלת חשבון שירות. תוצג כתובת האימייל בחשבון שירות ייחודי, לדוגמה:
chronicle-12345678@chronicle-gcp-prod.iam.gserviceaccount.comמעתיקים את כתובת האימייל הזו לשימוש בשלב הבא.
הענקת הרשאות IAM לחשבון השירות של Google SecOps
לחשבון השירות של Google SecOps צריך להיות מוקצות שתי הרשאות בקטגוריה של Cloud Storage: צפייה באובייקט אחסון כדי לקרוא את אובייקטי היומן, והרשאה ברמת הקטגוריה כדי לקרוא את המטא-נתונים של הקטגוריה.
- עוברים אל Cloud Storage > Buckets (קטגוריות).
- לוחצים על שם הקטגוריה.
- עוברים לכרטיסייה Permissions.
- לוחצים על הענקת גישה.
- מספקים את פרטי ההגדרה הבאים:
- Add principals (הוספת ישויות מורשות): מדביקים את כתובת האימייל בחשבון של חשבון השירות של Google SecOps.
- הקצאת תפקידים: בוחרים את שתי האפשרויות הבאות:
- צפייה באובייקט אחסון: קריאת אובייקטים של יומנים.
- קריאה בקטגוריה באחסון מדור קודם: קריאת המטא-נתונים של הקטגוריה. אם בחרתם באפשרות המחיקה מחיקת קבצים שהועברו וספריות ריקות, במקום זאת בוחרים באפשרות Storage Legacy Bucket Writer, שמעניקה גם את הרשאת המחיקה.
לוחצים על Save.
הגדרה של פיד ב-Google SecOps להטמעה של יומנים מ-BeyondTrust EPM
- עוברים אל SIEM Settings > Feeds (הגדרות SIEM > פידים).
- לוחצים על הוספת פיד חדש.
- לוחצים על Configure a single feed (הגדרת פיד יחיד).
- בשדה שם הפיד, מזינים שם לפיד (לדוגמה,
BeyondTrust EPM logs). - בוחרים באפשרות Google Cloud Storage V2 בתור סוג המקור.
- בוחרים באפשרות BeyondTrust Endpoint Privilege Management בתור סוג היומן.
- לוחצים על הבא.
מציינים ערכים לפרמטרים הבאים של הקלט:
URL של קטגוריה של Cloud Storage: מזינים את ה-URI של קטגוריה של Cloud Storage עם נתיב הקידומת:
gs://beyondtrust-epm-logs/beyondtrust-epm/החלפה:
-
beyondtrust-epm-logs: שם הקטגוריה שלכם ב-Cloud Storage. -
beyondtrust-epm/: קידומת אופציונלית או נתיב לתיקייה שבה מאוחסנים היומנים (אם לא מציינים כלום, היומנים מאוחסנים בתיקיית הבסיס).
-
לדוגמה:
- קטגוריית הבסיס:
gs://beyondtrust-epm-logs/ - עם קידומת:
gs://beyondtrust-epm-logs/beyondtrust-epm/
- קטגוריית הבסיס:
אפשרות למחיקת המקור: בוחרים את אפשרות המחיקה לפי ההעדפה שלכם:
- אף פעם לא למחוק קבצים: אף פעם לא למחוק קבצים מהמקור. מומלץ להשתמש באפשרות הזו למטרות בדיקה.
מחיקת קבצים שהועברו וספריות ריקות: מחיקת קבצים וספריות ריקות מהמקור אחרי השלמת אחזור מוצלח.
הגיל המקסימלי של הקובץ: כולל קבצים שעברו שינוי במספר הימים האחרון. ברירת המחדל היא 180 ימים.
מרחב השמות של הנכס: מרחב השמות של הנכס.
תוויות להוספה: התווית שתתווסף לאירועים מהפיד הזה.
לוחצים על הבא.
בודקים את ההגדרות של הפיד החדש במסך סיום ולוחצים על שליחה.
טבלת מיפוי UDM
| שדה ביומן | מיפוי UDM | היגיון |
|---|---|---|
DomainIdentifier_label |
additional.fields |
ממוזג |
WatsonEventType_label |
additional.fields |
ממוזג |
action_label |
additional.fields |
ממוזג |
activity_id_label |
additional.fields |
ממוזג |
app_id_label |
additional.fields |
ממוזג |
app_insights_instrumentation_key_label |
additional.fields |
ממוזג |
asimov_instrumentation_key_label |
additional.fields |
ממוזג |
authorization_request_control_authorization_label |
additional.fields |
ממוזג |
bundle_name_label |
additional.fields |
ממוזג |
changedBy_label |
additional.fields |
ממוזג |
code_signature_subject_name_label |
additional.fields |
ממוזג |
collector_api_key_label |
additional.fields |
ממוזג |
command_label |
additional.fields |
ממוזג |
configuration_application_group_description_label |
additional.fields |
ממוזג |
configuration_application_group_identifier_label |
additional.fields |
ממוזג |
configuration_application_group_name_label |
additional.fields |
ממוזג |
configuration_application_identifier_label |
additional.fields |
ממוזג |
configuration_application_type_label |
additional.fields |
ממוזג |
configuration_identifier_label |
additional.fields |
ממוזג |
configuration_message_identifier_label |
additional.fields |
ממוזג |
configuration_message_name_label |
additional.fields |
ממוזג |
configuration_message_type_label |
additional.fields |
ממוזג |
configuration_name_label |
additional.fields |
ממוזג |
configuration_revision_number_label |
additional.fields |
ממוזג |
configuration_rule_identifier_label |
additional.fields |
ממוזג |
configuration_rule_on_demand_label |
additional.fields |
ממוזג |
configuration_rule_script_outcome_rule_affected_label |
additional.fields |
ממוזג |
configuration_token_identifier_label |
additional.fields |
ממוזג |
configuration_token_name_label |
additional.fields |
ממוזג |
configuration_workstyle_description_label |
additional.fields |
ממוזג |
configuration_workstyle_identifier_label |
additional.fields |
ממוזג |
configuration_workstyle_name_label |
additional.fields |
ממוזג |
content_length_label |
additional.fields |
ממוזג |
content_type_label |
additional.fields |
ממוזג |
domainNetBIOSName_label |
additional.fields |
ממוזג |
domain_label |
additional.fields |
ממוזג |
entity_label |
additional.fields |
ממוזג |
entity_name_label |
additional.fields |
ממוזג |
event_action_label |
additional.fields |
ממוזג |
exitstatus_label |
additional.fields |
ממוזג |
file_hash_md5_label |
additional.fields |
ממוזג |
file_hash_sha1_label |
additional.fields |
ממוזג |
file_hash_sha256_label |
additional.fields |
ממוזג |
file_version_label |
additional.fields |
ממוזג |
gid_label |
additional.fields |
ממוזג |
group_data_id_label |
additional.fields |
ממוזג |
group_id_label |
additional.fields |
ממוזג |
group_label |
additional.fields |
ממוזג |
handle_label |
additional.fields |
ממוזג |
host_name_label |
additional.fields |
ממוזג |
host_uptime_labels |
additional.fields |
ממוזג |
http_host_label |
additional.fields |
ממוזג |
id_label |
additional.fields |
ממוזג |
iolog_label |
additional.fields |
ממוזג |
is_opted_in_label |
additional.fields |
ממוזג |
linenum_label |
additional.fields |
ממוזג |
local_identifier_label |
additional.fields |
ממוזג |
locale_label |
additional.fields |
ממוזג |
master_utcoffset_label |
additional.fields |
ממוזג |
masterlocale_label |
additional.fields |
ממוזג |
owner_identifier_label |
additional.fields |
ממוזג |
parent_entity_id_label |
additional.fields |
ממוזג |
parent_process_exec_label |
additional.fields |
ממוזג |
parent_process_label |
additional.fields |
ממוזג |
pbmasterdnodename_label |
additional.fields |
ממוזג |
pipeName_label |
additional.fields |
ממוזג |
process_entity_id_label |
additional.fields |
ממוזג |
process_hash_label |
additional.fields |
ממוזג |
process_name_label |
additional.fields |
ממוזג |
process_parent_name_label |
additional.fields |
ממוזג |
process_start_time_label |
additional.fields |
ממוזג |
processexe_label |
additional.fields |
ממוזג |
product_label |
additional.fields |
ממוזג |
product_type_label |
additional.fields |
ממוזג |
product_version_label |
additional.fields |
ממוזג |
requestuser_label |
additional.fields |
ממוזג |
runargv_label |
additional.fields |
ממוזג |
runcwd_label |
additional.fields |
ממוזג |
runeffectivegroup_label |
additional.fields |
ממוזג |
runeffectiveuser_label |
additional.fields |
ממוזג |
runhost_label |
additional.fields |
ממוזג |
schema_version_label |
additional.fields |
ממוזג |
sku_name_label |
additional.fields |
ממוזג |
telemetry_level_label |
additional.fields |
ממוזג |
tenant_id_label |
additional.fields |
ממוזג |
type_label |
additional.fields |
ממוזג |
uid_label |
additional.fields |
ממוזג |
user_id_label |
additional.fields |
ממוזג |
user_name_label |
additional.fields |
ממוזג |
vs_exe_version_label |
additional.fields |
ממוזג |
inter_host |
intermediary.hostname |
מיפוי ישיר |
Processes.description |
metadata.description |
מיפוי ישיר |
details |
metadata.description |
מיפוי ישיר |
event_data.reason |
metadata.description |
מיפוי ישיר |
file.pe.description |
metadata.description |
מיפוי ישיר |
created |
metadata.event_timestamp |
ניתוח הכתובות שברשימה: ISO8601 |
datetime |
metadata.event_timestamp |
ניתוח הכתובות שברשימה: MMM dd HH:mm:ss |
has_principal |
metadata.event_type |
מיפוי: true → STATUS_UPDATE |
has_user |
metadata.event_type |
מיפוי: true → USER_UNCATEGORIZED |
parent_working_directory_label |
metadata.ingestion_labels |
ממוזג |
working_directory_label |
metadata.ingestion_labels |
ממוזג |
auditType |
metadata.product_event_type |
מיפוי ישיר |
event_datas.ActionId |
metadata.product_log_id |
מיפוי ישיר |
labels.related_item_id |
metadata.product_log_id |
מיפוי ישיר |
uniqueid |
metadata.product_log_id |
מיפוי ישיר |
masterdversion |
metadata.product_version |
מיפוי ישיר |
headers.http_version |
network.application_protocol_version |
מיפוי ישיר |
headers.request_method |
network.http.method |
מיפוי ישיר |
host.os.platform |
principal.administrative_domain |
מיפוי ישיר |
Processes.process |
principal.application |
מיפוי ישיר |
agent_ephemeral_id_label |
principal.asset.attribute.labels |
ממוזג |
agent_id_label |
principal.asset.attribute.labels |
ממוזג |
agent_version_label |
principal.asset.attribute.labels |
ממוזג |
ecs_version_label |
principal.asset.attribute.labels |
ממוזג |
_hardware |
principal.asset.hardware |
ממוזג |
host.hostname |
principal.asset.hostname |
מיפוי ישיר |
submithost |
principal.asset.hostname |
מיפוי ישיר |
ip_address |
principal.asset.ip |
ממוזג |
masterhostip |
principal.asset.ip |
ממוזג |
submithostip |
principal.asset.ip |
ממוזג |
file.path |
principal.file.full_path |
מיפוי ישיר |
lineinfile |
principal.file.full_path |
מיפוי ישיר |
host.hostname |
principal.hostname |
מיפוי ישיר |
submithost |
principal.hostname |
מיפוי ישיר |
ip_address |
principal.ip |
ממוזג |
masterhostip |
principal.ip |
ממוזג |
submithostip |
principal.ip |
ממוזג |
mac |
principal.mac |
ממוזג |
host.os.name |
principal.platform_version |
מיפוי ישיר |
host.os.version |
principal.platform_version |
מיפוי ישיר |
process.command_line |
principal.process.command_line |
מיפוי ישיר |
runcommand |
principal.process.command_line |
מיפוי ישיר |
process.executable |
principal.process.file.full_path |
מיפוי ישיר |
cmd |
principal.process.parent_process.command_line |
מיפוי ישיר |
Processes.process_path |
principal.process.parent_process.file.full_path |
מיפוי ישיר |
process.parent.executable |
principal.process.parent_process.file.full_path |
מיפוי ישיר |
Processes.parent_process_id |
principal.process.parent_process.pid |
מיפוי ישיר |
Processes.process_id |
principal.process.pid |
מיפוי ישיר |
logpid |
principal.process.pid |
מיפוי ישיר |
file.Owner.DomainName |
principal.user.company_name |
מיפוי ישיר |
file.Owner.Name |
principal.user.user_display_name |
מיפוי ישיר |
runuser |
principal.user.user_display_name |
מיפוי ישיר |
userName |
principal.user.user_display_name |
מיפוי ישיר |
Processes.user |
principal.user.userid |
מיפוי ישיר |
userId |
principal.user.userid |
מיפוי ישיר |
userid |
principal.user.userid |
מיפוי ישיר |
Processes.user_id |
principal.user.windows_sid |
מיפוי ישיר |
EPMWinMac.Configuration.Rule.Action |
security_result.action |
ממוזג |
security_result_action |
security_result.action |
ממוזג |
event_data.outcome |
security_result.category_details |
ממוזג |
host.os.version |
security_result.category_details |
ממוזג |
EPMWinMac.Configuration.Application.Description |
security_result.description |
מיפוי ישיר |
EPMWinMac.Configuration.Message.Description |
security_result.description |
מיפוי ישיר |
host.os.type |
src.administrative_domain |
מיפוי ישיר |
file.name |
src.file.names |
ממוזג |
host.os.full |
src.platform_version |
מיפוי ישיר |
host.os.family |
target.administrative_domain |
מיפוי ישיר |
Processes.dest |
target.asset.hostname |
מיפוי ישיר |
file.extension |
target.file.mime_type |
מיפוי ישיר |
Processes.dest |
target.hostname |
מיפוי ישיר |
host.domain |
target.hostname |
מיפוי ישיר |
file_DriveType_label |
target.resource.attribute.labels |
ממוזג |
file_drive_letter_label |
target.resource.attribute.labels |
ממוזג |
owner_label |
target.resource.attribute.labels |
ממוזג |
| לא רלוונטי | metadata.event_type |
קבוע: USER_UNCATEGORIZED |
| לא רלוונטי | metadata.product_name |
קבוע: Beyondtrust Privilege Management |
| לא רלוונטי | metadata.vendor_name |
קבוע: Beyondtrust Privilege Management |
| לא רלוונטי | network.application_protocol |
קבוע: HTTP |
| לא רלוונטי | principal.platform |
קבוע: MAC |
יומן שינויים
צפייה ביומן השינויים של מנתח התוכן הזה
הבעיה עדיין לא נפתרה? קבלת תשובות מחברי הקהילה וממומחי Google SecOps.