Skip to content

Wazuh

XplicitTrust provides a custom decoder and ruleset for Wazuh.

The decoder extracts every field of the JSON message body into data.*. The rules generate alerts per event type, with compliance tags for PCI DSS, GDPR, HIPAA and NIST 800-53 where applicable.

Decoder and Rules

Copy the files to /var/ossec/etc/decoders/ and /var/ossec/etc/rules/ respectively, then restart the Wazuh manager.

Wazuh does not accept RFC 5425 syslog over TLS directly. Terminate TLS in front of the manager (e.g. stunnel) and relay the messages with rsyslog or syslog-ng to the manager's syslog input. Use the default Octet counting (RFC 5425) framing on the forwarder.

Original Event Time

Wazuh sets an alert's timestamp to the moment the event was processed by the analysis engine, not the time the event occurred. A relay in front of the manager (rsyslog forwarding by default) also downgrades the message to RFC 3164, which drops the year and the time zone from the syslog header. Under normal operation the difference is a few seconds. After a forwarder outage, however, the event forwarder replays the entire backlog on recovery, and all replayed alerts then show the recovery time -- even though each message carries the original event time in the data.created_at field.

To index alerts at their original event time instead, override the alert timestamp in the Filebeat ingest pipeline on the Wazuh manager:

  1. Edit /usr/share/filebeat/module/wazuh/alerts/ingest/pipeline.json and insert this processor into the processors array, immediately before the existing date processor. It has to come after the json processor at the top of the array, which is what unpacks the alert and makes data.created_at available:

    {
      "set": {
        "if": "ctx.data?.created_at != null",
        "field": "timestamp",
        "value": "{{data.created_at}}"
      }
    }
    
  2. Reload the pipeline and restart Filebeat:

    filebeat setup --pipelines
    systemctl restart filebeat
    
  3. Verify the pipeline before relying on it. Its failure handler drops any alert it cannot process, so a mistake shows up as missing alerts rather than as an error message. Simulate the pipeline with a document that carries data.created_at; the pipeline name is filebeat-<Filebeat version>-wazuh-alerts-pipeline:

    curl -sk -u admin:<indexer password> -H 'Content-Type: application/json' \
      'https://127.0.0.1:9200/_ingest/pipeline/filebeat-7.10.2-wazuh-alerts-pipeline/_simulate?verbose=true' \
      -d '{"docs":[{"_source":{"message":"{\"timestamp\":\"2026-10-15T17:38:52.953+0200\",\"data\":{\"created_at\":\"2026-10-15T17:38:47.989Z\"}}","fields":{"index_prefix":"wazuh-alerts-4.x-"}}}]}'
    

    Every processor must report "status" : "success". After the set step the document's timestamp must equal the created_at value, and the date_index_name step must show an _index with the event's date (2026.10.15 in this example), not the processing date.

Since both the date processor (which sets @timestamp) and the date_index_name processor (which selects the daily index) read the timestamp field, alerts are indexed at their true event time and land in the correct daily indices. The millisecond precision of created_at is what makes this work with the stock pipeline: date_index_name expects yyyy-MM-dd'T'HH:mm:ss.SSSXX by default and would drop a timestamp without a fractional part.

Notes:

  • The override only applies to alerts that carry data.created_at -- XplicitTrust events. Alerts from other sources are unaffected.
  • Alerts indexed before the change keep their processing time; only new alerts get the event time.
  • The on-disk archives (/var/ossec/logs/alerts/alerts.json) still record the processing time; only the indexed documents change.
  • The processing time is not kept in the indexed document once overridden -- if you need both times, leave the pipeline unmodified and query data.created_at directly instead.
  • Delivery is at-least-once: after an outage an event can arrive twice. Two alerts with the same data.event_id are the same event.

Rule Reference

The ruleset alerts on the event types below. Every other XplicitTrust event, for example one of a type added after you installed the rules, is still stored as an alert of the base rule 100100 at level 3, described by its class and type, so nothing the forwarder delivers is dropped. Update the rules to give a new type its own level and description.

Event types are matched exactly, so web_app_login does not also match web_app_login_denied.

Rules 100128, 100138 and 100139 refine the compliance change rules: a change that switches a protection off or degrades it gets the higher level, any other change of the same type keeps the rule above it. The correlation rules count events per user, device or source address, never across the whole tenant, so unrelated events of different people do not add up.

