Citrix Monitor Service-Logs erfassen

Unterstützt in:

In diesem Dokument wird beschrieben, wie Sie Citrix Monitor Service-Logs mit Google Cloud Storage in Google Security Operations aufnehmen. Der Parser wandelt Logs im JSON-Rohformat in ein strukturiertes Format um, das dem Google SecOps UDM entspricht. Es werden relevante Felder aus dem Rohlog extrahiert, den entsprechenden UDM-Feldern zugeordnet und die Daten mit zusätzlichem Kontext wie Nutzerinformationen, Computerdetails und Netzwerkaktivitäten angereichert.

Hinweis

Prüfen Sie, ob folgende Voraussetzungen erfüllt sind:

  • Eine Google SecOps-Instanz
  • Ein GCP-Projekt mit aktivierter Cloud Storage API
  • Berechtigungen zum Erstellen und Verwalten von GCS-Buckets
  • Berechtigungen zum Verwalten von IAM-Richtlinien für GCS-Buckets
  • Berechtigungen zum Erstellen von Cloud Run-Funktionen, Pub/Sub-Themen und Cloud Scheduler-Jobs
  • Berechtigungen zum Erstellen von Dienstkonten und Verwalten von IAM-Rollen
  • Privilegierter Zugriff auf den Citrix Cloud-Mandanten
  • Citrix Cloud-API-Anmeldedaten (Client-ID, Clientschlüssel, Kunden-ID)

Voraussetzungen für den Citrix Monitor Service

  1. Melden Sie sich in der Citrix Cloud Console an.
  2. Rufen Sie Identitäts- und Zugriffsverwaltung > API-Zugriff auf.
  3. Klicken Sie auf Client erstellen.
  4. Kopieren Sie die folgenden Details und speichern Sie sie an einem sicheren Ort:

    • Client-ID
    • Client-Secret
    • Kunden-ID (in der Citrix Cloud Console sichtbar)
    • API-Basis-URL:
      • USA/EU/AP-S: https://api.cloud.com
      • Japan: https://api.citrixcloud.jp

Google Cloud Storage-Bucket erstellen

  1. Rufen Sie die Google Cloud Console auf.
  2. Wählen Sie Ihr Projekt aus oder erstellen Sie ein neues.
  3. Rufen Sie im Navigationsmenü Cloud Storage > Buckets auf.
  4. Klicken Sie auf Bucket erstellen.
  5. Geben Sie die folgenden Konfigurationsdetails an:

    Einstellung Wert
    Bucket benennen Geben Sie einen global eindeutigen Namen ein, z. B. citrix-monitor-logs.
    Standorttyp Wählen Sie je nach Bedarf aus (Region, Dual-Region, Multi-Region).
    Standort Wählen Sie den Speicherort aus, z. B. us-central1.
    Speicherklasse Standard (empfohlen für Logs, auf die häufig zugegriffen wird)
    Zugriffssteuerung Einheitlich (empfohlen)
    Schutzmaßnahmen Optional: Objektversionsverwaltung oder Aufbewahrungsrichtlinie aktivieren
  6. Klicken Sie auf Erstellen.

Dienstkonto für Cloud Run-Funktion erstellen

Die Cloud Run-Funktion benötigt ein Dienstkonto mit Berechtigungen zum Schreiben in den GCS-Bucket.

Dienstkonto erstellen

  1. Wechseln Sie in der GCP Console zu IAM & Verwaltung > Dienstkonten.
  2. Klicken Sie auf Dienstkonto erstellen.
  3. Geben Sie die folgenden Konfigurationsdetails an:
    • Name des Dienstkontos: Geben Sie citrix-monitor-collector-sa ein.
    • Beschreibung des Dienstkontos: Geben Sie Service account for Cloud Run function to collect Citrix Monitor Service logs ein.
  4. Klicken Sie auf Erstellen und fortfahren.
  5. Im Abschnitt Diesem Dienstkonto Zugriff auf das Projekt erteilen:
    1. Klicken Sie auf Rolle auswählen.
    2. Suchen Sie nach Storage-Objekt-Administrator und wählen Sie die Rolle aus.
    3. Klicken Sie auf + Weitere Rolle hinzufügen.
    4. Suchen Sie nach Cloud Run Invoker und wählen Sie die Rolle aus.
    5. Klicken Sie auf + Weitere Rolle hinzufügen.
    6. Suchen Sie nach Cloud Functions Invoker und wählen Sie die Rolle aus.
  6. Klicken Sie auf Weiter.
  7. Klicken Sie auf Fertig.

