github.com/sevki/docker@v1.7.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  	"net/http/httputil"
    12  	"os"
    13  	"path"
    14  	"path/filepath"
    15  	"runtime"
    16  	"strings"
    17  	"sync"
    18  	"time"
    19  
    20  	"github.com/Sirupsen/logrus"
    21  	"github.com/docker/docker/autogen/dockerversion"
    22  	"github.com/docker/docker/pkg/parsers/kernel"
    23  	"github.com/docker/docker/pkg/timeoutconn"
    24  	"github.com/docker/docker/pkg/transport"
    25  	"github.com/docker/docker/pkg/useragent"
    26  )
    27  
    28  var (
    29  	ErrAlreadyExists = errors.New("Image already exists")
    30  	ErrDoesNotExist  = errors.New("Image does not exist")
    31  	errLoginRequired = errors.New("Authentication is required.")
    32  )
    33  
    34  type TimeoutType uint32
    35  
    36  const (
    37  	NoTimeout TimeoutType = iota
    38  	ReceiveTimeout
    39  	ConnectTimeout
    40  )
    41  
    42  // dockerUserAgent is the User-Agent the Docker client uses to identify itself.
    43  // It is populated on init(), comprising version information of different components.
    44  var dockerUserAgent string
    45  
    46  func init() {
    47  	httpVersion := make([]useragent.VersionInfo, 0, 6)
    48  	httpVersion = append(httpVersion, useragent.VersionInfo{"docker", dockerversion.VERSION})
    49  	httpVersion = append(httpVersion, useragent.VersionInfo{"go", runtime.Version()})
    50  	httpVersion = append(httpVersion, useragent.VersionInfo{"git-commit", dockerversion.GITCOMMIT})
    51  	if kernelVersion, err := kernel.GetKernelVersion(); err == nil {
    52  		httpVersion = append(httpVersion, useragent.VersionInfo{"kernel", kernelVersion.String()})
    53  	}
    54  	httpVersion = append(httpVersion, useragent.VersionInfo{"os", runtime.GOOS})
    55  	httpVersion = append(httpVersion, useragent.VersionInfo{"arch", runtime.GOARCH})
    56  
    57  	dockerUserAgent = useragent.AppendVersions("", httpVersion...)
    58  }
    59  
    60  type httpsRequestModifier struct {
    61  	mu        sync.Mutex
    62  	tlsConfig *tls.Config
    63  }
    64  
    65  // DRAGONS(tiborvass): If someone wonders why do we set tlsconfig in a roundtrip,
    66  // it's because it's so as to match the current behavior in master: we generate the
    67  // certpool on every-goddam-request. It's not great, but it allows people to just put
    68  // the certs in /etc/docker/certs.d/.../ and let docker "pick it up" immediately. Would
    69  // prefer an fsnotify implementation, but that was out of scope of my refactoring.
    70  func (m *httpsRequestModifier) ModifyRequest(req *http.Request) error {
    71  	var (
    72  		roots   *x509.CertPool
    73  		certs   []tls.Certificate
    74  		hostDir string
    75  	)
    76  
    77  	if req.URL.Scheme == "https" {
    78  		hasFile := func(files []os.FileInfo, name string) bool {
    79  			for _, f := range files {
    80  				if f.Name() == name {
    81  					return true
    82  				}
    83  			}
    84  			return false
    85  		}
    86  
    87  		if runtime.GOOS == "windows" {
    88  			hostDir = path.Join(os.TempDir(), "/docker/certs.d", req.URL.Host)
    89  		} else {
    90  			hostDir = path.Join("/etc/docker/certs.d", req.URL.Host)
    91  		}
    92  		logrus.Debugf("hostDir: %s", hostDir)
    93  		fs, err := ioutil.ReadDir(hostDir)
    94  		if err != nil && !os.IsNotExist(err) {
    95  			return nil
    96  		}
    97  
    98  		for _, f := range fs {
    99  			if strings.HasSuffix(f.Name(), ".crt") {
   100  				if roots == nil {
   101  					roots = x509.NewCertPool()
   102  				}
   103  				logrus.Debugf("crt: %s", hostDir+"/"+f.Name())
   104  				data, err := ioutil.ReadFile(filepath.Join(hostDir, f.Name()))
   105  				if err != nil {
   106  					return err
   107  				}
   108  				roots.AppendCertsFromPEM(data)
   109  			}
   110  			if strings.HasSuffix(f.Name(), ".cert") {
   111  				certName := f.Name()
   112  				keyName := certName[:len(certName)-5] + ".key"
   113  				logrus.Debugf("cert: %s", hostDir+"/"+f.Name())
   114  				if !hasFile(fs, keyName) {
   115  					return fmt.Errorf("Missing key %s for certificate %s", keyName, certName)
   116  				}
   117  				cert, err := tls.LoadX509KeyPair(filepath.Join(hostDir, certName), path.Join(hostDir, keyName))
   118  				if err != nil {
   119  					return err
   120  				}
   121  				certs = append(certs, cert)
   122  			}
   123  			if strings.HasSuffix(f.Name(), ".key") {
   124  				keyName := f.Name()
   125  				certName := keyName[:len(keyName)-4] + ".cert"
   126  				logrus.Debugf("key: %s", hostDir+"/"+f.Name())
   127  				if !hasFile(fs, certName) {
   128  					return fmt.Errorf("Missing certificate %s for key %s", certName, keyName)
   129  				}
   130  			}
   131  		}
   132  		m.mu.Lock()
   133  		m.tlsConfig.RootCAs = roots
   134  		m.tlsConfig.Certificates = certs
   135  		m.mu.Unlock()
   136  	}
   137  	return nil
   138  }
   139  
   140  func NewTransport(timeout TimeoutType, secure bool) http.RoundTripper {
   141  	tlsConfig := &tls.Config{
   142  		// Avoid fallback to SSL protocols < TLS1.0
   143  		MinVersion:         tls.VersionTLS10,
   144  		InsecureSkipVerify: !secure,
   145  	}
   146  
   147  	tr := &http.Transport{
   148  		DisableKeepAlives: true,
   149  		Proxy:             http.ProxyFromEnvironment,
   150  		TLSClientConfig:   tlsConfig,
   151  	}
   152  
   153  	switch timeout {
   154  	case ConnectTimeout:
   155  		tr.Dial = func(proto string, addr string) (net.Conn, error) {
   156  			// Set the connect timeout to 30 seconds to allow for slower connection
   157  			// times...
   158  			d := net.Dialer{Timeout: 30 * time.Second, DualStack: true}
   159  
   160  			conn, err := d.Dial(proto, addr)
   161  			if err != nil {
   162  				return nil, err
   163  			}
   164  			// Set the recv timeout to 10 seconds
   165  			conn.SetDeadline(time.Now().Add(10 * time.Second))
   166  			return conn, nil
   167  		}
   168  	case ReceiveTimeout:
   169  		tr.Dial = func(proto string, addr string) (net.Conn, error) {
   170  			d := net.Dialer{DualStack: true}
   171  
   172  			conn, err := d.Dial(proto, addr)
   173  			if err != nil {
   174  				return nil, err
   175  			}
   176  			conn = timeoutconn.New(conn, 1*time.Minute)
   177  			return conn, nil
   178  		}
   179  	}
   180  
   181  	if secure {
   182  		// note: httpsTransport also handles http transport
   183  		// but for HTTPS, it sets up the certs
   184  		return transport.NewTransport(tr, &httpsRequestModifier{tlsConfig: tlsConfig})
   185  	}
   186  
   187  	return tr
   188  }
   189  
   190  // DockerHeaders returns request modifiers that ensure requests have
   191  // the User-Agent header set to dockerUserAgent and that metaHeaders
   192  // are added.
   193  func DockerHeaders(metaHeaders http.Header) []transport.RequestModifier {
   194  	modifiers := []transport.RequestModifier{
   195  		transport.NewHeaderRequestModifier(http.Header{"User-Agent": []string{dockerUserAgent}}),
   196  	}
   197  	if metaHeaders != nil {
   198  		modifiers = append(modifiers, transport.NewHeaderRequestModifier(metaHeaders))
   199  	}
   200  	return modifiers
   201  }
   202  
   203  type debugTransport struct{ http.RoundTripper }
   204  
   205  func (tr debugTransport) RoundTrip(req *http.Request) (*http.Response, error) {
   206  	dump, err := httputil.DumpRequestOut(req, false)
   207  	if err != nil {
   208  		fmt.Println("could not dump request")
   209  	}
   210  	fmt.Println(string(dump))
   211  	resp, err := tr.RoundTripper.RoundTrip(req)
   212  	if err != nil {
   213  		return nil, err
   214  	}
   215  	dump, err = httputil.DumpResponse(resp, false)
   216  	if err != nil {
   217  		fmt.Println("could not dump response")
   218  	}
   219  	fmt.Println(string(dump))
   220  	return resp, err
   221  }
   222  
   223  func HTTPClient(transport http.RoundTripper) *http.Client {
   224  	if transport == nil {
   225  		transport = NewTransport(ConnectTimeout, true)
   226  	}
   227  
   228  	return &http.Client{
   229  		Transport:     transport,
   230  		CheckRedirect: AddRequiredHeadersToRedirectedRequests,
   231  	}
   232  }
   233  
   234  func trustedLocation(req *http.Request) bool {
   235  	var (
   236  		trusteds = []string{"docker.com", "docker.io"}
   237  		hostname = strings.SplitN(req.Host, ":", 2)[0]
   238  	)
   239  	if req.URL.Scheme != "https" {
   240  		return false
   241  	}
   242  
   243  	for _, trusted := range trusteds {
   244  		if hostname == trusted || strings.HasSuffix(hostname, "."+trusted) {
   245  			return true
   246  		}
   247  	}
   248  	return false
   249  }
   250  
   251  func AddRequiredHeadersToRedirectedRequests(req *http.Request, via []*http.Request) error {
   252  	if via != nil && via[0] != nil {
   253  		if trustedLocation(req) && trustedLocation(via[0]) {
   254  			req.Header = via[0].Header
   255  			return nil
   256  		}
   257  		for k, v := range via[0].Header {
   258  			if k != "Authorization" {
   259  				for _, vv := range v {
   260  					req.Header.Add(k, vv)
   261  				}
   262  			}
   263  		}
   264  	}
   265  	return nil
   266  }