Files
buildx/driver/remote/driver.go
T
MohammadHasan Akbari 877de7edf2 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>
2026-07-11 10:17:12 +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() {
opts = append([]client.ClientOpt{
client.WithContextDialer(func(ctx context.Context, _ string) (net.Conn, error) {
return d.Dial(ctx)
}),
client.WithTracerDelegate(delegated.DefaultExporter),
}, opts...)
// 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
})
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
}