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