Core: Exchanged AuthSession refreshes with the parent's client credential, silently changing identity
- Dominant language
- Java
- Stars
- 9.2k
- Forks
- 3.5k
- Avg merge
- 2d 11h
- Merged PRs (30d)
- 132
Description
### Apache Iceberg version
1.10.1 (also reproduces on `main`)
### Query engine
Trino
### Please describe the bug 🐞
When `token-exchange-enabled` is set to `false`, a session that was created by token exchange refreshes itself using the parent's client credential. The refreshed token identifies the catalog client rather than the exchanged subject, so the session changes identity with no error reported.
Configuration that reproduces it:
* a catalog configured with `credential` and `token-exchange-enabled=false`
* a `SessionContext` supplying a typed subject token such as `urn:ietf:params:oauth:token-type:jwt`, which is what Trino sends when `iceberg.rest-catalog.session=USER`
With those credentials, `OAuth2Manager.maybeCreateChildSession` skips the `token` and `credential` branches and reaches `newSessionFromTokenExchange`, so the contextual session is minted by exchanging the user's subject token.
`AuthSession.fromTokenExchange` builds that child through `fromTokenResponse`, which copies the parent configuration with `AuthConfig.builder().from(parent.config())`. That inherits both `credential` and `exchangeEnabled` from the catalog session.
On refresh, `OAuth2Util.refreshToken` branches only on `exchangeEnabled`. Since the child inherited `false`, it takes the `client_credentials` path using the inherited credential, and the catalog client's token replaces the user's token in the session. Requests continue to succeed, so there is nothing to observe apart from the change in effective principal.
For a one hour token this happens roughly 55 minutes into the session. `AuthSessionCache` uses `expireAfterAccess`, so the sessions that live long enough to refresh are exactly the ones belonging to active users.
The inherited value also reaches table sessions, because `RESTSessionCatalog` passes the contextual session as the parent when it calls `AuthManager.tableSession`.
There is currently no test coverage for this combination. The two tests in `TestRESTCatalog` that set `token-exchange-enabled=false` never create a session through `fromTokenExchange`: one passes empty context credentials, and the other passes `token`, which takes the `fromAccessToken` path instead.
Suggested fix: a session created by exchanging a subject token should be renewed the same way, independent of the catalog level flag. #13809 added the flag to control how credential derived sessions refresh, and applying it to exchange derived sessions changes which principal the session represents.
Related, and not covered by the above: `AuthSession.fromAccessToken` inherits the parent credential in the same way, so the bearer token path carries the same hazard.
Contributor guide
Research direction
Start with AuthSession.fromTokenExchange and OAuth2Util.refreshToken, then inspect the token-exchange-disabled cases in TestRESTCatalog. Trace how the parent AuthConfig is copied and how refresh selects credentials. Add coverage for a subject-token session with token-exchange-enabled=false and verify that refresh preserves the exchanged subject identity.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java
- Domain
- authentication, backend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100