Wazuh 5 Security Analytics – Tenant separation of findings with separated event data streams

103 views
Skip to first unread message

Jörg Schin.

unread,
Jul 27, 2026, 9:28:06 AMJul 27
to Wazuh | Mailing List

Hello everyone,

I am currently testing Wazuh 5.x in a multi-tenant environment and I have successfully separated the event data streams per tenant.

I can route events into different data streams based on the agent group, for example:

wazuh-events-v5-system-activity-kunde-1 wazuh-events-v5-system-activity-kunde-2

Example configuration:

root@wazuh5-0:~# cat /var/wazuh-manager/etc/outputs/standard/indexer.yml
name: output/indexer/0
enabled: true

metadata:
  module: wazuh
  title: Indexer data stream outputs
  description: Output integrations events to wazuh-indexer

outputs:
  - first_of:
    - check: >-
        array_contains($wazuh.agent.groups, "kunde-1")
        AND
        string_not_equal($wazuh.integration.category, "unclassified")
      then:
        - wazuh-indexer:
            index: "wazuh-events-v5-${wazuh.integration.category}-kunde-1"

    - check: >-
        array_contains($wazuh.agent.groups, "kunde-2")
        AND
        string_not_equal($wazuh.integration.category, "unclassified")
      then:
        - wazuh-indexer:
            index: "wazuh-events-v5-${wazuh.integration.category}-kunde-2"

I can route events into different data streams based on the agent group, for example:

wazuh-events-v5-system-activity-kunde-1 wazuh-events-v5-system-activity-kunde-2

Example configuration:

- wazuh-indexer: index: "wazuh-events-v5-${wazuh.integration.category}-kunde-1"

Afterwards, I created a custom Security Analytics detector using the default Wazuh FIM integration rules with the following data source:

wazuh-events-v5-system-activity-kunde-1

The detector works correctly. The Wazuh FIM rules trigger as expected and findings are generated.

However, when looking at the generated finding in the Discovery view, the finding is still written into the default Wazuh finding data stream:

_index: .ds-wazuh-findings-v5-system-activity-000001

The finding still contains the reference to the originating event:

event.index: .ds-wazuh-events-v5-system-activity-kunde-1-000001

but the finding itself is not written into a tenant-specific finding data stream.

