fix(catalog/rest): sign SigV4 with credentials from catalog properties - #1999
Conversation
The REST catalog's SigV4 signer only used the AWS default credential chain (config.LoadDefaultConfig), ignoring the s3.* credential properties passed to the catalog. This forced callers targeting AWS SigV4 REST catalogs (S3 Tables, Glue) to export AWS_* environment variables even when they supplied credentials via catalog properties. Build a static credentials provider from s3.access-key-id / s3.secret-access-key / s3.session-token when present, and only fall back to the default chain otherwise. An explicitly provided aws.Config (WithAwsConfig) still wins. This mirrors the same gap tracked for the Python client in apache/iceberg-python#2070. Signed-off-by: iremcaginyurtturk <cagin.yurtturk@getbruin.com>
zeroshade
left a comment
There was a problem hiding this comment.
The SigV4-from-properties fix genuinely works and does not silently downgrade to unsigned, but the wiring line is pinned by no test (package stays green when it is deleted) and a partial credential pair silently signs as the ambient identity instead of erroring.
Re-review verification: 0 of 1 prior findings confirmed fixed at ffa55bb (each verified by mutating the fix and observing the suite go red, not by taking the claim on trust).
Verification performed
go build ./catalog/... (OK); go vet ./catalog/rest/ (clean); go test ./catalog/rest/ -timeout=300s -> ok 5.908s; go test ./catalog/rest/ -run TestStaticCredsFromProps -v -> PASS. Mutation 1: neutralized `cfg.Credentials = creds` -> full package STILL ok 5.946s (fix unpinned). Mutation 2: swapped access/secret args in NewStaticCredentialsProvider -> TestStaticCredsFromProps FAILS (helper test is non-vacuous). Throwaway probe catalog/rest/pr1999_probe_test.go (5 tests: props-reach-signer, no-creds-anywhere, partial-creds, precedence, server-override) all ran and were then deleted. Final: git status --porcelain empty, go vet ./catalog/hive/ clean.
This review was drafted by an AI-assisted tool and confirmed by an Apache Iceberg Go maintainer. After you've addressed the points above and pushed an update, an Apache Iceberg Go maintainer — a real person — will take the next look at the PR. The findings cite the project's review criteria; if you think one of them is mis-applied, please reply on the PR and a maintainer will weigh in.
More on how Apache Iceberg Go handles maintainer review: CONTRIBUTING.md.
Address review feedback: - Add TestSigV4SignsWithPropsCredentials, which asserts the SigV4 Authorization header is signed with the credentials from catalog properties. This pins the wiring: removing the cfg.Credentials assignment makes the test fail. - staticCredsFromProps now returns an error when only one of the access-key / secret-access-key pair is set, instead of silently falling back to the ambient default identity. Neither set still falls back to the default chain. Signed-off-by: iremcaginyurtturk <cagin.yurtturk@getbruin.com>
|
Thanks for the review — both points addressed in cca7c83:
|
Address review feedback (minor): the SigV4 signing identity resolves as WithAwsConfig > s3.* catalog properties > AWS default credential chain. Document this on WithSigV4 and in the rest.sigv4-enabled configuration reference. Signed-off-by: iremcaginyurtturk <cagin.yurtturk@getbruin.com>
|
All four review findings addressed: 1. (major) Fix pinned by no test — 2. (major) Partial 3. (minor) Undocumented precedence — 4. (minor) Server Full |
laskoviymishka
left a comment
There was a problem hiding this comment.
Nice, this is basically there. The props-credential path works, and the two things flagged last round are both handled now:
TestSigV4SignsWithPropsCredentialspins the wiring end to end. It asserts the Authorization header carriesCredential=AKIDEXAMPLEPROPS/, which only holds when the props creds are actually signing rather than the default chain.staticCredsFromPropsnow errors on a lone access-key or secret-key instead of silently signing as a different identity, and both cases are covered.
The one thing I'd still fix is the token-only case: a lone s3.session-token with no access/secret falls into the empty-pair branch and returns (nil, nil), so it silently drops to the default chain. We already have internal/awsconfig.ValidateStaticCredentials for exactly this check. It's what io/gocloud/s3 and catalog/glue use, and it returns the ErrIncompleteStaticCredentials sentinel, so delegating to it closes the gap and makes the error errors.Is-detectable in one move instead of a bare fmt.Errorf. Worth a token-only test case too.
Not blocking, but while we're here: sourcing the signing creds from s3.* diverges from Java, which uses rest.access-key-id / rest.secret-access-key. You've documented s3.* as intentional so I'm fine with it, but an operator coming from the Java client configuring rest.* will silently fall through to the default chain. Might be worth a line in the godoc calling that out, or accepting rest.* as an alias down the line. wdyt?
The token-only fix is the one I'd still like to see; everything else is optional. This is close.
…Credentials Delegate staticCredsFromProps to internal/awsconfig.ValidateStaticCredentials so a token-only s3.session-token (no access/secret key) returns the ErrIncompleteStaticCredentials sentinel instead of silently dropping to the default credential chain. Add a token-only test case and assert the sentinel via errors.Is. Document the s3.* vs Java rest.* divergence in the WithSigV4 godoc and configuration.md.
Accept rest.access-key-id / rest.secret-access-key / rest.session-token as aliases for the s3.* signing-credential properties, resolved per field with the s3.* keys taking precedence. This lets operators migrating from the Java client configure SigV4 signing with the property names they already use instead of silently falling through to the AWS default credential chain.
zeroshade
left a comment
There was a problem hiding this comment.
REQUEST_CHANGES. The 09-08 wiring and documentation feedback is addressed: TestSigV4SignsWithPropsCredentials pins the fix end to end, the lone-access/lone-secret/token-only cases now return ErrIncompleteStaticCredentials, and precedence is documented — WithAwsConfig wins, complete property credentials override the default chain, and an absent tuple preserves the ambient chain. The identity cutover for users who already set s3.* while relying on ambient catalog credentials is explicit in both the godoc and the configuration reference, which is what I asked for. Server /v1/config overrides selecting the post-bootstrap identity stays within the trusted-control-plane model.
Two credential-boundary problems remain; details inline. The first is in scope under SECURITY-THREAT-MODEL.md: "a secret or delegated credential reaching a new audience" is an explicit reassess trigger that overrides the trusted-control-plane downgrade.
Non-blocking: catalog/rest/rest_internal_test.go:138 discards the response without closing the body.
Two credential-boundary fixes for the props-based SigV4 signing path: - Resolve the credential tuple atomically per namespace. Resolving each field independently let a partial s3.* pair be completed with a rest.* field (or inherit an unrelated rest.* token), signing as a hybrid identity that still passed validation. Now if any s3.* field is set we validate and use only the s3.* tuple, otherwise only the rest.* tuple; never backfill across namespaces. - Do not re-sign a cross-origin redirect. sessionTransport signed every hop, so a redirect to a different origin exposed the SigV4 Authorization header and session token to an unconfigured host. Signing is now skipped when the request origin (scheme/host/effective port) differs from the configured catalog origin. Tests cover the mixed-partial and s3-pair-plus-rest-token cases and assert a cross-origin redirect target receives neither the Authorization header nor the session token. Also closes a response body left open in an existing test. Signed-off-by: iremcaginyurtturk <cagin.yurtturk@getbruin.com>
zeroshade
left a comment
There was a problem hiding this comment.
Re-reviewed at d401049. Both blockers from my 09-21 review are fixed:
staticCredsFromProps(rest.go:1180-1198) now takes one namespace whole and never fills a missing field from the other. The mixed-partial and s3-pair + rest-token cases are covered.- Cross-origin redirect hops are no longer signed (
sameOriginguard, rest.go:327). I removed the guard locally andTestSigv4DoesNotSignCrossOriginRedirectfailed with the second server receivingCredential=AKIDEXAMPLEPROPS/..., so the test pins it.
The response-body close and @laskoviymishka's token-only / ValidateStaticCredentials / rest.* alias points are in too. What remains is inline and non-blocking: credential precedence vs Java/pyiceberg, stale "resolved per field" wording, optional test style.
Merge order vs #2060: this lands first. #2060 removes the inline SigV4 path from createSession, so it will need to carry the property credentials through SignerConfig into catalog/rest/sigv4, keep the signingOrigin check in core RoundTrip around SignRequest, and port these tests.
| namespaces := [][3]string{ | ||
| {iceio.S3AccessKeyID, iceio.S3SecretAccessKey, iceio.S3SessionToken}, | ||
| {keyRestAccessKeyID, keyRestSecretAccessKey, keyRestSessionToken}, | ||
| } |
There was a problem hiding this comment.
Non-blocking, but worth settling before this ships because it is user-visible. With both namespaces set, s3.* signs and rest.* is ignored (pinned by the "s3.* keys take precedence over rest.* aliases" case in the test).
Java's AwsProperties.restCredentialsProvider() signs only with rest.access-key-id / rest.secret-access-key / rest.session-token, then client.credentials-provider, then the default chain. It never reads s3.*. pyiceberg signs with client.access-key-id etc. So if someone sets s3.* for FileIO (MinIO, or a scoped data principal) and rest.* for the catalog, catalog requests get signed with the data credentials. The same happens if a server returns s3.* in /v1/config defaults/overrides, since those end up in additionalProps.
I'd flip this to rest.* > s3.* > default chain: swap these two entries and invert the precedence assertion in TestStaticCredsFromProps. This builds on @laskoviymishka's Java-parity note. Fine here if you prefer, otherwise a follow-up.
| // The Java-client property names (rest.access-key-id / rest.secret-access-key / | ||
| // rest.session-token) are accepted as aliases, resolved per field with the s3.* | ||
| // keys taking precedence when both are set. |
There was a problem hiding this comment.
This is stale after d401049: resolution is no longer per field. staticCredsFromProps uses the s3.* tuple if any s3.* key is set (and errors if that tuple is incomplete), otherwise the rest.* tuple. Fields never mix across namespaces. The same wording is in the const comment at rest.go:97-99 and in configuration.md:59.
Suggested text: "If any s3.* credential key is set, only the s3.* tuple is used and it must be complete; otherwise the rest.* tuple is used."
| | `catalog.<name>.sql-driver` | `database/sql` driver name for the SQL catalog. Maps to the `sql.driver` property. The default CLI binary only compiles in `sqliteshim`; other drivers require a custom build. | | ||
| | `catalog.<name>.sql-dialect` | SQL dialect for the SQL catalog (`postgres`, `mysql`, `sqlite`, `mssql`, `oracle`). Maps to the `sql.dialect` property. The default CLI binary only ships `sqlite` via `sqliteshim`; other dialects need a custom build with their drivers. | | ||
| | `catalog.<name>.rest.sigv4-enabled` | Enable AWS SigV4 signing for REST. | | ||
| | `catalog.<name>.rest.sigv4-enabled` | Enable AWS SigV4 signing for REST. When enabled, requests are signed with the `s3.*` credential properties if set (`s3.access-key-id` / `s3.secret-access-key` / `s3.session-token`), otherwise with the AWS default credential chain. The Java-client names (`rest.access-key-id` / `rest.secret-access-key` / `rest.session-token`) are accepted as aliases, resolved per field with the `s3.*` keys taking precedence. | |
There was a problem hiding this comment.
Apart from the stale "resolved per field" wording (see options.go), this row is in the CLI YAML table, and the CLI config cannot set s3.* or rest.* credentials. config.RestOptions only has sigv4-enabled / signing-name / signing-region, and cmd/iceberg/main.go:392-398 only maps those. I'd keep this row as "Enable AWS SigV4 signing for REST." and move the credential-resolution text to the REST catalog options section (the AWS SigV4 row at line 74), where WithAdditionalProps can actually supply these keys.
| "golang.org/x/sync/errgroup" | ||
| ) | ||
|
|
||
| func TestStaticCredsFromProps(t *testing.T) { |
There was a problem hiding this comment.
Optional, as @laskoviymishka noted earlier: this reads better as a table-driven test with t.Parallel(), which matches the rest of the file. Not blocking.
Carry the property-credential signing from apache#1999 into the optional sigv4 backend, now that catalog/rest no longer links the AWS SDK. SignerConfig gains a Props field holding the post-override catalog properties, and resolveSigner passes opts.additionalProps through it. staticCredsFromProps and the rest.* alias keys move into catalog/rest/sigv4, where the registered signer factory builds a static credentials provider from the s3.* / rest.* properties and falls back to the AWS default chain. An explicit sigv4.WithAwsConfig fully specifies the credentials and never consults the properties. The cross-origin signing guard stays in core RoundTrip: it is signer-agnostic, so it now also covers a signer installed verbatim via WithSigner. Tests move with the code - the staticCredsFromProps table and the props-credential end-to-end test live in the sigv4 package, while the cross-origin redirect test stays in core and drives the guard through a marking signer.
* feat(rest): make SigV4 backend optional Move AWS SigV4 signing out of `catalog/rest` into `catalog/rest/sigv4`, so plain REST users no longer pull in the AWS SDK. Breaking change: `rest.WithAwsConfig` is removed. Use `sigv4.WithAwsConfig(...)` and add a blank import of `catalog/rest/sigv4` to enable SigV4 signing. Follows the driver pattern established for cloud IO in #696. * fix(rest): resolve SigV4 signing scope through a signer factory Addresses review feedback on #2060. * Update website/src/configuration.md Co-authored-by: Matt Topol <zotthewizard@gmail.com> * feat(rest): sign SigV4 with catalog-property credentials via the backend Carry the property-credential signing from #1999 into the optional sigv4 backend, now that catalog/rest no longer links the AWS SDK. SignerConfig gains a Props field holding the post-override catalog properties, and resolveSigner passes opts.additionalProps through it. staticCredsFromProps and the rest.* alias keys move into catalog/rest/sigv4, where the registered signer factory builds a static credentials provider from the s3.* / rest.* properties and falls back to the AWS default chain. An explicit sigv4.WithAwsConfig fully specifies the credentials and never consults the properties. The cross-origin signing guard stays in core RoundTrip: it is signer-agnostic, so it now also covers a signer installed verbatim via WithSigner. Tests move with the code - the staticCredsFromProps table and the props-credential end-to-end test live in the sigv4 package, while the cross-origin redirect test stays in core and drives the guard through a marking signer. --------- Co-authored-by: Matt Topol <zotthewizard@gmail.com>
Problem
The REST catalog's SigV4 signer builds its
aws.Configonly fromconfig.LoadDefaultConfig(the AWS default credential chain), so thes3.*credential properties passed to the catalog are ignored for request signing.As a result, connecting to an AWS SigV4 REST catalog (S3 Tables at
s3tables.<region>.amazonaws.com/iceberg, or the Glue Iceberg REST endpoint) fails withno EC2 IMDS role foundunlessAWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY/AWS_SESSION_TOKENare also present in the environment — even when the caller already supplied those credentials vias3.access-key-id/s3.secret-access-key/s3.session-token.This is the same gap tracked for the Python client in apache/iceberg-python#2070.
Fix
In
createSession, when no explicitaws.Configwas provided (WithAwsConfig), build a static credentials provider from thes3.*credential properties when a key pair is present, and fall back to the default chain otherwise. An explicitly providedaws.Configstill takes precedence.Testing
TestStaticCredsFromProps(full key pair → provider with session token; lone access key → no provider; empty → no provider).AWS_*environment variables.go test ./catalog/rest/passes.