registry.go 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. // Package registry contains client primitives to interact with a remote Docker registry.
  2. package registry
  3. import (
  4. "crypto/tls"
  5. "errors"
  6. "fmt"
  7. "io/ioutil"
  8. "net"
  9. "net/http"
  10. "os"
  11. "path/filepath"
  12. "strings"
  13. "time"
  14. "github.com/Sirupsen/logrus"
  15. "github.com/docker/distribution/registry/client/transport"
  16. "github.com/docker/go-connections/sockets"
  17. "github.com/docker/go-connections/tlsconfig"
  18. )
  19. var (
  20. // ErrAlreadyExists is an error returned if an image being pushed
  21. // already exists on the remote side
  22. ErrAlreadyExists = errors.New("Image already exists")
  23. )
  24. func newTLSConfig(hostname string, isSecure bool) (*tls.Config, error) {
  25. // PreferredServerCipherSuites should have no effect
  26. tlsConfig := tlsconfig.ServerDefault()
  27. tlsConfig.InsecureSkipVerify = !isSecure
  28. if isSecure && CertsDir != "" {
  29. hostDir := filepath.Join(CertsDir, cleanPath(hostname))
  30. logrus.Debugf("hostDir: %s", hostDir)
  31. if err := ReadCertsDirectory(tlsConfig, hostDir); err != nil {
  32. return nil, err
  33. }
  34. }
  35. return tlsConfig, nil
  36. }
  37. func hasFile(files []os.FileInfo, name string) bool {
  38. for _, f := range files {
  39. if f.Name() == name {
  40. return true
  41. }
  42. }
  43. return false
  44. }
  45. // ReadCertsDirectory reads the directory for TLS certificates
  46. // including roots and certificate pairs and updates the
  47. // provided TLS configuration.
  48. func ReadCertsDirectory(tlsConfig *tls.Config, directory string) error {
  49. fs, err := ioutil.ReadDir(directory)
  50. if err != nil && !os.IsNotExist(err) {
  51. return err
  52. }
  53. for _, f := range fs {
  54. if strings.HasSuffix(f.Name(), ".crt") {
  55. if tlsConfig.RootCAs == nil {
  56. systemPool, err := tlsconfig.SystemCertPool()
  57. if err != nil {
  58. return fmt.Errorf("unable to get system cert pool: %v", err)
  59. }
  60. tlsConfig.RootCAs = systemPool
  61. }
  62. logrus.Debugf("crt: %s", filepath.Join(directory, f.Name()))
  63. data, err := ioutil.ReadFile(filepath.Join(directory, f.Name()))
  64. if err != nil {
  65. return err
  66. }
  67. tlsConfig.RootCAs.AppendCertsFromPEM(data)
  68. }
  69. if strings.HasSuffix(f.Name(), ".cert") {
  70. certName := f.Name()
  71. keyName := certName[:len(certName)-5] + ".key"
  72. logrus.Debugf("cert: %s", filepath.Join(directory, f.Name()))
  73. if !hasFile(fs, keyName) {
  74. return fmt.Errorf("Missing key %s for client certificate %s. Note that CA certificates should use the extension .crt.", keyName, certName)
  75. }
  76. cert, err := tls.LoadX509KeyPair(filepath.Join(directory, certName), filepath.Join(directory, keyName))
  77. if err != nil {
  78. return err
  79. }
  80. tlsConfig.Certificates = append(tlsConfig.Certificates, cert)
  81. }
  82. if strings.HasSuffix(f.Name(), ".key") {
  83. keyName := f.Name()
  84. certName := keyName[:len(keyName)-4] + ".cert"
  85. logrus.Debugf("key: %s", filepath.Join(directory, f.Name()))
  86. if !hasFile(fs, certName) {
  87. return fmt.Errorf("Missing client certificate %s for key %s", certName, keyName)
  88. }
  89. }
  90. }
  91. return nil
  92. }
  93. // DockerHeaders returns request modifiers with a User-Agent and metaHeaders
  94. func DockerHeaders(userAgent string, metaHeaders http.Header) []transport.RequestModifier {
  95. modifiers := []transport.RequestModifier{}
  96. if userAgent != "" {
  97. modifiers = append(modifiers, transport.NewHeaderRequestModifier(http.Header{
  98. "User-Agent": []string{userAgent},
  99. }))
  100. }
  101. if metaHeaders != nil {
  102. modifiers = append(modifiers, transport.NewHeaderRequestModifier(metaHeaders))
  103. }
  104. return modifiers
  105. }
  106. // HTTPClient returns an HTTP client structure which uses the given transport
  107. // and contains the necessary headers for redirected requests
  108. func HTTPClient(transport http.RoundTripper) *http.Client {
  109. return &http.Client{
  110. Transport: transport,
  111. CheckRedirect: addRequiredHeadersToRedirectedRequests,
  112. }
  113. }
  114. func trustedLocation(req *http.Request) bool {
  115. var (
  116. trusteds = []string{"docker.com", "docker.io"}
  117. hostname = strings.SplitN(req.Host, ":", 2)[0]
  118. )
  119. if req.URL.Scheme != "https" {
  120. return false
  121. }
  122. for _, trusted := range trusteds {
  123. if hostname == trusted || strings.HasSuffix(hostname, "."+trusted) {
  124. return true
  125. }
  126. }
  127. return false
  128. }
  129. // addRequiredHeadersToRedirectedRequests adds the necessary redirection headers
  130. // for redirected requests
  131. func addRequiredHeadersToRedirectedRequests(req *http.Request, via []*http.Request) error {
  132. if via != nil && via[0] != nil {
  133. if trustedLocation(req) && trustedLocation(via[0]) {
  134. req.Header = via[0].Header
  135. return nil
  136. }
  137. for k, v := range via[0].Header {
  138. if k != "Authorization" {
  139. for _, vv := range v {
  140. req.Header.Add(k, vv)
  141. }
  142. }
  143. }
  144. }
  145. return nil
  146. }
  147. // NewTransport returns a new HTTP transport. If tlsConfig is nil, it uses the
  148. // default TLS configuration.
  149. func NewTransport(tlsConfig *tls.Config) *http.Transport {
  150. if tlsConfig == nil {
  151. tlsConfig = tlsconfig.ServerDefault()
  152. }
  153. direct := &net.Dialer{
  154. Timeout: 30 * time.Second,
  155. KeepAlive: 30 * time.Second,
  156. DualStack: true,
  157. }
  158. base := &http.Transport{
  159. Proxy: http.ProxyFromEnvironment,
  160. Dial: direct.Dial,
  161. TLSHandshakeTimeout: 10 * time.Second,
  162. TLSClientConfig: tlsConfig,
  163. // TODO(dmcgowan): Call close idle connections when complete and use keep alive
  164. DisableKeepAlives: true,
  165. }
  166. proxyDialer, err := sockets.DialerFromEnvironment(direct)
  167. if err == nil {
  168. base.Dial = proxyDialer.Dial
  169. }
  170. return base
  171. }