github.com/mssola/docker@v1.8.1/registry/registry.go (about)

     1  package registry
     2  
     3  import (
     4  	"crypto/tls"
     5  	"crypto/x509"
     6  	"errors"
     7  	"fmt"
     8  	"io/ioutil"
     9  	"net"
    10  	"net/http"
    11  	"os"
    12  	"path/filepath"
    13  	"runtime"
    14  	"strings"
    15  	"time"
    16  
    17  	"github.com/Sirupsen/logrus"
    18  	"github.com/docker/distribution/registry/api/errcode"
    19  	"github.com/docker/distribution/registry/api/v2"
    20  	"github.com/docker/distribution/registry/client"
    21  	"github.com/docker/distribution/registry/client/transport"
    22  	"github.com/docker/docker/autogen/dockerversion"
    23  	"github.com/docker/docker/pkg/parsers/kernel"
    24  	"github.com/docker/docker/pkg/tlsconfig"
    25  	"github.com/docker/docker/pkg/useragent"
    26  )
    27  
    28  var (
    29  	// ErrAlreadyExists is an error returned if an image being pushed
    30  	// already exists on the remote side
    31  	ErrAlreadyExists = errors.New("Image already exists")
    32  	errLoginRequired = errors.New("Authentication is required.")
    33  )
    34  
    35  // dockerUserAgent is the User-Agent the Docker client uses to identify itself.
    36  // It is populated on init(), comprising version information of different components.
    37  var dockerUserAgent string
    38  
    39  func init() {
    40  	httpVersion := make([]useragent.VersionInfo, 0, 6)
    41  	httpVersion = append(httpVersion, useragent.VersionInfo{"docker", dockerversion.VERSION})
    42  	httpVersion = append(httpVersion, useragent.VersionInfo{"go", runtime.Version()})
    43  	httpVersion = append(httpVersion, useragent.VersionInfo{"git-commit", dockerversion.GITCOMMIT})
    44  	if kernelVersion, err := kernel.GetKernelVersion(); err == nil {
    45  		httpVersion = append(httpVersion, useragent.VersionInfo{"kernel", kernelVersion.String()})
    46  	}
    47  	httpVersion = append(httpVersion, useragent.VersionInfo{"os", runtime.GOOS})
    48  	httpVersion = append(httpVersion, useragent.VersionInfo{"arch", runtime.GOARCH})
    49  
    50  	dockerUserAgent = useragent.AppendVersions("", httpVersion...)
    51  }
    52  
    53  func newTLSConfig(hostname string, isSecure bool) (*tls.Config, error) {
    54  	// PreferredServerCipherSuites should have no effect
    55  	tlsConfig := tlsconfig.ServerDefault
    56  
    57  	tlsConfig.InsecureSkipVerify = !isSecure
    58  
    59  	if isSecure {
    60  		hostDir := filepath.Join(CertsDir, hostname)
    61  		logrus.Debugf("hostDir: %s", hostDir)
    62  		if err := ReadCertsDirectory(&tlsConfig, hostDir); err != nil {
    63  			return nil, err
    64  		}
    65  	}
    66  
    67  	return &tlsConfig, nil
    68  }
    69  
    70  func hasFile(files []os.FileInfo, name string) bool {
    71  	for _, f := range files {
    72  		if f.Name() == name {
    73  			return true
    74  		}
    75  	}
    76  	return false
    77  }
    78  
    79  // ReadCertsDirectory reads the directory for TLS certificates
    80  // including roots and certificate pairs and updates the
    81  // provided TLS configuration.
    82  func ReadCertsDirectory(tlsConfig *tls.Config, directory string) error {
    83  	fs, err := ioutil.ReadDir(directory)
    84  	if err != nil && !os.IsNotExist(err) {
    85  		return err
    86  	}
    87  
    88  	for _, f := range fs {
    89  		if strings.HasSuffix(f.Name(), ".crt") {
    90  			if tlsConfig.RootCAs == nil {
    91  				// TODO(dmcgowan): Copy system pool
    92  				tlsConfig.RootCAs = x509.NewCertPool()
    93  			}
    94  			logrus.Debugf("crt: %s", filepath.Join(directory, f.Name()))
    95  			data, err := ioutil.ReadFile(filepath.Join(directory, f.Name()))
    96  			if err != nil {
    97  				return err
    98  			}
    99  			tlsConfig.RootCAs.AppendCertsFromPEM(data)
   100  		}
   101  		if strings.HasSuffix(f.Name(), ".cert") {
   102  			certName := f.Name()
   103  			keyName := certName[:len(certName)-5] + ".key"
   104  			logrus.Debugf("cert: %s", filepath.Join(directory, f.Name()))
   105  			if !hasFile(fs, keyName) {
   106  				return fmt.Errorf("Missing key %s for certificate %s", keyName, certName)
   107  			}
   108  			cert, err := tls.LoadX509KeyPair(filepath.Join(directory, certName), filepath.Join(directory, keyName))
   109  			if err != nil {
   110  				return err
   111  			}
   112  			tlsConfig.Certificates = append(tlsConfig.Certificates, cert)
   113  		}
   114  		if strings.HasSuffix(f.Name(), ".key") {
   115  			keyName := f.Name()
   116  			certName := keyName[:len(keyName)-4] + ".cert"
   117  			logrus.Debugf("key: %s", filepath.Join(directory, f.Name()))
   118  			if !hasFile(fs, certName) {
   119  				return fmt.Errorf("Missing certificate %s for key %s", certName, keyName)
   120  			}
   121  		}
   122  	}
   123  
   124  	return nil
   125  }
   126  
   127  // DockerHeaders returns request modifiers that ensure requests have
   128  // the User-Agent header set to dockerUserAgent and that metaHeaders
   129  // are added.
   130  func DockerHeaders(metaHeaders http.Header) []transport.RequestModifier {
   131  	modifiers := []transport.RequestModifier{
   132  		transport.NewHeaderRequestModifier(http.Header{"User-Agent": []string{dockerUserAgent}}),
   133  	}
   134  	if metaHeaders != nil {
   135  		modifiers = append(modifiers, transport.NewHeaderRequestModifier(metaHeaders))
   136  	}
   137  	return modifiers
   138  }
   139  
   140  // HTTPClient returns a HTTP client structure which uses the given transport
   141  // and contains the necessary headers for redirected requests
   142  func HTTPClient(transport http.RoundTripper) *http.Client {
   143  	return &http.Client{
   144  		Transport:     transport,
   145  		CheckRedirect: addRequiredHeadersToRedirectedRequests,
   146  	}
   147  }
   148  
   149  func trustedLocation(req *http.Request) bool {
   150  	var (
   151  		trusteds = []string{"docker.com", "docker.io"}
   152  		hostname = strings.SplitN(req.Host, ":", 2)[0]
   153  	)
   154  	if req.URL.Scheme != "https" {
   155  		return false
   156  	}
   157  
   158  	for _, trusted := range trusteds {
   159  		if hostname == trusted || strings.HasSuffix(hostname, "."+trusted) {
   160  			return true
   161  		}
   162  	}
   163  	return false
   164  }
   165  
   166  // addRequiredHeadersToRedirectedRequests adds the necessary redirection headers
   167  // for redirected requests
   168  func addRequiredHeadersToRedirectedRequests(req *http.Request, via []*http.Request) error {
   169  	if via != nil && via[0] != nil {
   170  		if trustedLocation(req) && trustedLocation(via[0]) {
   171  			req.Header = via[0].Header
   172  			return nil
   173  		}
   174  		for k, v := range via[0].Header {
   175  			if k != "Authorization" {
   176  				for _, vv := range v {
   177  					req.Header.Add(k, vv)
   178  				}
   179  			}
   180  		}
   181  	}
   182  	return nil
   183  }
   184  
   185  func shouldV2Fallback(err errcode.Error) bool {
   186  	logrus.Debugf("v2 error: %T %v", err, err)
   187  	switch err.Code {
   188  	case v2.ErrorCodeUnauthorized, v2.ErrorCodeManifestUnknown:
   189  		return true
   190  	}
   191  	return false
   192  }
   193  
   194  // ErrNoSupport is an error type used for errors indicating that an operation
   195  // is not supported. It encapsulates a more specific error.
   196  type ErrNoSupport struct{ Err error }
   197  
   198  func (e ErrNoSupport) Error() string {
   199  	if e.Err == nil {
   200  		return "not supported"
   201  	}
   202  	return e.Err.Error()
   203  }
   204  
   205  // ContinueOnError returns true if we should fallback to the next endpoint
   206  // as a result of this error.
   207  func ContinueOnError(err error) bool {
   208  	switch v := err.(type) {
   209  	case errcode.Errors:
   210  		return ContinueOnError(v[0])
   211  	case ErrNoSupport:
   212  		return ContinueOnError(v.Err)
   213  	case errcode.Error:
   214  		return shouldV2Fallback(v)
   215  	case *client.UnexpectedHTTPResponseError:
   216  		return true
   217  	}
   218  	// let's be nice and fallback if the error is a completely
   219  	// unexpected one.
   220  	// If new errors have to be handled in some way, please
   221  	// add them to the switch above.
   222  	return true
   223  }
   224  
   225  // NewTransport returns a new HTTP transport. If tlsConfig is nil, it uses the
   226  // default TLS configuration.
   227  func NewTransport(tlsConfig *tls.Config) *http.Transport {
   228  	if tlsConfig == nil {
   229  		var cfg = tlsconfig.ServerDefault
   230  		tlsConfig = &cfg
   231  	}
   232  	return &http.Transport{
   233  		Proxy: http.ProxyFromEnvironment,
   234  		Dial: (&net.Dialer{
   235  			Timeout:   30 * time.Second,
   236  			KeepAlive: 30 * time.Second,
   237  			DualStack: true,
   238  		}).Dial,
   239  		TLSHandshakeTimeout: 10 * time.Second,
   240  		TLSClientConfig:     tlsConfig,
   241  		// TODO(dmcgowan): Call close idle connections when complete and use keep alive
   242  		DisableKeepAlives: true,
   243  	}
   244  }