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 5a4f0deb set header x-amz-content-sha256 before signing (#637)
5a4f0deb is described below
commit 5a4f0debf949045a6ddcb1d315eabd38474ad22c
Author: M Alvee <[email protected]>
AuthorDate: Thu Dec 4 23:29:29 2025 +0600
set header x-amz-content-sha256 before signing (#637)
set header x-amz-content-sha256 before signing to prevent any signature
mismatch issues
---
catalog/rest/rest.go | 4 ++
catalog/rest/rest_internal_test.go | 120 +++++++++++++++++++++++++++++++++++++
2 files changed, 124 insertions(+)
diff --git a/catalog/rest/rest.go b/catalog/rest/rest.go
index ced05862..89a1a81a 100644
--- a/catalog/rest/rest.go
+++ b/catalog/rest/rest.go
@@ -237,6 +237,10 @@ func (s *sessionTransport) RoundTrip(r *http.Request)
(*http.Response, error) {
return nil, err
}
+ // Set the x-amz-content-sha256 header before signing.
+ // This header is required for AWS SigV4 signature verification.
+ r.Header.Set("x-amz-content-sha256", payloadHash)
+
// modifies the request in place
err = s.signer.SignHTTP(r.Context(), creds, r, payloadHash,
s.service, s.cfg.Region, time.Now())
diff --git a/catalog/rest/rest_internal_test.go
b/catalog/rest/rest_internal_test.go
index 98f8404f..62135aca 100644
--- a/catalog/rest/rest_internal_test.go
+++ b/catalog/rest/rest_internal_test.go
@@ -267,6 +267,126 @@ func TestSigv4EmptyStringHash(t *testing.T) {
require.Equal(t, payloadHash, emptyStringHash)
}
+func TestSigv4ContentSha256Header(t *testing.T) {
+ t.Parallel()
+
+ cfg, err := config.LoadDefaultConfig(context.Background(), func(opts
*config.LoadOptions) error {
+ opts.Credentials = credentials.StaticCredentialsProvider{
+ Value: aws.Credentials{
+ AccessKeyID: "test-access-key",
+ SecretAccessKey: "test-secret-key",
+ },
+ }
+
+ return nil
+ })
+ require.NoError(t, err)
+
+ t.Run("header set when sigv4 enabled", func(t *testing.T) {
+ t.Parallel()
+ var capturedHeader string
+ mux := http.NewServeMux()
+ srv := httptest.NewServer(mux)
+ defer srv.Close()
+
+ mux.HandleFunc("/v1/config", func(w http.ResponseWriter, r
*http.Request) {
+ json.NewEncoder(w).Encode(map[string]any{
+ "defaults": map[string]any{}, "overrides":
map[string]any{},
+ })
+ })
+
+ mux.HandleFunc("/test", func(w http.ResponseWriter, r
*http.Request) {
+ capturedHeader = r.Header.Get("x-amz-content-sha256")
+ w.WriteHeader(http.StatusOK)
+ })
+
+ cat, err := NewCatalog(context.Background(), "rest", srv.URL,
+ WithSigV4(),
+ WithSigV4RegionSvc("us-east-1", "s3"),
+ WithAwsConfig(cfg))
+ require.NoError(t, err)
+
+ req, err := http.NewRequestWithContext(context.Background(),
http.MethodGet, srv.URL+"/test", nil)
+ require.NoError(t, err)
+
+ _, err = cat.cl.Do(req)
+ require.NoError(t, err)
+
+ assert.NotEmpty(t, capturedHeader, "x-amz-content-sha256 header
should be set when sigv4 is enabled")
+ assert.Equal(t, emptyStringHash, capturedHeader, "header should
contain hash of empty body")
+ })
+
+ t.Run("header not set when sigv4 disabled", func(t *testing.T) {
+ t.Parallel()
+ var capturedHeader string
+ headerPresent := false
+ mux := http.NewServeMux()
+ srv := httptest.NewServer(mux)
+ defer srv.Close()
+
+ mux.HandleFunc("/v1/config", func(w http.ResponseWriter, r
*http.Request) {
+ json.NewEncoder(w).Encode(map[string]any{
+ "defaults": map[string]any{}, "overrides":
map[string]any{},
+ })
+ })
+
+ mux.HandleFunc("/test", func(w http.ResponseWriter, r
*http.Request) {
+ capturedHeader = r.Header.Get("x-amz-content-sha256")
+ _, headerPresent = r.Header["X-Amz-Content-Sha256"]
+ w.WriteHeader(http.StatusOK)
+ })
+
+ cat, err := NewCatalog(context.Background(), "rest", srv.URL)
+ require.NoError(t, err)
+
+ req, err := http.NewRequestWithContext(context.Background(),
http.MethodGet, srv.URL+"/test", nil)
+ require.NoError(t, err)
+
+ _, err = cat.cl.Do(req)
+ require.NoError(t, err)
+
+ assert.Empty(t, capturedHeader, "x-amz-content-sha256 header
should not be set when sigv4 is disabled")
+ assert.False(t, headerPresent, "x-amz-content-sha256 header
should not be present when sigv4 is disabled")
+ })
+
+ t.Run("header contains correct hash for request body", func(t
*testing.T) {
+ t.Parallel()
+ var capturedHeader string
+ mux := http.NewServeMux()
+ srv := httptest.NewServer(mux)
+ defer srv.Close()
+
+ mux.HandleFunc("/v1/config", func(w http.ResponseWriter, r
*http.Request) {
+ json.NewEncoder(w).Encode(map[string]any{
+ "defaults": map[string]any{}, "overrides":
map[string]any{},
+ })
+ })
+
+ mux.HandleFunc("/test", func(w http.ResponseWriter, r
*http.Request) {
+ capturedHeader = r.Header.Get("x-amz-content-sha256")
+ w.WriteHeader(http.StatusOK)
+ })
+
+ cat, err := NewCatalog(context.Background(), "rest", srv.URL,
+ WithSigV4(),
+ WithSigV4RegionSvc("us-east-1", "s3"),
+ WithAwsConfig(cfg))
+ require.NoError(t, err)
+
+ body := []byte(`{"test": "data"}`)
+ expectedHash := sha256.Sum256(body)
+ expectedHashStr := hex.EncodeToString(expectedHash[:])
+
+ req, err := http.NewRequestWithContext(context.Background(),
http.MethodPost, srv.URL+"/test", bytes.NewReader(body))
+ require.NoError(t, err)
+
+ _, err = cat.cl.Do(req)
+ require.NoError(t, err)
+
+ assert.Equal(t, expectedHashStr, capturedHeader, "header should
contain correct hash of request body")
+ })
+}
+
func TestSigv4ConcurrentSigners(t *testing.T) {
t.Parallel()
mux := http.NewServeMux()