registry.go 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. package registry
  2. import (
  3. "crypto/tls"
  4. "crypto/x509"
  5. "errors"
  6. "fmt"
  7. "io/ioutil"
  8. "net"
  9. "net/http"
  10. "os"
  11. "path"
  12. "strings"
  13. "time"
  14. log "github.com/Sirupsen/logrus"
  15. "github.com/docker/docker/utils"
  16. )
  17. var (
  18. ErrAlreadyExists = errors.New("Image already exists")
  19. ErrDoesNotExist = errors.New("Image does not exist")
  20. errLoginRequired = errors.New("Authentication is required.")
  21. )
  22. type TimeoutType uint32
  23. const (
  24. NoTimeout TimeoutType = iota
  25. ReceiveTimeout
  26. ConnectTimeout
  27. )
  28. func newClient(jar http.CookieJar, roots *x509.CertPool, certs []tls.Certificate, timeout TimeoutType, secure bool) *http.Client {
  29. tlsConfig := tls.Config{
  30. RootCAs: roots,
  31. // Avoid fallback to SSL protocols < TLS1.0
  32. MinVersion: tls.VersionTLS10,
  33. Certificates: certs,
  34. }
  35. if !secure {
  36. tlsConfig.InsecureSkipVerify = true
  37. }
  38. httpTransport := &http.Transport{
  39. DisableKeepAlives: true,
  40. Proxy: http.ProxyFromEnvironment,
  41. TLSClientConfig: &tlsConfig,
  42. }
  43. switch timeout {
  44. case ConnectTimeout:
  45. httpTransport.Dial = func(proto string, addr string) (net.Conn, error) {
  46. // Set the connect timeout to 5 seconds
  47. d := net.Dialer{Timeout: 5 * time.Second, DualStack: true}
  48. conn, err := d.Dial(proto, addr)
  49. if err != nil {
  50. return nil, err
  51. }
  52. // Set the recv timeout to 10 seconds
  53. conn.SetDeadline(time.Now().Add(10 * time.Second))
  54. return conn, nil
  55. }
  56. case ReceiveTimeout:
  57. httpTransport.Dial = func(proto string, addr string) (net.Conn, error) {
  58. d := net.Dialer{DualStack: true}
  59. conn, err := d.Dial(proto, addr)
  60. if err != nil {
  61. return nil, err
  62. }
  63. conn = utils.NewTimeoutConn(conn, 1*time.Minute)
  64. return conn, nil
  65. }
  66. }
  67. return &http.Client{
  68. Transport: httpTransport,
  69. CheckRedirect: AddRequiredHeadersToRedirectedRequests,
  70. Jar: jar,
  71. }
  72. }
  73. func doRequest(req *http.Request, jar http.CookieJar, timeout TimeoutType, secure bool) (*http.Response, *http.Client, error) {
  74. var (
  75. pool *x509.CertPool
  76. certs []tls.Certificate
  77. )
  78. if secure && req.URL.Scheme == "https" {
  79. hasFile := func(files []os.FileInfo, name string) bool {
  80. for _, f := range files {
  81. if f.Name() == name {
  82. return true
  83. }
  84. }
  85. return false
  86. }
  87. hostDir := path.Join("/etc/docker/certs.d", req.URL.Host)
  88. log.Debugf("hostDir: %s", hostDir)
  89. fs, err := ioutil.ReadDir(hostDir)
  90. if err != nil && !os.IsNotExist(err) {
  91. return nil, nil, err
  92. }
  93. for _, f := range fs {
  94. if strings.HasSuffix(f.Name(), ".crt") {
  95. if pool == nil {
  96. pool = x509.NewCertPool()
  97. }
  98. log.Debugf("crt: %s", hostDir+"/"+f.Name())
  99. data, err := ioutil.ReadFile(path.Join(hostDir, f.Name()))
  100. if err != nil {
  101. return nil, nil, err
  102. }
  103. pool.AppendCertsFromPEM(data)
  104. }
  105. if strings.HasSuffix(f.Name(), ".cert") {
  106. certName := f.Name()
  107. keyName := certName[:len(certName)-5] + ".key"
  108. log.Debugf("cert: %s", hostDir+"/"+f.Name())
  109. if !hasFile(fs, keyName) {
  110. return nil, nil, fmt.Errorf("Missing key %s for certificate %s", keyName, certName)
  111. }
  112. cert, err := tls.LoadX509KeyPair(path.Join(hostDir, certName), path.Join(hostDir, keyName))
  113. if err != nil {
  114. return nil, nil, err
  115. }
  116. certs = append(certs, cert)
  117. }
  118. if strings.HasSuffix(f.Name(), ".key") {
  119. keyName := f.Name()
  120. certName := keyName[:len(keyName)-4] + ".cert"
  121. log.Debugf("key: %s", hostDir+"/"+f.Name())
  122. if !hasFile(fs, certName) {
  123. return nil, nil, fmt.Errorf("Missing certificate %s for key %s", certName, keyName)
  124. }
  125. }
  126. }
  127. }
  128. if len(certs) == 0 {
  129. client := newClient(jar, pool, nil, timeout, secure)
  130. res, err := client.Do(req)
  131. if err != nil {
  132. return nil, nil, err
  133. }
  134. return res, client, nil
  135. }
  136. client := newClient(jar, pool, certs, timeout, secure)
  137. res, err := client.Do(req)
  138. return res, client, err
  139. }
  140. func trustedLocation(req *http.Request) bool {
  141. var (
  142. trusteds = []string{"docker.com", "docker.io"}
  143. hostname = strings.SplitN(req.Host, ":", 2)[0]
  144. )
  145. if req.URL.Scheme != "https" {
  146. return false
  147. }
  148. for _, trusted := range trusteds {
  149. if hostname == trusted || strings.HasSuffix(hostname, "."+trusted) {
  150. return true
  151. }
  152. }
  153. return false
  154. }
  155. func AddRequiredHeadersToRedirectedRequests(req *http.Request, via []*http.Request) error {
  156. if via != nil && via[0] != nil {
  157. if trustedLocation(req) && trustedLocation(via[0]) {
  158. req.Header = via[0].Header
  159. return nil
  160. }
  161. for k, v := range via[0].Header {
  162. if k != "Authorization" {
  163. for _, vv := range v {
  164. req.Header.Add(k, vv)
  165. }
  166. }
  167. }
  168. }
  169. return nil
  170. }