Skip to content

Security overview

How Lakana decides who may do what, where it keeps secrets, what it records, and how a query is kept inside what its author may read. This page is the short version for people who use or run an instance; each part links to the page with the steps and the exact rules.

Who may do what

Every request is made by a principal: a person or a service account, signed in with a token. A principal may do something only because a grant says so. A grant gives an action, or a role (a named set of actions such as VIEWER, ANALYST, DATA_SCIENTIST, DATA_ENGINEER or ADMIN), to a principal or a group, on an object: the instance, an environment, a namespace or a table. Without a grant the answer is no, and the refusal is recorded.

Where secrets live

A secret (a storage key, a repository token, a package index password, a webhook address) is stored encrypted in the instance's database, under a master key that you set with LAKANA_SECRETS__MASTER_KEY. No route, command or page returns its value again; to change it you set it again. A secret is bound to one use, for example one package index or one repository, and Lakana refuses it for anything else, so the person who registers an index cannot send a secret to a host of their choice. A secret is decrypted only where it is used, and only while it is used.

Keep the master key and the .env file of a Compose bundle with the people who run the instance: whoever holds them holds every secret, and a lost master key makes every stored secret unrecoverable. The steps are in Manage secrets and Use a private package index.

What the audit log records

The audit log is an append-only list of events: who signed in or failed to, what was granted and revoked, who created, rotated or deleted a secret, who changed an environment, a filter or a mask, who deployed, ran or undid something, and every refusal. A completed query is recorded with its SQL text. For that reason reading the log needs its own right, READ_AUDIT_LOG, which the ADMIN role holds, and seeing query text and column values in other people's events (the SQL text of a query, the watermark of an incremental write) needs one more, READ_QUERY_TEXT, which no role holds: an administrator sees a query's SHA-256 and length instead, and of a watermark only that it is hidden, until someone grants the right to them by name. Your own events always show in full. An operator can also store less text in new events (LAKANA_AUDIT__QUERY_TEXT: full, hash or off); see Other people's query text and column values. lakana audit list filters it by event type, environment, principal and time (the command). To keep the log bounded, repeated refusals of one principal are counted after a number per minute that an operator sets (LAKANA_DENIAL_AUDIT__PER_PRINCIPAL_PER_MINUTE, settings). For the same reason a successful sign-in is recorded once per token every 15 minutes, not on every request: a screen that refreshes itself every few seconds would otherwise write thousands of AUTHENTICATION_SUCCEEDED events a day. The next event of the token says in detail.suppressed how many sign-ins were left out. Each token counts on its own, and its first use, a new token and the first use after a refused attempt always write an event. Each server process counts on its own, so a deployment with two server processes can write two events per window. Failed sign-ins are recorded as before. LAKANA_AUDIT__SIGN_IN_WINDOW_MINUTES sets the window (0 records every request; settings).

Seeing other people's activity is recorded too. When a manager lists other people's queries (lakana query list), an administrator sees who read a table (lakana table health), or someone reads other people's events in the audit log itself (lakana audit list), the log gets an ACTIVITY_VIEWED event: who looked, at which environment or table, with which filters, and how many rows of how many other people the answer showed, never their names or their SQL. It is written once per person and view per hour, so paging, other filters and a screen that refreshes itself add nothing; a filter on one person (--submitter) is its own view. Looking only at your own activity records nothing. If the event cannot be written, the read is refused rather than served unrecorded. The events and the setting that turns them off are in the lists reference.

How a query is kept inside its limits

Policies are applied by rewriting the SQL a person submits, but Lakana does not rely on the rewrite alone, and it does not try to forbid dangerous DuckDB functions one by one. A query is kept inside what its author may read by credential scoping:

  • The worker works out, from the catalog, the files the author may read, and gives DuckDB a time-limited link to exactly those files.
  • The process that runs the query holds no storage credential, no database address and no token, and the local file system is closed to it. A query that reads a path it was not given has nothing to read it with.
  • Limits on time, memory, rows and bytes stop a query that asks for too much (Stop a query).

A query only reads. It is one SELECT (also with WITH, or a set operation such as UNION), or a VALUES list such as VALUES (1, 'a'), (2, 'b'), whose columns are named col0, col1 and so on. A statement that changes data or the session is refused wherever it sits, also inside a CTE or a subquery, and so is a query that reads files directly instead of a table: register the data as a table and select from it.

What this does not protect

  • One instance is one trust domain for Python code. A Python task runs as the worker's operating system user, so someone who may deploy Python code can read what the worker reads, filters and masks included. Deploy Python only from people you would trust with the stored data, or run one instance per group that does not trust the other (why).
  • Files read outside Lakana. A filter or a mask guards Lakana's own query path, not the files in the object storage. Keep the storage credentials with the people who run the instance.
  • Local mode. With storage on the local disk there is no cloud credential to scope, so a local instance relies on the other checks alone. Use a team deployment (Deploying with Compose) for more than one person.

For the connections an instance makes, see What leaves the machine.