FlatQL
Sign in Start free

Use case

Elasticsearch index level access for a team, without cluster users

One shared connection, one set of credentials nobody on the team can read, and a rule per member saying which index patterns they may query and whether they may write. FlatQL checks it on every request.

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 orders to organizations needs both.
  • A raw request such as GET /orders/_search is checked on the path.
  • A pattern in a statement, say "orders-*", is allowed only when a grant pattern covers it entirely. orders* covers orders-*; orders-2026 does 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.

FAQ

Questions

Anything else, write to us.

Does FlatQL create users or roles on the cluster?

No. The connection uses one credential you supply. Members authenticate to FlatQL, and FlatQL decides per request whether the index they named is inside their grant. The cluster sees a single identity.

Can a member see the connection password or API key?

No. Credentials are encrypted server-side and never returned to the browser. Members pick the connection by name.

What happens when a member queries an index outside their grant?

The request is refused before it reaches the cluster with "Index not allowed. You have no access to that index", and SHOW TABLES, autocomplete and the index list only show what the grant covers.

Does this replace Elasticsearch security?

It complements it. Keep the cluster's own security on, give FlatQL one credential with the access the team as a whole needs, and divide that access among people in FlatQL.

Which plan includes roles and the audit log?

Per-index roles and the audit log are Team features. Pro is a single-user licence. See pricing or contact us for team arrangements.

Start on Free. It stays free.

Connect a cluster and run a join in five minutes. No card, no expiry.

Guides

How FlatQL handles the jobs Elasticsearch makes hard.