Files
buildx/driver/remote/driver.go
MohammadHasan Akbari b2878907cc remote: set grpc authority as a default option
Add the ":authority" dial option to the default client options instead of
appending it after the caller-provided options, so that an authority
explicitly passed by the caller takes precedence over the driver default.

Signed-off-by: MohammadHasan Akbari <jarqvi.jarqvi@gmail.com>
2026-07-14 09:46:00 +04:00

216 lines
5.7 KiB
Go

package remote
import (
"context"
"crypto/tls"
"crypto/x509"
"net"
"net/url"
"os"
"strings"
"sync"
"time"
"github.com/docker/buildx/driver"
util "github.com/docker/buildx/driver/remote/util"
"github.com/docker/buildx/util/progress"
"github.com/moby/buildkit/client"
"github.com/moby/buildkit/client/connhelper"
"github.com/moby/buildkit/util/tracing/delegated"
"github.com/pkg/errors"
"google.golang.org/grpc"
)
type Driver struct {
factory driver.Factory
driver.InitConfig
// if you add fields, remember to update docs:
// https://github.com/docker/docs/blob/main/content/build/drivers/remote.md
*tlsOpts
defaultLoad bool
// remote driver caches the client because its Bootstrap/Info methods reuse it internally
clientOnce sync.Once
client *client.Client
err error
}
type tlsOpts struct {
serverName string
caCert string
cert string
key string
}
func (d *Driver) Bootstrap(ctx context.Context, l progress.Logger) error {
c, err := d.Client(ctx)
if err != nil {
return err
}
return progress.Wrap("[internal] waiting for connection", l, func(_ progress.SubLogger) error {
cancelCtx, cancel := context.WithCancelCause(ctx)
ctx, _ := context.WithTimeoutCause(cancelCtx, 20*time.Second, errors.WithStack(context.DeadlineExceeded)) //nolint:govet // no need to manually cancel this context as we already rely on parent
defer func() { cancel(errors.WithStack(context.Canceled)) }()
return c.Wait(ctx)
})
}
func (d *Driver) Info(ctx context.Context) (*driver.Info, error) {
c, err := d.Client(ctx)
if err != nil {
return &driver.Info{
Status: driver.Inactive,
}, nil
}
if _, err := c.ListWorkers(ctx); err != nil {
return &driver.Info{
Status: driver.Inactive,
}, nil
}
return &driver.Info{
Status: driver.Running,
}, nil
}
func (d *Driver) Version(ctx context.Context) (string, error) {
return "", nil
}
func (d *Driver) Stop(ctx context.Context, force bool) error {
return nil
}
func (d *Driver) Rm(ctx context.Context, force, rmVolume, rmDaemon bool) error {
return nil
}
func (d *Driver) Client(ctx context.Context, opts ...client.ClientOpt) (*client.Client, error) {
d.clientOnce.Do(func() {
defaultOpts := []client.ClientOpt{
client.WithContextDialer(func(ctx context.Context, _ string) (net.Conn, error) {
return d.Dial(ctx)
}),
client.WithTracerDelegate(delegated.DefaultExporter),
}
// 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 so HTTP/2 reverse proxies (e.g. Envoy)
// can route on it. It is added as a default option so an authority
// explicitly passed by the caller still takes precedence.
if authority := d.clientAuthority(); authority != "" {
defaultOpts = append(defaultOpts, client.WithGRPCDialOption(grpc.WithAuthority(authority)))
}
opts = append(defaultOpts, opts...)
c, err := client.New(ctx, d.EndpointAddr, opts...)
d.client = c
d.err = err
})
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)
if err != nil {
return nil, err
}
if ch != nil {
return ch.ContextDialer(ctx, addr)
}
network, addr, ok := strings.Cut(addr, "://")
if !ok {
return nil, errors.Errorf("invalid endpoint address: %s", d.EndpointAddr)
}
conn, err := util.DialContext(ctx, network, addr)
if err != nil {
return nil, errors.WithStack(err)
}
if d.tlsOpts != nil {
cfg, err := loadTLS(d.tlsOpts)
if err != nil {
return nil, errors.Wrap(err, "error loading tls config")
}
conn = tls.Client(conn, cfg)
}
return conn, nil
}
func loadTLS(opts *tlsOpts) (*tls.Config, error) {
cfg := &tls.Config{
ServerName: opts.serverName,
RootCAs: x509.NewCertPool(),
}
if opts.caCert != "" {
ca, err := os.ReadFile(opts.caCert)
if err != nil {
return nil, errors.Wrap(err, "could not read ca certificate")
}
if ok := cfg.RootCAs.AppendCertsFromPEM(ca); !ok {
return nil, errors.New("failed to append ca certs")
}
}
if opts.cert != "" || opts.key != "" {
cert, err := tls.LoadX509KeyPair(opts.cert, opts.key)
if err != nil {
return nil, errors.Wrap(err, "could not read certificate/key")
}
cfg.Certificates = append(cfg.Certificates, cert)
}
return cfg, nil
}
func (d *Driver) Features(ctx context.Context) map[driver.Feature]bool {
return map[driver.Feature]bool{
driver.OCIExporter: true,
driver.DockerExporter: true,
driver.CacheExport: true,
driver.MultiPlatform: true,
driver.DirectPush: true,
driver.DefaultLoad: d.defaultLoad,
}
}
func (d *Driver) HostGatewayIP(ctx context.Context) (net.IP, error) {
return nil, errors.New("host-gateway is not supported by the remote driver")
}
func (d *Driver) Factory() driver.Factory {
return d.factory
}
func (d *Driver) IsMobyDriver() bool {
return false
}
func (d *Driver) Config() driver.InitConfig {
return d.InitConfig
}