PingOne Advanced Identity Cloud-Logs erfassen

Unterstützt in:

In diesem Dokument wird beschrieben, wie Sie PingOne Advanced Identity Cloud-Logs über Amazon S3 in Google Security Operations aufnehmen.

Hinweise

  • Google SecOps-Instanz
  • Privilegierter Zugriff auf den PingOne Advanced Identity Cloud-Mandanten
  • Privilegierter Zugriff auf AWS (S3, IAM, Lambda, EventBridge)

PingOne-API-Schlüssel und FQDN des Mandanten abrufen

  1. Melden Sie sich in der Admin-Konsole von Advanced Identity Cloud an.
  2. Klicken Sie auf das Nutzersymbol und dann auf Tenant Settings (Mandanteneinstellungen).
  3. Klicken Sie auf dem Tab Globale Einstellungen auf API-Schlüssel protokollieren.
  4. Klicken Sie auf New Log API Key (Neuen Log-API-Schlüssel) und geben Sie einen Namen für den Schlüssel an.
  5. Klicken Sie auf Schlüssel erstellen.
  6. Kopieren Sie die Werte api_key_id und api_key_secret und speichern Sie sie an einem sicheren Ort. Der Wert api_key_secret wird nicht noch einmal angezeigt.
  7. Klicken Sie auf Fertig.
  8. Rufen Sie die Mandanteneinstellungen > „Details“ auf und suchen Sie nach dem FQDN des Mandanten (z. B. example.tomcat.pingone.com).

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. pingone-aic-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 Zugriffsschlüssel und den geheimen 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": "AllowPutPingOneAICObjects",
          "Effect": "Allow",
          "Action": "s3:PutObject",
          "Resource": "arn:aws:s3:::pingone-aic-logs/*"
        },
        {
          "Sid": "AllowGetStateObject",
          "Effect": "Allow",
          "Action": ["s3:GetObject"],
          "Resource": "arn:aws:s3:::pingone-aic-logs/pingone-aic/logs/state.json"
        }
      ]
    }
    
    • Ersetzen Sie pingone-aic-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 an.

  6. Geben Sie der Rolle den Namen WritePingOneAICToS3Role 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 pingone_aic_to_s3
    Laufzeit Python 3.13
    Architektur x86_64
    Ausführungsrolle WritePingOneAICToS3Role
  4. Nachdem die Funktion erstellt wurde, öffnen Sie den Tab Code, löschen Sie den Stub und geben Sie den folgenden Code ein (pingone_aic_to_s3.py):

    #!/usr/bin/env python3
    
    import os, json, time, urllib.parse
    from urllib.request import Request, urlopen
    from urllib.error import HTTPError, URLError
    import boto3
    
    FQDN = os.environ["AIC_TENANT_FQDN"].strip("/")
    API_KEY_ID = os.environ["AIC_API_KEY_ID"]
    API_KEY_SECRET = os.environ["AIC_API_SECRET"]
    S3_BUCKET = os.environ["S3_BUCKET"]
    S3_PREFIX = os.environ.get("S3_PREFIX", "pingone-aic/logs/").strip("/")
    SOURCES = [s.strip() for s in os.environ.get("SOURCES", "am-everything,idm-everything").split(",") if s.strip()]
    PAGE_SIZE = min(int(os.environ.get("PAGE_SIZE", "500")), 1000)  # hard cap per docs
    MAX_PAGES = int(os.environ.get("MAX_PAGES", "20"))
    STATE_KEY = os.environ.get("STATE_KEY", "pingone-aic/logs/state.json")
    LOOKBACK_SECONDS = int(os.environ.get("LOOKBACK_SECONDS", "3600"))
    
    s3 = boto3.client("s3")
    
    def _headers():
        return {"x-api-key": API_KEY_ID, "x-api-secret": API_KEY_SECRET}
    
    def _iso(ts: float) -> str:
        return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime(ts))
    
    def _http_get(url: str, timeout: int = 60, max_retries: int = 5) -> dict:
        attempt, backoff = 0, 1.0
        while True:
            req = Request(url, method="GET", headers=_headers())
            try:
                with urlopen(req, timeout=timeout) as r:
                    data = r.read()
                    return json.loads(data.decode("utf-8"))
            except HTTPError as e:
                # 429: respect X-RateLimit-Reset (epoch seconds) if present
                if e.code == 429 and attempt < max_retries:
                    reset = e.headers.get("X-RateLimit-Reset")
                    now = int(time.time())
                    delay = max(1, int(reset) - now) if (reset and reset.isdigit()) else int(backoff)
                    time.sleep(delay); attempt += 1; backoff *= 2; continue
                if 500 <= e.code <= 599 and attempt < max_retries:
                    time.sleep(backoff); attempt += 1; backoff *= 2; continue
                raise
            except URLError:
                if attempt < max_retries:
                    time.sleep(backoff); attempt += 1; backoff *= 2; continue
                raise
    
    def _load_state() -> dict:
        try:
            obj = s3.get_object(Bucket=S3_BUCKET, Key=STATE_KEY)
            return json.loads(obj["Body"].read())
        except Exception:
            return {"sources": {}}
    
    def _save_state(state: dict):
        s3.put_object(Bucket=S3_BUCKET, Key=STATE_KEY,
                      Body=json.dumps(state, separators=(",", ":")).encode("utf-8"),
                      ContentType="application/json")
    
    def _write_page(payload: dict, source: str) -> str:
        ts = time.gmtime()
        key = f"{S3_PREFIX}/{time.strftime('%Y/%m/%d/%H%M%S', ts)}-pingone-aic-{source}.json"
        s3.put_object(Bucket=S3_BUCKET, Key=key,
                      Body=json.dumps(payload, separators=(",", ":")).encode("utf-8"),
                      ContentType="application/json")
        return key
    
    def _bounded_begin_time(last_ts: str | None, now: float) -> str:
        # beginTime must be <= 24h before endTime (now if endTime omitted)
        # if last_ts older than 24h → cap to now-24h; else use last_ts; else lookback
        twenty_four_h_ago = now - 24*3600
        if last_ts:
            try:
                t_struct = time.strptime(last_ts[:19] + "Z", "%Y-%m-%dT%H:%M:%SZ")
                t_epoch = int(time.mktime(t_struct))
            except Exception:
                t_epoch = int(now - LOOKBACK_SECONDS)
            begin_epoch = max(t_epoch, int(twenty_four_h_ago))
        else:
            begin_epoch = max(int(now - LOOKBACK_SECONDS), int(twenty_four_h_ago))
        return _iso(begin_epoch)
    
    def fetch_source(source: str, last_ts: str | None):
        base = f"https://{FQDN}/monitoring/logs"
        now = time.time()
        params = {
            "source": source,
            "_pageSize": str(PAGE_SIZE),
            "_sortKeys": "timestamp",
            "beginTime": _bounded_begin_time(last_ts, now)
        }
    
        pages = 0
        written = 0
        newest_ts = last_ts
        cookie = None
    
        while pages < MAX_PAGES:
            if cookie:
                params["_pagedResultsCookie"] = cookie
            qs = urllib.parse.urlencode(params, quote_via=urllib.parse.quote)
            data = _http_get(f"{base}?{qs}")
            _write_page(data, source)
    
            results = data.get("result") or data.get("results") or []
            for item in results:
                t = item.get("timestamp") or item.get("payload", {}).get("timestamp")
                if t and (newest_ts is None or t > newest_ts):
                    newest_ts = t
    
            written += len(results)
            cookie = data.get("pagedResultsCookie")
            pages += 1
            if not cookie:
                break
    
        return {"source": source, "pages": pages, "written": written, "newest_ts": newest_ts}
    
    def lambda_handler(event=None, context=None):
        state = _load_state()
        state.setdefault("sources", {})
        summary = []
        for source in SOURCES:
            last_ts = state["sources"].get(source, {}).get("last_ts")
            res = fetch_source(source, last_ts)
            if res.get("newest_ts"):
                state["sources"][source] = {"last_ts": res["newest_ts"]}
            summary.append(res)
        _save_state(state)
        return {"ok": True, "summary": summary}
    
    if __name__ == "__main__":
        print(lambda_handler())
    
    
  5. Klicken Sie auf Konfiguration> Umgebungsvariablen> Bearbeiten> Neue Umgebungsvariable hinzufügen.

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

    Schlüssel Beispiel
    S3_BUCKET pingone-aic-logs
    S3_PREFIX pingone-aic/logs/
    STATE_KEY pingone-aic/logs/state.json
    AIC_TENANT_FQDN example.tomcat.pingone.com
    AIC_API_KEY_ID <api_key_id>
    AIC_API_SECRET <api_key_secret>
    SOURCES am-everything,idm-everything
    PAGE_SIZE 500
    MAX_PAGES 20
    LOOKBACK_SECONDS 3600
  7. Bleiben Sie nach dem Erstellen der Funktion auf der zugehörigen Seite oder öffnen Sie Lambda > Funktionen > Ihre Funktion.

  8. Wählen Sie den Tab Konfiguration aus.

  9. Klicken Sie im Bereich Allgemeine Konfiguration auf Bearbeiten.

  10. Ä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 Zeitplan: Preis (1 hour).
    • Ziel: Ihre Lambda-Funktion.
    • Name: pingone-aic-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 auf und klicken Sie auf Nutzer hinzufügen.
  2. Geben Sie die folgenden Konfigurationsdetails an:
    • Nutzer: Geben Sie einen eindeutigen Namen ein, z. B. secops-reader.
    • Zugriffstyp: Wählen Sie Zugriffsschlüssel – programmatischer Zugriff aus.
    • Klicken Sie auf Nutzer erstellen.
  3. Richtlinie mit minimalen Leseberechtigungen anhängen (benutzerdefiniert): Nutzer > secops-reader auswählen > Berechtigungen > Berechtigungen hinzufügen > Richtlinien direkt anhängen > Richtlinie erstellen
  4. Geben Sie im JSON-Editor die folgende Richtlinie ein:

    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Effect": "Allow",
          "Action": ["s3:GetObject"],
          "Resource": "arn:aws:s3:::<your-bucket>/*"
        },
        {
          "Effect": "Allow",
          "Action": ["s3:ListBucket"],
          "Resource": "arn:aws:s3:::<your-bucket>"
        }
      ]
    }
    
  5. Legen Sie secops-reader-policy als Name fest.

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

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

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

