endpoint.go 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. package registry
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "io/ioutil"
  6. "net"
  7. "net/http"
  8. "net/url"
  9. "strings"
  10. log "github.com/Sirupsen/logrus"
  11. )
  12. // scans string for api version in the URL path. returns the trimmed hostname, if version found, string and API version.
  13. func scanForAPIVersion(hostname string) (string, APIVersion) {
  14. var (
  15. chunks []string
  16. apiVersionStr string
  17. )
  18. if strings.HasSuffix(hostname, "/") {
  19. chunks = strings.Split(hostname[:len(hostname)-1], "/")
  20. apiVersionStr = chunks[len(chunks)-1]
  21. } else {
  22. chunks = strings.Split(hostname, "/")
  23. apiVersionStr = chunks[len(chunks)-1]
  24. }
  25. for k, v := range apiVersions {
  26. if apiVersionStr == v {
  27. hostname = strings.Join(chunks[:len(chunks)-1], "/")
  28. return hostname, k
  29. }
  30. }
  31. return hostname, DefaultAPIVersion
  32. }
  33. func NewEndpoint(hostname string, insecureRegistries []string) (*Endpoint, error) {
  34. endpoint, err := newEndpoint(hostname, insecureRegistries)
  35. if err != nil {
  36. return nil, err
  37. }
  38. // Try HTTPS ping to registry
  39. endpoint.URL.Scheme = "https"
  40. if _, err := endpoint.Ping(); err != nil {
  41. //TODO: triggering highland build can be done there without "failing"
  42. if endpoint.secure {
  43. // If registry is secure and HTTPS failed, show user the error and tell them about `--insecure-registry`
  44. // in case that's what they need. DO NOT accept unknown CA certificates, and DO NOT fallback to HTTP.
  45. return nil, fmt.Errorf("Invalid registry endpoint %s: %v. If this private registry supports only HTTP or HTTPS with an unknown CA certificate, please add `--insecure-registry %s` to the daemon's arguments. In the case of HTTPS, if you have access to the registry's CA certificate, no need for the flag; simply place the CA certificate at /etc/docker/certs.d/%s/ca.crt", endpoint, err, endpoint.URL.Host, endpoint.URL.Host)
  46. }
  47. // If registry is insecure and HTTPS failed, fallback to HTTP.
  48. log.Debugf("Error from registry %q marked as insecure: %v. Insecurely falling back to HTTP", endpoint, err)
  49. endpoint.URL.Scheme = "http"
  50. _, err2 := endpoint.Ping()
  51. if err2 == nil {
  52. return endpoint, nil
  53. }
  54. return nil, fmt.Errorf("Invalid registry endpoint %q. HTTPS attempt: %v. HTTP attempt: %v", endpoint, err, err2)
  55. }
  56. return endpoint, nil
  57. }
  58. func newEndpoint(hostname string, insecureRegistries []string) (*Endpoint, error) {
  59. var (
  60. endpoint = Endpoint{}
  61. trimmedHostname string
  62. err error
  63. )
  64. if !strings.HasPrefix(hostname, "http") {
  65. hostname = "https://" + hostname
  66. }
  67. trimmedHostname, endpoint.Version = scanForAPIVersion(hostname)
  68. endpoint.URL, err = url.Parse(trimmedHostname)
  69. if err != nil {
  70. return nil, err
  71. }
  72. endpoint.secure = isSecure(endpoint.URL.Host, insecureRegistries)
  73. return &endpoint, nil
  74. }
  75. type Endpoint struct {
  76. URL *url.URL
  77. Version APIVersion
  78. secure bool
  79. }
  80. // Get the formated URL for the root of this registry Endpoint
  81. func (e Endpoint) String() string {
  82. return fmt.Sprintf("%s/v%d/", e.URL.String(), e.Version)
  83. }
  84. func (e Endpoint) VersionString(version APIVersion) string {
  85. return fmt.Sprintf("%s/v%d/", e.URL.String(), version)
  86. }
  87. func (e Endpoint) Ping() (RegistryInfo, error) {
  88. if e.String() == IndexServerAddress() {
  89. // Skip the check, we now this one is valid
  90. // (and we never want to fallback to http in case of error)
  91. return RegistryInfo{Standalone: false}, nil
  92. }
  93. req, err := http.NewRequest("GET", e.String()+"_ping", nil)
  94. if err != nil {
  95. return RegistryInfo{Standalone: false}, err
  96. }
  97. resp, _, err := doRequest(req, nil, ConnectTimeout, e.secure)
  98. if err != nil {
  99. return RegistryInfo{Standalone: false}, err
  100. }
  101. defer resp.Body.Close()
  102. jsonString, err := ioutil.ReadAll(resp.Body)
  103. if err != nil {
  104. return RegistryInfo{Standalone: false}, fmt.Errorf("Error while reading the http response: %s", err)
  105. }
  106. // If the header is absent, we assume true for compatibility with earlier
  107. // versions of the registry. default to true
  108. info := RegistryInfo{
  109. Standalone: true,
  110. }
  111. if err := json.Unmarshal(jsonString, &info); err != nil {
  112. log.Debugf("Error unmarshalling the _ping RegistryInfo: %s", err)
  113. // don't stop here. Just assume sane defaults
  114. }
  115. if hdr := resp.Header.Get("X-Docker-Registry-Version"); hdr != "" {
  116. log.Debugf("Registry version header: '%s'", hdr)
  117. info.Version = hdr
  118. }
  119. log.Debugf("RegistryInfo.Version: %q", info.Version)
  120. standalone := resp.Header.Get("X-Docker-Registry-Standalone")
  121. log.Debugf("Registry standalone header: '%s'", standalone)
  122. // Accepted values are "true" (case-insensitive) and "1".
  123. if strings.EqualFold(standalone, "true") || standalone == "1" {
  124. info.Standalone = true
  125. } else if len(standalone) > 0 {
  126. // there is a header set, and it is not "true" or "1", so assume fails
  127. info.Standalone = false
  128. }
  129. log.Debugf("RegistryInfo.Standalone: %t", info.Standalone)
  130. return info, nil
  131. }
  132. // isSecure returns false if the provided hostname is part of the list of insecure registries.
  133. // Insecure registries accept HTTP and/or accept HTTPS with certificates from unknown CAs.
  134. func isSecure(hostname string, insecureRegistries []string) bool {
  135. if hostname == IndexServerURL.Host {
  136. return true
  137. }
  138. host, _, err := net.SplitHostPort(hostname)
  139. if err != nil {
  140. host = hostname
  141. }
  142. if host == "127.0.0.1" || host == "localhost" {
  143. return false
  144. }
  145. if len(insecureRegistries) == 0 {
  146. return true
  147. }
  148. for _, h := range insecureRegistries {
  149. if hostname == h {
  150. return false
  151. }
  152. }
  153. return true
  154. }