registry/registry.go
4a92b8a0
 // Package registry contains client primitives to interact with a remote Docker registry.
9bb3dc98
 package registry
 
 import (
05243104
 	"crypto/tls"
dc9d6c1c
 	"errors"
ed13c3ab
 	"fmt"
 	"io/ioutil"
c860945b
 	"net"
9bb3dc98
 	"net/http"
05243104
 	"os"
ed13c3ab
 	"path/filepath"
9bb3dc98
 	"strings"
c860945b
 	"time"
44d54ba0
 
276c640b
 	"github.com/docker/distribution/registry/client/transport"
20702708
 	"github.com/docker/go-connections/sockets"
8e034802
 	"github.com/docker/go-connections/tlsconfig"
1009e6a4
 	"github.com/sirupsen/logrus"
9bb3dc98
 )
 
e3f68b22
 var (
4fcb9ac4
 	// ErrAlreadyExists is an error returned if an image being pushed
 	// already exists on the remote side
6f0068f2
 	ErrAlreadyExists = errors.New("Image already exists")
e3f68b22
 )
dc9d6c1c
 
e863a07b
 func newTLSConfig(hostname string, isSecure bool) (*tls.Config, error) {
 	// PreferredServerCipherSuites should have no effect
7a8c7b47
 	tlsConfig := tlsconfig.ServerDefault()
e863a07b
 
 	tlsConfig.InsecureSkipVerify = !isSecure
 
bcd0f0cd
 	if isSecure && CertsDir != "" {
831b0030
 		hostDir := filepath.Join(CertsDir, cleanPath(hostname))
e863a07b
 		logrus.Debugf("hostDir: %s", hostDir)
7a8c7b47
 		if err := ReadCertsDirectory(tlsConfig, hostDir); err != nil {
e863a07b
 			return nil, err
 		}
 	}
 
7a8c7b47
 	return tlsConfig, nil
e863a07b
 }
 
19515a7a
 func hasFile(files []os.FileInfo, name string) bool {
 	for _, f := range files {
 		if f.Name() == name {
 			return true
05243104
 		}
45a29238
 	}
19515a7a
 	return false
a01cc3ca
 }
 
ed13c3ab
 // ReadCertsDirectory reads the directory for TLS certificates
 // including roots and certificate pairs and updates the
 // provided TLS configuration.
 func ReadCertsDirectory(tlsConfig *tls.Config, directory string) error {
 	fs, err := ioutil.ReadDir(directory)
 	if err != nil && !os.IsNotExist(err) {
 		return err
 	}
 
 	for _, f := range fs {
 		if strings.HasSuffix(f.Name(), ".crt") {
 			if tlsConfig.RootCAs == nil {
66a5e34c
 				systemPool, err := tlsconfig.SystemCertPool()
 				if err != nil {
 					return fmt.Errorf("unable to get system cert pool: %v", err)
 				}
 				tlsConfig.RootCAs = systemPool
ed13c3ab
 			}
 			logrus.Debugf("crt: %s", filepath.Join(directory, f.Name()))
 			data, err := ioutil.ReadFile(filepath.Join(directory, f.Name()))
 			if err != nil {
 				return err
 			}
 			tlsConfig.RootCAs.AppendCertsFromPEM(data)
 		}
 		if strings.HasSuffix(f.Name(), ".cert") {
 			certName := f.Name()
 			keyName := certName[:len(certName)-5] + ".key"
 			logrus.Debugf("cert: %s", filepath.Join(directory, f.Name()))
 			if !hasFile(fs, keyName) {
9b47b7b1
 				return fmt.Errorf("missing key %s for client certificate %s. Note that CA certificates should use the extension .crt", keyName, certName)
ed13c3ab
 			}
 			cert, err := tls.LoadX509KeyPair(filepath.Join(directory, certName), filepath.Join(directory, keyName))
 			if err != nil {
 				return err
 			}
 			tlsConfig.Certificates = append(tlsConfig.Certificates, cert)
 		}
 		if strings.HasSuffix(f.Name(), ".key") {
 			keyName := f.Name()
 			certName := keyName[:len(keyName)-4] + ".cert"
 			logrus.Debugf("key: %s", filepath.Join(directory, f.Name()))
 			if !hasFile(fs, certName) {
d5e2802e
 				return fmt.Errorf("Missing client certificate %s for key %s", certName, keyName)
ed13c3ab
 			}
 		}
 	}
 
 	return nil
 }
 
