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
}