Citrix Monitor Service-Logs erfassen

Unterstützt in:

In diesem Dokument wird beschrieben, wie Sie Citrix Monitor Service-Logs mit Amazon S3 in Google Security Operations aufnehmen. Der Parser wandelt Rohlogs im JSON-Format 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.

Hinweise

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

  • Google SecOps-Instanz
  • Berechtigter Zugriff auf den Citrix Cloud-Mandanten
  • Privilegierter Zugriff auf AWS (S3, IAM, Lambda, EventBridge)

Voraussetzungen für den Citrix Monitor Service erfassen (IDs, API-Schlüssel, Organisations-IDs, Tokens)

  1. Melden Sie sich in der Citrix Cloud Console an.
  2. Rufen Sie Identitäts- und Zugriffsverwaltung> API-Zugriff auf.
  3. Klicken Sie auf Create Client.
  4. Kopieren und speichern Sie die folgenden Details an einem sicheren Ort:
    • Client-ID
    • Client-Secret
    • Kunden-ID (in der Citrix Cloud Console sichtbar)
    • API-Basis-URL:
      • Global: https://api.cloud.com
      • Japan: https://api.citrixcloud.jp

AWS S3-Bucket und IAM für Google SecOps konfigurieren

  1. Erstellen Sie einen Amazon S3-Bucket. Folgen Sie dazu dieser Anleitung: Bucket erstellen.
  2. Speichern Sie den Namen und die Region des Buckets zur späteren Verwendung (z. B. citrix-monitor-logs).
  3. Erstellen Sie einen Nutzer gemäß dieser Anleitung: IAM-Nutzer erstellen.
  4. Wählen Sie den erstellten Nutzer aus.
  5. Wählen Sie den Tab Sicherheitsanmeldedaten aus.
  6. Klicken Sie im Abschnitt Zugriffsschlüssel auf Zugriffsschlüssel erstellen.
  7. Wählen Sie als Anwendungsfall Drittanbieterdienst aus.
  8. Klicken Sie auf Weiter.
  9. Optional: Fügen Sie ein Beschreibungstag hinzu.
  10. Klicken Sie auf Zugriffsschlüssel erstellen.
  11. Klicken Sie auf CSV-Datei herunterladen, um den Access Key (Zugriffsschlüssel) und den Secret Access Key (geheimer Zugriffsschlüssel) zur späteren Verwendung zu speichern.
  12. Klicken Sie auf Fertig.
  13. Wählen Sie den Tab Berechtigungen aus.
  14. Klicken Sie im Bereich Berechtigungsrichtlinien auf Berechtigungen hinzufügen.
  15. Wählen Sie Berechtigungen hinzufügen aus.
  16. Wählen Sie Richtlinien direkt anhängen aus.
  17. Suchen Sie nach der Richtlinie AmazonS3FullAccess und wählen Sie sie aus.
  18. Klicken Sie auf Weiter.
  19. Klicken Sie auf Berechtigungen hinzufügen.

IAM-Richtlinie und ‑Rolle für S3-Uploads konfigurieren

  1. Rufen Sie in der AWS-Konsole IAM > Richtlinien > Richtlinie erstellen > JSON-Tab auf.
  2. Geben Sie die folgende Richtlinie ein:

    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Sid": "AllowPutObjects",
          "Effect": "Allow",
          "Action": "s3:PutObject",
          "Resource": "arn:aws:s3:::citrix-monitor-logs/*"
        },
        {
          "Sid": "AllowGetStateObject",
          "Effect": "Allow",
          "Action": "s3:GetObject",
          "Resource": "arn:aws:s3:::citrix-monitor-logs/citrix_monitor/state.json"
        }
      ]
    }
    
    • Ersetzen Sie citrix-monitor-logs, wenn Sie einen anderen Bucket-Namen eingegeben haben.
  3. Klicken Sie auf Weiter > Richtlinie erstellen.

  4. Rufen Sie IAM > Rollen > Rolle erstellen > AWS-Service > Lambda auf.

  5. Hängen Sie die neu erstellte Richtlinie und die verwaltete Richtlinie AWSLambdaBasicExecutionRole an.

  6. Geben Sie der Rolle den Namen CitrixMonitorLambdaRole und klicken Sie auf Rolle erstellen.

