Skip to content

Antalya 26.6: Forward auth tokens to data lake catalogs - #2329

Open
zvonand wants to merge 16 commits into
antalya-26.6from
feature/antalya-26.6/aouth-forward-to-datalake
Open

zvonand wants to merge 16 commits into
antalya-26.6from
feature/antalya-26.6/aouth-forward-to-datalake

Conversation

@zvonand

@zvonand zvonand commented Sep 8, 2026

Copy link
Copy Markdown
Member

Changelog category (leave one):

  • New Feature

Changelog entry (a user-readable short description of the changes that goes to CHANGELOG.md):

Forward auth tokens to data lake catalogs

CI/CD Options

Exclude tests:

  • Fast test
  • Integration Tests
  • Stateless tests
  • Stateful tests
  • Unit tests
  • Performance tests
  • Aarch64 tests
  • All with ASAN
  • All with TSAN
  • All with MSAN
  • All with UBSAN
  • All with Coverage
  • All Regression
  • Disable CI Cache

Regression jobs to run:

  • Fast suites (mostly <1h)
  • Aggregate Functions (2h)
  • Alter (1.5h)
  • Benchmark (30m)
  • CAS (content-addressed storage; Antalya only)
  • ClickHouse Keeper (1h)
  • Iceberg (2h)
  • LDAP (1h)
  • OAuth (5m)
  • Parquet (1.5h)
  • RBAC (1.5h)
  • SSL Server (1h)
  • S3 (2h)
  • S3 Export (2h)
  • Swarms (30m)
  • Tiered Storage (2h)

@zvonand zvonand added antalya-26.6 port-antalya PRs to be ported to all new Antalya releases labels Sep 8, 2026
@zvonand
zvonand force-pushed the feature/antalya-26.6/aouth-forward-to-datalake branch from 5d6ced1 to 33ba90f Compare September 9, 2026 06:48
@github-actions

github-actions Bot commented Sep 9, 2026 •

Copy link
Copy Markdown

Workflow [PR], commit [7a09b52]

@zvonand
zvonand force-pushed the feature/antalya-26.6/aouth-forward-to-datalake branch from 4acc349 to b5f1b30 Compare September 9, 2026 20:59
@hpcnt-stewart

Copy link
Copy Markdown

cool

@zvonand
zvonand force-pushed the feature/antalya-26.6/aouth-forward-to-datalake branch from dadc31f to 37a108b Compare September 21, 2026 12:15
A `DataLakeCatalog` database contacted its catalog as one shared service
identity, so the catalog could not see, authorize or audit the human behind a
query, and every ClickHouse user got identical catalog and storage access.

With `oauth_forward_user_token = 1` the catalog is contacted as the user
running the query. It requires the server-level `enable_token_forwarding`
setting -- off by default, and re-read on every forwarded request, so turning
it off stops already-authenticated sessions from forwarding -- and users who
authenticated with a token (`Authorization: Bearer`, or `--jwt` on the native
protocol). The token is carried on the session and copied into the query
context. There is no service session to fall back to: a query without a token
fails with `CATALOG_USER_TOKEN_NOT_AVAILABLE` instead of reaching the catalog
as the service principal.

How the token is used depends on the catalog type; the setting is rejected for
every other type rather than silently ignored:

- An Iceberg REST catalog takes bearer tokens directly, optionally after an
  RFC 8693 exchange at `oauth_token_exchange_uri`, with
  `oauth_subject_token_type` / `oauth_requested_token_type` describing the
  token and `oauth_forward_actor_token` sending the service principal's own
  token as the `actor_token` for delegation. Exchanged session tokens and the
  credentials the catalog vends are cached per user, bounded by
  `oauth_user_token_cache_ttl` and `vended_credentials_cache_ttl`.

- Glue authenticates with AWS SigV4 and takes no bearer token, so the token
  goes to AWS STS instead: `AssumeRoleWithWebIdentity` against `aws_role_arn`
  returns temporary credentials that sign every Glue request and the S3 reads
  that follow, with the ClickHouse user name as the `RoleSessionName`. The
  Glue client and its credentials provider are built per user and cached on
  the token fingerprint, so one user is never handed another's session.
  `CREATE DATABASE` rejects the combinations that could not be honoured:
  forwarding without `aws_role_arn`, with static `aws_access_key_id` /
  `aws_secret_access_key`, or with `oauth_token_exchange_uri`.

Rotating credentials with `ALTER DATABASE ... MODIFY SETTING
catalog_credential` is carried out as the calling user when forwarding is on,
and an `auth_generation` counter scopes every cached token and vended
credential to the credentials it was minted under, so a request that
authenticated before the ALTER cannot put a rotated-away artifact back into
the caches. A lazy `/v1/config` load racing with the same ALTER is dropped
rather than republishing the credentials it started with.

