remote: use endpoint address for buildkit client authority

The remote driver created the buildkit client with an empty address:

    client.New(ctx, "", opts...)

With an empty address the buildkit client falls back to the system
default address (the local unix socket) and derives the gRPC
":authority" pseudo-header from it, which ends up being "localhost".
The actual connection was still correct because the remote driver
provides its own dialer, but the wrong authority broke HTTP/2 reverse
proxies (such as Envoy) that route based on ":authority".

Pass the configured endpoint address to client.New so the authority is
derived from the remote endpoint hostname (e.g.
my-buildkit.example.com:443). The custom dialer is preserved, so the
dial target and TLS/SNI behavior are unchanged.

Fixes #3880

Signed-off-by: MohammadHasan Akbari <jarqvi.jarqvi@gmail.com>
This commit is contained in:
MohammadHasan Akbari
2026-06-28 15:24:28 +04:00
parent 1d805d2ea2
commit d3d1828d84
2 changed files with 77 additions and 1 deletions
+7 -1
View File
@@ -93,7 +93,13 @@ func (d *Driver) Client(ctx context.Context, opts ...client.ClientOpt) (*client.
}),
client.WithTracerDelegate(delegated.DefaultExporter),
}, opts...)
c, err := client.New(ctx, "", opts...)
// Pass the configured endpoint address (rather than an empty string) so
// the buildkit client derives the gRPC ":authority" pseudo-header from
// the remote endpoint hostname. An empty address falls back to the
// system-default buildkit address, which makes the authority resolve to
// "localhost". The connection itself still goes through the custom
// dialer above, so the actual dial target is unaffected.
c, err := client.New(ctx, d.EndpointAddr, opts...)
d.client = c
d.err = err
})
+70
View File
@@ -0,0 +1,70 @@
package remote
import (
"context"
"net"
"testing"
"time"
"github.com/docker/buildx/driver"
"github.com/stretchr/testify/require"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
)
// TestClientAuthority verifies that the remote driver derives the gRPC
// ":authority" pseudo-header from the configured endpoint address instead of
// defaulting to "localhost" (see docker/buildx#3880). It stands up an
// in-process gRPC server on a loopback listener and asserts the authority of
// the request it receives matches the endpoint host.
func TestClientAuthority(t *testing.T) {
ctx, cancel := context.WithTimeoutCause(context.Background(), 10*time.Second, context.DeadlineExceeded)
defer cancel()
lc := net.ListenConfig{}
lis, err := lc.Listen(ctx, "tcp", "127.0.0.1:0")
require.NoError(t, err)
defer lis.Close()
authorityCh := make(chan string, 1)
srv := grpc.NewServer(grpc.UnknownServiceHandler(func(_ any, stream grpc.ServerStream) error {
authority := ""
if md, ok := metadata.FromIncomingContext(stream.Context()); ok {
if a := md.Get(":authority"); len(a) > 0 {
authority = a[0]
}
}
select {
case authorityCh <- authority:
default:
}
return status.Error(codes.Unimplemented, "unimplemented")
}))
go func() {
_ = srv.Serve(lis)
}()
defer srv.Stop()
d := &Driver{
InitConfig: driver.InitConfig{
EndpointAddr: "tcp://" + lis.Addr().String(),
},
}
c, err := d.Client(ctx)
require.NoError(t, err)
defer c.Close()
// Any RPC will do: it fails server-side with Unimplemented, but the server
// records the ":authority" it received from the client before responding.
_, _ = c.ListWorkers(ctx)
select {
case authority := <-authorityCh:
require.Equal(t, lis.Addr().String(), authority)
case <-ctx.Done():
t.Fatal("timed out waiting for request to reach the server")
}
}