Bitwarden Enterprise-Ereignisprotokolle erfassen
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
- In der Bitwarden-Admin-Konsole.
- Rufen Sie die Einstellungen> Organisationsinformationen> API-Schlüssel ansehen auf.
- Kopieren Sie die folgenden Details und speichern Sie sie an einem sicheren Ort:
- Client-ID
- Client-Secret
- 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
)
- IDENTITY_URL::
AWS S3-Bucket und IAM für Google SecOps konfigurieren
- Erstellen Sie einen Amazon S3-Bucket. Folgen Sie dazu dieser Anleitung: Bucket erstellen.
- Speichern Sie den Namen und die Region des Buckets zur späteren Verwendung (z. B.
bitwarden-events
). - Erstellen Sie einen Nutzer gemäß dieser Anleitung: IAM-Nutzer erstellen.
- Wählen Sie den erstellten Nutzer aus.
- Wählen Sie den Tab Sicherheitsanmeldedaten aus.
- Klicken Sie im Abschnitt Zugriffsschlüssel auf Zugriffsschlüssel erstellen.
- Wählen Sie als Anwendungsfall Drittanbieterdienst aus.
- Klicken Sie auf Weiter.
- Optional: Fügen Sie ein Beschreibungstag hinzu.
- Klicken Sie auf Zugriffsschlüssel erstellen.
- Klicken Sie auf CSV-Datei herunterladen, um den Zugriffsschlüssel und den geheimen Zugriffsschlüssel zur späteren Verwendung zu speichern.
- Klicken Sie auf Fertig.
- Wählen Sie den Tab Berechtigungen aus.
- Klicken Sie im Bereich Berechtigungsrichtlinien auf Berechtigungen hinzufügen.
- Wählen Sie Berechtigungen hinzufügen aus.
- Wählen Sie Richtlinien direkt anhängen aus.
- Suchen Sie nach der Richtlinie AmazonS3FullAccess und wählen Sie sie aus.
- Klicken Sie auf Weiter.
- Klicken Sie auf Berechtigungen hinzufügen.
IAM-Richtlinie und -Rolle für S3-Uploads konfigurieren
- Rufen Sie die AWS-Konsole > IAM > Richtlinien > Richtlinie erstellen > JSON-Tab auf.
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.
- Ersetzen Sie
Klicken Sie auf Weiter > Richtlinie erstellen.
Rufen Sie IAM > Rollen > Rolle erstellen > AWS-Service > Lambda auf.
Hängen Sie die neu erstellte Richtlinie an.
Geben Sie der Rolle den Namen
WriteBitwardenToS3Role
und klicken Sie auf Rolle erstellen.
Lambda-Funktion erstellen
- Rufen Sie in der AWS Console Lambda > Funktionen > Funktion erstellen auf.
- Klicken Sie auf Von Grund auf erstellen.
Geben Sie die folgenden Konfigurationsdetails an:
Einstellung Wert Name bitwarden_events_to_s3
Laufzeit Python 3.13 Architektur x86_64 Ausführungsrolle WriteBitwardenToS3Role
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())
Klicken Sie auf Konfiguration> Umgebungsvariablen> Bearbeiten> Neue Umgebungsvariable hinzufügen.
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
Bleiben Sie nach dem Erstellen der Funktion auf der zugehörigen Seite oder öffnen Sie Lambda > Funktionen > Ihre Funktion.
Wählen Sie den Tab Konfiguration aus.
Klicken Sie im Bereich Allgemeine Konfiguration auf Bearbeiten.
Ändern Sie Zeitlimit in 5 Minuten (300 Sekunden) und klicken Sie auf Speichern.
EventBridge-Zeitplan erstellen
- Gehen Sie zu Amazon EventBridge > Scheduler > Create schedule (Amazon EventBridge > Scheduler > Zeitplan erstellen).
- Geben Sie die folgenden Konfigurationsdetails an:
- Wiederkehrender Zeitplan: Preis (
1 hour
). - Ziel: Ihre Lambda-Funktion.
- Name:
bitwarden-events-1h
.
- Wiederkehrender Zeitplan: Preis (
- Klicken Sie auf Zeitplan erstellen.
Optional: IAM-Nutzer mit Lesezugriff und Schlüssel für Google SecOps erstellen
- Rufen Sie in der AWS-Konsole IAM > Nutzer auf und klicken Sie auf Nutzer hinzufügen.
- 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.
- Nutzer: Geben Sie einen eindeutigen Namen ein, z. B.
- Richtlinie mit minimalen Leseberechtigungen anhängen (benutzerdefiniert): Nutzer >
secops-reader
auswählen > Berechtigungen > Berechtigungen hinzufügen > Richtlinien direkt anhängen > Richtlinie erstellen 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>" } ] }
Legen Sie
secops-reader-policy
als Name fest.Gehen Sie zu Richtlinie erstellen> suchen/auswählen > Weiter > Berechtigungen hinzufügen.
Rufen Sie Sicherheitsanmeldedaten > Zugriffsschlüssel > Zugriffsschlüssel erstellen auf.
Laden Sie die CSV herunter (diese Werte werden in den Feed eingegeben).
Feed in Google SecOps konfigurieren, um die Bitwarden Enterprise-Ereignisprotokolle aufzunehmen
- Rufen Sie die SIEM-Einstellungen > Feeds auf.
- Klicken Sie auf + Neuen Feed hinzufügen.
- Geben Sie im Feld Feed name (Feedname) einen Namen für den Feed ein, z. B.
Bitwarden Events
. - Wählen Sie Amazon S3 V2 als Quelltyp aus.
- Wählen Sie Bitwarden-Ereignisse als Protokolltyp aus.
- Klicken Sie auf Weiter.
- 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.
- S3-URI:
- Klicken Sie auf Weiter.
- 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