Skip to content

feat(catalog-rest): introduce AuthManager/AuthSession, rework SigV4 as a wrapping auth manager#2815

Draft
plusplusjiajia wants to merge 1 commit into
apache:mainfrom
plusplusjiajia:feat/rest-auth-manager
Draft

feat(catalog-rest): introduce AuthManager/AuthSession, rework SigV4 as a wrapping auth manager#2815
plusplusjiajia wants to merge 1 commit into
apache:mainfrom
plusplusjiajia:feat/rest-auth-manager

Conversation

@plusplusjiajia

@plusplusjiajia plusplusjiajia commented Jul 12, 2026

Copy link
Copy Markdown
Member

Prototype for the AuthManager direction discussed in the review of #2660 (see the inline thread there). This draft includes #2660's SigV4 commits squashed in.

What it does

Mirrors Java's AuthManager API:

  • AuthManager/AuthSession traits in a new auth/ module: init_session() serves the GET /v1/config handshake, catalog_session(merged_props) serves everything after, so a manager can rebuild its
    session from server-merged properties.
  • Noop/OAuth2/SigV4 managers, selected via a new rest.auth.type property (the legacy rest.sigv4-enabled still works), injectable through RestCatalogBuilder::with_auth_manager.
  • OAuth2 token handling moves out of HttpClient into OAuth2Manager, with the cached token surviving the config handshake.
  • SigV4Manager wraps a delegate session instead of being mutually exclusive with token auth: the delegate's Authorization header is relocated to X-Iceberg-Authorization before signing, so it is covered by
    the signature — matching the Java RESTSigV4AuthSession behavior.
  • Server-supplied rest.signing-* from the config response is honored when building the post-handshake session; rest.auth.sigv4.delegate-auth-type (none/oauth2) explicitly selects the wrapped auth,
    defaulting to inference from token/credential presence; OAuth2Manager is publicly constructible (new() + with_*).

Java references: org.apache.iceberg.rest.auth (AuthManager/AuthSession) and
RESTSigV4AuthSession.

Known prototype simplifications

  • Manager type is fixed from user config; server config can't switch the auth type (same as Java).

@plusplusjiajia
plusplusjiajia force-pushed the feat/rest-auth-manager branch from e1ba91d to a97a247 Compare July 12, 2026 05:28

@CTTY CTTY left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Hey @plusplusjiajia , thanks for this contribution! I really like the general direction. To move forward, could you break this down to several smaller PRs? For example you can break it down to 2-3 PRs:

  1. Introduce AuthManager/Session traits and migrate existing oauth2 to use the new traits
  2. Add Sigv4 support


/// How the payload hash is encoded in the `x-amz-content-sha256` header.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PayloadHashMode {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

these should live in sigv4.rs?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@CTTY Agreed — it's SigV4-specific. In the split, the signer and the manager will live together under auth/sigv4, so PayloadHashMode moves there (part of the SigV4 PR).

warehouse: None,
props: HashMap::new(),
client: None,
custom_auth_manager: None,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

let's just name it auth_manager

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@CTTY Will rename to auth_manager in the split.

pub(crate) async fn token(&self) -> Option<String> {
let mut req = self
.request(Method::GET, &self.token_endpoint)
.request(Method::GET, "http://localhost/unused")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

why is this needed?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@CTTY It's a test-only shim: HttpClient no longer holds the token itself, so the #[cfg(test)] token() helper builds a throwaway request (never sent — authenticate only mutates headers) and reads the bearer back from the Authorization header. I'll replace it in the split with assertions on the headers the mock server observes.

@CTTY CTTY mentioned this pull request Jul 16, 2026
@plusplusjiajia

Copy link
Copy Markdown
Member Author

Hey @plusplusjiajia , thanks for this contribution! I really like the general direction. To move forward, could you break this down to several smaller PRs? For example you can break it down to 2-3 PRs:

  1. Introduce AuthManager/Session traits and migrate existing oauth2 to use the new traits
  2. Add Sigv4 support

@CTTY Thanks for the review! Agreed — I'll split this into:

  1. AuthManager/AuthSession traits + migrating the existing OAuth2 flow (pure refactor, no behavior change);
  2. SigV4 support on top — reworking feat(rest): support AWS SigV4 request signing for the REST catalog #2660 in place once the traits PR lands.

&self,
props: &HashMap<String, String>,
) -> Result<Arc<dyn AuthSession>>;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Could we add other methods like fn contextual_session() and fn table_session() too?

We'll need to extend the trait eventually, and since it's public it would otherwise become a breaking change.
Both of the methods will take a parent AuthSession and can get a default implementation like

fn table_session<'a>(&self, _: &TableIdent, parent: &'a dyn AuthSession) -> Result<&'a dyn AuthSession> {
    Ok(parent)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Also note that the fn contextual_session() would take something like a SessionContext which I'm only introducing in #2836. I can split it into its own PR so we can get it in faster.

fn contextual_session<'a>(&self, _: &SessionContext, parent: &'a dyn AuthSession) -> Result<&'a dyn AuthSession> {
    Ok(parent)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I just realized that we can still extend the trait later with methods like this that have a default implementation... never mind my above comment 🤦

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'm happy to extend it as a follow-up because the fn contextual_session() will be relevant for my goal to ship #2774 🙂

#[async_trait]
pub trait AuthSession: Debug + Send + Sync {
/// Applies authentication to the request (adds headers, signs, ...).
async fn authenticate(&self, request: &mut Request) -> Result<()>;

@DerGut DerGut Jul 17, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

IMO it would help to introduce a small wrapper around reqwest::Request to not expose the HTTP library we're using (internally) as part of public traits

Comment on lines +37 to +40
/// The auth manager living for the lifetime of the catalog.
auth_manager: Arc<dyn AuthManager>,
/// The session authenticating requests in the current phase.
session: Arc<dyn AuthSession>,

@DerGut DerGut Jul 17, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'm thinking that the AuthManager/ AuthSession might be better placed in the RestCatalog.

In this current implementation both are working fine, but if we add table_session() and contextual_session() to the mix, the AuthManager will have to translate TableIdents and SessionContexts to AuthSessions.
IMO this is not the layer of abstraction that the HttpClient should operate on. In my mind, an AuthManager manages the creation of AuthSessions which the client can then use to authenticate its requests. The client should therefore not itself have to deal with creating those sessions.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants