Skip to content

feat(iceberg): refreshable Google metadata-server catalog auth - #1567

Open
christophediprima wants to merge 2 commits into
fluree:mainfrom
christophediprima:feature/iceberg-google-metadata-auth
Open

feat(iceberg): refreshable Google metadata-server catalog auth#1567
christophediprima wants to merge 2 commits into
fluree:mainfrom
christophediprima:feature/iceberg-google-metadata-auth

Conversation

@christophediprima

Copy link
Copy Markdown
Contributor

A static bearer token for a Google Iceberg REST catalog (BigLake) is a short-lived
OAuth access token. It expires after roughly an hour, and BearerTokenAuth has no way to
renew it — so any long-running consumer of that catalog starts returning 401s and stays
broken until someone re-registers the graph source with a fresh token.

This adds AuthConfig::GoogleMetadata / GoogleMetadataAuth, which mints and
auto-refreshes tokens from the GCE/GKE instance metadata server (Workload Identity).

What changes

  • fluree-db-iceberg/src/auth/google_metadata.rs — the provider. Fetches from the
    metadata server with the required Metadata-Flavor: Google header, optional scopes
    (defaults to cloud-platform), and the same cache-plus-refresh behaviour
    OAuth2ClientCredentials already has.
  • fluree-db-iceberg/src/auth/token.rs — the jittered-expiry CachedToken is lifted out
    of oauth2.rs so both providers share one implementation rather than growing a second
    copy. oauth2.rs shrinks by 42 lines as a result; behaviour is unchanged.
  • {Iceberg,R2rml}CreateConfig::with_auth_google_metadata(scopes) and an
    auth_google_metadata field on POST /v1/fluree/iceberg/map.
  • Docs in docs/api/endpoints.md and docs/graph-sources/iceberg.md.

Scope notes

  • Storage credentials are untouched. The GCS reader's HMAC interop keys are static and
    non-expiring; only the catalog leg had the expiry problem.
  • The metadata server is only reachable on GCE/GKE. Local and CI runs keep using a static
    auth_bearer, which is why the tests below drive the provider against a stub server
    rather than the real endpoint.

Tests — two in google_metadata.rs: one asserts the flavor header, the scope string
and that a second call is served from cache rather than refetched; one asserts a metadata
failure surfaces rather than silently yielding no header.

Why it is worth having on its own: this is a correctness fix for anyone pointing
Fluree at BigLake, independent of anything else in #1422 — the expiring-token failure is
real today and there is no workaround short of periodically re-registering the source.

christophediprima and others added 2 commits July 30, 2026 12:16
A static bearer for a Google Iceberg REST catalog (BigLake) is a short-lived
OAuth access token: it expires after ~1h and `BearerTokenAuth` cannot renew it,
so a long-running materialization tracking worker starts returning 401s.

Add `AuthConfig::GoogleMetadata` + `GoogleMetadataAuth`, which mints and
auto-refreshes tokens from the GCE/GKE instance metadata server (Workload
Identity), mirroring the existing `OAuth2ClientCredentials` cache+refresh — the
jittered-expiry `CachedToken` is now shared via `auth::token`. Wire it through
`{Iceberg,R2rml}CreateConfig::with_auth_google_metadata` and the `/iceberg/map`
`auth_google_metadata` field.

The GCS reader's storage HMAC keys are unaffected (static, non-expiring). The
metadata server is only reachable on GCE/GKE; local runs keep using a static
`auth_bearer`.

Extracted from fluree#1422 as part 1 of the split proposed in review. Two adaptations
were needed against current main, neither visible in the diff:

  - `AuthConfig::hydrate` gained a `GoogleMetadata` arm. Spelled out rather than
    folded into a wildcard, so that match keeps failing compilation when a new
    variant appears — `GoogleMetadata` carries no `ConfigValue`, so there is no
    secret reference to resolve.
  - `IcebergCreateConfig` has since been refactored into `connection` +
    `table_identifier` with the auth builders living on `IcebergConnectionConfig`,
    so the `R2rmlCreateConfig` delegator needed a forwarding method alongside the
    existing `with_auth_bearer`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`test_every_auth_config_variant_redacts_on_emit` matches `AuthConfig` with no
wildcard arm on purpose: a new variant is meant to stop the test compiling
until someone has made an explicit decision about whether it carries secret
material. Adding `GoogleMetadata` tripped exactly that guard, so `clippy` and
`test` both failed to build `fluree-db-api`'s lib test target.

The decision it was asking for: GoogleMetadata carries no secret material and
earns no allowlist entry. Both of its fields are plain `String` rather than
`ConfigValue`, so there is no secret-capable field to seed with a sentinel —
the credential is minted per call from the instance metadata server and lives
only in the in-memory `CachedToken`, which is never serialized and whose
hand-written `Debug` prints `***`.

Adds the variant to the fixture so it goes through the redaction assertions
alongside the others, covers it in the canary match, and records the reasoning
where the next person to add a variant will read it.

Only `fmt` passed before this, because it is the one gating job that never
compiles. Verified with the flags CI actually uses — `--all-features` matters
here, since the canary is `#[cfg(feature = "iceberg")]`:

    cargo clippy --all --all-features --all-targets   # exit 0
    cargo test -p fluree-db-iceberg --all-features    # 272 passed
    cargo test -p fluree-db-api --all-features --lib  # 904 passed
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.

1 participant