Diese Rollen sind erforderlich für:

  • Storage-Objekt-Administrator: Protokolle in GCS-Bucket schreiben und Statusdateien verwalten
  • Cloud Run-Aufrufer: Pub/Sub darf die Funktion aufrufen.
  • Cloud Functions-Invoker: Funktionsaufruf zulassen

IAM-Berechtigungen für GCS-Bucket erteilen

Gewähren Sie dem Dienstkonto Schreibberechtigungen für den GCS-Bucket:

  1. Rufen Sie Cloud Storage > Buckets auf.
  2. Klicken Sie auf den Namen Ihres Buckets.
  3. Wechseln Sie zum Tab Berechtigungen.
  4. Klicken Sie auf Zugriff erlauben.
  5. Geben Sie die folgenden Konfigurationsdetails an:
    • Hauptkonten hinzufügen: Geben Sie die E‑Mail-Adresse des Dienstkontos (citrix-monitor-collector-sa@PROJECT_ID.iam.gserviceaccount.com) ein.
    • Rollen zuweisen: Wählen Sie Storage-Objekt-Administrator aus.
  6. Klicken Sie auf Speichern.

Pub/Sub-Thema erstellen

Erstellen Sie ein Pub/Sub-Thema, in dem Cloud Scheduler veröffentlicht und das von der Cloud Run-Funktion abonniert wird.

  1. Rufen Sie in der GCP Console Pub/Sub > Themen auf.
  2. Klicken Sie auf Thema erstellen.
  3. Geben Sie die folgenden Konfigurationsdetails an:
    • Themen-ID: Geben Sie citrix-monitor-trigger ein.
    • Übernehmen Sie die anderen Einstellungen.
  4. Klicken Sie auf Erstellen.

Cloud Run-Funktion zum Erfassen von Logs erstellen

Die Cloud Run-Funktion wird durch Pub/Sub-Nachrichten von Cloud Scheduler ausgelöst, um Logs von der Citrix Monitor Service API abzurufen und in GCS zu schreiben.

  1. Rufen Sie in der GCP Console Cloud Run auf.
  2. Klicken Sie auf Dienst erstellen.
  3. Wählen Sie Funktion aus, um eine Funktion mit einem Inline-Editor zu erstellen.
  4. Geben Sie im Abschnitt Konfigurieren die folgenden Konfigurationsdetails an:

    Einstellung Wert
    Dienstname citrix-monitor-collector
    Region Wählen Sie die Region aus, die Ihrem GCS-Bucket entspricht (z. B. us-central1).
    Laufzeit Wählen Sie Python 3.12 oder höher aus.
  5. Im Abschnitt Trigger (optional):

    1. Klicken Sie auf + Trigger hinzufügen.
    2. Wählen Sie Cloud Pub/Sub aus.
    3. Wählen Sie unter Cloud Pub/Sub-Thema auswählen das Thema (citrix-monitor-trigger) aus.
    4. Klicken Sie auf Speichern.
  6. Im Abschnitt Authentifizierung:

    1. Wählen Sie Authentifizierung erforderlich aus.
    2. Identitäts- und Zugriffsverwaltung
  7. Scrollen Sie nach unten und maximieren Sie Container, Netzwerk, Sicherheit.

  8. Rufen Sie den Tab Sicherheit auf:

    • Dienstkonto: Wählen Sie das Dienstkonto aus (citrix-monitor-collector-sa).
  9. Rufen Sie den Tab Container auf:

    1. Klicken Sie auf Variablen und Secrets.
    2. Klicken Sie für jede Umgebungsvariable auf + Variable hinzufügen:
    Variablenname Beispielwert
    GCS_BUCKET citrix-monitor-logs
    GCS_PREFIX citrix_monitor
    STATE_KEY citrix_monitor/state.json
    CITRIX_CLIENT_ID your-client-id
    CITRIX_CLIENT_SECRET your-client-secret
    CITRIX_CUSTOMER_ID your-customer-id
    API_BASE https://api.cloud.com
    ENTITIES Machines,Sessions,Connections,Applications,Users
    PAGE_SIZE 1000
    LOOKBACK_MINUTES 75
    USE_TIME_FILTER true
  10. Scrollen Sie auf dem Tab Variablen und Secrets nach unten zu Anfragen:

    • Zeitlimit für Anfragen: Geben Sie 600 Sekunden (10 Minuten) ein.
  11. Rufen Sie den Tab Einstellungen unter Container auf:

    • Im Abschnitt Ressourcen:
      • Arbeitsspeicher: Wählen Sie 512 MiB oder höher aus.
      • CPU: Wählen Sie 1 aus.
    • Klicken Sie auf Fertig.
  12. Scrollen Sie zu Ausführungsumgebung:

    • Wählen Sie Standard aus (empfohlen).
  13. Im Abschnitt Versionsskalierung:

    • Mindestanzahl von Instanzen: Geben Sie 0 ein.
    • Maximale Anzahl von Instanzen: Geben Sie 100 ein (oder passen Sie den Wert an die erwartete Last an).
  14. Klicken Sie auf Erstellen.

  15. Warten Sie ein bis zwei Minuten, bis der Dienst erstellt wurde.

  16. Nachdem der Dienst erstellt wurde, wird automatisch der Inline-Code-Editor geöffnet.

