Release Notes¶
Highlights for each Lakekeeper release. For the full commit-level
changelog, see the GitHub Releases
or CHANGELOG.md.
For Lakekeeper+ releases, see the Lakekeeper+ Release Notes.
📬 Get notified about new Lakekeeper releases
Subscribe by email to hear about new releases, or Watch → Releases on GitHub.
v0.14.0 (2026-10-07)¶
Highlights¶
- Grants API. One API lists, grants and revokes permissions on every object, such as projects, warehouses, namespaces, tables, views and tag definitions. It works the same way with every authorizer. With OpenFGA, it uses the permissions you already have, so nothing needs to be migrated. The API is in preview. See Grants API (#1945, #1953, #1957, #2085).
- Governance tags. Define tags for a project, attach them to warehouses, namespaces, tables, views, generic tables and columns, and find every object a tag is attached to. Tags are for classification and discovery. With Cedar in Lakekeeper Plus, policies can also use them to decide who can access what. The API is in preview. See Governance Tags (#1914, #1921, #1944, #1951).
- Move and rename namespaces. You can move a namespace to another parent in the same warehouse, rename it, or both, in the UI or with the management API. Everything in it moves with it, and files stay where they are. A namespace that contains other namespaces cannot be moved or renamed. See Moving and renaming namespaces (#1950).
Features¶
- Faster on large catalogs. With OpenFGA, checking access to a warehouse, which most catalog requests do, needs far fewer OpenFGA queries. On large warehouses, this check could take too long and fail with
503. Creating tables and views, finding namespaces and listing tables are also much faster on large warehouses (#2029, #2005, #1958). - Storage validation. You can check a warehouse's storage before you create the warehouse or change its storage, and you can check an existing warehouse at any time. Lakekeeper reports the result of each check. It also warns about problems that are hard to find later, such as a bucket CORS policy that stops the query engine in the UI from reading data. See Storage Validation (#1936, #2047, #2049, #2061).
- STACKIT Object Storage. A new
stackitstorage type asks only for the settings that STACKIT needs. Storage validation warns when other credentials groups in the same STACKIT project can also access the bucket. See STACKIT (#1978, #2045, #2050, #2055). - Alibaba Cloud OSS (beta). Warehouses can use Alibaba Cloud OSS, with temporary credentials from Alibaba Cloud STS (thanks @yoogoc). See Alibaba Cloud OSS (#1894).
- Required token claims. You can set rules for the claims in a token, and Lakekeeper rejects tokens that do not match them. For example, you can allow only the users of one organization when several organizations share an identity provider. The scope check (
LAKEKEEPER__OPENID_SCOPE) also reads thescpclaim, which Microsoft Entra ID uses. See Required claims (#2001). - Names for service accounts. When a token has no name claim,
LAKEKEEPER__OPENID_DISPLAY_NAME_TEMPLATEbuilds a name from its other claims, for exampleService Account {email}. It applies to users that are registered after you set it (#1939). - Headless workers. With
LAKEKEEPER__SERVE_HTTP_API=false,lakekeeper servedoes not serve the API or the UI. It only runs background tasks and answers health checks, so you can scale task workers separately from the API. See Task Queues (#2065). - Postgres schema.
LAKEKEEPER__PG_SCHEMAkeeps Lakekeeper's own database tables in a schema other thanpublic(thanks @vyruss). See Using a non-public Postgres schema (#1926). - Default project ID.
LAKEKEEPER__DEFAULT_PROJECT_IDsets the project that Lakekeeper uses when a request does not name one. Before, this was always00000000-0000-0000-0000-000000000000(thanks @bolma-lila) (#1930). - Choose the warehouse ID. When you create a warehouse, you can set its ID yourself, so tools such as Terraform know it in advance (#1983).
- Versioned audit log. Every audit record states its format version,
1.0in this release, and a published JSON Schema describes all records. New records show grant changes and retried requests that Lakekeeper answered with the result of the first request. Authorization records also contain the client's user agent. Existing parsers must be updated; see Audit log format below. See Audit log format (#1973, #2074, #1945, #1962, #1995). - User emails and role sources in the audit log. Audit records can carry the email of the users they name. This is off by default; turn it on with
LAKEKEEPER__AUDIT__TRACING__INCLUDE_USER_EMAIL=true. Roles on audit records also carry their provider and their ID there, for example the LDAP group behind a role. To leave that ID out, setLAKEKEEPER__AUDIT__TRACING__INCLUDE_ROLE_SOURCE_ID=false. See User Emails on Audit Records (#2091). - Remote signing follows the latest Iceberg REST specification. Lakekeeper supports the new way to configure remote signing that the specification now defines. Current clients keep working without changes. See Remote Signing (#1961).
- Limit for
referenced-by. Query engines can send the list of views through which they load a table, so that Lakekeeper can check view permissions. This list may contain at most 10 views by default; longer lists are rejected. Change the limit withLAKEKEEPER__REFERENCED_BY__MAX_NESTING_DEPTH. See Chain depth (#1980). - Empty namespace directories removed on ADLS and OneLake. Dropping a namespace with
purge=truealso removes its directory if it is empty (thanks @dadavidtseng). See Empty namespace directories (#1858). - Iceberg v3
unknowntype. Table and view schemas may use theunknowntype. Before, Lakekeeper rejected these schemas (#2063). - Console v0.26.0. New governance and projects pages. With OpenFGA, a Grants tab replaces the Permissions tab. Namespaces can be moved and renamed, STACKIT and Alibaba Cloud OSS warehouses can be added, new warehouses are checked before they are created, and tags and properties can be edited on the pages that show them. Fonts and the extensions of the query engine in the UI are bundled, so the console works without internet access (#1890, #1997, #2061, #2082, #2094).
Bug Fixes¶
- Remote signing and the cleanup of old metadata files check more strictly that they stay inside the table location.
- Idempotency keys. If a client reuses an
Idempotency-Keyfor a different operation, Lakekeeper rejects the request with400. Before, it answered with success without doing anything. Retries of staged table creation and recursive namespace drops now return the result of the first request. With a read replica, a quick retry could fail with409or404; Lakekeeper now checks the key on the primary database (#1959, #1981). - Cached table loads. A client that caches tables now gets a fresh copy when the warehouse settings change or when it asks for a different kind of storage access. Before, it could be told that its old copy was still valid. After the upgrade, these clients load each table in full once (#1946, #1966).
- Location checks. Lakekeeper always refuses to create a table or view inside another one's location. Before, the check could miss this with trailing slashes, backslashes or two creates at the same time. A view commit that sets the view's current location again, with a slash at the end, no longer deletes the view's files (#2005).
- Registering a table uses the access mode the client asks for (vended credentials or remote signing), returns credentials the same way as loading a table, and enforces the warehouse's allowed table format versions (#1954, #1964).
- Dropping a namespace no longer fails when a view uses it as its default namespace (#2062).
- A new namespace uses the upper and lower case of its parent. For example, creating
a.b.cunder the existinga.Bstoresa.B.c.lakekeeper migraterepairs existing namespaces, tables and views once (#1977, #2014). - Tables can no longer set
write.object-storage.pathorwrite.folder-storage.path. These old names ofwrite.data.pathmake engines write data files outside the table location. Tables that already have them can remove them (#2044). - Deleting a warehouse also deletes its storage credential from the secret store (#1985).
- S3 warehouses with
sts-enabledandaws-kms-key-arncan be created and updated when the bucket's default KMS key is a different one (thanks @teamrunninglake) (#2036). - GCS bucket names may contain underscores, and names with dots may be up to 222 characters long, as GCS allows (thanks @fallintoplace) (#2080, #2081).
- Creating a warehouse or changing its storage fails with
412 StorageProbeTimeoutwhen the storage does not answer in time, and the error names the check that did not finish. Before, the request failed with408and no details. Connections to storage now time out after 5 seconds instead of 10 (#2047). - When Lakekeeper cannot get temporary credentials from STS, the error shows what STS answered. When OneLake rejects vended SAS tokens, the error names the likely cause, usually a Fabric workspace setting (#2055, #2059).
- When the OPA bridge cannot find a warehouse's ID, for example because the warehouse did not exist yet, it looks the ID up again within 30 seconds. Before, it kept the failed result. Deploy the updated policies from
authz/opa-bridge/policiesto get this fix (thanks @sivakumar-mahalingam) (#2052). - With OpenFGA, a denied request to the permissions API returns
403 Forbiddeninstead of401 Unauthorized(#2087). - In the audit log, the authorization record for a change to permissions, roles or tags is written before the change is applied. An
allowedrecord means that the caller was allowed to try the change, not that it succeeded (thanks @AndreaBozzo) (#1924, #1934). - With
LAKEKEEPER__ENABLE_DEFAULT_PROJECT=falseand roles read from the token, requests that do not name a project no longer fail with400 MissingProjectId(#2071). - A server with every
LAKEKEEPER__TASK_*_WORKERSset to0, for example an API-only server next to headless workers, no longer stops right after it starts (#2065). - Management API filters that take several values, such as the warehouse status filter, work. Before, any request that used them failed with
400(#1942). - Listing namespaces, tables and views no longer keeps a database connection open while permissions are checked. Before, many list requests at the same time could use up the read connections and make other requests wait (#2058).
Breaking Changes¶
- Stricter OpenID settings. Lakekeeper refuses to start when an OpenID setting cannot work as written. Check for these cases before you upgrade. See Authentication (#2001).
- A scope is empty or contains a space; only one scope is allowed.
LAKEKEEPER__OPENID_SCOPEalso requiresLAKEKEEPER__OPENID_PROVIDER_URI. - A setting under
LAKEKEEPER__OPENID_PROVIDERS__<IDP_ID>__has an unknown name, for example because of a typo. - A provider checks a scope or required claims, and its
REQUIRE_CONNECTED_ON_STARTUPisfalse. - Two providers accept the same issuer, and the later one checks a scope or required claims. The main provider comes first, then the others in alphabetical order of their ID.
- Kubernetes authentication is enabled without
LAKEKEEPER__KUBERNETES_AUTHENTICATION_AUDIENCE, and a provider checks a scope or required claims.
- A scope is empty or contains a space; only one scope is allowed.
- OpenFGA: only
manage_grantscan revoke. A user withpass_grantscan still grant privileges they hold, but cannot revoke privileges (#1991). - OpenFGA: tools that query OpenFGA directly. In model v4.13, the relations
describe,select,createandmodifycontain only the grants made on that object. Privileges that come from ownership, a stronger privilege, an admin role or a grant on a parent object are in the new_effectiverelations, such asselect_effective. The Lakekeeper API is not affected (#2029). - OpenFGA: moving tables, views and generic tables. To rename a table, view or generic table into another namespace, a user needs
manage_grantsandmodifyon it, andcreateon the destination namespace. If the destination namespace uses managed access, the user also needsmanage_grantsthere. Renames within a namespace need the same privileges as before. As with grants, moving tables, views, generic tables and namespaces, and turning managed access of a namespace on or off, are not possible while acting under an assumed role (#2095). - Cancelling a soft-deletion task needs
undrop. Cancelling the soft-deletion task of a table, view or generic table restores it, so the user also needsundropon it (#2095). - Batch check:
update_storage_credentialremoved. A request toPOST /management/v1/action/batch-checkthat contains the warehouse actionupdate_storage_credentialfails with422. Checkupdate_storageinstead, which the storage-credential endpoint already required (#1936). - Code that builds on Lakekeeper's crates. Many public traits and types changed, among them
Authorizer,CatalogStore,AdmissionGateandLakekeeperStorage. Update your own implementations. Prebuilt binaries and images are not affected. - Audit log format. Existing parsers of the audit log must be updated. See Audit log format below.
Upgrade Notes¶
- Images only on Quay. Starting with this release, Lakekeeper images are no longer published to Docker Hub (
vakamo/lakekeeper). Usequay.io/lakekeeper/cataloginstead, for examplequay.io/lakekeeper/catalog:v0.14.0(#2090). - Plan downtime for the migration, and back up the database first.
lakekeeper migratemoves table and view schemas to a new storage format, which can take several minutes on large catalogs. While it runs, changes to tables and views wait, and in its last part reads wait too. Stop all 0.13 servers before you run it: their requests can make the migration fail, and it then rolls back completely. With the Helm chart, the migration job runs while the old pods still serve, so scale the deployment to zero beforehelm upgrade. After the migration, 0.13 servers refuse to start. To go back to 0.13, restore the backup (#1897). - Tables or views that would share a location stop the migration. The migration removes trailing slashes from stored table and view locations. If two entries then have the same location, for example
s3://b/tands3://b/t/,migratefails near the end withTrimWouldShareTabularLocationsand rolls back. Before you upgrade, run this query on the database. It must return no rows:SELECT warehouse_id, rtrim(fs_location, '/'), array_agg(tabular_id || ' ' || typ || ' ' || array_to_string(tabular_namespace_name, '.') || '.' || name || CASE WHEN deleted_at IS NOT NULL THEN ' (dropped)' ELSE '' END) FROM tabular GROUP BY 1, 2 HAVING count(*) > 1 AND count(DISTINCT fs_location) > 1;Both entries in a row point to the same files. On 0.13, remove one of them without deleting files:DELETE /catalog/v1/{prefix}/namespaces/{namespace}/tables/{table}?purgeRequested=false&force=true(use/views/{view}for a view). A normalDROP TABLEwould also delete the files of the other entry. If the entry is marked(dropped), first restore it withPOST /management/v1/warehouse/{warehouse_id}/deleted-tabulars/undrop(#2005). - OpenFGA model v4.13.
lakekeeper migrateinstalls model v4.13, and servers of this version always use it. IfLAKEKEEPER__OPENFGA__AUTHORIZATION_MODEL_VERSIONis set,migrateskips OpenFGA, and servers do not start until v4.13 is installed. Remove this setting before you runmigrate(#2029, #2095). - Update the OPA bridge together with Lakekeeper. Deploy the OPA bridge policies of this release together with this server. When Trino renames a table or view into another schema, the policies now also check
moveandaccept_moved_tabular, which older servers do not know (#2095). - Larger requests by default.
LAKEKEEPER__MAX_REQUEST_BODY_SIZEdefaults to 32 MiB instead of 2 MiB. Set it to2097152to keep the old limit (#1974). - Task queue renamed. The
tabular_expirationqueue is now calledsoft_deletion. RenameLAKEKEEPER__TASK_TABULAR_EXPIRATION_WORKERStoLAKEKEEPER__TASK_SOFT_DELETION_WORKERSbefore you upgrade. If the old name is set, Lakekeeper does not start and reportsduplicate field `task_soft_deletion_workers`. The task-queue config API still acceptstabular_expirationin its path (#1881). - Role members cache removed. Lakekeeper reads role members from the database. The
LAKEKEEPER__CACHE__ROLE_MEMBERS__*settings have no effect and log a warning at startup; remove them. Cache metrics withcache_type="role_members"are no longer reported (#2072). - Building from source requires Rust 1.95 or newer (#2063).
Audit log format¶
Breaking changes — an existing parser must be updated:
-
A field is absent when it has no value, and an empty list or map is written out. Nothing in an audit record is
null.A field the request did not supply is left out. A policy factor without a name, for example, has no
namekey instead of"name": null.An empty list or map is a value and is always written once an action can carry it:
before {"action_name": "commit"} after {"action_name": "commit", "updated_properties": {}, "removed_properties": [], "target_refs": [], "update_kinds": []}This applies to
properties,updated_properties,removed_properties,source,destination,target_refsandupdate_kindsin an action, and toauthorizations[].determined_byanderror.stack. A flag such asforce,purgeorrecursiveis also always written, astrueorfalse.What to do: read "none" from an empty value, not from a missing key. Keys that are genuinely optional, such as a name or an id, still need a presence check.
-
Authorization records always carry
actionsandentitiesas lists. The singularactionandentityfields are gone.before "action": {…} or "actions": [{…}, {…}] after "actions": [{…}] before .entity.namespace .action.action_name after .entities[0].namespace .actions[0].action_nameEach entry of
authorizations[]still carries a singularactionandentity.What to do: read
actionsandentitiesas lists, and iterate: a record can carry more than one. -
A
contextvalue has its own JSON type. It is no longer always a string.before "force": "true" "dry_run": "true" "writes": "2" after "force": true "dry_run": false "writes": 2- Booleans:
force,purge,recursive,dry_runandallow_partialin an action, andself_provisioningandself_readincontext. - Numbers:
writesanddeletes. - Principals:
principalsonapply_grantsandprincipalonread_subtree_grantsandrevoke_subtree_grantsare{"user": …}or{"role": …}, the formcontext.principalhas on grant records. A subtree request states withprincipal_scope(everyorone) whether it names one principal;principalis present only forone. - Strings, lists and maps keep their type.
A product that plugs into Lakekeeper may add
contextkeys that hold an object. Its schema gives the object's definition.What to do: compare a flag with
true, not with"true". Read each key's type from the schema. - Booleans:
-
The entries of
authorizations[].determined_byhave the shape the management API returns from a permission check, and there are two new kinds.before {"Policy": {"policy_id": "p-42", "effect": {"Permit": []}, "source": "cedar"}} after {"type": "policy", "policy-id": "p-42", "effect": "permit", "source": "cedar"}- The kind is in
type. Field names are kebab-case, as in the API.effectispermitorforbid. {"type": "system-authority"}: a built-in authority, not a configured policy, decided the allow. It may carrysourceandreason.{"type": "admission-gate"}: an admission gate would refuse the user. It carriesgate, andcheckwhen the gate names one.
What to do: switch on
type. The same code can parse these entries and a/checkresponse. - The kind is in
-
A request that names nothing to check records empty lists. The made-up
unknownentry is gone.A batch check with
checks: [], and a transaction commit with no table changes, recorded oneauthorizationsentry withentity_typeunknown, and for the commitaction_nameunknowntoo.before "authorizations": [{"action": {"action_name": "unknown"}, "entity": {"entity_type": "unknown"}, …}] after "actions": [], "entities": [], "authorizations": []decisionstill carries the outcome.What to do: stop matching
unknowninentity_typeandaction_name, and handle an emptyauthorizationslist. -
Every key and every value this log names itself is spelled
snake_case, andfailure_reasonis a plain string.Keys that carried a hyphen now carry an underscore:
- in an
entity:generic_table,generic_table_id,namespace_id,project_id,role_id,role_provider_id,role_source_id,server_id,table_id,table_location,tag_definition_id,task_id,user_id,view_id,warehouse_id - in an action:
allow_partial,created_before,dry_run,removed_properties,target_refs,update_kinds,updated_properties - in an authorization record's
context:invoked_by,self_provisioning,self_read - on an
authorizations[]entry:for_principal
Values:
actor_type before assumed-role after assumed_role before lakekeeper-internal after lakekeeper_internal failure_reason before {"ActionForbidden": []} after "action_forbidden"Every
failure_reasonvalue follows the same pattern:action_forbidden,resource_not_found,cannot_see_resource,internal_authorization_error,internal_catalog_error,invalid_request_data.Three sets keep the spelling of the vocabulary they come from:
entity_typeandresource_type(generic-table,tag-definition, as in the management API) andupdate_kinds(Iceberg's update names, such asadd-schema). The objects indetermined_bykeep the management API's field names,policy-idincluded.What to do: rename these keys and values in every query. A query on the old spelling matches nothing and raises no error, so check for the new names explicitly. In
jq,.failure_reason | keys[0]becomes.failure_reason. - in an
-
A role on a record carries its
provider_idandsource_idnext to its id.provider_idis always shown;LAKEKEEPER__AUDIT__TRACING__INCLUDE_ROLE_SOURCE_ID=falseleavessource_idout.before {"role": "1f7b…"} after {"role": "1f7b…", "provider_id": "corporate-ldap", "source_id": "engineering"}This applies wherever a role is named:
authorizations[].for_principal,context.principalongrant_createdandgrant_revoked,principalsonapply_grants, andprincipalonread_subtree_grantsandrevoke_subtree_grants. Both fields are absent when the role no longer exists or could not be looked up. With the setting off,source_idis absent everywhere, including onactor.assumed_role, which carries it otherwise.What to do: correlate on the role's id. Read
provider_idandsource_idas optional on a role a record names, andsource_idas optional onactor.assumed_role. Some providers let a source id be a free-form name, so it might hold personal data; turn the setting off if that matters for your log. -
Renaming a table, view or generic table into another namespace is recorded as
move, notrename, and namespaces have a new actionaccept_moved_tabular.renameTable, renameView, renameGenericTable into another namespace before rename after move destination namespace accept_moved_tabular sourceThe record of such a rename names
moveon the renamed entity, refused or allowed, on replays as well.destinationholds the path of the destination namespace; the entity's new name is not part of it, unlike for a namespacemove, wheredestinationis the full new path. A rename within one namespace is still recorded asrename. Whether the namespace changes is decided on the paths in the request, ignoring upper and lower case of ASCII letters: two paths that differ only in the case of a non-ASCII letter are recorded asmove.accept_moved_tabularis the check on the destination namespace.sourceholds the path of the namespace the entity is moved from. It appears in records of permission checks that name it, such as/management/v1/action/batch-check.The record of cancelling soft-deletion tasks is unchanged: it still names
control_tasks. Cancelling such a task now also requiresundropon its table, view or generic table, and a refusal is recorded as a deniedcontrol_tasks.What to do: if you match table, view or generic-table renames on
rename, matchmoveas well.
Additions — an existing parser keeps working:
-
Authorization records carry three new optional fields about the request:
user_agent,break_glassandidempotency_key.user_agent: the request'sUser-Agentheader, as sent and unverified.break_glass: the reason the caller stated in thex-break-glassheader, as sent and unverified.idempotency_key: the request'sIdempotency-Key.
Each is absent when the request did not send it.
What to do: nothing. Treat
user_agentandbreak_glassas claims, never as identity. -
Three more operations record the override that makes them destructive.
role delete force revokes the grants the role still holds warehouse delete force deletes a protected warehouse and everything in it generic-table drop force skips the warehouse's soft-deletion window purge deletes the data filesThey use the same keys as namespace delete, table drop and view drop. The key is present whichever way the flag went.
What to do: if you alert on destructive operations, include these three.
"force": trueis the forced form. -
Three new kinds of audit record: grant changes, admission decisions and idempotent replays.
operation: "grant_created"and"grant_revoked": one record per grant an apply actually changed, withoutcome: "success"andcontextprincipal,privilege,resource_type, andresource_idandwarehouse_idwhere they apply.operation: "admission_decided": a request an admission gate refused.outcomeisforbiddenorunavailable;contextcarriesgate,status,error_type,message,error_id, anddenied_bywhen the gate named a rule.record_type: "replay": a request answered from an idempotency record without being executed. It carries theactionsandentitiesthe request named and theidempotency_keythat matched, and nodecision.
What to do: nothing, unless you want these records. Select them by
operationor byrecord_type. -
Records can carry the email of the users they name. Off by default;
LAKEKEEPER__AUDIT__TRACING__INCLUDE_USER_EMAIL=trueturns it on.actor.email the principal's email, for principal and assumed_role actors authorizations[].for_principal.email the user's email, for a user subject actions[].principals[].email the user's email, for a user an apply_grants request names actions[].principal.email the user's email, when a subtree grant request names one user context.principal.email the recipient's email, on grant_created and grant_revokedEach is best-effort: absent when the email is not known, never
null. Roles,anonymousandlakekeeper_internalactors never carry one. An email is metadata, not identity: emails are not unique and can change.What to do: correlate on
principal,userandrole, never onemail. If you enable the setting, treat the log as holding personal data: an email stays in the log after the user is deleted. -
Audit records carry four new top-level fields:
record_type,emitters,time, andrequest_idwhen a request caused the record."record_type": "authorization", "emitters": {"lakekeeper": "1.0"}, "request_id": "019684ff-…", "time": "2026-03-14T09:26:53.589793Z"record_typenames the record's shape:authorization,replayoroperation.emittersnames every product that contributed to the record. Each key is a product name, each value theMAJOR.MINORversion of what that product contributes. A record names two products when a component plugged into Lakekeeper supplied an action name or acontextkey.request_idis the request that caused the record: the value of thex-request-idresponse header, as sent by the caller or generated by Lakekeeper. It is absent from an operation record no request caused.timeis when the event happened, in UTC, as RFC 3339 with microseconds.
audit_formatgoverns the record's overall shape. A product's value inemittersgoverns what that product contributes. The two are equal on a record only Lakekeeper produced; do not rely on that.What to do: route on
record_type. Read a product's version from its key, for example.emitters.lakekeeper. Correlate records with a request throughrequest_id, and order them bytime.
Also worth knowing — the format itself did not change:
-
Audit records are emitted on the fixed
tracingtargetlakekeeper::audit.A log filter naming
lakekeeper::service::events::backends::audit,lakekeeper::service::admissionor a prefix of either belowlakekeepermatches no audit record. The server warns at start-up when it finds such a filter.What to do: select audit records with
RUST_LOG=warn,lakekeeper::audit=info, or suppress them withRUST_LOG=info,lakekeeper::audit=warn. -
The audit log has a published JSON Schema:
audit/schema.jsonin the documentation, linked from the "Audit Log Schema" page.- Point a validator at the document to check a whole record. Its root,
AuditRecord, requiresevent_source,audit_formatandrecord_typeand selects the shape byrecord_type:AuthorizationRecord,ReplayRecordorOperationRecord. - Every field and key is a property with its type and description.
ActionRecordlists, peraction_name, the keys that action carries. - A value set the log may extend lists its values under
x-audit-values, so a validator accepts a value a later release adds. The closed setsdecision,privilege_scope,root_leveland a policy'seffectuseenum. - A product plugged into Lakekeeper publishes its own schema for what it contributes.
emitterson a record says which schemas apply.
What to do: nothing. Use the schema to validate records or to generate a parser.
- Point a validator at the document to check a whole record. Its root,
Records from v0.14.0 carry audit_format 1.0 (the first version).
v0.13.6 (2026-09-22)¶
Bug Fixes¶
- Clients that ask for vended credentials, such as DuckDB, no longer get
403errors on S3 warehouses that do not vend credentials. Lakekeeper sent astorage-credentialsentry without credentials, and these clients then sent unsigned requests (#1923). - The S3 signer now signs
ListObjectsV2requests for a prefix inside a table location. This makes Spark'sremove_orphan_fileswithprefix_listing => truework with remote signing (#1925). - Catalog and management responses are now marked
Cache-Control: privateand vary on the caller's identity. A shared HTTP cache can no longer give one user's response, which may contain vended credentials, to another user (#1947). - The Iceberg Java client now detects that Lakekeeper supports idempotency keys, because
/configreturnsidempotency-key-lifetimeas a top-level field, as the spec requires (#1952). - The
X-Iceberg-Access-Delegationheader accepts a comma-separated list such asvended-credentials,remote-signing. A value with non-ASCII characters no longer causes a500error (#1948). - ADLS now reuses connections, and connecting to ADLS, S3 or GCS times out after 10 seconds. Before, ADLS ignored its connection pool and connect timeout, GCS had no connect timeout, and S3 had none with access keys or anonymous access. Deleting a large table on GCS no longer keeps memory for every file at the same time, which was about 2 GB for a million files (#2022).
v0.13.5 (2026-09-15)¶
Features¶
- S3 remote signing for generic tables. Clients can now read and write generic table data through Lakekeeper's S3 signer. This makes generic tables usable on S3-compatible storage without STS (thanks @N-Clerkx) (#1910).
Bug Fixes¶
- Security:
rustlsis updated to 0.23.45 for RUSTSEC-2026-0285. A TLS peer could send handshake messages without encryption that should have been encrypted. The handshake itself stayed protected (f5b8993).
v0.13.4 (2026-09-10)¶
Features¶
- GovCloud, China and ISO region support. Vended-credential policies now carry the correct ARN partition (
aws-us-gov,aws-cn,aws-iso*,aws-eusc), derived automatically from the region, endpoint or role ARN, soAssumeRolesucceeds for buckets outside the commercial partition — nothing to configure (thanks @123digits) (#1928).
Bug Fixes¶
- Fixed a permissions leak: a table, view or generic table renamed into another namespace kept inheriting grants from the namespace it left (#2013).
- Renaming a table onto a name that is already taken now returns
409 Conflictinstead of404 Not Found, matching the Iceberg REST spec, and renaming onto a soft-deleted name succeeds — as create already did (#1955). - Fixed four independent causes of resident memory growing until restart: jemalloc now compiles with
thp:never, the route table is no longer rebuilt per accepted connection, connections get TCP keepalive and a header-read timeout so vanished peers are dropped, and unmatched request paths no longer become permanent Prometheus series. Settled RSS fell 68% in testing (#1990).
Upgrade Notes¶
- Deployments that renamed a table, view or generic table across namespaces should run
lakekeeper openfga reconcile --mode add-and-delete-driftonce after upgrading. The defaultadd-missingmode cannot repair it — the stale permission edge is a surplus tuple, not a missing one.
v0.13.3 (2026-08-16)¶
Features¶
- Stable Kubernetes service-account identities.
LAKEKEEPER__KUBERNETES_AUTHENTICATION_SUBJECT_SOURCE=usernamederives a service account's user ID fromsystem:serviceaccount:<namespace>:<name>instead of the token UID, so roles and instance admins can be pre-provisioned (e.g. via the Terraform provider) and survive a cluster rebuild. The defaultuidis unchanged (#1899). - Provider-managed roles are protected from drift. Roles owned by a configured role provider (LDAP, Entra, Okta, token) can no longer be created, updated, deleted, rebound or have their members changed through the management API, since the next provider sync would silently clobber those edits. Nothing changes without a role provider configured (#1891).
Bug Fixes¶
- Fixed two denial-of-service advisories (RUSTSEC-2026-0194 and RUSTSEC-2026-0195) reachable through the S3 remote signer, which parses a client-supplied XML body;
quick-xmlis bumped to 0.41 (#1885). - Loading a table with
referenced-byno longer runs a discarded authorization check on the target view, removing a wasted authorizer evaluation per request and a misleadingSelectViewentry from audit logs (#1886).
v0.13.1 (2026-06-30)¶
Bug Fixes¶
- Console updated to v0.19.0; the default OAuth
client_idno longer carries a leading slash (ac1094a).
v0.13.0 (2026-06-30)¶
Highlights¶
- Generic Table API. Register non-Iceberg tables (e.g. Lance, Delta) as first-class generic tables and get credential vending, list/load/rename/drop, soft-delete/undrop, protection, and full authorization — without faking Iceberg metadata (#1673, #1813).
- Operator-owned warehouses. A
managed_bymarker lets a control plane (operator/IaC) own a warehouse, locking spec mutations (delete, rename, (de)activate, storage profile, protection, format-version policy) to instance admins even when authorization would otherwise allow them (#1828). - Cache hardening for large fleets. Hot read-through caches now coalesce concurrent identical misses (single-flight) and jitter their TTLs, cutting thundering-herd load on the database and on rate-limited cloud STS/SAS endpoints (#1833, #1837).
Features¶
- Per-warehouse table format-version policy. Set the allowed Iceberg table format versions and an optional default per warehouse, enforced on create/commit/upgrade (#1786).
- Customer-managed KMS encryption. Warehouses with
aws-kms-key-arnnow advertises3.sse.type=kmsto clients, so writes via vended credentials are encrypted with your KMS key (#1847). - Schedule a task directly.
POST .../task-queue/{queue_name}/scheduleschedules a task for a single table without waiting for a commit hook (#1783). - Task failures are debuggable. Failed tasks now surface the root-cause failure reason in task-detail responses, no server-log access required (#1873).
- Pluggable admission gates. A new post-authentication seam lets an external service reject already-authenticated principals that have no entitlement on this instance and contribute their resolved roles (#1865, #1866, #1869).
- Authorizer-independent role-membership API. A single management surface lists, adds, and removes a role's members (users or roles) and shows what a role or user belongs to — the same API regardless of the configured authorizer. The membership edges keep a single source of truth: the authorizer's own store when it manages assignments (e.g. OpenFGA), otherwise the catalog's Postgres tables (#1829).
- Iceberg 1.11 remote signing. Emits the new
signer.uri/signer.endpointproperties alongside the legacys3.signer.*keys, so clients ≥1.11 stop logging deprecation warnings while older clients keep working (#1820). - Per-decision authorization audit. Authorization audit events now record the contributing policies behind each allow/deny outcome (#1844).
- More observability. New client-side Postgres connection-pool metrics and event-listener dispatch timing metrics (#1838, #1863).
- Console v0.15.1 — generic-tables (Lance/Delta) tab, Iceberg format-version policy editor, OneLake/Fabric backend, and per-queue maintenance summaries (#1855).
Bug Fixes¶
GET /confignow authorizes only the requested warehouse (fixing timeouts on large projects) and masks hidden/unknown warehouses identically so existence and UUIDs can't leak (#1788).- Conditional
loadTableno longer returns304once the cached response's vended credentials have expired (#1862). - S3: keep the STS vended-credential policy within the packed-size limit and emit a reliable table resource ARN for paths with special characters (#1857).
- Azure (ADLS): add a connect timeout and a larger retry budget so transient connect failures are retried; retry storage OAuth token acquisition for ADLS and GCS (#1815, #1827).
- Postgres: migration locks are now transaction-scoped, so a failed migration no longer leaks an advisory lock that could permanently block future migrations (#1790).
Breaking Changes¶
- Default storage layout is now flat — new namespaces use
<base>/<tabular-uuid>instead of nesting tabulars under the parent-namespace UUID. Only namespaces created on/after 0.13 are affected; existing namespaces and tabulars keep their persisted paths (not retroactive, no migration), so a warehouse spanning the upgrade can hold a mix. To keep the previous behavior for new namespaces, explicitly configure the full-hierarchy layout (#1853). - Event backends are separate crates. The NATS and Kafka backends moved out of the core
lakekeepercrate intolakekeeper-events-natsandlakekeeper-events-kafka. Prebuilt binaries and images are unaffected (same env vars); building from source must depend on the new crates — thenats,kafka, andvendored-protocfeatures are removed fromlakekeeper(#1814). - Postgres backend is a separate crate. Catalog Postgres logic moved into
lakekeeper-storage-postgres, makinglakekeeperbackend-agnostic. Source consumers oflakekeeper::implementations::*must now depend on the new crate; prebuilt binary/image users are unaffected (#1812). - Role source-system rebind is now permission-gated.
PUT /role/{id}/source-systemrequires membership-control permission (OpenFGA model v4.6→v4.7), closing a privilege-escalation gap (#1848). - Custom authorizers.
are_allowed_*_actions_implnow returnVec<AuthorizationDecision>instead ofVec<bool>, andRoleActiongained a non-CopyUpdateSourceSystemvariant; out-of-tree authorizer implementations must adapt. Built-in OpenFGA users are unaffected (#1844, #1848). - Regenerate action client SDKs.
GET …/actionsnow advertises action kinds (Lakekeeper*ActionKind); the wire JSON is unchanged, but generated clients should be regenerated (#1860).
Upgrade Notes¶
- Downgrade protection.
servenow refuses to start against a database already migrated by a newer binary and does not retry. After a rollback, start the older binary withserve --force-start, accepting the schema-incompatibility risk (#1861). - Docker base images moved from Debian 12 (bookworm) to Debian 13 (trixie) (#1794).
- The docker-compose examples now use SeaweedFS instead of MinIO for object storage (#1811).
v0.12.4 (2026-06-17)¶
Features¶
- Multiple OIDC providers. Authenticate tokens from several identity providers at once (e.g. Okta for users + a cloud OIDC issuer for service accounts) via
LAKEKEEPER__OPENID_PROVIDERS; fully backwards-compatible with the existing single-provider config (#1760). - Microsoft OneLake / Fabric storage + private endpoints. New OneLake (ADLS Gen2) storage profile with configurable endpoint modes including workspace private link (#1852); Console updated to v0.14.3 for OneLake support.
Bug Fixes¶
/healthnow returns HTTP503(not200) when aggregate health is unhealthy or unknown, so Kubernetes HTTP probes correctly detect an unhealthy server; the JSON body is unchanged (#1802).
v0.12.3 (2026-05-26)¶
Features¶
- Read-only maintenance mode. Set
LAKEKEEPER__MAINTENANCE_MODE=read-onlyto reject mutating requests with503+Retry-Afterduring planned maintenance (#1765). - Atomic core + extension migrations. Schema migrations now apply in a single transaction, so an interrupted upgrade can't leave a half-migrated database (aa734bf).
- Users may share an email address. The unique-email constraint was dropped, so multiple users can have the same email (#1755).
- Survey opt-out. New
LAKEKEEPER__UI__ENABLE_SURVEYSflag disables in-console surveys and their third-party requests (719150b). - Reserved
systemrole provider for catalog-managed roles (#1776). - Console. New "Export for GitHub" support bundle (server info + UI config, no tokens), a Feedback button, role-provider IDs in the overview, and a two-column Server Settings layout (719150b).
Bug Fixes¶
- Fixed table property removal being lost when no properties remained (#1767).
- Views now preserve protection (
protected=true) across commits — previously lost on update (thanks @fallintoplace) (#1770). force=trueis now respected when dropping soft-deletion warehouses that contain views (#1779).- Fixed a memory leak from stale Vault (KV2) health status (thanks @fallintoplace) (#1773).
- Postgres: rewrote the namespace trigger so
pg_restorecan replay it (#1781).
Upgrade Notes¶
- Minimum supported Rust version (MSRV) raised to 1.94 — affects building from source.
v0.12.2 (2026-05-10)¶
Features¶
- Storage locations are canonicalised at parse time to avoid path aliases (#1743).
- Object size is now exposed on
FileInfo(#1741).
Bug Fixes¶
- Security: hardened S3 STS/CEL credential-vending policies against path injection (#1740).
- Azure (ADLS): pre-encode
%in blob names so the SDK no longer collapses distinct paths onto the same alias (#1746). - Postgres: apply
pg_acquire_timeoutto all connection-pool initialisations (#1744).
v0.12.1 (2026-05-04)¶
Highlights¶
- Instance Admins. Designate break-glass principals that bypass control-plane authorization for management actions via
LAKEKEEPER__INSTANCE_ADMINS(a list of<idp_id>~<subject>IDs). The bypass excludes data-plane operations and role-assumed requests (#1716). - Safe switch to OpenFGA. Existing deployments can adopt or rebuild OpenFGA:
openfga reconcilerebuilds hierarchy tuples from the catalog (with dry-run and drift-deletion), andreopen-bootstrapre-enables bootstrap for recovery (#1731, #1733). - OpenDAL dropped. Storage I/O now goes exclusively through the hyperscaler-native backends wrapped by
lakekeeper-io, including vended-credential validation (#1737). - Security. Upgraded
rustls-webpkito 0.103.12 for RUSTSEC-2026-0098 (#1713).
Features¶
- Trusted engines for views (
referenced-by). Validates thereferenced-byparameter and resolves view-on-view chains for batch authorization, enabling secure DEFINER-style execution for trusted query engines — configured underLAKEKEEPER__TRUSTED_ENGINES__<NAME>(#1647). - Protected security-relevant properties. Only a matched trusted engine may set or remove view owner / run-as properties (case variants rejected), and commits can no longer overwrite immutable table properties such as
encryption.key-id(#1700, #1724). - Richer audit.
introspect_permissionevents now include the inner check tuples and their decisions, and events record whether access was granted internally, via an instance admin, or by the authorizer (#1697). - Data-plane
Selectaction for views, plus a publicresolve_principalAPI for downstream API-to-authzUserOrRoleconversion (#1721, #1703). - OPA bridge. View-on-view queries via
CreateViewWithSelectFromColumns, the TrinoADD_FILESoperation, and a warehouse/namespace broad-access fast path for batch authorization (#1712, #1727). - Extended Server Info with console information and commit SHAs (#1725).
Bug Fixes¶
- Added
webpki_root_certs/ UBI native certs to the S3 client to fix TLS trust issues (#1720). - Namespace/table case handling: allow renaming a table to a different case of its own name; lookups return the caller's case, ID lookups the canonical case (7c26309).
- Pinned
gcloud-storage/gcloud-authto~1.2to avoid areqwest-middlewareconflict (#1701). - ADLS: remove the actual matched SAS token key rather than its prefix (76a091b).
- Console: fixed base-URL trailing slash, Vite 8 authentication breakage, and a stale warehouse name after rename (#1729, #1723).
Upgrade Notes¶
- Switching to OpenFGA on an existing instance is now safe: run
openfga reconcile(dry-run first; drift-deletion mode to also remove stale tuples), andreopen-bootstrapto re-enter bootstrap if needed. The minimum required OpenFGA version was raised. - OpenDAL removed — storage now relies solely on the native S3/GCS/ADLS backends in
lakekeeper-io; re-verify storage and vended-credential config after upgrade. - Deploying into a custom Postgres schema is now supported and documented (#1714).
v0.12.0 (2026-04-01)¶
Highlights¶
- Audit Event System. Authorization decisions and catalog operations emit dedicated audit events with exactly-once-per-call delivery, giving a reliable trail of who did what (b77c687).
- Idempotency keys for safe retries. Send an
Idempotency-Keyheader on mutating requests and Lakekeeper replays the original response instead of applying the change twice — on by default, scoped per warehouse, with a 30-minute key lifetime (#1671). - Customizable storage layouts. Choose how namespace/table paths are templated on S3/GCS/ADLS using
{uuid}/{name}placeholders (#1615, #1628). - Structured JSON logs. Log output is now structured JSON with objects as field values, ready for log-pipeline ingestion (b77c687).
Features¶
- Configurable STS endpoint. Set a separate
sts-endpointon an S3 storage profile when your S3-compatible storage exposes STS on a different host (#1653). - Fallback subject claims. OpenID subject-claim config accepts a comma-separated list; the first matching claim in the token wins, easing varied-IdP integration (#1646).
- Request size/time limits. New
LAKEKEEPER__MAX_REQUEST_BODY_SIZE(default 2 MB) andLAKEKEEPER__MAX_REQUEST_TIME(default30s) guard against oversized and slow requests (#1583). - Trusted engines configuration. Declare trusted engines (e.g. Trino) with a selectable Invoker/Definer security model; the engine is auto-detected from token audience and recorded in request metadata (#1629).
- Role assignment store and cache with provider-scoped role identifiers, plus a roles cache for faster authorization (#1638, #1623).
- Iceberg V3 Variant datatype support, validated against Spark 4 integration tests (daa7947).
- Tokio runtime metrics exported for runtime observability (#1664).
- Reduced memory footprint by switching the allocator to jemalloc (0eaeedc).
- OPA bridge improvements: batch-authorization optimization, configurable admin users, system-schema handling, and request-context forwarding (#1674, #1662).
- Faster listing of namespaces, tables, and views (#1618).
- Console. New Home dashboard with usage statistics and API-call charts; branch operations (create, rename, delete, rollback, fast-forward); a Properties dialog for tables/views/namespaces; storage-layout configuration; and a local query engine with memory management (#1621, #1634).
Bug Fixes¶
- Fixed duplicate results when paginating
list_tabulars(#1682, #1684). - Fixed a memory leak in the S3 identity cache (0eaeedc).
- Allowed updating the storage-profile region when an S3 endpoint is set (#1678).
- Patched security advisories in crypto dependencies (
aws-lc-sys/rustls-webpki) (#1672) and anlz4_flexmemory-leak advisory (#1665).
Breaking Changes¶
- Cache metrics unified. Per-cache metric names are replaced by shared names distinguished by a
cache_typelabel (#1641). - Structured log format. Logs now emit structured JSON with objects as field values instead of flat text (b77c687).
Upgrade Notes¶
- Cache metrics: migrate dashboards/alerts from the old per-cache metric names to the unified
lakekeeper_cache_hits_total/lakekeeper_cache_misses_total/lakekeeper_cache_size, filtering by thecache_typelabel (role,warehouse,namespace,secrets,stc). - Structured logs: log consumers must parse JSON rather than plain text. Set
LAKEKEEPER__DEBUG__EXTENDED_LOGS=trueto includefilename/line_numberfields. - S3 credential fields dropped their
aws_prefix; the old names remain accepted as aliases, but update to the new names (#1685).