{
  "_index": ".ds-wazuh-findings-v5-system-activity-000001",
  "_id": "qDNjo58BvyC2NTzl7e05",
  "_version": 1,
  "_score": null,
  "_source": {
    "wazuh": {
      "cluster": {
        "node": "node01",
        "name": "wazuh"
      },
      "protocol": {
        "location": "syscheck",
        "queue": 56
      },
      "agent": {
        "host": {
          "hostname": "linux-system",
          "os": {
            "name": "Debian GNU/Linux",
            "type": "linux",
            "version": "13 (trixie)",
            "platform": "debian"
          },
          "architecture": "x86_64"
        },
        "name": "linux-system",
        "groups": [
          "default",
          "kunde-1"
        ],
        "id": "010",
        "version": "v5.0.0"
      },
      "integration": {
        "name": "wazuh-fim",
        "decoders": [
          "decoder/core-wazuh-message/0",
          "decoder/wazuh-fim/0"
        ],
        "category": "system-activity"
      },
      "rule": {
        "sigma_id": "1aad45c2-84e4-4a24-be34-fac0feaec400",
        "level": "low",
        "compliance": {
          "iso_27001": [
            "A.12.4.1",
            "A.12.6.1",
            "A.16.1.2"
          ],
          "hipaa": [
            "164.308.a.1.ii.D",
            "164.308.a.6",
            "164.312.b"
          ],
          "pci_dss": [
            "6.2",
            "10.5",
            "11.4"
          ],
          "tsc": [
            "A1.2",
            "CC7.2",
            "CC7.3"
          ],
          "nis2": [
            "21.2.a",
            "21.2.e",
            "23"
          ],
          "nist_800_171": [
            "3.3.1",
            "3.3.2",
            "3.4.1",
            "3.14.6",
            "3.14.7"
          ],
          "fedramp": [
            "AU-6",
            "CM-6",
            "SI-4"
          ],
          "nist_800_53": [
            "AU-6",
            "CM-6",
            "SI-4"
          ],
          "cmmc": [
            "AU.L2-3.3.1",
            "CM.L2-3.4.1",
            "SI.L2-3.14.1"
          ],
          "gdpr": [
            "IV_32.1.a",
            "IV_33.1"
          ]
        },
        "mitre": {
          "technique": {
            "name": [
              "Ingress Tool Transfer",
              "Masquerading"
            ],
            "id": [
              "T1105",
              "T1036"
            ]
          },
          "tactic": {
            "name": [
              "Persistence",
              "Stealth"
            ],
            "id": [
              "TA0003",
              "TA0005"
            ]
          }
        },
        "id": "1aad45c2-84e4-4a24-be34-fac0feaec400",
        "title": "Wazuh FIM - File created - /root/test_vim_detector.txt",
        "tags": [
          "low",
          "wazuh-fim",
          "attack.persistence",
          "attack.defense-evasion",
          "attack.t1105",
          "attack.t1036"
        ],
        "status": "stable"
      },
      "event": {
        "id": "6e190421-5a09-4f8a-a43f-4dd95a6767e1"
      },
      "space": {
        "name": "standard"
      }
    },
    "@timestamp": "2026-07-27T11:43:40.743Z",
    "file": {
      "inode": "1310876",
      "mode": "realtime",
      "owner": "root",
      "path": "/root/test_vim_detector.txt",
      "uid": "0",
      "extension": "txt",
      "gid": "0",
      "size": 14,
      "directory": "/root",
      "hash": {
        "sha1": "df2ca6c6f62f0615421625a82bfc839165e6dc2a",
        "sha256": "735c037f88a74f84337e65b50e421db37b0eea1ba29a6a462662da9732be5ee7",
        "md5": "75b4e6ff08c7790f2969e3d626035166"
      },
      "group": "root"
    },
    "related": {
      "user": [
        "root"
      ],
      "hash": [
        "75b4e6ff08c7790f2969e3d626035166",
        "df2ca6c6f62f0615421625a82bfc839165e6dc2a",
        "735c037f88a74f84337e65b50e421db37b0eea1ba29a6a462662da9732be5ee7"
      ]
    },
    "event": {
      "original": "{\"collector\":\"file\",\"module\":\"fim\",\"data\":{\"event\":{\"created\":\"2026-07-27T11:43:40.754Z\",\"type\":\"added\"},\"file\":{\"size\":14,\"permissions\":[\"rw-r--r--\"],\"uid\":\"0\",\"owner\":\"root\",\"gid\":\"0\",\"group\":\"root\",\"inode\":\"1310876\",\"device\":\"2049\",\"mtime\":\"2026-07-27T11:43:40.000Z\",\"hash\":{\"md5\":\"75b4e6ff08c7790f2969e3d626035166\",\"sha1\":\"df2ca6c6f62f0615421625a82bfc839165e6dc2a\",\"sha256\":\"735c037f88a74f84337e65b50e421db37b0eea1ba29a6a462662da9732be5ee7\"},\"path\":\"/root/test_vim_detector.txt\",\"mode\":\"realtime\"}}}",
      "kind": "event",
      "action": "created",
      "index": ".ds-wazuh-events-v5-system-activity-kunde-1-000001",
      "category": [
        "file"
      ],
      "type": [
        "creation"
      ],
      "dataset": "wazuh.fim",
      "doc_id": "KjNjo58BvyC2NTzldOwt",
      "outcome": "success"
    },
    "data_stream": {
      "type": "logs",
      "dataset": "wazuh.fim"
    }
  },
  "fields": {
    "@timestamp": [
      "2026-07-27T11:43:40.743Z"
    ]
  },
  "highlight": {
    "wazuh.agent.name": [
      "@opensearch-dashboards-highlighted-field@linux-system@/opensearch-dashboards-highlighted-field@"
    ]
  },
  "sort": [
    1785152620743
  ]
}

