gopkg.in/docker/docker.v23@v23.0.11/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  	"crypto/tls"
     6  	"net"
     7  	"net/http"
     8  	"os"
     9  	"path/filepath"
    10  	"strings"
    11  	"time"
    12  
    13  	"github.com/docker/distribution/registry/client/transport"
    14  	"github.com/docker/go-connections/tlsconfig"
    15  	"github.com/sirupsen/logrus"
    16  )
    17  
    18  // HostCertsDir returns the config directory for a specific host.
    19  func HostCertsDir(hostname string) string {
    20  	return filepath.Join(CertsDir(), cleanPath(hostname))
    21  }
    22  
    23  // newTLSConfig constructs a client TLS configuration based on server defaults
    24  func newTLSConfig(hostname string, isSecure bool) (*tls.Config, error) {
    25  	// PreferredServerCipherSuites should have no effect
    26  	tlsConfig := tlsconfig.ServerDefault()
    27  
    28  	tlsConfig.InsecureSkipVerify = !isSecure
    29  
    30  	if isSecure && CertsDir() != "" {
    31  		hostDir := HostCertsDir(hostname)
    32  		logrus.Debugf("hostDir: %s", hostDir)
    33  		if err := ReadCertsDirectory(tlsConfig, hostDir); err != nil {
    34  			return nil, err
    35  		}
    36  	}
    37  
    38  	return tlsConfig, nil
    39  }
    40  
    41  func hasFile(files []os.DirEntry, name string) bool {
    42  	for _, f := range files {
    43  		if f.Name() == name {
    44  			return true
    45  		}
    46  	}
    47  	return false
    48  }
    49  
    50  // ReadCertsDirectory reads the directory for TLS certificates
    51  // including roots and certificate pairs and updates the
    52  // provided TLS configuration.
    53  func ReadCertsDirectory(tlsConfig *tls.Config, directory string) error {
    54  	fs, err := os.ReadDir(directory)
    55  	if err != nil && !os.IsNotExist(err) {
    56  		return invalidParam(err)
    57  	}
    58  
    59  	for _, f := range fs {
    60  		if strings.HasSuffix(f.Name(), ".crt") {
    61  			if tlsConfig.RootCAs == nil {
    62  				systemPool, err := tlsconfig.SystemCertPool()
    63  				if err != nil {
    64  					return invalidParamWrapf(err, "unable to get system cert pool")
    65  				}
    66  				tlsConfig.RootCAs = systemPool
    67  			}
    68  			logrus.Debugf("crt: %s", filepath.Join(directory, f.Name()))
    69  			data, err := os.ReadFile(filepath.Join(directory, f.Name()))
    70  			if err != nil {
    71  				return err
    72  			}
    73  			tlsConfig.RootCAs.AppendCertsFromPEM(data)
    74  		}
    75  		if strings.HasSuffix(f.Name(), ".cert") {
    76  			certName := f.Name()
    77  			keyName := certName[:len(certName)-5] + ".key"
    78  			logrus.Debugf("cert: %s", filepath.Join(directory, f.Name()))
    79  			if !hasFile(fs, keyName) {
    80  				return invalidParamf("missing key %s for client certificate %s. CA certificates must use the extension .crt", keyName, certName)
    81  			}
    82  			cert, err := tls.LoadX509KeyPair(filepath.Join(directory, certName), filepath.Join(directory, keyName))
    83  			if err != nil {
    84  				return err
    85  			}
    86  			tlsConfig.Certificates = append(tlsConfig.Certificates, cert)
    87  		}
    88  		if strings.HasSuffix(f.Name(), ".key") {
    89  			keyName := f.Name()
    90  			certName := keyName[:len(keyName)-4] + ".cert"
    91  			logrus.Debugf("key: %s", filepath.Join(directory, f.Name()))
    92  			if !hasFile(fs, certName) {
    93  				return invalidParamf("missing client certificate %s for key %s", certName, keyName)
    94  			}
    95  		}
    96  	}
    97  
    98  	return nil
    99  }
   100  
   101  // Headers returns request modifiers with a User-Agent and metaHeaders
   102  func Headers(userAgent string, metaHeaders http.Header) []transport.RequestModifier {
   103  	modifiers := []transport.RequestModifier{}
   104  	if userAgent != "" {
   105  		modifiers = append(modifiers, transport.NewHeaderRequestModifier(http.Header{
   106  			"User-Agent": []string{userAgent},
   107  		}))
   108  	}
   109  	if metaHeaders != nil {
   110  		modifiers = append(modifiers, transport.NewHeaderRequestModifier(metaHeaders))
   111  	}
   112  	return modifiers
   113  }
   114  
   115  // httpClient returns an HTTP client structure which uses the given transport
   116  // and contains the necessary headers for redirected requests
   117  func httpClient(transport http.RoundTripper) *http.Client {
   118  	return &http.Client{
   119  		Transport:     transport,
   120  		CheckRedirect: addRequiredHeadersToRedirectedRequests,
   121  	}
   122  }
   123  
   124  func trustedLocation(req *http.Request) bool {
   125  	var (
   126  		trusteds = []string{"docker.com", "docker.io"}
   127  		hostname = strings.SplitN(req.Host, ":", 2)[0]
   128  	)
   129  	if req.URL.Scheme != "https" {
   130  		return false
   131  	}
   132  
   133  	for _, trusted := range trusteds {
   134  		if hostname == trusted || strings.HasSuffix(hostname, "."+trusted) {
   135  			return true
   136  		}
   137  	}
   138  	return false
   139  }
   140  
   141  // addRequiredHeadersToRedirectedRequests adds the necessary redirection headers
   142  // for redirected requests
   143  func addRequiredHeadersToRedirectedRequests(req *http.Request, via []*http.Request) error {
   144  	if len(via) != 0 && via[0] != nil {
   145  		if trustedLocation(req) && trustedLocation(via[0]) {
   146  			req.Header = via[0].Header
   147  			return nil
   148  		}
   149  		for k, v := range via[0].Header {
   150  			if k != "Authorization" {
   151  				for _, vv := range v {
   152  					req.Header.Add(k, vv)
   153  				}
   154  			}
   155  		}
   156  	}
   157  	return nil
   158  }
   159  
   160  // newTransport returns a new HTTP transport. If tlsConfig is nil, it uses the
   161  // default TLS configuration.
   162  func newTransport(tlsConfig *tls.Config) *http.Transport {
   163  	if tlsConfig == nil {
   164  		tlsConfig = tlsconfig.ServerDefault()
   165  	}
   166  
   167  	direct := &net.Dialer{
   168  		Timeout:   30 * time.Second,
   169  		KeepAlive: 30 * time.Second,
   170  	}
   171  
   172  	return &http.Transport{
   173  		Proxy:               http.ProxyFromEnvironment,
   174  		DialContext:         direct.DialContext,
   175  		TLSHandshakeTimeout: 10 * time.Second,
   176  		TLSClientConfig:     tlsConfig,
   177  		// TODO(dmcgowan): Call close idle connections when complete and use keep alive
   178  		DisableKeepAlives: true,
   179  	}
   180  }