Lambda-Funktion erstellen

  1. Rufen Sie in der AWS Console Lambda > Funktionen > Funktion erstellen auf.
  2. Klicken Sie auf Von Grund auf erstellen.
  3. Geben Sie die folgenden Konfigurationsdetails an:

    Einstellung Wert
    Name CitrixMonitorCollector
    Laufzeit Python 3.13
    Architektur x86_64
    Ausführungsrolle CitrixMonitorLambdaRole
  4. Nachdem die Funktion erstellt wurde, öffnen Sie den Tab Code, löschen Sie den Stub und geben Sie den folgenden Code ein (CitrixMonitorCollector.py):

    import os
    import json
    import uuid
    import datetime
    import urllib.parse
    import urllib.request
    import urllib.error
    import boto3
    import botocore
    
    # 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"
    
    s3 = boto3.client("s3")
    
    def http_post_form(url, data_dict):
        """POST form data to get authentication token."""
        data = urllib.parse.urlencode(data_dict).encode("utf-8")
        req = urllib.request.Request(url, data=data, headers={
            "Accept": "application/json",
            "Content-Type": "application/x-www-form-urlencoded",
        })
        with urllib.request.urlopen(req, timeout=45) as resp:
            return json.loads(resp.read().decode("utf-8"))
    
    def http_get_json(url, headers):
        """GET JSON data from API endpoint."""
        req = urllib.request.Request(url, headers=headers)
        with urllib.request.urlopen(req, timeout=90) as resp:
            return json.loads(resp.read().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:
            params.append("$filter=" + urllib.parse.quote(filter_query, safe="()= ':-TZ0123456789"))
        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."""
        # Try ModifiedDate filter if timestamps are provided
        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 urllib.error.HTTPError as e:
                # If ModifiedDate filtering fails, fall back to unfiltered query
                if e.code == 400 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 read_state_file(bucket, key):
        """Read the last processed timestamp from S3 state file."""
        try:
            obj = s3.get_object(Bucket=bucket, Key=key)
            content = obj["Body"].read().decode("utf-8")
            state = json.loads(content)
            timestamp_str = state.get("last_hour_utc")
            if timestamp_str:
                return datetime.datetime.fromisoformat(timestamp_str.replace("Z", "+00:00")).replace(tzinfo=None)
        except botocore.exceptions.ClientError as e:
            if e.response["Error"]["Code"] == "NoSuchKey":
                return None
            raise
        return None
    
    def write_state_file(bucket, key, dt_utc):
        """Write the current processed timestamp to S3 state file."""
        state = {"last_hour_utc": dt_utc.isoformat() + "Z"}
        s3.put_object(
            Bucket=bucket, 
            Key=key, 
            Body=json.dumps(state, separators=(",", ":")), 
            ContentType="application/json"
        )
    
    def write_ndjson_to_s3(bucket, key, rows):
        """Write rows as NDJSON to S3."""
        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").encode("utf-8")
        s3.put_object(
            Bucket=bucket, 
            Key=key, 
            Body=body, 
            ContentType="application/x-ndjson"
        )
    
    def lambda_handler(event, context):
        """Main Lambda handler function."""
    
        # Environment variables
        bucket = os.environ["S3_BUCKET"]
        prefix = os.environ.get("S3_PREFIX", "citrix_monitor").strip("/")
        state_key = os.environ.get("STATE_KEY") or f"{prefix}/state.json"
        customer_id = os.environ["CITRIX_CUSTOMER_ID"]
        client_id = os.environ["CITRIX_CLIENT_ID"]
        client_secret = os.environ["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"
    
        # Time window calculation
        now = datetime.datetime.utcnow()
        fallback_hour = (now - datetime.timedelta(minutes=lookback_minutes)).replace(minute=0, second=0, microsecond=0)
    
        last_processed = read_state_file(bucket, state_key)
        target_hour = (last_processed + datetime.timedelta(hours=1)) if last_processed else fallback_hour
        start_iso = target_hour.isoformat() + "Z"
        end_iso = (target_hour + datetime.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-s3-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:
                        s3_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_s3(bucket, s3_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:
                s3_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_s3(bucket, s3_key, rows_batch)
                total_records += len(rows_batch)
    
        # Update state file
        write_state_file(bucket, state_key, target_hour)
    
        return {
            "statusCode": 200,
            "body": json.dumps({
                "success": True, 
                "hour_collected": start_iso, 
                "records_written": total_records, 
                "entities_processed": entities
            })
        }
    
  5. Rufen Sie Konfiguration > Umgebungsvariablen auf.

  6. Klicken Sie auf Bearbeiten> Neue Umgebungsvariable hinzufügen.

  7. Geben Sie die folgenden Umgebungsvariablen ein und ersetzen Sie die Platzhalter durch Ihre Werte:

    Schlüssel Beispielwert
    S3_BUCKET citrix-monitor-logs
    S3_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
  8. Bleiben Sie nach dem Erstellen der Funktion auf der zugehörigen Seite oder öffnen Sie Lambda > Funktionen > CitrixMonitorCollector.

  9. Wählen Sie den Tab Konfiguration aus.

  10. Klicken Sie im Bereich Allgemeine Konfiguration auf Bearbeiten.

  11. Ändern Sie Zeitlimit in 5 Minuten (300 Sekunden) und klicken Sie auf Speichern.

EventBridge-Zeitplan erstellen

  1. Gehen Sie zu Amazon EventBridge > Scheduler > Create schedule (Amazon EventBridge > Scheduler > Zeitplan erstellen).
  2. Geben Sie die folgenden Konfigurationsdetails an:
    • Wiederkehrender Termin: Rate (1 hour)
    • Ziel: Ihre Lambda-Funktion CitrixMonitorCollector
    • Name: CitrixMonitorCollector-1h
  3. Klicken Sie auf Zeitplan erstellen.

Optional: IAM-Nutzer mit Lesezugriff und Schlüssel für Google SecOps erstellen

  1. Rufen Sie in der AWS-Konsole IAM > Nutzer > Nutzer hinzufügen auf.
  2. Klicken Sie auf Add users (Nutzer hinzufügen).
  3. Geben Sie die folgenden Konfigurationsdetails an:
    • Nutzer: secops-reader
    • Zugriffstyp: Zugriffsschlüssel – Programmatischer Zugriff
  4. Klicken Sie auf Nutzer erstellen.
  5. Minimale Leseberechtigung (benutzerdefiniert) anhängen: Nutzer > secops-reader > Berechtigungen > Berechtigungen hinzufügen > Richtlinien direkt anhängen > Richtlinie erstellen.
  6. Geben Sie im JSON-Editor die folgende Richtlinie ein:

    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Effect": "Allow",
          "Action": ["s3:GetObject"],
          "Resource": "arn:aws:s3:::citrix-monitor-logs/*"
        },
        {
          "Effect": "Allow",
          "Action": ["s3:ListBucket"],
          "Resource": "arn:aws:s3:::citrix-monitor-logs"
        }
      ]
    }
    
  7. Legen Sie secops-reader-policy als Name fest.

  8. Gehen Sie zu Richtlinie erstellen> suchen/auswählen > Weiter > Berechtigungen hinzufügen.

  9. Rufen Sie Sicherheitsanmeldedaten > Zugriffsschlüssel > Zugriffsschlüssel erstellen auf.

  10. Laden Sie die CSV herunter (diese Werte werden in den Feed eingegeben).

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. Geben Sie im Feld Feedname einen Namen für den Feed ein, z. B. Citrix Monitor Service logs.
  4. Wählen Sie Amazon S3 V2 als Quelltyp aus.
  5. Wählen Sie Citrix Monitor als Logtyp aus.
  6. Klicken Sie auf Weiter.
  7. Geben Sie Werte für die folgenden Eingabeparameter an:
    • S3-URI: s3://citrix-monitor-logs/citrix_monitor/
    • Optionen zum Löschen der Quelle: Wählen Sie die gewünschte Option zum Löschen aus.
    • Maximales Dateialter: Dateien einschließen, die in den letzten Tagen geändert wurden. Standardmäßig 180 Tage.
    • Zugriffsschlüssel-ID: Nutzerzugriffsschlüssel mit Zugriff auf den S3-Bucket.
    • Geheimer Zugriffsschlüssel: Der geheime Schlüssel des Nutzers mit Zugriff auf den S3-Bucket.
    • Asset-Namespace: Der Asset-Namespace.
    • Aufnahmelabels: Das Label, das auf die Ereignisse aus diesem Feed angewendet wird.
  8. Klicken Sie auf Weiter.
  9. 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