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/arrow-go.git


The following commit(s) were added to refs/heads/main by this push:
     new 5d300df6 fix(flightsql): protect configured authorization metadata 
(#1153)
5d300df6 is described below

commit 5d300df638f2448896e336c1d6cb86784dfdfce2
Author: Minh Vu <[email protected]>
AuthorDate: Wed Aug 12 21:01:54 2026 +0200

    fix(flightsql): protect configured authorization metadata (#1153)
    
    ## What
    
    The Flight SQL credential metadata was populated with configured
    authentication and then overwritten by arbitrary connection parameters.
    A parameter named authorization could replace a configured token or
    basic credential. Parameters are now copied first and configured
    authentication is applied last.
    
    ## Test
    
    - go test ./arrow/flight/flightsql/driver -run
    TestRequestMetadataKeepsConfiguredAuthorization -count=1
---
 .../flight/flightsql/driver/auth_metadata_test.go  | 89 ++++++++++++++++++++++
 arrow/flight/flightsql/driver/utils.go             | 11 ++-
 2 files changed, 94 insertions(+), 6 deletions(-)

diff --git a/arrow/flight/flightsql/driver/auth_metadata_test.go 
b/arrow/flight/flightsql/driver/auth_metadata_test.go
new file mode 100644
index 00000000..3a6b8f1c
--- /dev/null
+++ b/arrow/flight/flightsql/driver/auth_metadata_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 driver
+
+import (
+       "context"
+       "net"
+       "testing"
+
+       "github.com/stretchr/testify/require"
+       "google.golang.org/grpc"
+       "google.golang.org/grpc/codes"
+       "google.golang.org/grpc/credentials/insecure"
+       "google.golang.org/grpc/health"
+       healthpb "google.golang.org/grpc/health/grpc_health_v1"
+       "google.golang.org/grpc/metadata"
+       "google.golang.org/grpc/status"
+       "google.golang.org/grpc/test/bufconn"
+)
+
+func TestRequestMetadataKeepsConfiguredAuthorization(t *testing.T) {
+       credentials := grpcCredentials{
+               token:  "trusted",
+               params: map[string]string{"Authorization": "Bearer attacker", 
"Database": "analytics"},
+       }
+
+       metadata, err := credentials.GetRequestMetadata(context.Background())
+       require.NoError(t, err)
+       require.Equal(t, map[string]string{
+               "authorization": "Bearer trusted",
+               "database":      "analytics",
+       }, metadata)
+}
+
+func TestRequestMetadataKeepsConfiguredAuthorizationOnTransport(t *testing.T) {
+       listener := bufconn.Listen(1024 * 1024)
+       server := grpc.NewServer(grpc.UnaryInterceptor(
+               func(ctx context.Context, req interface{}, _ 
*grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
+                       authorization := metadata.ValueFromIncomingContext(ctx, 
"authorization")
+                       if len(authorization) != 1 || authorization[0] != 
"Bearer trusted" {
+                               return nil, 
status.Errorf(codes.Unauthenticated, "unexpected authorization metadata: %q", 
authorization)
+                       }
+                       return handler(ctx, req)
+               },
+       ))
+       healthpb.RegisterHealthServer(server, health.NewServer())
+       go func() {
+               _ = server.Serve(listener)
+       }()
+       t.Cleanup(func() {
+               server.Stop()
+               require.NoError(t, listener.Close())
+       })
+
+       credentials := grpcCredentials{
+               token:  "trusted",
+               params: map[string]string{"Authorization": "Bearer attacker"},
+       }
+       conn, err := grpc.NewClient(
+               "passthrough:///bufnet",
+               grpc.WithContextDialer(func(context.Context, string) (net.Conn, 
error) {
+                       return listener.Dial()
+               }),
+               grpc.WithTransportCredentials(insecure.NewCredentials()),
+               grpc.WithPerRPCCredentials(credentials),
+       )
+       require.NoError(t, err)
+       t.Cleanup(func() { require.NoError(t, conn.Close()) })
+
+       client := healthpb.NewHealthClient(conn)
+       for i := 0; i < 100; i++ {
+               _, err = client.Check(context.Background(), 
&healthpb.HealthCheckRequest{})
+               require.NoErrorf(t, err, "request %d", i+1)
+       }
+}
diff --git a/arrow/flight/flightsql/driver/utils.go 
b/arrow/flight/flightsql/driver/utils.go
index c0e2b3a1..cb895ea1 100644
--- a/arrow/flight/flightsql/driver/utils.go
+++ b/arrow/flight/flightsql/driver/utils.go
@@ -19,6 +19,7 @@ import (
        "context"
        "encoding/base64"
        "fmt"
+       "strings"
        "time"
 
        "github.com/apache/arrow-go/v18/arrow"
@@ -37,19 +38,17 @@ type grpcCredentials struct {
 func (g grpcCredentials) GetRequestMetadata(ctx context.Context, uri 
...string) (map[string]string, error) {
        md := make(map[string]string, len(g.params)+1)
 
-       // Authentication parameters
+       for k, v := range g.params {
+               md[strings.ToLower(k)] = v
+       }
+
        switch {
        case g.token != "":
                md["authorization"] = "Bearer " + g.token
        case g.username != "":
-
                md["authorization"] = "Basic " + 
base64.StdEncoding.EncodeToString([]byte(g.username+":"+g.password))
        }
 
-       for k, v := range g.params {
-               md[k] = v
-       }
-
        return md, nil
 }
 

Reply via email to