This is an automated email from the ASF dual-hosted git repository.
zeroshade pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/iceberg-go.git
The following commit(s) were added to refs/heads/main by this push:
new 11b19a62 fix(io/s3): allow s3.signer.uri property; only reject
explicit s3.remote-signing-enabled=true (#744)
11b19a62 is described below
commit 11b19a627d3abfe7a36f49628eebed106fd59795
Author: Max Kuznetsov <[email protected]>
AuthorDate: Thu Feb 19 19:48:23 2026 +0000
fix(io/s3): allow s3.signer.uri property; only reject explicit
s3.remote-signing-enabled=true (#744)
## Summary
* Remove `s3.signer.uri` from the list of unsupported properties.
* Instead, only error when `s3.remote-signing-enabled` is explicitly set
to `true` (which requires a remote signing implementation that doesn't
exist yet).
* When `s3.remote-signing-enabled` is absent or `false`, `s3.signer.uri`
is silently ignored - matching the behavior of the [Java
implementation](https://github.com/apache/iceberg/blob/de3125afe64fc2b171a52b6e884c72f901e3cba1/aws/src/main/java/org/apache/iceberg/aws/s3/S3FileIOProperties.java#L294-L296).
## Motivation
The [REST Catalog
spec](https://github.com/apache/iceberg/blob/de3125afe64fc2b171a52b6e884c72f901e3cba1/open-api/rest-catalog-open-api.yaml#L3472-L3480)
defines `s3.remote-signing-enabled` as the flag that controls whether
remote signing is active (`LoadTableResult` schema). The `s3.signer.uri`
property is a configuration detail for the signer endpoint, not a
trigger for signing itself.
Specifically, the [R2 Data
Catalog](https://developers.cloudflare.com/r2/data-catalog/) includes
`s3.signer.uri` in the /v1/config response but returns
`s3.remote-signing-enabled: false` when loading a table and works fine
with vended credentials.
Since iceberg-go treats `s3.signer.uri` as unsupported and fails
immediately, connecting to an R2 Data Catalog using just the Catalog URI
and token is not possible.
A workaround exists that involves passing the S3 endpoint and keys
directly, but it's not ideal since it requires managing additional
credentials.
<details>
<summary>Workaround with explicit S3 credentials</summary>
```go
cat, err := rest.NewCatalog(
ctx,
"r2-catalog",
catalogURI,
rest.WithWarehouseLocation(warehouse),
rest.WithOAuthToken(token),
rest.WithAdditionalProps(iceberg.Properties{
"s3.endpoint": s3Endpoint,
"s3.access-key-id": accessKeyID,
"s3.secret-access-key": secretAccessKey,
"s3.region": "auto",
}),
)
```
</details>
---------
Co-authored-by: Max Kuznetsov <[email protected]>
---
io/s3.go | 10 ++++++-
io/s3_test.go | 89 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 98 insertions(+), 1 deletion(-)
diff --git a/io/s3.go b/io/s3.go
index 98ad4c63..435962ab 100644
--- a/io/s3.go
+++ b/io/s3.go
@@ -19,6 +19,7 @@ package io
import (
"context"
+ "errors"
"fmt"
"net/http"
"net/url"
@@ -47,12 +48,12 @@ const (
S3ProxyURI = "s3.proxy-uri"
S3ConnectTimeout = "s3.connect-timeout"
S3SignerUri = "s3.signer.uri"
+ S3RemoteSigningEnabled = "s3.remote-signing-enabled"
S3ForceVirtualAddressing = "s3.force-virtual-addressing"
)
var unsupportedS3Props = []string{
S3ConnectTimeout,
- S3SignerUri,
}
// ParseAWSConfig parses S3 properties and returns a configuration.
@@ -64,6 +65,13 @@ func ParseAWSConfig(ctx context.Context, props
map[string]string) (*aws.Config,
}
}
+ // Remote S3 request signing is not implemented yet.
+ if v, ok := props[S3RemoteSigningEnabled]; ok {
+ if enabled, err := strconv.ParseBool(v); err == nil && enabled {
+ return nil, errors.New("remote S3 request signing is
not supported")
+ }
+ }
+
opts := []func(*config.LoadOptions) error{}
if tok, ok := props["token"]; ok {
diff --git a/io/s3_test.go b/io/s3_test.go
new file mode 100644
index 00000000..a7ac95aa
--- /dev/null
+++ b/io/s3_test.go
@@ -0,0 +1,89 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package io
+
+import (
+ "context"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func TestParseAWSConfigRemoteSigningEnabled(t *testing.T) {
+ t.Parallel()
+
+ t.Run("signer uri present with remote signing explicitly enabled",
func(t *testing.T) {
+ t.Parallel()
+
+ _, err := ParseAWSConfig(context.Background(),
map[string]string{
+ S3SignerUri: "https://signer.example.com",
+ S3RemoteSigningEnabled: "true",
+ })
+ require.ErrorContains(t, err, "remote S3 request signing is not
supported")
+ })
+
+ t.Run("signer uri present with remote signing explicitly disabled",
func(t *testing.T) {
+ t.Parallel()
+
+ _, err := ParseAWSConfig(context.Background(),
map[string]string{
+ S3SignerUri: "https://signer.example.com",
+ S3RemoteSigningEnabled: "false",
+ S3Region: "us-east-1",
+ })
+ require.NoError(t, err)
+ })
+
+ t.Run("signer uri present without remote signing property", func(t
*testing.T) {
+ t.Parallel()
+
+ _, err := ParseAWSConfig(context.Background(),
map[string]string{
+ S3SignerUri: "https://signer.example.com",
+ S3Region: "us-west-2",
+ })
+ require.NoError(t, err)
+ })
+
+ t.Run("remote signing enabled without signer uri", func(t *testing.T) {
+ t.Parallel()
+
+ _, err := ParseAWSConfig(context.Background(),
map[string]string{
+ S3RemoteSigningEnabled: "true",
+ })
+ require.ErrorContains(t, err, "remote S3 request signing is not
supported")
+ })
+
+ t.Run("no signer properties at all", func(t *testing.T) {
+ t.Parallel()
+
+ cfg, err := ParseAWSConfig(context.Background(),
map[string]string{
+ S3Region: "eu-west-1",
+ })
+ require.NoError(t, err)
+ assert.Equal(t, "eu-west-1", cfg.Region)
+ })
+}
+
+func TestParseAWSConfigUnsupportedProperty(t *testing.T) {
+ t.Parallel()
+
+ _, err := ParseAWSConfig(context.Background(), map[string]string{
+ S3ConnectTimeout: "5000",
+ })
+ require.ErrorContains(t, err, "unsupported S3 property")
+}