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