github.com/demonoid81/moby@v0.0.0-20200517203328-62dd8e17c460/registry/registry.go (about)

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