diff --git a/integrations/webhook-relay/manifest.yaml b/integrations/webhook-relay/manifest.yaml index ae4b826..2fd6e73 100644 --- a/integrations/webhook-relay/manifest.yaml +++ b/integrations/webhook-relay/manifest.yaml @@ -1,8 +1,8 @@ id: webhook_relay name: Webhook Relay -version: 1.0.0 +version: 1.0.1 description: "Reads the alerts that vendors pushed to riposte-relay, the public mailbox for alert webhooks (riposte/relay/). Riposte accepts no inbound connection, so a source whose only output is a webhook — Rapid7 InsightIDR Essential basic detection rules, for one — posts to the relay, and an alert rule on this integration reads one relay source back over outbound HTTPS. The relay knows no vendor: the rule chooses the client, the alert type and the mapper. A read consumes nothing; the rule's watermark is the read's `since`, so after a failover the standby reads the same mailbox from its own watermark." -changelog: "1.0.0 — Initial release: fetch_messages (ingestion: one alert per relayed message, `since` watermark, page walk), relay_status, test_connection, and a generic mapper that keeps the raw body and the checks the message passed." +changelog: "1.0.1 — The generic mapper fills the OCSF fields that have a place for a relayed message: metadata.logged_time (receipt), metadata.log_provider and log_name (relay, source), metadata.labels (the checks passed, relay:hmac…), raw_data (the payload). The sender address goes to unmapped.relay.sender_ip, not src_endpoint.ip: it is the vendor's cloud, and as an entity it would tie every alert of that vendor together. metadata.product.name no longer holds the source id. | 1.0.0 — Initial release: fetch_messages (ingestion: one alert per relayed message, `since` watermark, page walk), relay_status, test_connection, and a generic mapper that keeps the raw body and the checks the message passed." category: ingestion config_schema: diff --git a/integrations/webhook-relay/mappers/fetch_messages.yaml b/integrations/webhook-relay/mappers/fetch_messages.yaml index 31efcea..80b6d02 100644 --- a/integrations/webhook-relay/mappers/fetch_messages.yaml +++ b/integrations/webhook-relay/mappers/fetch_messages.yaml @@ -13,6 +13,14 @@ description: | falls back to the source. Severity is left to the alert rule's default: a generic payload has no scale this mapper could trust. + The OCSF side carries what has a place in the schema: the relay's message id as the + finding uid, the receipt time (time and metadata.logged_time), the source as + metadata.log_name, the checks the delivery passed as metadata.labels (relay:hmac…), + the payload as raw_data. The sender's address goes to unmapped.relay.sender_ip and + NOT src_endpoint.ip: it is the vendor's cloud, and as an entity it would tie every + alert of that vendor together. + + The custom fields repeat what layouts and automation conditions read most easily. custom:webhook_verified is the trust level: the checks the delivery passed at the relay (path_token, header_token, hmac, ip). An alert authenticated by a URL alone is plausible, not proven — declare the field and keep destructive automation off @@ -29,7 +37,26 @@ field_mappings: # Key/value rows of the decoded payload's top level, for a table field. "custom:webhook_payload": "jsonata: json ? $toTable(json) : body" ocsf: + # ── Finding ─────────────────────────────────────────────────────── + # The relay's message id: stable for one delivery. A vendor mapper replaces it with + # the vendor's own alert id. - { source_path: "id", ocsf_field: "finding_info.uid" } - { source_path: "id", ocsf_field: "metadata.uid" } + # When the relay received it — the best time a payload of unknown shape offers. + # A vendor mapper puts the detection's own time in `time` and keeps this one. - { source_path: "received_at", ocsf_field: "time" } - - { source_path: "source", ocsf_field: "metadata.product.name" } + - { source_path: "received_at", ocsf_field: "metadata.logged_time" } + # ── Where it came from ──────────────────────────────────────────── + - { source_path: "'riposte-relay'", ocsf_field: "metadata.log_provider" } + - { source_path: "source", ocsf_field: "metadata.log_name" } + # The checks the delivery passed, as labels: relay:path_token, relay:hmac, relay:ip. + # Kept an array even with one check (the singleton trap). + - { source_path: "[verified.('relay:' & $)]", ocsf_field: "metadata.labels" } + # The payload exactly as sent: what an analyst and the case synthesis read when no + # vendor mapper exists yet. + - { source_path: "body", ocsf_field: "raw_data" } + # The sender's address is the VENDOR's (its cloud, its collector), never the + # attacker's or the victim's: as src_endpoint.ip it would become an entity, and + # every alert of that vendor would correlate on it. It stays out of the schema. + - { source_path: "remote_ip", ocsf_field: "unmapped.relay.sender_ip" } + - { source_path: "content_type", ocsf_field: "unmapped.relay.content_type" }