Funktionscode hinzufügen

  1. Geben Sie main unter Funktionseinstiegspunkt ein.
  2. Erstellen Sie im Inline-Codeeditor zwei Dateien:

    • Erste Datei: main.py::
    import functions_framework
    from google.cloud import storage
    import json
    import os
    import urllib3
    from datetime import datetime, timedelta, timezone
    import uuid
    
    # Citrix Cloud OAuth2 endpoint template
    TOKEN_URL_TMPL = "{api_base}/cctrustoauth2/{customerid}/tokens/clients"
    DEFAULT_API_BASE = "https://api.cloud.com"
    MONITOR_BASE_PATH = "/monitorodata"
    
    # Initialize HTTP client
    http = urllib3.PoolManager()
    
    # Initialize Storage client
    storage_client = storage.Client()
    
    def http_post_form(url, data_dict):
        """POST form data to get authentication token."""
        encoded_data = urllib3.request.urlencode(data_dict)
        response = http.request(
            'POST',
            url,
            body=encoded_data,
            headers={
                'Accept': 'application/json',
                'Content-Type': 'application/x-www-form-urlencoded'
            }
        )
        return json.loads(response.data.decode('utf-8'))
    
    def http_get_json(url, headers):
        """GET JSON data from API endpoint."""
        response = http.request('GET', url, headers=headers)
        return json.loads(response.data.decode('utf-8'))
    
    def get_citrix_token(api_base, customer_id, client_id, client_secret):
        """Get Citrix Cloud authentication token."""
        url = TOKEN_URL_TMPL.format(
            api_base=api_base.rstrip('/'),
            customerid=customer_id
        )
        payload = {
            'grant_type': 'client_credentials',
            'client_id': client_id,
            'client_secret': client_secret
        }
        response = http_post_form(url, payload)
        return response['access_token']
    
    def build_entity_url(api_base, entity, filter_query=None, top=None):
        """Build OData URL with optional filter and pagination."""
        base = api_base.rstrip('/') + MONITOR_BASE_PATH + '/' + entity
        params = []
        if filter_query:
            # Encode filter query with safe characters for OData
            encoded_filter = urllib3.request.urlencode({'$filter': filter_query})[9:]  # Remove '$filter='
            params.append('$filter=' + encoded_filter)
        if top:
            params.append('$top=' + str(top))
        return base + ('?' + '&'.join(params) if params else '')
    
    def fetch_entity_rows(entity, start_iso=None, end_iso=None, page_size=1000, headers=None, api_base=DEFAULT_API_BASE):
        """Fetch entity data with optional time filtering and pagination."""
        first_url = None
        if start_iso and end_iso:
            filter_query = f"(ModifiedDate ge {start_iso} and ModifiedDate lt {end_iso})"
            first_url = build_entity_url(api_base, entity, filter_query, page_size)
        else:
            first_url = build_entity_url(api_base, entity, None, page_size)
    
        url = first_url
        while url:
            try:
                data = http_get_json(url, headers)
                items = data.get('value', [])
                for item in items:
                    yield item
                url = data.get('@odata.nextLink')
            except Exception as e:
                # If ModifiedDate filtering fails, fall back to unfiltered query
                if 'Bad Request' in str(e) and start_iso and end_iso:
                    print(f"ModifiedDate filter not supported for {entity}, falling back to unfiltered query")
                    url = build_entity_url(api_base, entity, None, page_size)
                    continue
                else:
                    raise
    
    def load_state(bucket, key):
        """Read the last processed timestamp from GCS state file."""
        try:
            blob = bucket.blob(key)
            if blob.exists():
                content = blob.download_as_text()
                state = json.loads(content)
                timestamp_str = state.get('last_hour_utc')
                if timestamp_str:
                    return datetime.fromisoformat(timestamp_str.replace('Z', '+00:00')).replace(tzinfo=None)
        except Exception as e:
            print(f"Warning: Could not load state: {str(e)}")
        return None
    
    def save_state(bucket, key, dt_utc):
        """Write the current processed timestamp to GCS state file."""
        state = {'last_hour_utc': dt_utc.isoformat() + 'Z'}
        blob = bucket.blob(key)
        blob.upload_from_string(
            json.dumps(state, separators=(',', ':')),
            content_type='application/json'
        )
    
    def write_ndjson_to_gcs(bucket, key, rows):
        """Write rows as NDJSON to GCS."""
        body_lines = []
        for row in rows:
            json_line = json.dumps(row, separators=(',', ':'), ensure_ascii=False)
            body_lines.append(json_line)
        body = '\n'.join(body_lines) + '\n'
        blob = bucket.blob(key)
        blob.upload_from_string(body, content_type='application/x-ndjson')
    
    @functions_framework.cloud_event
    def main(cloud_event):
        """
        Cloud Run function triggered by Pub/Sub to fetch Citrix Monitor Service logs and write to GCS.
    
        Args:
            cloud_event: CloudEvent object containing Pub/Sub message
        """
    
        # Get environment variables
        bucket_name = os.environ.get('GCS_BUCKET')
        prefix = os.environ.get('GCS_PREFIX', 'citrix_monitor').strip('/')
        state_key = os.environ.get('STATE_KEY') or f"{prefix}/state.json"
        customer_id = os.environ.get('CITRIX_CUSTOMER_ID')
        client_id = os.environ.get('CITRIX_CLIENT_ID')
        client_secret = os.environ.get('CITRIX_CLIENT_SECRET')
        api_base = os.environ.get('API_BASE', DEFAULT_API_BASE)
        entities = [e.strip() for e in os.environ.get('ENTITIES', 'Machines,Sessions,Connections,Applications,Users').split(',') if e.strip()]
        page_size = int(os.environ.get('PAGE_SIZE', '1000'))
        lookback_minutes = int(os.environ.get('LOOKBACK_MINUTES', '75'))
        use_time_filter = os.environ.get('USE_TIME_FILTER', 'true').lower() == 'true'
    
        if not all([bucket_name, customer_id, client_id, client_secret]):
            print('Error: Missing required environment variables')
            return
    
        try:
            # Get GCS bucket
            bucket = storage_client.bucket(bucket_name)
    
            # Time window calculation
            now = datetime.utcnow()
            fallback_hour = (now - timedelta(minutes=lookback_minutes)).replace(minute=0, second=0, microsecond=0)
            last_processed = load_state(bucket, state_key)
            target_hour = (last_processed + timedelta(hours=1)) if last_processed else fallback_hour
            start_iso = target_hour.isoformat() + 'Z'
            end_iso = (target_hour + timedelta(hours=1)).isoformat() + 'Z'
    
            # Authentication
            token = get_citrix_token(api_base, customer_id, client_id, client_secret)
            headers = {
                'Authorization': f'CWSAuth bearer={token}',
                'Citrix-CustomerId': customer_id,
                'Accept': 'application/json',
                'Accept-Encoding': 'gzip, deflate, br',
                'User-Agent': 'citrix-monitor-gcs-collector/1.0'
            }
    
            total_records = 0
    
            # Process each entity type
            for entity in entities:
                rows_batch = []
                try:
                    entity_generator = fetch_entity_rows(
                        entity=entity,
                        start_iso=start_iso if use_time_filter else None,
                        end_iso=end_iso if use_time_filter else None,
                        page_size=page_size,
                        headers=headers,
                        api_base=api_base
                    )
    
                    for row in entity_generator:
                        # Store raw Citrix data directly for proper parser recognition
                        rows_batch.append(row)
    
                        # Write in batches to avoid memory issues
                        if len(rows_batch) >= 1000:
                            gcs_key = f"{prefix}/{entity}/year={target_hour.year:04d}/month={target_hour.month:02d}/day={target_hour.day:02d}/hour={target_hour.hour:02d}/part-{uuid.uuid4().hex}.ndjson"
                            write_ndjson_to_gcs(bucket, gcs_key, rows_batch)
                            total_records += len(rows_batch)
                            rows_batch = []
    
                except Exception as ex:
                    print(f"Error processing entity {entity}: {str(ex)}")
                    continue
    
                # Write remaining records
                if rows_batch:
                    gcs_key = f"{prefix}/{entity}/year={target_hour.year:04d}/month={target_hour.month:02d}/day={target_hour.day:02d}/hour={target_hour.hour:02d}/part-{uuid.uuid4().hex}.ndjson"
                    write_ndjson_to_gcs(bucket, gcs_key, rows_batch)
                    total_records += len(rows_batch)
    
            # Update state file
            save_state(bucket, state_key, target_hour)
    
            print(f"Successfully processed {total_records} records for hour {start_iso}")
            print(f"Entities processed: {', '.join(entities)}")
    
        except Exception as e:
            print(f'Error processing Citrix Monitor logs: {str(e)}')
            raise
    
    • Zweite Datei: requirements.txt::
    functions-framework==3.*
    google-cloud-storage==2.*
    urllib3>=2.0.0
    
  3. Klicken Sie auf Bereitstellen, um die Funktion zu speichern und bereitzustellen.

  4. Warten Sie, bis die Bereitstellung abgeschlossen ist (2–3 Minuten).