Feed in Google SecOps konfigurieren, um PingOne Advanced Identity Cloud-Logs aufzunehmen

  1. Rufen Sie die SIEM-Einstellungen > Feeds auf.
  2. Klicken Sie auf Neuen Feed hinzufügen.
  3. Geben Sie im Feld Feed name (Feedname) einen Namen für den Feed ein, z. B. PingOne Advanced Identity Cloud.
  4. Wählen Sie Amazon S3 V2 als Quelltyp aus.
  5. Wählen Sie PingOne Advanced Identity Cloud als Logtyp aus.
  6. Klicken Sie auf Weiter.
  7. Geben Sie Werte für die folgenden Eingabeparameter an:
    • S3-URI: s3://pingone-aic-logs/pingone-aic/logs/
    • Optionen zum Löschen der Quelle: Wählen Sie die gewünschte Option zum Löschen aus.
    • Maximales Dateialter: Standardmäßig 180 Tage.
    • Zugriffsschlüssel-ID: Zugriffsschlüssel des Nutzers mit Zugriff auf den S3-Bucket.
    • Secret Access Key (Geheimer Zugriffsschlüssel): Geheimer Nutzersicherheitsschlüssel mit Zugriff auf den S3-Bucket.
    • Asset-Namespace: Der Asset-Namespace.
    • Aufnahmelabels: Das Label, das auf die Ereignisse aus diesem Feed angewendet werden soll.
  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