Multi-tenant mode¶
Multi-tenant mode lets one Signalith install serve several separate customers, for example as a managed security provider. Each customer is a tenant, with its own events, findings, investigations, detections, dashboards, hunting notebooks, lists, playbooks, notifications, retention and agent tokens, and its own users.
Being built
Multi-tenant mode arrives in steps. This version runs tenants on a server that does not share its users with other servers; the Settings page and the tenant picker come next. An install without tenants works exactly as before.
Who is who¶
| Belongs to | Can | |
|---|---|---|
| Provider users | No tenant: the built-in admin, every user who existed before, and directory and single sign-on users | Work in the default tenant, switch to any tenant, and use the pages about the server itself (Settings, Servers, Security, Audit, sign-in setup, tenants). Their role applies in whichever tenant they are in. |
| Tenant users | One tenant | Work only in their tenant, with their role there. A tenant admin manages the tenant's detections, dashboards, agent tokens and so on, but never the server itself. |
The default tenant is the data an install had before it had tenants. It keeps the configured index prefix and folders, so nothing moves when you upgrade.
How tenants are kept apart¶
- Indices. Tenant
acmeof an install whoseopensearch.index_prefixissignalithstores its data in indices that start withsignalith.t.acme, such assignalith.t.acme-authentication-2026.10.16andsignalith.t.acme_findings. The default tenant's start withsignalith-andsignalith_. Tenant IDs are letters and digits only, so no index pattern of one tenant can match another tenant's indices, and a search'sindex=must start with the tenant's own prefix.opensearch.index_prefixmay not contain.t.. - Files. Each tenant's files are in
tenants/<id>/in the state folder: its lookups, detections, dashboards, archives, response scripts and settings files. - Its own server. Signalith runs each tenant's data with a server of its own inside the
same process, built from the tenant's own settings. Settings saved on the Settings page
that apply at once (the AI model, search limits and so on) apply to every tenant too. A tenant's request is answered by
that server only, with that tenant's stores, so nothing can fall back to another
tenant's data. If it is not running, the request is refused (
503, codetenant_unavailable). - Agents. A tenant's agents send with agent tokens made in that tenant (Add data, while
in the tenant). Their events go to the tenant's indices only. Tokens in
signalith.yamland the environment are the default tenant's. - Sign-in. Everyone signs in on the same page. A tenant user's session can only ever
work in their tenant. A provider analyst or admin can switch to any tenant (viewers
cannot), and every switch is in the audit log. If the tenant a provider user switched
to is turned off, or their role is lowered to viewer, their next request is refused
(
409, codetenant_unavailable) and the session goes back to the default tenant, never to another tenant's data silently. - Audit. Every change made in a tenant other than the default one is in the audit log
with
tenant=<id>, and so is every log line of that tenant's server, searches included. - Users. A tenant's admins see and manage only their tenant's users, and the users they add belong to their tenant. Only provider users add provider users. User names are unique across the install. A tenant's admins do not count toward the install's last admin.
- Requests to other addresses. Threat intel feeds, notification channels and response action settings make the server send requests to addresses someone types in. They are kept per tenant, but only provider users can change or test them, so a tenant's users can never make the server reach the provider's own network.
- Pages about the server. Tenant users get
403(codeprovider_only) from every route about the server itself, whatever their role, and see only whether things work, never how the server runs (see Roles of every route). - Turning a tenant off. Its users cannot sign in (they are told their organization's access is turned off), its agents are refused, its background jobs stop, and its data stays.
Tenant IDs are 2 to 24 lower-case letters and digits, starting with a letter, such as
acme or contoso2. default is the default tenant's.
Limits of this version¶
- Tenants work on a server that keeps its users in its own files. Once servers share their users in OpenSearch, which a server running the background jobs sets up on its own, tenants cannot be added and provider users cannot switch; the next version lifts this.
- Directory (LDAP) and single sign-on users are provider users.
- Syslog, Microsoft 365, Okta, vulnerability scans, network sensors, osquery hosts, API tokens and compliance notes are the default tenant's. Content updates of the detections go to the default tenant; tenants get the detections built into Signalith.
- The buttons in Slack and Teams messages work in the default tenant only: only provider users link chat accounts, and a click on a tenant's message is refused.
- Custom log parsers are the provider's: only provider admins change them, and they read every tenant's records alike.
- Downgrading to a version without multi-tenant mode is not supported once there are tenant users: an older version would read them as provider users.
API¶
GET /api/tenants lists the tenants a provider user can switch to, the default tenant
first, and the tenant the request works in:
{"tenants": [{"id": "default", "name": "Default", "default": true},
{"id": "acme", "name": "Acme"}],
"active": "default", "enabled": true}
POST /api/tenants/switch with {"tenant": "acme"} moves the signed-in session to that
tenant (404 for a tenant that does not exist or is turned off). Only a browser session
can switch, as an analyst or admin: API tokens and HTTP Basic sign-ins work in the default
tenant. Both routes are for provider users only.
POST /api/tenants with {"id": "acme", "name": "Acme Corp"} adds a tenant and starts its
data. PATCH /api/tenants/{id} with {"name": "..."} renames it, and with
{"disabled": true} turns it off (false on again). Both are for the provider's admins,
who confirm their password if they signed in a while ago.
GET /api/me says which tenant the user belongs to ("tenant", empty for provider users)
and which they work in now ("active_tenant").