From 1b328e31db746a018dd431f434d40f3858d1503c Mon Sep 17 00:00:00 2001 From: 1Pro-L4R8 Date: Wed, 28 Oct 2026 05:03:46 +0700 Subject: [PATCH] docs: vended credentials follow catalog grants (platform #760) A read grant now vends read-only storage credentials, and write or manage read-write-delete. Document the rule on the catalog detail page, the behaviour change for engines and clients with only read grants, the access-denied symptom of a read-only write, and an upgrade note. Close KI-311 and add KI-315 (credential lifetime and shared keys) and KI-316 (cross-warehouse register on overlapping storage). Co-Authored-By: Claude Opus 5.5 --- .../control-plane/catalogs/catalog-detail.md | 21 +++++++++++++++++-- .../control-plane/query-engines/provision.md | 3 ++- .../query-engines/register-external.md | 2 ++ .../deploy-operate/storage-and-catalog.md | 9 ++++++-- .../troubleshooting/catalog-storage-errors.md | 5 +++++ platform/deploy-operate/upgrade.md | 21 +++++++++++++++++++ platform/faq.md | 8 +++++++ platform/known-issues.md | 6 ++++-- 8 files changed, 68 insertions(+), 7 deletions(-) diff --git a/platform/control-plane/catalogs/catalog-detail.md b/platform/control-plane/catalogs/catalog-detail.md index 58f605e..52798b7 100644 --- a/platform/control-plane/catalogs/catalog-detail.md +++ b/platform/control-plane/catalogs/catalog-detail.md @@ -98,6 +98,22 @@ An engine's client holds `catalog · manage`, so it can do all of these. For exa | 400 | A rename or commit body egret cannot read | *malformed rename request: …*, *malformed multi-table commit: …*, *malformed table commit: …* | | 413 | A rename body over 1 MiB, or a commit body over 16 MiB | *the body of a rename request is larger than 1048576 bytes* | +### Storage credentials follow the grant {#storage-credentials-follow-the-grant} + +When a client loads a table, the catalog hands it short-lived S3 credentials scoped to that table's location, so it can read the table's files without static object-store keys. What those credentials allow depends on the role's grant on the table: + +| The role's grant covering the table | Vended credentials | +|---|---| +| **read** (on the catalog, the namespace or the table) | Read-only: `s3:GetObject` and `s3:GetObjectVersion` on the table's location. The client can scan the table but cannot write, overwrite or delete any of its files. | +| **write** or **manage** | Read, write and delete on the table's location. | +| None | No credentials: the load itself is refused with 403. | + +The same rule applies with the `X-Iceberg-Access-Delegation: vended-credentials` header, without the header, and on `GET …/tables/{t}/credentials`. Only `X-Iceberg-Access-Delegation: client-managed` asks for no credentials. A role with several grants gets the widest one that covers the table: **read** on the namespace `sales` plus **write** on `sales.orders` vends read-write-delete on `sales.orders` and read-only on every other table in `sales`. Creating or staging a table (which needs **write** on the namespace) vends read-write-delete on the new table's location. A view load vends read-only credentials on the view's metadata location. + +An engine or client with only **read** grants can therefore read but not write. Its writes fail at the object store with *access denied* when it uploads the first data file, before anything reaches the catalog. PyIceberg, for example, raises `OSError: … AWS Error ACCESS_DENIED during CreateMultipartUpload operation: Access Denied` on an append. Engines provisioned by egret hold `catalog · manage` and write as before. + +The credentials stay valid for about an hour, and the catalog reuses one key for every caller with the same permission on the same table. Revoking or narrowing a grant stops new credentials at once, but does not cancel the ones already handed out (KI-315). + ## Catalog Clients (warehouse) *Machine credentials (client id + secret) an engine or notebook uses to authenticate.* @@ -120,7 +136,7 @@ A dropped table keeps its classifications in the **Recycle bin**, and **Undrop** ![The Connect tab of demo_warehouse with the REST catalog URI and the PyIceberg snippet](/img/platform/admin/cp-catalog-connect.png) -*egret exposes this warehouse as an Iceberg REST catalog. Point any Iceberg-compatible engine at it using a Catalog Client's credentials.* The tab shows the **CATALOG URI**, **WAREHOUSE** and **OAUTH TOKEN URL**, and ready-made snippets for **PyIceberg**, **Trino** and **StarRocks**. Replace the `:` placeholders with a client's values, and the example table `demo_ns.events` with one of yours. If the engine runs in Docker, use the egret host reachable from that container instead of the public address. On the demo stack the snippet's `s3.endpoint` is `http://rustfs:9000`, an address that resolves only inside the Docker network. The snippets ask egret to vend short-lived, table-scoped S3 credentials, so no static object-store keys are needed. +*egret exposes this warehouse as an Iceberg REST catalog. Point any Iceberg-compatible engine at it using a Catalog Client's credentials.* The tab shows the **CATALOG URI**, **WAREHOUSE** and **OAUTH TOKEN URL**, and ready-made snippets for **PyIceberg**, **Trino** and **StarRocks**. Replace the `:` placeholders with a client's values, and the example table `demo_ns.events` with one of yours. If the engine runs in Docker, use the egret host reachable from that container instead of the public address. On the demo stack the snippet's `s3.endpoint` is `http://rustfs:9000`, an address that resolves only inside the Docker network. The snippets ask egret to vend short-lived, table-scoped S3 credentials, so no static object-store keys are needed. A client whose role holds only **read** grants gets read-only credentials; see [Storage credentials follow the grant](#storage-credentials-follow-the-grant). ## Settings (warehouse) {#settings-warehouse} @@ -141,7 +157,8 @@ A dropped table keeps its classifications in the **Recycle bin**, and **Undrop** - A catalog test before attaching checks only that the host answers on its port; a TPC-H catalog has no host to check. - Attach and detach on Trino restart the coordinator. -- A Catalog Client with only a **read** grant receives vended S3 credentials that can also write and delete the files of the table it loads, so it can damage that table without any catalog write (KI-311). Give **read** grants only to clients you would trust with write access to those tables until this is fixed. +- Vended credentials outlive a revoked or narrowed grant by up to about an hour, and callers with the same permission on a table share one key (KI-315). +- Two warehouses that share a location can let a namespace writer in one of them obtain read-write credentials on a table of the other, when their storage connections write the same endpoint differently (KI-316). Give each warehouse its own bucket or key prefix. - Any **write** grant lets a role create and list namespaces, and a grant on one table lets it list every table name in that namespace (KI-312). ## Further reading diff --git a/platform/control-plane/query-engines/provision.md b/platform/control-plane/query-engines/provision.md index bee0209..45ef4a7 100644 --- a/platform/control-plane/query-engines/provision.md +++ b/platform/control-plane/query-engines/provision.md @@ -70,7 +70,7 @@ A StarRocks engine has its own configuration screen with sections such as **Repl ## What happens next 1. Egret starts the cluster, creates a **policy profile** with the engine's name (for example `docs-trino`, with the tag service `docs-trino_tag`) and binds it. -2. The attached catalogs are applied to the engine (state **Applied** on the **Catalogs** tab). +2. The attached catalogs are applied to the engine (state **Applied** on the **Catalogs** tab). For an egret warehouse, egret creates a Catalog Client for the engine under the warehouse's `engine` role, which holds `catalog · manage`, so the engine reads and writes every table. 3. Assign the engine to a workspace on its **Workspaces** tab. It then appears in the workspace's SQL Lab as a connection with the engine's name, and in **Workspace settings → Connections** under **Provisioned by egret**. 4. The Governance area lists the engine under **Engine & policy**, and grants on its catalogs reach it. @@ -97,6 +97,7 @@ The demo's Docker provisioner only starts images from `trinodb/trino`, with no p - A Trino catalog attach or detach restarts the coordinator, so queries in flight are lost. - The Ingress section is offered on the Docker provisioner, which refuses any ingress (KI-264). +- egret reuses an existing role named `engine` on the warehouse without checking its grants. If that role was narrowed to **read**, the engine can query but its writes fail at the object store with *access denied*, because the catalog vends read-only storage credentials (see [Storage credentials follow the grant](../catalogs/catalog-detail.md#storage-credentials-follow-the-grant)). ## Further reading diff --git a/platform/control-plane/query-engines/register-external.md b/platform/control-plane/query-engines/register-external.md index 2d1e140..939e39d 100644 --- a/platform/control-plane/query-engines/register-external.md +++ b/platform/control-plane/query-engines/register-external.md @@ -52,6 +52,8 @@ A Trino profile created in the console has no tag service and cannot be bound. U *Declare which registry catalogs this engine serves, so grants on them reach its profile. egret does not create these catalogs on your engine — create them there with the same names.* Without **catalog_registry:read** the list is replaced by *Listing catalogs needs the catalog_registry:read permission, which your account does not have. You can register the engine now and declare catalogs later.* +For an egret warehouse, you create the catalog on your engine yourself with a Catalog Client from the catalog's **Connect** tab. The client's role decides what the engine may do: with only **read** grants, the storage credentials the catalog vends are read-only, so the engine can query the tables but its writes fail at the object store with *access denied*. Give the role **write** or **manage** if the engine writes. See [Storage credentials follow the grant](../catalogs/catalog-detail.md#storage-credentials-follow-the-grant). + ## After you register A dialog, *Engine registered: save its Ranger credential*, shows the engine's Ranger credential once: *Copy it now: egret shows this password once.* Its button depends on the profile: diff --git a/platform/deploy-operate/storage-and-catalog.md b/platform/deploy-operate/storage-and-catalog.md index 1fe7293..be01b94 100644 --- a/platform/deploy-operate/storage-and-catalog.md +++ b/platform/deploy-operate/storage-and-catalog.md @@ -89,7 +89,10 @@ s3.path-style-access=true ``` With `vended-credentials-enabled`, engines get credentials scoped to the table they load and the static `s3.aws-*` keys are -not needed. A warehouse is provisioned with STS enabled and remote signing off, and egret refuses every S3 signer request +not needed. The credentials follow the client's grant: a role with only `read` on the table gets read-only credentials, and +`write` or `manage` gets read, write and delete, so an engine whose client holds only `read` grants cannot write (see +[Storage credentials follow the grant](../control-plane/catalogs/catalog-detail#storage-credentials-follow-the-grant)). +A warehouse is provisioned with STS enabled and remote signing off, and egret refuses every S3 signer request (`…/aws/s3/sign`) with 403, whatever the client's grants. ### Setting it up in the app @@ -107,7 +110,9 @@ not needed. A warehouse is provisioned with STS enabled and remote signing off, :::caution One warehouse, one prefix A warehouse without a `key_prefix` claims the whole bucket, and the catalog rejects a warehouse whose location overlaps an -existing one. Give each warehouse its own bucket or key prefix. +existing one. Give each warehouse its own bucket or key prefix. The overlap check compares the storage connections' endpoints +as written, so `http://rustfs:9000` and `http://rustfs:9000/`, or two host names for one store, are not seen as the same +store (KI-316). ::: ### Check it diff --git a/platform/deploy-operate/troubleshooting/catalog-storage-errors.md b/platform/deploy-operate/troubleshooting/catalog-storage-errors.md index 366d31d..3dc7a97 100644 --- a/platform/deploy-operate/troubleshooting/catalog-storage-errors.md +++ b/platform/deploy-operate/troubleshooting/catalog-storage-errors.md @@ -56,6 +56,7 @@ of a real failure: | 400 *malformed rename request*, *malformed multi-table commit* or *malformed table commit* | The request body is not the JSON the Iceberg REST API defines. | | 413 *the body of a … is larger than … bytes* | A rename body is limited to 1 MiB, and a commit body to 16 MiB. | | 403 on the warehouse | The token's warehouse differs from the one requested. | +| A write fails at the object store with *access denied* (PyIceberg: `ACCESS_DENIED during CreateMultipartUpload`), while reads of the same table work | The client's role has only a `read` grant covering the table, so the catalog vended read-only storage credentials. Give the role `write` on the table, its namespace or the catalog. Nothing was committed. See [Storage credentials follow the grant](../../control-plane/catalogs/catalog-detail#storage-credentials-follow-the-grant). | | Warehouse creation refused | A warehouse without a `key_prefix` claims the whole bucket, and the catalog rejects a location that overlaps another warehouse. Give each its own bucket or prefix. | egret's refusals use the Iceberg REST error format, `{"error": {"message", "type", "code"}}`, so the client shows the @@ -107,6 +108,10 @@ The catalog hands engines short-lived, table-scoped credentials by calling STS ` without STS support cannot serve them. RustFS supports it; MinIO's current public images are no longer available. Without vended credentials, engines need static `s3.*` keys in their catalog configuration. +Vended credentials follow the client's grant: `read` vends read-only credentials, `write` and `manage` read-write-delete. +A write by a read-only client fails at the object store, not in the catalog (see the table above). Credentials already +handed out stay valid for up to about an hour after a grant is revoked or narrowed (KI-315). + ## Storage connections in the app **Control Plane → Storage** lists registered connections. A connection whose secret cannot be read (a changed master key) or diff --git a/platform/deploy-operate/upgrade.md b/platform/deploy-operate/upgrade.md index 48f5581..be73b02 100644 --- a/platform/deploy-operate/upgrade.md +++ b/platform/deploy-operate/upgrade.md @@ -165,6 +165,27 @@ A role reaches no table it could not already reach by spelling the name as its g limited to 1 MiB and 16 MiB (413). See [What each operation needs](../control-plane/catalogs/catalog-detail#what-each-operation-needs). +### Vended credentials follow catalog grants (platform #760) + +From platform #760 the S3 credentials egret's Iceberg catalog vends on a table load follow the client's grant. There is no +migration and no API change. + +- A role with only a `read` grant covering the table (on the table, its namespace or the catalog) gets read-only credentials + (`s3:GetObject`, `s3:GetObjectVersion`). Before, every client that could load a table got read-write-delete credentials on + its files, even without the `X-Iceberg-Access-Delegation` header (KI-311). +- `write` and `manage` still get read-write-delete. The same rule applies on `…/tables/{t}/credentials`. View loads stay + read-only. +- **Behaviour change:** an engine or client that uses a Catalog Client with only `read` grants can still read, but it can no + longer write or delete table files. Its writes now fail at the object store with *access denied* (PyIceberg: + `ACCESS_DENIED during CreateMultipartUpload`) instead of reaching the catalog. If such an engine is meant to write, give its + role `write` or `manage`. +- Engines provisioned by egret hold `catalog · manage` and are not affected; Trino's creates, inserts, updates, deletes, + renames and drops were checked. +- Credentials vended before the upgrade, read-write-delete ones handed to readers included, stay valid until they expire, + about an hour after they were issued (KI-315). + +See [Storage credentials follow the grant](../control-plane/catalogs/catalog-detail#storage-credentials-follow-the-grant). + ### StarRocks 4.1.6 enforces view privileges See [Engines and Ranger setup](./engines-and-ranger#supported-versions). diff --git a/platform/faq.md b/platform/faq.md index 5525171..1566eb1 100644 --- a/platform/faq.md +++ b/platform/faq.md @@ -176,6 +176,14 @@ table name is not enough. A commit needs `write` on the table its body names, an A recursive namespace drop needs a `catalog`-level grant. Grants match names case-insensitively, as the catalog does. See [What each operation needs](./control-plane/catalogs/catalog-detail#what-each-operation-needs). +### Why can a catalog client read a table but not write to it, with *access denied* from the object store? + +The storage credentials the catalog vends follow the client's grant. A role with only `read` on the table (or on its +namespace or the catalog) gets read-only credentials, so it can scan the table but its writes fail at the object store +when it uploads the first file; PyIceberg reports `ACCESS_DENIED during CreateMultipartUpload`. Give the role `write` on the +table, its namespace or the catalog. Engines provisioned by egret hold `catalog · manage` and are not affected. See +[Storage credentials follow the grant](./control-plane/catalogs/catalog-detail#storage-credentials-follow-the-grant). + ### Why did my StarRocks policies stop applying after a restart? The activation `access_control = ranger` lives in the front end's `fe.conf`, which a recreated container regenerates, and diff --git a/platform/known-issues.md b/platform/known-issues.md index 78a397f..acf1207 100644 --- a/platform/known-issues.md +++ b/platform/known-issues.md @@ -37,7 +37,7 @@ and missing pieces. Entries are **recorded, not fixed**; they feed the product r ## Findings from the detailed pages -Collected while writing the detailed pages for each area (October 2026), checked against the running demo instance and the product source. KI-154 to KI-213 come from re-verifying every area on the Docker demo stack (`just demo-up`, October 2026), which also closed or updated older rows (marked *Status* or *Docker demo stack*). KI-214 to KI-235 come from a whole-app UI consistency sweep of 78 screens (admin, light theme, 1440×900, same stack), then the dark theme, 768 and 390 px widths, the editors and a non-admin member. KI-236 to KI-246 come from the Wave 1 fixes of the Known-issues fix programme (October 2026), which also closed the rows marked *fixed in platform #…*. KI-247 to KI-259 come from the Wave 2 fixes (SQL Lab, charts, datasets and the Data API: platform #720, #721, #724 and #725), which closed or partly closed the rows they name. KI-260 to KI-266 come from the Wave 2b fixes (alerts and reports delivery, authentication and roles, engines and monitor: platform #726, #727 and #728), which closed the rows they name; KI-021 was closed as by design and KI-148 as no longer applying. KI-267 to KI-269 come from the Wave 3 page-structure fix (titles, page width, tabs, status badges, list links and detail headers: platform #734), which closed KI-214, KI-215, KI-216, KI-222, KI-223 and KI-224; KI-270 was seen during the docs screenshot re-shoot (docs #31). KI-271 to KI-279 come from the Wave 3 controls and states fixes (shared select and date picker, Notifications and the dashboard Filter settings panel: platform #736; the refusal inside the shell, sidebar and settings cleanup, small-screen layout: platform #737) and from the storage and Docker engine fix (platform #735), which closed KI-005, KI-012, KI-217, KI-218 and KI-235 (the last two partly as written), KI-225, KI-227, KI-230, KI-231, KI-233, KI-234, KI-236 and KI-263. KI-280 to KI-283 come from the Wave 3 dashboard fixes (platform #739), which closed KI-003, KI-064 to KI-069, KI-074, KI-075 and KI-168; the SQL Lab fixes of the same wave (platform #740) closed KI-082 to KI-085, KI-089, KI-091 to KI-094, KI-160 to KI-162, KI-228 and KI-229 and found no new defect. KI-303 to KI-307 come from StarRocks enforcing whole-table classifications (platform #755), which closed KI-286 and KI-288; KI-303 was found while building it and the others in its live check. KI-284 and KI-285 are follow-ups of the username takeover fix (platform #741: a deleted user's engine username is never given out again, and the engines' `egret-admin` policies follow the active platform admins). KI-286 to KI-288 are engine gaps that **Who can read?** and the rule dialog state since platform #744 (verify a mask per user), and KI-289 was found in its live check. KI-290 to KI-295 come from the Wave 3 chart editor fixes (platform #743), which closed KI-008, KI-050, KI-053 to KI-056 and KI-058, and the alerts and reports fixes (platform #742), which closed KI-038, KI-039, KI-171 and KI-219 to KI-221; KI-295, an S2 bug in the Schedule tab that #742 found, was fixed by platform #745. KI-296 to KI-299 come from the Wave 3 Data API and Apps fixes (platform #746), which closed KI-025, KI-027, KI-031, KI-042, KI-176, KI-177 and KI-182 to KI-184; the workflows fixes (platform #747), which closed KI-014, KI-119, KI-121 to KI-123, KI-189 to KI-191, KI-193 and KI-226, and found KI-120 not to reproduce; and the Control Plane, Governance and settings fixes (platform #748), which closed KI-001, KI-007, KI-011, KI-016, KI-131, KI-132, KI-203, KI-204 and KI-212. KI-298 and KI-299 were found while writing the docs for them. KI-300 and KI-301 come from the Wave 3 small UX cleanup (platform #751), which closed KI-251, KI-280 to KI-282, KI-290 to KI-294 and KI-296 to KI-299 and left KI-283 open; KI-302 was found while writing the docs for it. The print page and wording fixes (platform #754) closed KI-283, KI-301 and KI-302 and found no new defect. KI-303 to KI-307 come from StarRocks enforcing whole-table classifications (platform #755), which closed KI-286 and KI-288; KI-303 was found while building it and the others in its live check. KI-308 to KI-310 come from classifications following a renamed table or column (platform #757), which closed KI-303: KI-308 and KI-309 are the related defects it recorded and left for a later package, and KI-310 is the cost of its hold. KI-311 to KI-314 come from the catalog guard authorizing renames and commits by the tables they change (platform #758), which closed no row: KI-311 and KI-312 were found while building it, KI-313 and KI-314 in its live check. Same legend as above. **Open S1 items:** none (KI-013, KI-040, KI-059, KI-060, KI-112, KI-136, KI-159, KI-175 and KI-187 are closed). +Collected while writing the detailed pages for each area (October 2026), checked against the running demo instance and the product source. KI-154 to KI-213 come from re-verifying every area on the Docker demo stack (`just demo-up`, October 2026), which also closed or updated older rows (marked *Status* or *Docker demo stack*). KI-214 to KI-235 come from a whole-app UI consistency sweep of 78 screens (admin, light theme, 1440×900, same stack), then the dark theme, 768 and 390 px widths, the editors and a non-admin member. KI-236 to KI-246 come from the Wave 1 fixes of the Known-issues fix programme (October 2026), which also closed the rows marked *fixed in platform #…*. KI-247 to KI-259 come from the Wave 2 fixes (SQL Lab, charts, datasets and the Data API: platform #720, #721, #724 and #725), which closed or partly closed the rows they name. KI-260 to KI-266 come from the Wave 2b fixes (alerts and reports delivery, authentication and roles, engines and monitor: platform #726, #727 and #728), which closed the rows they name; KI-021 was closed as by design and KI-148 as no longer applying. KI-267 to KI-269 come from the Wave 3 page-structure fix (titles, page width, tabs, status badges, list links and detail headers: platform #734), which closed KI-214, KI-215, KI-216, KI-222, KI-223 and KI-224; KI-270 was seen during the docs screenshot re-shoot (docs #31). KI-271 to KI-279 come from the Wave 3 controls and states fixes (shared select and date picker, Notifications and the dashboard Filter settings panel: platform #736; the refusal inside the shell, sidebar and settings cleanup, small-screen layout: platform #737) and from the storage and Docker engine fix (platform #735), which closed KI-005, KI-012, KI-217, KI-218 and KI-235 (the last two partly as written), KI-225, KI-227, KI-230, KI-231, KI-233, KI-234, KI-236 and KI-263. KI-280 to KI-283 come from the Wave 3 dashboard fixes (platform #739), which closed KI-003, KI-064 to KI-069, KI-074, KI-075 and KI-168; the SQL Lab fixes of the same wave (platform #740) closed KI-082 to KI-085, KI-089, KI-091 to KI-094, KI-160 to KI-162, KI-228 and KI-229 and found no new defect. KI-303 to KI-307 come from StarRocks enforcing whole-table classifications (platform #755), which closed KI-286 and KI-288; KI-303 was found while building it and the others in its live check. KI-284 and KI-285 are follow-ups of the username takeover fix (platform #741: a deleted user's engine username is never given out again, and the engines' `egret-admin` policies follow the active platform admins). KI-286 to KI-288 are engine gaps that **Who can read?** and the rule dialog state since platform #744 (verify a mask per user), and KI-289 was found in its live check. KI-290 to KI-295 come from the Wave 3 chart editor fixes (platform #743), which closed KI-008, KI-050, KI-053 to KI-056 and KI-058, and the alerts and reports fixes (platform #742), which closed KI-038, KI-039, KI-171 and KI-219 to KI-221; KI-295, an S2 bug in the Schedule tab that #742 found, was fixed by platform #745. KI-296 to KI-299 come from the Wave 3 Data API and Apps fixes (platform #746), which closed KI-025, KI-027, KI-031, KI-042, KI-176, KI-177 and KI-182 to KI-184; the workflows fixes (platform #747), which closed KI-014, KI-119, KI-121 to KI-123, KI-189 to KI-191, KI-193 and KI-226, and found KI-120 not to reproduce; and the Control Plane, Governance and settings fixes (platform #748), which closed KI-001, KI-007, KI-011, KI-016, KI-131, KI-132, KI-203, KI-204 and KI-212. KI-298 and KI-299 were found while writing the docs for them. KI-300 and KI-301 come from the Wave 3 small UX cleanup (platform #751), which closed KI-251, KI-280 to KI-282, KI-290 to KI-294 and KI-296 to KI-299 and left KI-283 open; KI-302 was found while writing the docs for it. The print page and wording fixes (platform #754) closed KI-283, KI-301 and KI-302 and found no new defect. KI-303 to KI-307 come from StarRocks enforcing whole-table classifications (platform #755), which closed KI-286 and KI-288; KI-303 was found while building it and the others in its live check. KI-308 to KI-310 come from classifications following a renamed table or column (platform #757), which closed KI-303: KI-308 and KI-309 are the related defects it recorded and left for a later package, and KI-310 is the cost of its hold. KI-311 to KI-314 come from the catalog guard authorizing renames and commits by the tables they change (platform #758), which closed no row: KI-311 and KI-312 were found while building it, KI-313 and KI-314 in its live check. KI-315 and KI-316 come from vended credentials following catalog grants (platform #760), which closed KI-311: KI-315 was recorded in its design and KI-316 in its review. Same legend as above. **Open S1 items:** none (KI-013, KI-040, KI-059, KI-060, KI-112, KI-136, KI-159, KI-175 and KI-187 are closed). ### Data API, Alerts and Apps @@ -361,10 +361,12 @@ Collected while writing the detailed pages for each area (October 2026), checked | KI-308 | Governance / Grants | Security | S2 | A path-selector grant or rule (a grant, a mask or a row filter written on the table's path or one of its columns, not selected by a classification) and a hand-written Ranger policy stay on the old name when a table or column is renamed; only classifications follow it (platform #757). An allow is lost, which fails closed, but a **deny** no longer applies to the renamed table, and a path mask or row filter stops masking or filtering it, so data leaks. A table created later under the old name inherits all of them. *Workaround:* before a rename, write the deny, mask or filter on the new name too (or select by a classification, which follows the table), and remove the old-name grants and rules after it. (Found while building platform #757.) | | KI-309 | Control plane / Catalogs | Bug | S2 | A catalog grant on a table (**Control plane → Catalogs → \ → Roles & grants**, for example `analytics.orders · read`) stays on the old name when the table is renamed. A catalog client whose role has only that grant loses access to the renamed table, and gains it on a new table created under the old name. A grant on **All tables** of the namespace, or on the whole catalog, still applies while the table stays in that namespace. *Workaround:* add the grant on the new name and remove the old one. (Found while building platform #757.) | | KI-310 | Governance / Classifications | Limitation | S3 | A rename of a classified table or column waits until every bound engine serves the moved classifications. With StarRocks bound this costs about two policy polls (36 to 56 seconds measured with the 30-second poll), because the StarRocks plugin reports a version one poll after it downloads it; the limit is 90 seconds. Meanwhile another classified rename in the same catalog, and a schema change or undrop of a table there that carries a whole-table classification with a clearance, wait up to 30 seconds and are then refused with a 409 to retry. A bound engine whose plugins do not poll makes every classified rename fail after 90 seconds with *engines did not confirm the moved classifications within 90 s: …*. A rename run from SQL Lab gives up first, at SQL Lab's 60-second query limit (`engine.query_timeout_secs`), and shows *timeout: the query ran longer than the 60 s limit* instead of egret's reason; egret still refuses the rename and undoes the copy. (Measured in the live check of platform #757.) | -| KI-311 | Control plane / Catalogs | Security | S2 | **High severity.** A Catalog Client whose role holds only a **read** grant (on a table or on a namespace) receives vended S3 credentials that can also write and delete the table's files. egret's catalog hands out read-write-delete credentials for every table load, and also when the client sends no `X-Iceberg-Access-Delegation` header, so a reader can overwrite or delete the table's data and metadata files without any catalog write: in a measurement, a client with a table **read** grant made the table unreadable for everyone. The credentials are scoped to the loaded table's location: they cannot list, read or write other tables' files or the bucket root. A namespace **read** grant reaches every table in the namespace this way. *Workaround:* give **read** grants only to clients you would trust with write access to those tables. Being fixed in the next package. (Found while building platform #758, measured afterwards.) | +| KI-311 | Control plane / Catalogs | Security | S2 | **High severity.** A Catalog Client whose role holds only a **read** grant (on a table or on a namespace) receives vended S3 credentials that can also write and delete the table's files. egret's catalog hands out read-write-delete credentials for every table load, and also when the client sends no `X-Iceberg-Access-Delegation` header, so a reader can overwrite or delete the table's data and metadata files without any catalog write: in a measurement, a client with a table **read** grant made the table unreadable for everyone. The credentials are scoped to the loaded table's location: they cannot list, read or write other tables' files or the bucket root. A namespace **read** grant reaches every table in the namespace this way. *Workaround:* give **read** grants only to clients you would trust with write access to those tables. Being fixed in the next package. (Found while building platform #758, measured afterwards.) *Status:* fixed in platform #760 (October 2026): the vended credentials follow the client's grant. A **read** grant, on a table or on a namespace, vends read-only credentials (`s3:GetObject` and `s3:GetObjectVersion` on the table's location), with or without the delegation header and on `…/tables/{t}/credentials`; **write** or **manage** vends read-write-delete. A view load vends read-only credentials. Measured in its live check: the table reader's overwrite and deletes of its table's files are refused (403), it still scans the table, and its PyIceberg append fails with `ACCESS_DENIED during CreateMultipartUpload`. An engine or client with only **read** grants can therefore no longer write; engines provisioned by egret hold `catalog · manage` and are not affected. Credentials handed out before the upgrade stay valid until they expire (KI-315). See [Storage credentials follow the grant](./control-plane/catalogs/catalog-detail#storage-credentials-follow-the-grant). | | KI-312 | Control plane / Catalogs | Security | S3 | Two catalog operations are wider than the grants suggest. Creating a namespace is allowed for a role with any **write** grant, because egret does not read the namespace from the request body, and listing every namespace in the catalog is allowed for a role with any grant. A grant on one table lets the role list every table and view name in that table's namespace. Names only: no table, data or credentials, and a namespace a role creates gives it no access. (Found while building platform #758.) | | KI-313 | Control plane / Catalogs | Security | S3 | egret sets no time limit on reading the body of an Iceberg rename or commit. A client with a valid token that declares a body and sends nothing keeps the connection open with no answer (130 seconds and more measured). Memory stays capped (1 MiB for a rename, 16 MiB for a commit), and a request without a valid token is refused at once with 401. Behind the demo stack's edge, nginx buffers the body first, so the wait moves there. (Found in the live check of platform #758.) | | KI-314 | Deploy / Demo stack | Bug | S2 | Behind the demo stack's edge on a port other than 80, `GET /catalog/v1/config` advertises its `uri` without the port (for example `http://egret.localtest.me/catalog` when the stack runs on 18780). PyIceberg follows the advertised address and fails with *connection refused* on port 80. *Workaround:* run the demo stack on port 80, or run the client on the stack's network and reach egret directly. *Steps:* `DEMO_PORT=18780`, then `load_catalog(uri="http://egret.localtest.me:18780/catalog", …).list_namespaces()`. The cause is not confirmed. (Found in the live check of platform #758.) | +| KI-315 | Control plane / Catalogs | Limitation | S3 | Vended storage credentials are valid for about an hour (the catalog's STS default of 3600 seconds), and the catalog caches them per table location and permission. Revoking or narrowing a grant stops new credentials at once, but credentials already handed out keep working until they expire: a client whose **write** grant was removed can still write the table's files for up to about an hour. Callers with the same permission on the same table are handed the same key, so the object store cannot tell them apart, and one of them cannot be cut off alone. A reader is never handed a writer's key. (Recorded in the design of platform #760.) | +| KI-316 | Control plane / Catalogs | Security | S3 | **Low severity; needs an administrator misconfiguration.** When two warehouses share a location in one bucket and their storage connections write the endpoint differently (for example `http://rustfs:9000` and `http://rustfs:9000/`, or two host names for one store), the catalog does not see them as overlapping. A client with **write** on a namespace in one warehouse can then upload a forged metadata file, register it with the other warehouse's table location, and get read-write-delete credentials on that table's files. Within one warehouse this is refused (409). *Workaround:* give each warehouse its own bucket or key prefix, and write a store's endpoint the same way in every storage connection. (Found in the review of platform #760, from the code; not reproduced.) | ## Not verified