January 29, 2026
•
7 min read
•
Alexandra Costea Ifrim
I'm a developer and I have been doing research into SOC operations and daily struggles. During conversations somebody mentioned the Wazuh multi-tenancy setup and I thought that should be automated. So I built a tool to do it.
This article walks through what's actually happening under the hood — first manually, so you understand each piece, then shows you how to automate it with a single command.
Wazuh is a powerful open-source SIEM, but it wasn't designed with multi-tenancy as a first-class feature. When you're an MSP or SOC managing 10, 50, or 100+ customers on a single Wazuh deployment, you need:
Let's build this step by step.
Before we automate, let's look at how it works. We'll use acmecorp (lowercase is recommended for index compatibility) as our example tenant.
Agent groups are the foundation. When you assign an agent to a group, Wazuh tags all logs from that agent with the group name.
# First, authenticate and get a JWT token
TOKEN=$(curl -s -k -X POST "https://WAZUH_HOST:55000/security/user/authenticate" \
-u "wazuh:your-password" | jq -r '.data.token')
# Create the agent group
curl -k -X POST "https://WAZUH_HOST:55000/groups" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"group_id": "acmecorp"}'
This is critical infrastructure that must be configured once on your Wazuh Manager before provisioning any tenants. It tells Filebeat to route alerts into tenant-specific indices based on the agent group.
Ensure your /etc/filebeat/filebeat.yml (on the Wazuh Manager or Indexer) is configured to use the agent.group metadata for dynamic index naming:
output.elasticsearch:
hosts: ["https://localhost:9200"]
# Dynamic routing: Ensures acmecorp logs go to wazuh-alerts-acmecorp-*
index: "wazuh-alerts-%{[agent.group]}-4.x-%{+yyyy.MM.dd}"
systemctl restart filebeat
Without this, all tenants' alerts will land in the same generic index, defeating the entire isolation strategy. This is a one-time setup — once configured, it works for all current and future tenants.
Notification channels tell OpenSearch where to send alerts. Each tenant needs their own channel pointing to their webhook.
curl -k -X POST "https://OPENSEARCH_HOST:9200/_plugins/_notifications/configs" \
-u "admin:your-password" \
-H "Content-Type: application/json" \
-d '{
"config_id": "channel_acmecorp",
"config": {
"name": "AcmeCorp Alerts Channel",
"config_type": "webhook",
"is_enabled": true,
"webhook": {
"url": "https://soc.acmecorp.com/api/wazuh-alerts",
"method": "POST"
}
}
}'
Monitors are scheduled queries. Each tenant needs one that searches only their indices and filters by their agent group.
curl -k -X POST "https://OPENSEARCH_HOST:9200/_plugins/_alerting/monitors" \
-u "admin:your-password" \
-H "Content-Type: application/json" \
-d '{
"name": "AcmeCorp Alert Monitor",
"type": "monitor",
"monitor_type": "query_level_monitor",
"schedule": { "period": { "interval": 1, "unit": "MINUTES" } },
"inputs": [{
"search": {
"indices": ["wazuh-alerts-acmecorp-*"],
"query": {
"bool": {
"must": [
{"match": {"agent.group": "acmecorp"}},
{"range": {"timestamp": {"gte": "{{period_end}}||-1m"}}}
]
}
}
}
}],
"triggers": [{
"name": "Alert Trigger",
"actions": [{
"name": "Send to webhook",
"destination_id": "channel_acmecorp",
"message_template": { "source": "{{ctx.results[0]}}" }
}]
}]
}'
This monitor runs every minute, checks for new alerts in AcmeCorp's indices, and sends them to their webhook.
If tenants access OpenSearch Dashboards directly, you need Document Level Security to ensure they only see their own data.
curl -k -X PUT "https://OPENSEARCH_HOST:9200/_plugins/_security/api/roles/tenant_acmecorp" \
-u "admin:your-password" \
-H "Content-Type: application/json" \
-d '{
"index_permissions": [{
"index_patterns": ["wazuh-alerts-*"],
"dls": "{\"match\": {\"agent.group\": \"acmecorp\"}}",
"allowed_actions": ["read", "search"]
}]
}'
Now that you see the complexity, let's automate it.
The Wazuh Tenant Orchestrator handles Steps 1, 3, 4, and 5 automatically (you still need to configure Filebeat once as shown in Step 2).
git clone https://github.com/lex-org/wazuh-tenant-orchestrator.git
cd wazuh-tenant-orchestrator
poetry install
Configure your .env with your Wazuh and OpenSearch credentials, then provision a tenant.
python main.py --tenant "acmecorp" --webhook "https://soc.acmecorp.com/api/alerts"
The real power for an MSP/SOC comes from integration. The tool includes a built-in REST API designed to plug directly into SOAR platforms like Splunk SOAR, Tines, or Shuffle. This allows you to trigger tenant provisioning automatically when a new customer is marked as "Closed-Won" in your CRM.
All endpoints require an X-API-Key header for security.
| Endpoint | Description |
|----------|-------------|
| POST /api/v1/tenants | Provisions a new tenant (Group, Channel, Monitor, Role) |
| GET /api/v1/tenants/{id} | Checks provisioning status and resource IDs |
| DELETE /api/v1/tenants/{id} | Deprovisions a tenant and cleans up all resources |
--tenant name to ensure it matches OpenSearch index naming rules perfectly.filebeat.yml dynamic index naming.I posted the tool on GitHub and Reddit's netsec community. In 4 days: 82 unique visitors, 32 unique cloners, 8 GitHub stars, and +4,600 views on Reddit. For a niche tool solving a narrow problem in an already-niche SIEM platform, that's validation.
This wasn't about Wazuh specifically. It validated something bigger: small SOC teams are infrastructure-starved, and developers haven't been paying attention. They're not asking for more AI-powered detection engines or another vendor dashboard. They need tools that eliminate operational friction — automation that actually lets them do security work.
GitHub: wazuh-tenant-orchestrator
Sound familiar?
We're building SOCmate with early partner teams. If this resonates with your challenges, let's talk.
Get in touch