My question is:

Is there a planned or already supported way to make Security Analytics findings inherit the tenant/customer context from the source event and write findings into separate finding data streams?

For example:

wazuh-findings-v5-system-activity-kunde-1 wazuh-findings-v5-system-activity-kunde-2

or through a configurable mapping/routing mechanism between event data streams and finding data streams?

Currently, the event separation works very well, but the generated Security Analytics findings remain in a shared finding stream.

I would like to avoid maintaining custom changes to the Wazuh source code and would like to know if a native multi-tenant approach for separated event and finding streams is planned for future versions.

Thank you very much for your feedback.

Best regards,
Jörg



Message has been deleted

Antonio David Gutiérrez

unread,
Jul 27, 2026, 10:57:06 AMJul 27
to Wazuh | Mailing List

Hi Jörg,

Thanks for the detailed write-up and the example config, it is very clear.

To confirm what you're seeing: this is expected behavior with the current Security Analytics implementation in Wazuh 5.x, not a bug. Your event routing works correctly because it's controlled by your own outputs/indexer.yml rules, which key off wazuh.agent.groups. Findings, however, are generated by the Security Analytics pipeline and are written to wazuh-findings-v5-{category} data streams, where the destination is determined only by the event category (e.g. system-activity), not by agent group or tenant. There's currently no equivalent first_of/conditional routing step on the findings side the way there is for events, so all findings for a given category land in the same shared stream regardless of which tenant-scoped event stream triggered them. The event.index field you noticed is just a reference back to the source event — it isn't used for routing.

On another note: the embedded dashboards also have a related limitation. They use a specific index pattern depending on the dashboard, and this affects tenant separation as well. In 4.x, the dashboard would use whichever index pattern was selected. In 5.x, the embedded dashboards use the standard index pattern, and this can't be changed without modifying the dashboard or the dashboard definition files themselves. In other words, switching to a tenant-specific index pattern isn't currently available in the embedded dashboards — if you need that, you'd have to build a custom dashboard or apply code-level changes.

In the meantime, a couple of options that avoid touching Wazuh source code:

  1. Post-write routing/reindexing: you can handle this by reading the finding's embedded event source (which includes the originating agent group) and using an ingest pipeline or scheduled job to reindex/copy findings into tenant-specific streams based on that field. Not ideal, but it keeps changes outside of core Wazuh.
  2. Open a feature request: since this is a gap in the findings pipeline specifically, it'd be worth filing this as a feature request on wazuh/wazuh-indexer-security-analytics (that's where finding generation/writing logic lives). Referencing your exact example — tenant-scoped events working via outputs/indexer.yml, but findings not inheriting that same context — would make a strong case. Open issue in https://github.com/wazuh/wazuh-indexer-security-analytics/issues/new?template=feature_request.md

For another hand, the embeded dashboards use a specific index pattern (depending on the dashboard), if you want to use another index pattern that only matches the tenant indices, you will need to use a custom dashboard or apply changes to the code of the Wazuh dashboard. In 4.x the dashboard uses the selected index pattern, but in 5.x, the embeded dashboard uses the standard index pattern and it can not be changed without modifying the dashboard o dashboard definition files, in any case, switching of tenant index pattern is not available in the embeded dashboards.

Jörg Schin.

unread,
Jul 28, 2026, 6:02:11 AMJul 28
to Wazuh | Mailing List

Hi Antonio,

thank you very much for the detailed explanation and the incredibly fast response.

Your clarification confirmed exactly what I was seeing during my testing. It's good to know that this is the current expected behavior and not something I had misconfigured.

I also appreciate the suggested workarounds. I'll first try implementing the post-write reindexing approach based on the embedded event information to see whether it fits my use case without requiring any modifications to the Wazuh source code.

At the same time, I have opened a feature request on GitHub describing this use case in more detail. I think native support for tenant-aware findings would be a valuable enhancement for MSSP and other multi-tenant deployments, while still leaving the implementation details to the development team.

Thank you as well for pointing out the current limitation of the embedded dashboards. That explains another behavior I noticed during my testing and helps me better understand the current design.

Once again, thank you for taking the time to investigate this and for your helpful suggestions.

Best regards,

Jörg

Kevin Branch

unread,
Oct 8, 2026, 6:35:59 PM (5 days ago) Oct 8
to Wazuh | Mailing List
I commented on your GitHub issue:
https://github.com/wazuh/wazuh-indexer-security-analytics/issues/276#issuecomment-6070219263 

In summary, I'd say the general Wazuh multi-tenancy data isolation use case is better covered in 5 than in 4.  Split/dynamic routing of findings is indeed not supported in 5, but sounds risky to me anyway.   I see per-tenant ILM index management and even per-tenant index snapshots as desirable, but the risk of data stream explosion and shard exhaustion seems substantial if Wazuh were to support split/dynamic routing of indexes for both events and findings.

DLS tenant isolation based on wazuh.agent.groups should be able to achieve much in this area without splitting up the data streams.  Do you think that might meet your needs?

Kevin Branch
Blue Wolf Ninja

Jörg Schin.

unread,
Oct 9, 2026, 2:44:56 AM (4 days ago) Oct 9
to Wazuh | Mailing List

Hi Kevin,

Thanks for your feedback! I think your suggestion regarding DLS is a good approach, and I agree with your concerns about the risks associated with dynamic routing and the potential for data stream and shard proliferation.

However, some of our customers require their data to be stored in separate indices or data streams, even in a shared environment, due to their internal security policies and compliance requirements. This is one of the reasons why tenant-specific event and finding streams would be valuable for us.

With Wazuh 4.x, I implemented the routing myself using a dedicated Fluent Bit server with Lua scripts. Although this solution did not consume many hardware resources, it introduced additional infrastructure components that we had to maintain and monitor.

I also used Fluent Bit pipelines in Wazuh 4.x for log enrichment. With Wazuh 5, I can now handle this through the normalization process in decoders, which is another significant improvement in my opinion.

The fact that Wazuh 5 handles event routing and log normalization natively is a real plus. It reduces the need for external components and simplifies the overall architecture considerably.

I would love to see the native routing capability extended to findings as well, ideally through a configurable mechanism that allows administrators to choose between shared and tenant-specific finding streams.

That said, regardless of which direction the developers ultimately decide to take, I believe the changes introduced in Wazuh 5 are a significant improvement. Depending on how the functionality evolves, I may need to build a workaround for my specific use case, but that is not to take away from the improvements already made.

I understand the concerns regarding scalability, though, and agree that any implementation should take data stream and shard management into account.

Thanks again for taking the time to share your thoughts!

Best regards,
Jörg

Nguyen Xuan Dong

unread,
Oct 11, 2026, 11:37:14 PM (2 days ago) Oct 11
to Wazuh | Mailing List
Some numbers that might help with the DLS route Kevin suggested. On 5.0.0-rc1, in a test container, I sent the same SSH failure lines for three fake tenants (agent groups tenant-a, tenant-b and tenant-c). All 7 findings landed in .ds-wazuh-findings-v5-system-activity-000001, as Antonio described, but each finding carried wazuh.agent.id, name and groups copied from the source event. A term query on wazuh.agent.groups for tenant-a returned exactly that tenant's 3 findings and nothing from the others, and event.index plus event.doc_id led back to the right source event. So a DLS role keyed on wazuh.agent.groups looks like it has what it needs on the findings side too. I didn't test DLS itself (security plugin was off) or a real agent; in my run the groups came from the event header I sent.

Two things that bit me along the way. The findings API (_plugins/_security_analytics/findings/_search) has no agent or tenant filter and rejects agent_id as an unrecognized parameter, so per-tenant views have to query the findings index directly. And when I added my own tenant field to the agent block (wazuh.agent.tenant), ingest still returned 200 but the indexer dropped the whole event with strict_dynamic_mapping_exception, and the only trace was a WARNING in the manager log. Worth knowing if you enrich in decoders.
Reply all
Reply to author
Forward
0 new messages