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