An asynchronous insert keeps the token verified when it was pushed and flushes
under it; the token's fingerprint takes part in the queue key, so inserts
authenticated with different tokens never share a batch.

Tests: the `test_datalake_token_forwarding`,
`test_datalake_glue_token_forwarding` and `test_datalake_sso_lakekeeper`
(Keycloak with Lakekeeper) integration suites;
`gtest_rest_catalog_token_forwarding` and
`gtest_sts_assume_role_with_web_identity` unit tests; the
`05027_datalake_token_forwarding_masking`,
`05028_datalake_token_forwarding_fail_closed` and
`05053_glue_token_forwarding_validation` stateless tests.
@zvonand
zvonand force-pushed the feature/antalya-26.6/aouth-forward-to-datalake branch from 37a108b to 1072cc3 Compare September 21, 2026 12:21
Remove commentary that restates the code and keep concise explanations of
credential lifetime, publication ordering, and test synchronization.

Consolidate exchange and STS assertions, remove duplicate integration cases,
and drop checks that do not exercise token expiry or rotation. Retain user
isolation, credential-rotation races, async token lifetime, and fail-closed
coverage.

Related: #2329
Drop the three added stateless tests and their references. Runtime refusal
and log-safety checks overlap retained unit and integration coverage, while
masking uses existing behavior. The remaining cases restate straightforward
settings-validation branches rather than cover distinct regressions.

Related: #2329
Reduce the token-forwarding unit suite to five cases covering cache
invalidation, failed preparation, and in-flight credential/config races.
Remove unused forwarding helpers and simplify shared request recording.

Related: #2329
Shorten documentation and comments, remove redundant code, and replace
unnecessary integration setup with direct forwarding checks.

Pass the query token to `updateMetadata` during Iceberg compaction.

Validation: full unit-test build; 19 affected gtests and all 7 remaining
integration cases passed.

Related: #2329
Simplify request serialization, settings validation, and uncached token
exchange. Remove unused state, instrumentation, fixture settings, and the
STS rejection gtest already covered by integration testing.

Trim repeated documentation and restore unrelated client OAuth encoding
to the release-branch implementation.

Validation: rebuilt ClickHouse and unit tests; affected gtests and all
seven integration cases passed. After removing the redundant gtest, both
remaining STS mock tests and both Glue integration tests passed.