Rule ID Level Event Description
100100 3 (base) Every XplicitTrust event without a rule of its own
100101 3 logged_in User or client login
100102 3 logged_out User or client logout
100103 10 failed Authentication failed
100104 10 failed_refresh Session refresh failed
100105 12 access_denied Access denied by policy
100106 3 pre_auth_created Pre-authentication session created
100107 3 session_refresh Session refreshed
100108 3 tenant_entered Partner user entered tenant
100109 3 tenant_left Partner user left tenant
100110 5 created Configuration object created
100111 5 updated Configuration object updated
100112 7 deleted Configuration object deleted
100113 5 enabled Configuration object enabled
100114 7 disabled Configuration object disabled
100115 3 fetched User groups fetched from IdP
100116 5 imported User groups imported from IdP
100117 3 synced User groups synced with IdP
100118 3 domains_fetched Domains fetched from IdP (also the older spelling domains fetched)
100119 3 configuration_downloaded Asset configuration downloaded (also configuration downloaded)
100120 3 client_online Client came online
100121 3 client_offline Client went offline
100122 3 asset_online Asset came online
100123 5 asset_offline Asset went offline
100124 5 public_ip_changed Client public IP changed
100125 10 ip_blocked Login blocked by IP allowlist
100126 10 geo_blocked Login blocked by country restriction
100127 10 session_ip_mismatch Session blocked after IP address change
100128 10 security_product_changed A security product is now off, snoozed or expired
100130 7 os_version_changed Client OS version changed
100131 7 security_product_changed Security product changed
100132 7 disk_encryption_changed Disk encryption state changed
100133 7 geolocation_changed Client geolocation changed
100134 5 client_version_changed Client version changed
100135 7 security_provider_changed Security provider changed
100136 7 domain_membership_changed Domain membership changed
100137 7 entra_id_membership_changed Entra ID membership changed
100138 12 disk_encryption_changed Disk encryption turned off
100139 10 security_provider_changed Antivirus or firewall health degraded to poor
100140 12 (correlation) Brute force: 5+ failed logins for the same account in 2 minutes
100141 12 (correlation) 5+ Web App login denials for the same user in 10 minutes
100142 12 (correlation) The same user logged in from two countries within an hour
100143 13 (correlation) The same device denied access 5+ times in 10 minutes
100144 12 (correlation) 5+ logins blocked by IP allowlist or country restriction from the same address in 10 minutes
100145 12 (correlation) The same user deleted 10+ objects in 5 minutes
100146 12 (correlation) 10+ failed logins in 2 minutes across accounts (password spraying, or a failing identity provider)
100147 12 (correlation) Brute force: 5+ failed logins from the same address in 2 minutes
100150 5 asset_isolation_enabled Asset isolation enabled
100151 10 asset_isolation_disabled Asset isolation disabled
100152 5 asset_isolation_rule_added Isolation rule added
100153 5 asset_isolation_rule_changed Isolation rule changed
100154 7 asset_isolation_rule_removed Isolation rule removed
100160 5 new_host New host on a monitored network
100161 5 new_service New service on a monitored host
100162 3 host_disappeared Monitored host disappeared
100163 3 service_closed Monitored service closed
100164 3 scan_completed Network scan completed
100170 7 cpu_usage_high Client CPU usage high
100171 3 cpu_usage_normal Client CPU usage back to normal
100172 7 memory_usage_high Client memory usage high
100173 3 memory_usage_normal Client memory usage back to normal
100180 5 notification.* Notification raised in the console
100181 3 notification_cleared.* Notification resolved
100182 7 notification.broker_failed Broker unreachable
100183 7 notification.split_dns_failed Split DNS broken
100184 7 notification.dhcp_failed DHCP failed
100185 7 notification.event_forwarder_failed Event forwarder failing
100190 3 web_app_login_started Web App login started
100191 3 web_app_login Web App login
100192 10 web_app_login_denied Web App login denied, with the reason
100193 7 web_app_sign_out_all Everyone signed out of a Web App
100194 3 upstream_firewall.credentials_delivered The agent fetched the firewall credentials
100195 5 upstream_firewall.credentials_handed_over Firewall credentials handed over to the agent
100196 3 upstream_firewall.credentials_verified The firewall accepted the agent's credentials
100197 7 upstream_firewall.credentials_expired Credentials expired before the agent fetched them
100198 10 upstream_firewall.credentials_invalid The firewall rejected the agent's credentials
100199 10 upstream_firewall.sync_failed Firewall sync failed
100200 3 upstream_firewall.sync_recovered Firewall sync recovered