Giving five analysts access to a cluster usually ends one of two ways: five cluster users with hand-written role documents, or one admin API key in a shared vault. The first is correct and slow; the second is fast and uncomfortable. FlatQL takes a third route. Credentials live in a shared connection that members never see, and a grant per member says which index patterns they can read and whether they can write. Every request, SQL or raw DSL, is checked against that grant before anything leaves for the cluster.
How a grant works#
A grant is a connection, a list of index patterns and a write flag. Patterns use shell-style wildcards, so orders* covers orders, orders-2026 and orders_archive, and logs-* covers every daily log index. A member can hold a direct grant, or inherit grants from one or more named roles such as "Analyst" or "Support", which an admin defines once and attaches to people. The patterns from every source are merged; write is allowed if any source allows it. Owners and admins always have full access, and a member with no grant on a connection does not see that connection at all.
Enforcement is in FlatQL's request path, not on the cluster:
- A SQL statement is parsed and every table name is checked. A statement that joins
orderstoorganizationsneeds both. - A raw request such as
GET /orders/_searchis checked on the path. - A pattern in a statement, say
"orders-*", is allowed only when a grant pattern covers it entirely.orders*coversorders-*;orders-2026does not. SHOW TABLES,_cat/indices, alias lists and autocomplete are filtered to the grant, so members do not see names they cannot query.- A write (document edit, bulk action, index creation, data load) needs the write flag, or it fails with "Read-only access".
The result is one cluster identity doing the work and a per-person boundary around it. The teams page covers inviting members, building roles and the Access graph, which draws system roles, named roles and people as nodes so you can see who inherits what.
Production stays read-only#
Connections carry an environment tag and a safety profile. A connection marked read-only accepts only read requests: _search, _count, _sql, _cat, _mapping, _field_caps, point-in-time and scroll handling, and the like. Anything else is refused until an admin turns writes on for that connection. For a production connection the Guard-rails settings can make read-only the starting state, with "confirm destructive" and "block wildcard delete" on by default so a DELETE /orders* is blocked outright and a _delete_by_query asks for confirmation.
This applies on top of the per-member grant. A member with write on staging-* still cannot write to a production connection that is locked, and an admin with full access still gets the confirmation prompt on a destructive request. Security lists everything FlatQL does and does not store.
A member's query and its refusal#
Take a support engineer with a role that grants orders* on the "Prod EU" connection, read-only. They run:
SELECT customer_id, COUNT(*) AS orders, SUM(amount) AS spent
FROM orders
WHERE status = 'paid' AND placed_at >= '2026-10-01'
GROUP BY customer_id
ORDER BY spent DESC
LIMIT 25;
FlatQL extracts orders from the statement, matches it against orders*, and lets it through. The grouping is pushed to the cluster as a composite aggregation; POST /orders/_search carries this body:
{
"size": 0,
"query": {
"bool": {
"filter": [
{ "term": { "status": "paid" } },
{ "range": { "placed_at": { "gte": "2026-10-01" } } }
]
}
},
"aggs": {
"g": {
"composite": {
"size": 1000,
"sources": [
{ "k0": { "terms": { "field": "customer_id", "missing_bucket": true } } }
]
},
"aggs": {
"m1": { "sum": { "field": "amount" } }
}
}
}
}
The engine sorts the buckets by spent and keeps 25. The same engineer then tries SELECT * FROM organizations LIMIT 10. FlatQL stops the statement before any request is built and answers with a 403: "Index not allowed. You have no access to organizations", with the hint to ask a team admin to grant it under Team. The cluster never sees the attempt. A SELECT against "*" is refused the same way, because no grant pattern covers it. And a POST /orders/_doc from the raw request editor fails with "Read-only access. Your access to this connection is read-only", since the role has no write flag. The per-index access control post walks through the design.
Audit log and history#
Every administrative action is written to the audit log with who did it, when, the target and the detail: invitations, role changes, grant changes (with the connection, patterns and write flag), role creation and deletion, member disabling, exports, bulk actions, document edits and data loads. Owners and admins read it under Team. Alongside it, the query history records every statement run on every connection with the user, duration and row count, which answers the other half of the access question: not only who could query what, but who did.
Roles, per-index grants and the audit log are Team features; Pro is a single-user licence. The pricing page lists the plans.
Without FlatQL#
Cluster users and roles. Elasticsearch security and the OpenSearch security plugin both let you define roles with index patterns and privileges, then map users to them. This is the right foundation for machine identities and for applications, and FlatQL's own connection credential should be scoped this way. For people, it means creating and rotating a cluster user per person, editing role documents when someone moves team, and giving each person a credential they can paste into any client. Kibana Spaces add a layer for dashboards but do not change which indices a user's credential can reach; the Kibana comparison has more.
One shared admin key. Everyone pastes the same API key into Cerebro, elasticsearch-head or a curl alias. Nothing distinguishes one person's query from another's, the key reaches every index, and revoking it means re-issuing to everyone.
A proxy. A reverse proxy in front of the cluster can enforce path-based rules per user. It needs to be built, hosted and kept in step with index naming, and it cannot see inside a SQL statement.
Set up the first grant#
Create an account, connect a cluster with a credential that has the access the whole team needs, invite the first member, and give them one pattern. Plans are on the pricing page.