Related: #2329
The endpoint is checked against `remote_url_allow_hosts`, so forwarded
user tokens cannot be sent to arbitrary hosts via `AssumeRoleWithWebIdentity`.
zvonand added a commit to Altinity/oauth-examples that referenced this pull request Sep 29, 2026
glue.py signs in to Entra with MSAL and passes the access token to
clickhouse-connect. ClickHouse verifies it and forwards it to STS
AssumeRoleWithWebIdentity for a DataLakeCatalog Glue database
(Altinity/ClickHouse#2329), so Glue is called with per-user credentials.
Replace `TokenRequest` with `Poco::URI::QueryParameters`, simplify settings validation, and remove unnecessary test-server ownership wrappers.

Validated with 32 catalog, STS, and async-insert unit tests.

Related: #2329
@zvonand

zvonand commented Sep 30, 2026

Copy link
Copy Markdown
Member Author

@blau-ai

@zvonand

zvonand commented Sep 30, 2026

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-07T11:47:00.034486Z fb3889b Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@blau-ai

This comment was marked as outdated.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fc378c0429

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/Databases/DataLake/RestCatalog.cpp Outdated
Comment thread src/Databases/DataLake/DatabaseDataLake.cpp
@svb-alt svb-alt added the antalya label Oct 1, 2026
@ianton-ru

This comment was marked as outdated.

Throw `BAD_ARGUMENTS` when `oauth_forward_actor_token = 1` is set without
`oauth_forward_user_token`, on a Glue catalog, or on a REST catalog without
`oauth_token_exchange_uri`, instead of silently falling back to plain passthrough.

#2329 (comment)
@zvonand

zvonand commented Oct 7, 2026

Copy link
Copy Markdown
Member Author

@codex review

@zvonand

zvonand commented Oct 7, 2026

Copy link
Copy Markdown
Member Author

@blau-ai

@zvonand

zvonand commented Oct 7, 2026 •

Copy link
Copy Markdown
Member Author

Manually tested against "Real" AWS Glue (token exchanged for credentials), with Azure as external OIDC provider

@blau-ai

This comment was marked as outdated.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fb3889b13f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/Databases/DataLake/GlueCatalog.cpp Outdated
Comment on lines 729 to 732
return [this, storage_id, credentials_provider]() -> std::shared_ptr<IStorageCredentials>
{
LOG_DEBUG(log, "Refreshing AWS credentials for {} after expired token", storage_id.getNameForLogs());
auto credentials = credentials_provider->GetAWSCredentials();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Recheck forwarding before refreshing Glue credentials

When a long-running Glue-backed S3 read receives ExpiredToken, this callback invokes the captured web-identity provider without rechecking the hot-reloadable enable_token_forwarding switch. If an administrator disables forwarding after the storage was created, the provider can still send the retained user token to STS and mint new credentials, whereas normal catalog calls stop in validateForwardedToken; recheck the switch when the callback runs and fail without contacting STS when it is disabled.

Useful? React with 👍 / 👎.

Comment thread src/Databases/DataLake/GlueCatalog.cpp Outdated
Comment on lines +755 to +757
auto [value, _] = metadata_objects.getOrSet(metadata_uri, [&]()
{
auto [object_storage, bucket_name, metadata_path] = createObjectStorageForEarlyTableAccess(metadata_uri, table_metadata);
auto [object_storage, bucket_name, metadata_path] = createObjectStorageForEarlyTableAccess(client, metadata_uri, table_metadata);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Partition cached Glue metadata by forwarded identity

In a token-forwarding Glue catalog, the S3 client used to fetch Iceberg metadata is user-specific, but metadata_objects remains keyed only by metadata_uri. If user A warms an entry and user B can access the Glue table but lacks permission to read that S3 metadata object, B receives A's cached parsed object without the lambda—and therefore B's S3 authorization—ever running, exposing metadata-derived schema and snapshot information. Include the forwarded-token identity in this cache key or avoid sharing these entries across users.

Useful? React with 👍 / 👎.

… cache by token

The `GlueCatalog` credentials refresh callback now calls `getClient` on each
refresh, so it stops contacting STS once `enable_token_forwarding` is disabled.

`metadata_objects` is keyed by the forwarded token fingerprint and the metadata
URI, so a user never gets Iceberg metadata fetched with another user's
credentials.

Addresses review comments in #2329
…ature/antalya-26.6/aouth-forward-to-datalake
…ature/antalya-26.6/aouth-forward-to-datalake

# Conflicts:
#	src/Databases/DataLake/Common.h
#	src/Databases/DataLake/DatabaseDataLake.cpp
#	src/Databases/DataLake/DatabaseDataLake.h
#	src/Databases/DataLake/GlueCatalog.cpp
#	src/Databases/DataLake/GlueCatalog.h
#	src/Databases/DataLake/HiveCatalog.cpp
#	src/Databases/DataLake/HiveCatalog.h
#	src/Databases/DataLake/ICatalog.cpp
#	src/Databases/DataLake/ICatalog.h
#	src/Databases/DataLake/PaimonRestCatalog.cpp
#	src/Databases/DataLake/PaimonRestCatalog.h
#	src/Databases/DataLake/RestCatalog.cpp
#	src/Databases/DataLake/RestCatalog.h
#	src/Databases/DataLake/S3TablesCatalog.cpp
#	src/Databases/DataLake/S3TablesCatalog.h
#	src/Databases/DataLake/UnityCatalog.h
#	src/Interpreters/AsynchronousInsertQueue.h
#	src/Storages/ObjectStorage/DataLakes/Iceberg/IcebergMetadata.cpp
#	src/Storages/ObjectStorage/StorageObjectStorage.cpp
@Selfeer

This comment was marked as outdated.

…alogs

Audit: #2329 (comment)

- M-1: `validateCreateTableEngine` now loads the REST catalog config with the
  caller's forwarded token before checking the catalog storage type, so the
  engine-vs-catalog backend check is not skipped on a cold catalog.
- M-2: remove `config_mutex`; `/v1/config` is fetched without holding a lock
  and published under `auth_publish_mutex`.
- L-1: `StorageObjectStorage` captures the forwarded token only when it has a catalog.
- L-2: clamp `expires_in` before computing token expiry.
- L-3: document that catalog and token-exchange endpoints should use HTTPS.
- L-5: append a hash of the full principal to the Glue `RoleSessionName`
  when the principal is not a valid session name as is.
- L-6: remove `DataLakeCatalogUserTokenCacheBytes` and
  `DataLakeCatalogUserClientCacheBytes`, which counted entries, not bytes.
`AccessControl::setExternalAuthenticatorsConfig` re-reads `enable_token_forwarding`
on every config reload, but the setting was not listed in
`collectChangeableServerSettings`, so `system.server_settings` reported
`changeable_without_restart = No`.

#2626
…closing the connection

If resolving a table in `TCPHandler::processTablesStatusRequest` throws, the
exception is now logged and sent back to the client as `Exception`, keeping
the connection open. Before interserver-secret authentication, the exception
is still rethrown and the connection is closed.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

antalya antalya-26.6 port-antalya PRs to be ported to all new Antalya releases

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants