Skip to content

feat(rest): parse storage-credentials and bind a per-table FileIO from vended credentials#719

Open
plusplusjiajia wants to merge 4 commits into
apache:mainfrom
plusplusjiajia:feat-rest-storage-credentials
Open

feat(rest): parse storage-credentials and bind a per-table FileIO from vended credentials#719
plusplusjiajia wants to merge 4 commits into
apache:mainfrom
plusplusjiajia:feat-rest-storage-credentials

Conversation

@plusplusjiajia

@plusplusjiajia plusplusjiajia commented Jun 10, 2026

Copy link
Copy Markdown
Member

What

Parse storage-credentials from the REST LoadTableResponse and use the vended credentials to bind a per-table FileIO, so a loaded Table can read/write its (S3-compatible) storage without any manual wiring.

Why

REST catalogs hand out short-lived, scoped storage access via vended credentials (STS). Until now LoadTableResult.storage_credentials was dropped on the floor and every table fell back to the catalog's default FileIO, so a vended-credential table could not actually access storage.

How

  • Serde: add storage-credentials (prefix + config) to the LoadTableResponse (de)serializer, aligned with Java LoadCredentialsResponseParser / CredentialParser.
  • Credential selection: SelectS3StorageCredential picks the longest matching s3-family prefix, matching Java VendedCredentialsProvider.
  • Per-table FileIO: implement the REST catalog's TableFileIO hook added in feat(rest): add session-aware REST catalog #750 — when the load response carries an s3-family vended credential, merge catalog + table config + the credential and build an arrow-fs-s3 FileIO (resolving oss:// and other S3-compatible schemes), replacing the fail-closed ValidateNoFileIOConfig
    path; otherwise keep the catalog default. This fills the TODO(gangwu) left in feat(rest): add session-aware REST catalog #750.
  • S3 option alignment: prefer the standard client.region (fall back to legacy s3.region), strip the scheme from a full-URI s3.endpoint, and honor s3.path-style-access for virtual-hosted addressing

@plusplusjiajia plusplusjiajia force-pushed the feat-rest-storage-credentials branch 2 times, most recently from c1216fa to 00ef76a Compare June 14, 2026 10:03
@plusplusjiajia plusplusjiajia changed the title feat(rest): parse storage-credentials from LoadTableResponse feat(rest): parse storage-credentials and bind a per-table FileIO from vended credentials Jun 15, 2026
@plusplusjiajia plusplusjiajia force-pushed the feat-rest-storage-credentials branch from 968d073 to f8c78cc Compare June 16, 2026 08:43
@plusplusjiajia

Copy link
Copy Markdown
Member Author

Rebased onto main now that #750 merged. The per-table FileIO binding now lives in the TableFileIO hook — it builds an arrow-fs-s3 FileIO from the vended s3 credential, else keeps the fail-closed path, filling the TODO(gangwu) from #750. @wgtmac @gangwu PTAL.

@plusplusjiajia plusplusjiajia force-pushed the feat-rest-storage-credentials branch from f8c78cc to 43838fd Compare June 16, 2026 09:17
const std::vector<StorageCredential>& credentials) {
const StorageCredential* best = nullptr;
for (const auto& cred : credentials) {
if (cred.prefix.starts_with("s3") &&

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Prefix selection needs the actual storage path. The REST spec and Java S3FileIO choose the longest prefix that matches each path; picking the longest S3 prefix globally makes unmatched paths and other S3 prefixes use the wrong credential.

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.

@wgtmac Good catch. Fixed: SelectS3StorageCredential now picks the longest credential prefix that is actually a prefix of the table's storage location (passed down from metadata->location), instead of the longest s3 prefix globally — matching the REST spec / Java S3FileIO. It also treats s3/s3a/s3n as equivalent so an s3:// vended prefix still matches an s3a:// location.

// response. The current table commit response does not define config.
ICEBERG_ASSIGN_OR_RAISE(auto table_io, TableFileIO(context, table_config));
ICEBERG_ASSIGN_OR_RAISE(auto table_io,
TableFileIO(context, table_config, /*storage_credentials=*/{}));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This rebuilds the returned table with an empty credential list after commit. CommitTableResponse does not carry storage-credentials, so a table loaded with vended credentials loses its FileIO credentials after the first commit.

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.

@wgtmac Fixed. MakeTableFromCommitResponse now reuses the FileIO bound at load (threaded through TableScopedCatalog) instead of rebuilding it with an empty credential list.

for (const auto& [key, value] : table_config) {
properties[key] = value;
}
for (const auto& [key, value] : s3_cred.config) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This makes the STS values static. Java S3FileIO keeps the credential set and refreshes through credentials.uri when s3.session-token-expires-at-ms is present; long scans or reused tables will fail after the vended token expires.

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.

@wgtmac You're right that the vended STS values are static and will expire on long scans / reused tables. Implementing refresh properly needs a credential-aware FileIO with a background refresher that re-fetches via credentials.uri (mirroring Java's VendedCredentialsProvider + S3FileIO#scheduleCredentialRefresh) — a larger change than this PR. I've left an explicit TODO and would prefer to handle it as a follow-up. Shall I open an issue to track it? Happy to scope it there.

// Longest-prefix "s3" vended credential. Java's VendedCredentialsProvider
// resolves per path against the table location; we bind one at load time
// (fine for the common one-credential-per-table case).
if (const StorageCredential* s3_cred = SelectS3StorageCredential(storage_credentials);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

storage-credentials can also carry GCS/ADLS credentials. This path silently ignores non-S3 credentials and falls back to the catalog FileIO; if S3-only is intentional for now, please add an explicit TODO or fail-fast note.

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.

@wgtmac Addressed. When the server vends only non-S3 credentials (GCS/ADLS), TableFileIO now fails fast with NotSupported listing the prefixes, rather than silently using the catalog FileIO. If S3 credentials are vended but none covers this table's location, it falls through to the catalog FileIO (which may use ambient creds). S3-only support is intentional for now — see the TODO in TableFileIO.

@plusplusjiajia plusplusjiajia force-pushed the feat-rest-storage-credentials branch from dffa3b3 to 634a95e Compare June 23, 2026 08:22
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.

2 participants