Bitwarden Enterprise-Ereignisprotokolle erfassen

Unterstützt in:

In diesem Dokument wird beschrieben, wie Sie Bitwarden Enterprise-Ereignislogs mithilfe von Amazon S3 in Google Security Operations aufnehmen. Der Parser wandelt Rohereignisprotokolle im JSON-Format in ein strukturiertes Format um, das dem Chronicle UDM entspricht. Es werden relevante Felder wie Nutzerdetails, IP-Adressen und Ereignistypen extrahiert und den entsprechenden UDM-Feldern zugeordnet, um eine konsistente Sicherheitsanalyse zu ermöglichen.

Hinweise

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

Bitwarden-API-Schlüssel und ‑URL abrufen

  1. In der Bitwarden-Admin-Konsole.
  2. Rufen Sie die Einstellungen> Organisationsinformationen> API-Schlüssel ansehen auf.
  3. Kopieren Sie die folgenden Details und speichern Sie sie an einem sicheren Ort:
    • Client-ID
    • Client-Secret
  4. Bestimmen Sie Ihre Bitwarden-Endpunkte (basierend auf der Region):
    • IDENTITY_URL::https://identity.bitwarden.com/connect/token (EU: https://identity.bitwarden.eu/connect/token)
    • API_BASE::https://api.bitwarden.com (EU: https://api.bitwarden.eu)

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. bitwarden-events).
  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 die AWS-Konsole > IAM > Richtlinien > Richtlinie erstellen > JSON-Tab auf.
  2. Geben Sie die folgende Richtlinie ein:

    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Sid": "AllowPutBitwardenObjects",
          "Effect": "Allow",
          "Action": "s3:PutObject",
          "Resource": "arn:aws:s3:::bitwarden-events/*"
        },
        {
          "Sid": "AllowGetStateObject",
          "Effect": "Allow",
          "Action": "s3:GetObject",
          "Resource": "arn:aws:s3:::bitwarden-events/bitwarden/events/state.json"
        }
      ]
    }
    
    
    • Ersetzen Sie bitwarden-events, 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 WriteBitwardenToS3Role 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 bitwarden_events_to_s3
    Laufzeit Python 3.13
    Architektur x86_64
    Ausführungsrolle WriteBitwardenToS3Role
  4. Nachdem die Funktion erstellt wurde, öffnen Sie den Tab Code, löschen Sie den Stub und geben Sie den folgenden Code ein (bitwarden_events_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
    
    IDENTITY_URL = os.environ.get("IDENTITY_URL", "https://identity.bitwarden.com/connect/token")
    API_BASE = os.environ.get("API_BASE", "https://api.bitwarden.com").rstrip("/")
    CID = os.environ["BW_CLIENT_ID"]          # organization.ClientId
    CSECRET = os.environ["BW_CLIENT_SECRET"]  # organization.ClientSecret
    BUCKET = os.environ["S3_BUCKET"]
    PREFIX = os.environ.get("S3_PREFIX", "bitwarden/events/").strip("/")
    STATE_KEY = os.environ.get("STATE_KEY", "bitwarden/events/state.json")
    MAX_PAGES = int(os.environ.get("MAX_PAGES", "10"))
    
    HEADERS_FORM = {"Content-Type": "application/x-www-form-urlencoded"}
    HEADERS_JSON = {"Accept": "application/json"}
    
    s3 = boto3.client("s3")
    
    def _read_state():
        try:
            obj = s3.get_object(Bucket=BUCKET, Key=STATE_KEY)
            j = json.loads(obj["Body"].read())
            return j.get("continuationToken")
        except Exception:
            return None
    
    def _write_state(token):
        body = json.dumps({"continuationToken": token}).encode("utf-8")
        s3.put_object(Bucket=BUCKET, Key=STATE_KEY, Body=body, ContentType="application/json")
    
    def _http(req: Request, timeout: int = 60, max_retries: int = 5):
        attempt, backoff = 0, 1.0
        while True:
            try:
                with urlopen(req, timeout=timeout) as r:
                    return json.loads(r.read().decode("utf-8"))
            except HTTPError as e:
                # Retry on 429 and 5xx
                if (e.code == 429 or 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 _get_token():
        body = urllib.parse.urlencode({
            "grant_type": "client_credentials",
            "scope": "api.organization",
            "client_id": CID,
            "client_secret": CSECRET,
        }).encode("utf-8")
        req = Request(IDENTITY_URL, data=body, method="POST", headers=HEADERS_FORM)
        data = _http(req, timeout=30)
        return data["access_token"], int(data.get("expires_in", 3600))
    
    def _fetch_events(bearer: str, cont: str | None):
        params = {}
        if cont:
            params["continuationToken"] = cont
        qs = ("?" + urllib.parse.urlencode(params)) if params else ""
        url = f"{API_BASE}/public/events{qs}"
        req = Request(url, method="GET", headers={"Authorization": f"Bearer {bearer}", **HEADERS_JSON})
        return _http(req, timeout=60)
    
    def _write_page(obj: dict, run_ts_s: int, page_index: int) -> str:
        # Make filename unique per page to avoid overwrites in the same second
        key = f"{PREFIX}/{time.strftime('%Y/%m/%d/%H%M%S', time.gmtime(run_ts_s))}-page{page_index:05d}-bitwarden-events.json"
        s3.put_object(
            Bucket=BUCKET,
            Key=key,
            Body=json.dumps(obj, separators=(",", ":")).encode("utf-8"),
            ContentType="application/json",
        )
        return key
    
    def lambda_handler(event=None, context=None):
        bearer, _ttl = _get_token()
        cont = _read_state()
        run_ts_s = int(time.time())
    
        pages = 0
        written = 0
        while pages < MAX_PAGES:
            data = _fetch_events(bearer, cont)
            # write page
            _write_page(data, run_ts_s, pages)
            pages += 1
    
            # count entries (official shape: {"object":"list","data":[...], "continuationToken": "..."} )
            entries = []
            if isinstance(data.get("data"), list):
                entries = data["data"]
            elif isinstance(data.get("entries"), list):  # fallback if shape differs
                entries = data["entries"]
            written += len(entries)
    
            # next page token (official: "continuationToken")
            next_cont = data.get("continuationToken")
            if next_cont:
                cont = next_cont
                continue
            break
    
        # Save state only if there are more pages to continue in next run
        _write_state(cont if pages >= MAX_PAGES and cont else None)
        return {"ok": True, "pages": pages, "events_estimate": written, "nextContinuationToken": cont}
    
    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 bitwarden-events
    S3_PREFIX bitwarden/events/
    STATE_KEY bitwarden/events/state.json
    BW_CLIENT_ID <organization client_id>
    BW_CLIENT_SECRET <organization client_secret>
    IDENTITY_URL https://identity.bitwarden.com/connect/token (EU: https://identity.bitwarden.eu/connect/token)
    API_BASE https://api.bitwarden.com (EU: https://api.bitwarden.eu)
    MAX_PAGES 10
  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: bitwarden-events-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 die Bitwarden Enterprise-Ereignisprotokolle 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. Bitwarden Events.
  4. Wählen Sie Amazon S3 V2 als Quelltyp aus.
  5. Wählen Sie Bitwarden-Ereignisse als Protokolltyp aus.
  6. Klicken Sie auf Weiter.
  7. Geben Sie Werte für die folgenden Eingabeparameter an:
    • S3-URI: s3://bitwarden-events/bitwarden/events/
    • 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 wird.
  8. Klicken Sie auf Weiter.
  9. Prüfen Sie die neue Feedkonfiguration auf dem Bildschirm Abschließen und klicken Sie dann auf Senden.

UDM-Zuordnungstabelle

Logfeld UDM-Zuordnung Logik
actingUserId target.user.userid Wenn enriched.actingUser.userId leer oder null ist, wird dieses Feld verwendet, um das Feld target.user.userid auszufüllen.
collectionID security_result.detection_fields.key Füllt das Feld key in detection_fields in security_result aus.
collectionID security_result.detection_fields.value Füllt das Feld value in detection_fields in security_result aus.
Datum metadata.event_timestamp Geparst und in ein Zeitstempelformat konvertiert und event_timestamp zugeordnet.
enriched.actingUser.accessAll security_result.rule_labels.key Legt den Wert auf „Access_All“ in rule_labels in security_result fest.
enriched.actingUser.accessAll security_result.rule_labels.value Füllt das Feld value in rule_labels in security_result mit dem Wert aus enriched.actingUser.accessAll aus, der in einen String konvertiert wurde.
enriched.actingUser.email target.user.email_addresses Füllt das Feld email_addresses in target.user aus.
enriched.actingUser.id metadata.product_log_id Füllt das Feld product_log_id in metadata aus.
enriched.actingUser.id target.labels.key Legt den Wert auf „ID“ in target.labels fest.
enriched.actingUser.id target.labels.value Füllt das Feld value in target.labels mit dem Wert aus enriched.actingUser.id aus.
enriched.actingUser.name target.user.user_display_name Füllt das Feld user_display_name in target.user aus.
enriched.actingUser.object target.labels.key Legt den Wert in target.labels auf „Object“ fest.
enriched.actingUser.object target.labels.value Füllt das Feld value in target.labels mit dem Wert aus enriched.actingUser.object aus.
enriched.actingUser.resetPasswordEnrolled target.labels.key Legt den Wert in target.labels auf „ResetPasswordEnrolled“ fest.
enriched.actingUser.resetPasswordEnrolled target.labels.value Füllt das Feld value in target.labels mit dem Wert aus enriched.actingUser.resetPasswordEnrolled aus, der in einen String konvertiert wurde.
enriched.actingUser.twoFactorEnabled security_result.rule_labels.key Legt den Wert in rule_labels in security_result auf „Two Factor Enabled“ (Zwei-Faktor-Authentifizierung aktiviert) fest.
enriched.actingUser.twoFactorEnabled security_result.rule_labels.value Füllt das Feld value in rule_labels in security_result mit dem Wert aus enriched.actingUser.twoFactorEnabled aus, der in einen String konvertiert wurde.
enriched.actingUser.userId target.user.userid Füllt das Feld userid in target.user aus.
enriched.collection.id additional.fields.key Legt den Wert auf „Sammlungs-ID“ in additional.fields fest.
enriched.collection.id additional.fields.value.string_value Füllt das Feld string_value in additional.fields mit dem Wert aus enriched.collection.id aus.
enriched.collection.object additional.fields.key Legt den Wert in additional.fields auf „Collection Object“ fest.
enriched.collection.object additional.fields.value.string_value Füllt das Feld string_value in additional.fields mit dem Wert aus enriched.collection.object aus.
enriched.type metadata.product_event_type Füllt das Feld product_event_type in metadata aus.
groupId target.user.group_identifiers Fügt den Wert dem group_identifiers-Array in target.user hinzu.
ipAddress principal.ip Die IP-Adresse wurde aus dem Feld extrahiert und principal.ip zugeordnet.
extensions.auth Ein leeres Objekt wird vom Parser erstellt.
metadata.event_type Wird anhand von enriched.type und dem Vorhandensein von principal- und target-Informationen bestimmt. Mögliche Werte: USER_LOGIN, STATUS_UPDATE, GENERIC_EVENT.
security_result.action Wird anhand der enriched.type ermittelt. Mögliche Werte: ALLOW, BLOCK.
Objekt additional.fields.key Legt den Wert in additional.fields auf „Object“ fest.
Objekt additional.fields.value Füllt das Feld value in additional.fields mit dem Wert aus object aus.

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