Cloud Scheduler-Job erstellen

Cloud Scheduler veröffentlicht in regelmäßigen Abständen Nachrichten im Pub/Sub-Thema, wodurch die Cloud Run-Funktion ausgelöst wird.

  1. Rufen Sie in der GCP Console Cloud Scheduler auf.
  2. Klicken Sie auf Job erstellen.
  3. Geben Sie die folgenden Konfigurationsdetails an:

    Einstellung Wert
    Name citrix-monitor-collector-hourly
    Region Dieselbe Region wie für die Cloud Run-Funktion auswählen
    Frequenz 0 * * * * (jede Stunde, zur vollen Stunde)
    Zeitzone Zeitzone auswählen (UTC empfohlen)
    Zieltyp Pub/Sub
    Thema Wählen Sie das Thema aus (citrix-monitor-trigger).
    Nachrichtentext {} (leeres JSON-Objekt)
  4. Klicken Sie auf Erstellen.

Scheduler-Job testen

  1. Suchen Sie in der Cloud Scheduler-Konsole nach Ihrem Job.
  2. Klicken Sie auf Force run (Ausführung erzwingen), um die Ausführung manuell auszulösen.
  3. Warten Sie einige Sekunden und rufen Sie Cloud Run > Dienste > citrix-monitor-collector > Logs auf.
  4. Prüfen Sie, ob die Funktion erfolgreich ausgeführt wurde.
  5. Prüfen Sie im GCS-Bucket, ob Logs geschrieben wurden.

