github.com/moby/docker@v26.1.3+incompatible/registry/registry.go (about)

     1  // Package registry contains client primitives to interact with a remote Docker registry.
     2  package registry // import "github.com/docker/docker/registry"
     3  
     4  import (
     5  	"context"
     6  	"crypto/tls"
     7  	"net"
     8  	"net/http"
     9  	"os"
    10  	"path/filepath"
    11  	"strings"
    12  	"time"
    13  
    14  	"github.com/containerd/log"
    15  	"github.com/docker/distribution/registry/client/transport"
    16  	"github.com/docker/go-connections/tlsconfig"
    17  )
    18  
    19  // HostCertsDir returns the config directory for a specific host.
    20  func HostCertsDir(hostname string) string {
    21  	return filepath.Join(CertsDir(), cleanPath(hostname))
    22  }
    23  
    24  // newTLSConfig constructs a client TLS configuration based on server defaults
    25  func newTLSConfig(hostname string, isSecure bool) (*tls.Config, error) {
    26  	// PreferredServerCipherSuites should have no effect
    27  	tlsConfig := tlsconfig.ServerDefault()
    28  
    29  	tlsConfig.InsecureSkipVerify = !isSecure
    30  
    31  	if isSecure && CertsDir() != "" {
    32  		hostDir := HostCertsDir(hostname)
    33  		log.G(context.TODO()).Debugf("hostDir: %s", hostDir)
    34  		if err := ReadCertsDirectory(tlsConfig, hostDir); err != nil {
    35  			return nil, err
    36  		}
    37  	}
    38  
    39  	return tlsConfig, nil
    40  }
    41  
    42  func hasFile(files []os.DirEntry, name string) bool {
    43  	for _, f := range files {
    44  		if f.Name() == name {
    45  			return true
    46  		}
    47  	}
    48  	return false
    49  }
    50  
    51  // ReadCertsDirectory reads the directory for TLS certificates
    52  // including roots and certificate pairs and updates the
    53  // provided TLS configuration.
    54  func ReadCertsDirectory(tlsConfig *tls.Config, directory string) error {
    55  	fs, err := os.ReadDir(directory)
    56  	if err != nil && !os.IsNotExist(err) {
    57  		return invalidParam(err)
    58  	}
    59  
    60  	for _, f := range fs {
    61  		if strings.HasSuffix(f.Name(), ".crt") {
    62  			if tlsConfig.RootCAs == nil {
    63  				systemPool, err := tlsconfig.SystemCertPool()
    64  				if err != nil {
    65  					return invalidParamWrapf(err, "unable to get system cert pool")
    66  				}
    67  				tlsConfig.RootCAs = systemPool
    68  			}
    69  			log.G(context.TODO()).Debugf("crt: %s", filepath.Join(directory, f.Name()))
    70  			data, err := os.ReadFile(filepath.Join(directory, f.Name()))
    71  			if err != nil {
    72  				return err
    73  			}
    74  			tlsConfig.RootCAs.AppendCertsFromPEM(data)
    75  		}
    76  		if strings.HasSuffix(f.Name(), ".cert") {
    77  			certName := f.Name()
    78  			keyName := certName[:len(certName)-5] + ".key"
    79  			log.G(context.TODO()).Debugf("cert: %s", filepath.Join(directory, f.Name()))
    80  			if !hasFile(fs, keyName) {
    81  				return invalidParamf("missing key %s for client certificate %s. CA certificates must use the extension .crt", keyName, certName)
    82  			}
    83  			cert, err := tls.LoadX509KeyPair(filepath.Join(directory, certName), filepath.Join(directory, keyName))
    84  			if err != nil {
    85  				return err
    86  			}
    87  			tlsConfig.Certificates = append(tlsConfig.Certificates, cert)
    88  		}
    89  		if strings.HasSuffix(f.Name(), ".key") {
    90  			keyName := f.Name()
    91  			certName := keyName[:len(keyName)-4] + ".cert"
    92  			log.G(context.TODO()).Debugf("key: %s", filepath.Join(directory, f.Name()))
    93  			if !hasFile(fs, certName) {
    94  				return invalidParamf("missing client certificate %s for key %s", certName, keyName)
    95  			}
    96  		}
    97  	}
    98  
    99  	return nil
   100  }
   101  
   102  // Headers returns request modifiers with a User-Agent and metaHeaders
   103  func Headers(userAgent string, metaHeaders http.Header) []transport.RequestModifier {
   104  	modifiers := []transport.RequestModifier{}
   105  	if userAgent != "" {
   106  		modifiers = append(modifiers, transport.NewHeaderRequestModifier(http.Header{
   107  			"User-Agent": []string{userAgent},
   108  		}))
   109  	}
   110  	if metaHeaders != nil {
   111  		modifiers = append(modifiers, transport.NewHeaderRequestModifier(metaHeaders))
   112  	}
   113  	return modifiers
   114  }
   115  
   116  // newTransport returns a new HTTP transport. If tlsConfig is nil, it uses the
   117  // default TLS configuration.
   118  func newTransport(tlsConfig *tls.Config) *http.Transport {
   119  	if tlsConfig == nil {
   120  		tlsConfig = tlsconfig.ServerDefault()
   121  	}
   122  
   123  	direct := &net.Dialer{
   124  		Timeout:   30 * time.Second,
   125  		KeepAlive: 30 * time.Second,
   126  	}
   127  
   128  	return &http.Transport{
   129  		Proxy:               http.ProxyFromEnvironment,
   130  		DialContext:         direct.DialContext,
   131  		TLSHandshakeTimeout: 10 * time.Second,
   132  		TLSClientConfig:     tlsConfig,
   133  		// TODO(dmcgowan): Call close idle connections when complete and use keep alive
   134  		DisableKeepAlives: true,
   135  	}
   136  }