fix(server): rotate iceberg secret alongside DuckLake on STS expiry#563
Merged
Conversation
Follow-up to #562. That PR fixed the initial-attach path so iceberg works at session activation, but the hot-idle credential refresh path in duckdbservice/activation.go only rotates the DuckLake S3 secret (ducklake_s3). The iceberg secret (iceberg_sigv4) shares the same STS-minted credentials but was never being re-emitted, so iceberg queries on a long-lived worker would 403 ~1h after activation while DuckLake stayed fresh. Changes: - server/server.go: new RefreshIcebergSecret. Mirrors RefreshS3Secret — no-op when iceberg is disabled, rejects empty credentials with the same error shape as AttachIcebergCatalog (no silent fallback to a credential_chain that we removed in #562), re-emits the iceberg_sigv4 secret via the existing BuildIcebergSecretStmt, and handles DuckDB's "Current transaction is aborted" recovery the same way the S3 refresh does. Importantly, RefreshIcebergSecret does NOT short-circuit when the iceberg catalog is already attached (unlike AttachIcebergCatalog). The whole point of refresh is to overwrite the existing secret in place; DuckDB resolves the secret at request time so the new credentials take effect for the next iceberg query without an explicit reattach. - duckdbservice/service.go: new refreshIcebergSecret indirection on SessionPool, defaulted to server.RefreshIcebergSecret. Same shape as refreshS3Secret so tests can inject a stub. - duckdbservice/activation.go (reuseExistingActivation): two changes. 1. The needsRefresh predicate now triggers when Iceberg.Enabled is true *or* DuckLake.ObjectStore is set. Previously, an iceberg-only tenant (DuckLake metadata-only, S3 bucket empty, iceberg enabled) would skip refresh entirely because ObjectStore=="" gated it out — even though Iceberg.Enabled=true and the credentials WERE in the payload (the activator populates DuckLake.S3* from STS regardless of whether DuckLake itself uses S3). 2. The refresh block now invokes both refresh functions, each gated on its own enable check. They share the same DuckLake.S3* triple because the per-tenant IAM role grants both s3:* and s3tables:* and STS returns one credential bundle covering both. - server/iceberg_refresh_test.go: three new regression tests run in the server package (so they can exercise the exported RefreshIcebergSecret while the iceberg subpackage stays free of server-package imports). * TestRefreshIcebergSecretRotatesCredentials uses an in-memory DuckDB with the iceberg extension to verify back-to-back CREATE OR REPLACE with different credentials both succeed — the exact behavior STS rotation depends on. Skips when the extension can't be installed (air-gapped CI), same posture as the existing integration test. * TestRefreshIcebergSecretNoOpWhenDisabled confirms the activation layer can call this unconditionally without harm when the tenant hasn't opted in to iceberg. * TestRefreshIcebergSecretRejectsEmptyCredentials locks in the "fail loud, no silent fallback" invariant for the refresh path, matching what #562 established for the attach path. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Follow-up to #562. That PR fixed the initial-attach path so iceberg works at session activation, but the hot-idle credential-refresh path in `duckdbservice/activation.go` only rotates the DuckLake S3 secret (`ducklake_s3`). The iceberg secret (`iceberg_sigv4`) shares the same STS-minted credentials but was never being re-emitted, so iceberg queries on a long-lived worker would 403 ~1h after activation while DuckLake stayed fresh.
This PR adds the missing rotation.
What changed
`server/server.go` — new `RefreshIcebergSecret`
Mirrors `RefreshS3Secret`:
Importantly, `RefreshIcebergSecret` does not short-circuit when the iceberg catalog is already attached (unlike `AttachIcebergCatalog`). The whole point of refresh is to overwrite the existing secret in place; DuckDB resolves secrets at request time, so the new credentials take effect for the next iceberg query without an explicit reattach.
`duckdbservice/service.go` — `refreshIcebergSecret` indirection
New field on `SessionPool`, defaulted to `server.RefreshIcebergSecret`. Same shape as the existing `refreshS3Secret` so tests can inject a stub.
`duckdbservice/activation.go` (`reuseExistingActivation`)
Two changes:
`needsRefresh` predicate now triggers when `Iceberg.Enabled` or `DuckLake.ObjectStore` is set. Previously, an iceberg-only tenant (DuckLake metadata-only, S3 bucket empty, iceberg enabled — e.g. `test-org-smoke-1778167994` in dev) would skip refresh entirely because `ObjectStore==""` gated it out — even though `Iceberg.Enabled=true` and the credentials were in the payload. The activator populates `DuckLake.S3*` from STS regardless of whether DuckLake itself uses S3.
Refresh block now invokes both refresh functions, each gated on its own enable check. They share the same `DuckLake.S3*` triple because the per-tenant IAM role grants both `s3:` and `s3tables:` and STS returns one credential bundle covering both.
`server/iceberg_refresh_test.go` — new tests
Three tests in the server package (so they can exercise the exported `RefreshIcebergSecret` while the `server/iceberg` subpackage stays free of `server`-package imports):
Test plan
🤖 Generated with Claude Code