registry/registry.go
4a92b8a0
 // Package registry contains client primitives to interact with a remote Docker registry.
9bb3dc98
 package registry
 
 import (
05243104
 	"crypto/tls"
ed13c3ab
 	"crypto/x509"
dc9d6c1c
 	"errors"
ed13c3ab
 	"fmt"
 	"io/ioutil"
c860945b
 	"net"
9bb3dc98
 	"net/http"
05243104
 	"os"
ed13c3ab
 	"path/filepath"
9bb3dc98
 	"strings"
c860945b
 	"time"
44d54ba0
 
6f4d8470
 	"github.com/Sirupsen/logrus"
276c640b
 	"github.com/docker/distribution/registry/client/transport"
8e034802
 	"github.com/docker/go-connections/tlsconfig"
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
 	tlsConfig := tlsconfig.ServerDefault
 
 	tlsConfig.InsecureSkipVerify = !isSecure
 
bcd0f0cd
 	if isSecure && CertsDir != "" {
831b0030
 		hostDir := filepath.Join(CertsDir, cleanPath(hostname))
e863a07b
 		logrus.Debugf("hostDir: %s", hostDir)
 		if err := ReadCertsDirectory(&tlsConfig, hostDir); err != nil {
 			return nil, err
 		}
 	}
 
 	return &tlsConfig, nil
 }
 
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 {
 				// TODO(dmcgowan): Copy system pool
 				tlsConfig.RootCAs = x509.NewCertPool()
 			}
 			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) {
d5e2802e
 				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
 }
 
61a49bb6
 // DockerHeaders returns request modifiers with a User-Agent and metaHeaders
 func DockerHeaders(userAgent string, metaHeaders http.Header) []transport.RequestModifier {
 	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
 }
 
4fcb9ac4
 // HTTPClient returns a HTTP client structure which uses the given transport
 // 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 {
 		var cfg = tlsconfig.ServerDefault
 		tlsConfig = &cfg
 	}
 	return &http.Transport{
 		Proxy: http.ProxyFromEnvironment,
 		Dial: (&net.Dialer{
 			Timeout:   30 * time.Second,
 			KeepAlive: 30 * time.Second,
 			DualStack: true,
 		}).Dial,
 		TLSHandshakeTimeout: 10 * time.Second,
 		TLSClientConfig:     tlsConfig,
 		// TODO(dmcgowan): Call close idle connections when complete and use keep alive
 		DisableKeepAlives: true,
 	}
 }