Google SecOps-Dienstkonto abrufen

Google SecOps verwendet ein eindeutiges Dienstkonto, um Daten aus Ihrem GCS-Bucket zu lesen. Sie müssen diesem Dienstkonto Zugriff auf Ihren Bucket gewähren.

E-Mail-Adresse des Dienstkontos abrufen

  1. Rufen Sie die SIEM-Einstellungen > Feeds auf.
  2. Klicken Sie auf Neuen Feed hinzufügen.
  3. Klicken Sie auf Einzelnen Feed konfigurieren.
  4. Geben Sie im Feld Feedname einen Namen für den Feed ein, z. B. Citrix Monitor Service logs.
  5. Wählen Sie Google Cloud Storage V2 als Quelltyp aus.
  6. Wählen Sie Citrix Monitor als Logtyp aus.
  7. Klicken Sie auf Dienstkonto abrufen. Es wird eine eindeutige E-Mail-Adresse für das Dienstkonto angezeigt, z. B.:

    chronicle-12345678@chronicle-gcp-prod.iam.gserviceaccount.com
    
  8. Kopieren Sie diese E‑Mail-Adresse für den nächsten Schritt.

Dem Google SecOps-Dienstkonto IAM-Berechtigungen gewähren

Das Google SecOps-Dienstkonto benötigt die Rolle Storage-Objekt-Betrachter für Ihren GCS-Bucket.

  1. Rufen Sie Cloud Storage > Buckets auf.
  2. Klicken Sie auf den Namen Ihres Buckets.
  3. Wechseln Sie zum Tab Berechtigungen.
  4. Klicken Sie auf Zugriff erlauben.
  5. Geben Sie die folgenden Konfigurationsdetails an:
    • Hauptkonten hinzufügen: Fügen Sie die E‑Mail-Adresse des Google SecOps-Dienstkontos ein.
    • Rollen zuweisen: Wählen Sie Storage-Objekt-Betrachter aus.
  6. Klicken Sie auf Speichern.