de5c80b4
 // Headers returns request modifiers with a User-Agent and metaHeaders
 func Headers(userAgent string, metaHeaders http.Header) []transport.RequestModifier {
61a49bb6
 	modifiers := []transport.RequestModifier{}
 	if userAgent != "" {
 		modifiers = append(modifiers, transport.NewHeaderRequestModifier(http.Header{
 			"User-Agent": []string{userAgent},
 		}))
a01cc3ca
 	}
73823e5e
 	if metaHeaders != nil {
 		modifiers = append(modifiers, transport.NewHeaderRequestModifier(metaHeaders))
a01cc3ca
 	}
73823e5e
 	return modifiers
a01cc3ca
 }
 
c1be45fa
 // HTTPClient returns an HTTP client structure which uses the given transport
4fcb9ac4
 // and contains the necessary headers for redirected requests
a01cc3ca
 func HTTPClient(transport http.RoundTripper) *http.Client {
 	return &http.Client{
 		Transport:     transport,
4fcb9ac4
 		CheckRedirect: addRequiredHeadersToRedirectedRequests,
a01cc3ca
 	}
05243104
 }
 
a1245318
 func trustedLocation(req *http.Request) bool {
 	var (
 		trusteds = []string{"docker.com", "docker.io"}
 		hostname = strings.SplitN(req.Host, ":", 2)[0]
 	)
 	if req.URL.Scheme != "https" {
 		return false
 	}
 
 	for _, trusted := range trusteds {
daa89c42
 		if hostname == trusted || strings.HasSuffix(hostname, "."+trusted) {
a1245318
 			return true
 		}
 	}
 	return false
 }
 
4fcb9ac4
 // addRequiredHeadersToRedirectedRequests adds the necessary redirection headers
 // for redirected requests
 func addRequiredHeadersToRedirectedRequests(req *http.Request, via []*http.Request) error {
2a1b7f22
 	if via != nil && via[0] != nil {
a1245318
 		if trustedLocation(req) && trustedLocation(via[0]) {
 			req.Header = via[0].Header
45a29238
 			return nil
 		}
 		for k, v := range via[0].Header {
 			if k != "Authorization" {
 				for _, vv := range v {
 					req.Header.Add(k, vv)
a1245318
 				}
 			}
 		}
2a1b7f22
 	}
 	return nil
 }
19515a7a
 
4fcb9ac4
 // NewTransport returns a new HTTP transport. If tlsConfig is nil, it uses the
 // default TLS configuration.
19515a7a
 func NewTransport(tlsConfig *tls.Config) *http.Transport {
 	if tlsConfig == nil {
7a8c7b47
 		tlsConfig = tlsconfig.ServerDefault()
19515a7a
 	}
20702708
 
 	direct := &net.Dialer{
 		Timeout:   30 * time.Second,
 		KeepAlive: 30 * time.Second,
 		DualStack: true,
 	}
 
 	base := &http.Transport{
 		Proxy:               http.ProxyFromEnvironment,
 		Dial:                direct.Dial,
19515a7a
 		TLSHandshakeTimeout: 10 * time.Second,
 		TLSClientConfig:     tlsConfig,
 		// TODO(dmcgowan): Call close idle connections when complete and use keep alive
 		DisableKeepAlives: true,
 	}
20702708
 
 	proxyDialer, err := sockets.DialerFromEnvironment(direct)
 	if err == nil {
 		base.Dial = proxyDialer.Dial
 	}
 	return base
19515a7a
 }