github.com/kobeld/docker@v1.12.0-rc1/registry/registry.go (about)

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