registry.go 5.1 KB

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