remote: prefer servername for grpc authority
When the servername driver-opt is set it is also used for TLS SNI and certificate validation, so use it for the gRPC ":authority" pseudo-header as well, falling back to the endpoint host otherwise. This matches how the buildkit client derives the authority from the server name when TLS credentials are supplied. Since the driver terminates TLS in its own dialer, the authority is set explicitly via client.WithGRPCDialOption(grpc.WithAuthority(...)). Signed-off-by: MohammadHasan Akbari <jarqvi.jarqvi@gmail.com>
This commit is contained in:
+29
-6
@@ -5,6 +5,7 @@ import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -17,6 +18,7 @@ import (
|
||||
"github.com/moby/buildkit/client/connhelper"
|
||||
"github.com/moby/buildkit/util/tracing/delegated"
|
||||
"github.com/pkg/errors"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
type Driver struct {
|
||||
@@ -93,12 +95,16 @@ func (d *Driver) Client(ctx context.Context, opts ...client.ClientOpt) (*client.
|
||||
}),
|
||||
client.WithTracerDelegate(delegated.DefaultExporter),
|
||||
}, 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.
|
||||
// The remote driver establishes the connection itself through a custom
|
||||
// dialer (including TLS), so the buildkit client cannot derive the gRPC
|
||||
// ":authority" pseudo-header from the connection and would fall back to
|
||||
// "localhost". Set it explicitly from the configured endpoint so HTTP/2
|
||||
// reverse proxies (e.g. Envoy) can route on it. Passing the endpoint
|
||||
// address also keeps the gRPC dial target meaningful; the actual dial
|
||||
// target is unaffected as it still goes through the dialer above.
|
||||
if authority := d.clientAuthority(); authority != "" {
|
||||
opts = append(opts, client.WithGRPCDialOption(grpc.WithAuthority(authority)))
|
||||
}
|
||||
c, err := client.New(ctx, d.EndpointAddr, opts...)
|
||||
d.client = c
|
||||
d.err = err
|
||||
@@ -106,6 +112,23 @@ func (d *Driver) Client(ctx context.Context, opts ...client.ClientOpt) (*client.
|
||||
return d.client, d.err
|
||||
}
|
||||
|
||||
// clientAuthority returns the value to use for the gRPC ":authority"
|
||||
// pseudo-header when connecting to the remote endpoint. A configured
|
||||
// servername takes precedence, since it is also used for TLS SNI and
|
||||
// certificate validation; otherwise the authority is the endpoint host. This
|
||||
// mirrors how the buildkit client derives the authority when TLS credentials
|
||||
// are supplied. Endpoints without a host (e.g. unix sockets) have no authority.
|
||||
func (d *Driver) clientAuthority() string {
|
||||
if d.tlsOpts != nil && d.serverName != "" {
|
||||
return d.serverName
|
||||
}
|
||||
u, err := url.Parse(d.EndpointAddr)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return u.Host
|
||||
}
|
||||
|
||||
func (d *Driver) Dial(ctx context.Context) (net.Conn, error) {
|
||||
addr := d.EndpointAddr
|
||||
ch, err := connhelper.GetConnectionHelper(addr)
|
||||
|
||||
@@ -14,9 +14,47 @@ import (
|
||||
"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
|
||||
// TestClientAuthorityValue exercises the authority derivation logic: the
|
||||
// endpoint host is used by default, and a configured servername takes
|
||||
// precedence (it is also used for TLS SNI). See docker/buildx#3880.
|
||||
func TestClientAuthorityValue(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
endpoint string
|
||||
tls *tlsOpts
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "tcp endpoint without tls",
|
||||
endpoint: "tcp://my-buildkit.example.com:443",
|
||||
expected: "my-buildkit.example.com:443",
|
||||
},
|
||||
{
|
||||
name: "servername takes precedence over endpoint host",
|
||||
endpoint: "tcp://10.0.0.5:443",
|
||||
tls: &tlsOpts{serverName: "my-buildkit.example.com"},
|
||||
expected: "my-buildkit.example.com",
|
||||
},
|
||||
{
|
||||
name: "unix endpoint has no authority",
|
||||
endpoint: "unix:///run/buildkit/buildkitd.sock",
|
||||
expected: "",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
d := &Driver{
|
||||
InitConfig: driver.InitConfig{EndpointAddr: tt.endpoint},
|
||||
tlsOpts: tt.tls,
|
||||
}
|
||||
require.Equal(t, tt.expected, d.clientAuthority())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestClientAuthority verifies end-to-end that the remote driver sends the
|
||||
// configured endpoint address as the gRPC ":authority" pseudo-header 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) {
|
||||
|
||||
Reference in New Issue
Block a user