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"
a01cc3ca
 	"runtime"
9bb3dc98
 	"strings"
31cdc634
 	"syscall"
c860945b
 	"time"
44d54ba0
 
6f4d8470
 	"github.com/Sirupsen/logrus"
19515a7a
 	"github.com/docker/distribution/registry/api/errcode"
 	"github.com/docker/distribution/registry/api/v2"
a21ba12f
 	"github.com/docker/distribution/registry/client"
276c640b
 	"github.com/docker/distribution/registry/client/transport"
8054a303
 	"github.com/docker/docker/dockerversion"
a01cc3ca
 	"github.com/docker/docker/pkg/parsers/kernel"
cf8c0d0f
 	"github.com/docker/docker/pkg/useragent"
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
 
73823e5e
 // dockerUserAgent is the User-Agent the Docker client uses to identify itself.
 // It is populated on init(), comprising version information of different components.
 var dockerUserAgent string
 
 func init() {
 	httpVersion := make([]useragent.VersionInfo, 0, 6)
8054a303
 	httpVersion = append(httpVersion, useragent.VersionInfo{Name: "docker", Version: dockerversion.Version})
7aa28b6b
 	httpVersion = append(httpVersion, useragent.VersionInfo{Name: "go", Version: runtime.Version()})
8054a303
 	httpVersion = append(httpVersion, useragent.VersionInfo{Name: "git-commit", Version: dockerversion.GitCommit})
73823e5e
 	if kernelVersion, err := kernel.GetKernelVersion(); err == nil {
7aa28b6b
 		httpVersion = append(httpVersion, useragent.VersionInfo{Name: "kernel", Version: kernelVersion.String()})
73823e5e
 	}
7aa28b6b
 	httpVersion = append(httpVersion, useragent.VersionInfo{Name: "os", Version: runtime.GOOS})
 	httpVersion = append(httpVersion, useragent.VersionInfo{Name: "arch", Version: runtime.GOARCH})
73823e5e
 
 	dockerUserAgent = useragent.AppendVersions("", httpVersion...)
39f2f15a
 
 	if runtime.GOOS != "linux" {
 		V2Only = true
 	}
05243104
 }
 
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) {
d550cf0b
 				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) {
d550cf0b
 				return fmt.Errorf("Missing client certificate %s for key %s", certName, keyName)
ed13c3ab
 			}
 		}
 	}
 
 	return nil
 }
 
73823e5e
 // DockerHeaders returns request modifiers that ensure requests have
 // the User-Agent header set to dockerUserAgent and that metaHeaders
 // are added.
 func DockerHeaders(metaHeaders http.Header) []transport.RequestModifier {
 	modifiers := []transport.RequestModifier{
 		transport.NewHeaderRequestModifier(http.Header{"User-Agent": []string{dockerUserAgent}}),
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
 
9d6acbee
 // ShouldV2Fallback returns true if this error is a reason to fall back to v1.
 func ShouldV2Fallback(err errcode.Error) bool {
19515a7a
 	switch err.Code {
589a5226
 	case errcode.ErrorCodeUnauthorized, v2.ErrorCodeManifestUnknown, v2.ErrorCodeNameUnknown:
19515a7a
 		return true
 	}
 	return false
 }
 
4fcb9ac4
 // ErrNoSupport is an error type used for errors indicating that an operation
 // is not supported. It encapsulates a more specific error.
19515a7a
 type ErrNoSupport struct{ Err error }
 
 func (e ErrNoSupport) Error() string {
 	if e.Err == nil {
 		return "not supported"
 	}
 	return e.Err.Error()
 }
 
4fcb9ac4
 // ContinueOnError returns true if we should fallback to the next endpoint
 // as a result of this error.
19515a7a
 func ContinueOnError(err error) bool {
 	switch v := err.(type) {
 	case errcode.Errors:
1ebfa299
 		if len(v) == 0 {
 			return true
 		}
19515a7a
 		return ContinueOnError(v[0])
 	case ErrNoSupport:
 		return ContinueOnError(v.Err)
 	case errcode.Error:
9d6acbee
 		return ShouldV2Fallback(v)
a21ba12f
 	case *client.UnexpectedHTTPResponseError:
 		return true
31cdc634
 	case error:
90e2459e
 		return !strings.Contains(err.Error(), strings.ToLower(syscall.ENOSPC.Error()))
19515a7a
 	}
a21ba12f
 	// let's be nice and fallback if the error is a completely
 	// unexpected one.
 	// If new errors have to be handled in some way, please
 	// add them to the switch above.
 	return true
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,
 	}
 }