Feed in Google SecOps konfigurieren, um Citrix Monitor Service-Logs aufzunehmen

  1. Rufen Sie die SIEM-Einstellungen > Feeds auf.
  2. Klicken Sie auf Neuen Feed hinzufügen.
  3. Klicken Sie auf Einzelnen Feed konfigurieren.
  4. Geben Sie im Feld Feedname einen Namen für den Feed ein, z. B. Citrix Monitor Service logs.
  5. Wählen Sie Google Cloud Storage V2 als Quelltyp aus.
  6. Wählen Sie Citrix Monitor als Logtyp aus.
  7. Klicken Sie auf Weiter.
  8. Geben Sie Werte für die folgenden Eingabeparameter an:

    • Storage-Bucket-URL: Geben Sie den GCS-Bucket-URI mit dem Präfixpfad ein:

      gs://citrix-monitor-logs/citrix_monitor/
      
      • Ersetzen Sie:

        • citrix-monitor-logs: Der Name Ihres GCS-Buckets.
        • citrix_monitor: Optionales Präfix/Ordnerpfad, in dem Logs gespeichert werden (für den Stamm leer lassen).
    • Option zum Löschen der Quelle: Wählen Sie die gewünschte Löschoption aus:

      • Nie: Es werden nach Übertragungen nie Dateien gelöscht (empfohlen für Tests).
      • Übertragene Dateien löschen: Dateien werden nach der erfolgreichen Übertragung gelöscht.
      • Übertragene Dateien und leere Verzeichnisse löschen: Löscht Dateien und leere Verzeichnisse nach der erfolgreichen Übertragung.

    • Maximales Dateialter: Dateien einschließen, die in den letzten Tagen geändert wurden. Der Standardwert ist 180 Tage.

    • Asset-Namespace: Der Asset-Namespace.

    • Aufnahmelabels: Das Label, das auf die Ereignisse aus diesem Feed angewendet werden soll.

  9. Klicken Sie auf Weiter.

  10. Prüfen Sie die neue Feedkonfiguration auf dem Bildschirm Abschließen und klicken Sie dann auf Senden.

Benötigen Sie weitere Hilfe? Antworten von Community-Mitgliedern und Google SecOps-Experten erhalten