Browse code

Prevent push and pull to v1 registries by filtering the available endpoints. Add a daemon flag to control this behaviour. Add a warning message when pulling an image from a v1 registry. The default order of pull is slightly altered with this changset.

Previously it was:
https v2, https v1, http v2, http v1

now it is:
https v2, http v2, https v1, http v1

Prevent login to v1 registries by explicitly setting the version before ping to
prevent fallback to v1.

Add unit tests for v2 only mode. Create a mock server that can register
handlers for various endpoints. Assert no v1 endpoints are hit with legacy
registries disabled for the following commands: pull, push, build, run and
login. Assert the opposite when legacy registries are not disabled.

Signed-off-by: Richard Scothern <richard.scothern@gmail.com>

Richard Scothern authored on 2015/09/17 02:42:17
Showing 11 changed files
... ...
@@ -60,6 +60,9 @@ func (p *v1Puller) Pull(tag string) (fallback bool, err error) {
60 60
 		// TODO(dmcgowan): Check if should fallback
61 61
 		return false, err
62 62
 	}
63
+	out := p.config.OutStream
64
+	out.Write(p.sf.FormatStatus("", "%s: this image was pulled from a legacy registry.  Important: This registry version will not be supported in future versions of docker.", p.repoInfo.CanonicalName))
65
+
63 66
 	return false, nil
64 67
 }
65 68
 
... ...
@@ -32,10 +32,13 @@ func init() {
32 32
 type DockerRegistrySuite struct {
33 33
 	ds  *DockerSuite
34 34
 	reg *testRegistryV2
35
+	d   *Daemon
35 36
 }
36 37
 
37 38
 func (s *DockerRegistrySuite) SetUpTest(c *check.C) {
39
+	testRequires(c, DaemonIsLinux)
38 40
 	s.reg = setupRegistry(c)
41
+	s.d = NewDaemon(c)
39 42
 }
40 43
 
41 44
 func (s *DockerRegistrySuite) TearDownTest(c *check.C) {
... ...
@@ -45,6 +48,7 @@ func (s *DockerRegistrySuite) TearDownTest(c *check.C) {
45 45
 	if s.ds != nil {
46 46
 		s.ds.TearDownTest(c)
47 47
 	}
48
+	s.d.Stop()
48 49
 }
49 50
 
50 51
 func init() {
51 52
new file mode 100644
... ...
@@ -0,0 +1,147 @@
0
+package main
1
+
2
+import (
3
+	"fmt"
4
+	"github.com/go-check/check"
5
+	"io/ioutil"
6
+	"net/http"
7
+	"os"
8
+)
9
+
10
+func makefile(contents string) (string, func(), error) {
11
+	cleanup := func() {
12
+
13
+	}
14
+
15
+	f, err := ioutil.TempFile(".", "tmp")
16
+	if err != nil {
17
+		return "", cleanup, err
18
+	}
19
+	err = ioutil.WriteFile(f.Name(), []byte(contents), os.ModePerm)
20
+	if err != nil {
21
+		return "", cleanup, err
22
+	}
23
+
24
+	cleanup = func() {
25
+		err := os.Remove(f.Name())
26
+		if err != nil {
27
+			fmt.Println("Error removing tmpfile")
28
+		}
29
+	}
30
+	return f.Name(), cleanup, nil
31
+
32
+}
33
+
34
+// TestV2Only ensures that a daemon in v2-only mode does not
35
+// attempt to contact any v1 registry endpoints.
36
+func (s *DockerRegistrySuite) TestV2Only(c *check.C) {
37
+	reg, err := newTestRegistry(c)
38
+	if err != nil {
39
+		c.Fatal(err.Error())
40
+	}
41
+
42
+	reg.registerHandler("/v2/", func(w http.ResponseWriter, r *http.Request) {
43
+		w.WriteHeader(404)
44
+	})
45
+
46
+	reg.registerHandler("/v1/.*", func(w http.ResponseWriter, r *http.Request) {
47
+		c.Fatal("V1 registry contacted")
48
+	})
49
+
50
+	repoName := fmt.Sprintf("%s/busybox", reg.hostport)
51
+
52
+	err = s.d.Start("--insecure-registry", reg.hostport, "--no-legacy-registry=true")
53
+	if err != nil {
54
+		c.Fatalf("Error starting daemon: %s", err.Error())
55
+	}
56
+
57
+	dockerfileName, cleanup, err := makefile(fmt.Sprintf("FROM %s/busybox", reg.hostport))
58
+	if err != nil {
59
+		c.Fatalf("Unable to create test dockerfile")
60
+	}
61
+	defer cleanup()
62
+
63
+	s.d.Cmd("build", "--file", dockerfileName, ".")
64
+
65
+	s.d.Cmd("run", repoName)
66
+	s.d.Cmd("login", "-u", "richard", "-p", "testtest", "-e", "testuser@testdomain.com", reg.hostport)
67
+	s.d.Cmd("tag", "busybox", repoName)
68
+	s.d.Cmd("push", repoName)
69
+	s.d.Cmd("pull", repoName)
70
+}
71
+
72
+// TestV1 starts a daemon in 'normal' mode
73
+// and ensure v1 endpoints are hit for the following operations:
74
+// login, push, pull, build & run
75
+func (s *DockerRegistrySuite) TestV1(c *check.C) {
76
+	reg, err := newTestRegistry(c)
77
+	if err != nil {
78
+		c.Fatal(err.Error())
79
+	}
80
+
81
+	v2Pings := 0
82
+	reg.registerHandler("/v2/", func(w http.ResponseWriter, r *http.Request) {
83
+		v2Pings++
84
+		// V2 ping 404 causes fallback to v1
85
+		w.WriteHeader(404)
86
+	})
87
+
88
+	v1Pings := 0
89
+	reg.registerHandler("/v1/_ping", func(w http.ResponseWriter, r *http.Request) {
90
+		v1Pings++
91
+	})
92
+
93
+	v1Logins := 0
94
+	reg.registerHandler("/v1/users/", func(w http.ResponseWriter, r *http.Request) {
95
+		v1Logins++
96
+	})
97
+
98
+	v1Repo := 0
99
+	reg.registerHandler("/v1/repositories/busybox/", func(w http.ResponseWriter, r *http.Request) {
100
+		v1Repo++
101
+	})
102
+
103
+	reg.registerHandler("/v1/repositories/busybox/images", func(w http.ResponseWriter, r *http.Request) {
104
+		v1Repo++
105
+	})
106
+
107
+	err = s.d.Start("--insecure-registry", reg.hostport, "--no-legacy-registry=false")
108
+	if err != nil {
109
+		c.Fatalf("Error starting daemon: %s", err.Error())
110
+	}
111
+
112
+	dockerfileName, cleanup, err := makefile(fmt.Sprintf("FROM %s/busybox", reg.hostport))
113
+	if err != nil {
114
+		c.Fatalf("Unable to create test dockerfile")
115
+	}
116
+	defer cleanup()
117
+
118
+	s.d.Cmd("build", "--file", dockerfileName, ".")
119
+	if v1Repo == 0 {
120
+		c.Errorf("Expected v1 repository access after build")
121
+	}
122
+
123
+	repoName := fmt.Sprintf("%s/busybox", reg.hostport)
124
+	s.d.Cmd("run", repoName)
125
+	if v1Repo == 1 {
126
+		c.Errorf("Expected v1 repository access after run")
127
+	}
128
+
129
+	s.d.Cmd("login", "-u", "richard", "-p", "testtest", "-e", "testuser@testdomain.com", reg.hostport)
130
+	if v1Logins == 0 {
131
+		c.Errorf("Expected v1 login attempt")
132
+	}
133
+
134
+	s.d.Cmd("tag", "busybox", repoName)
135
+	s.d.Cmd("push", repoName)
136
+
137
+	if v1Repo != 2 || v1Pings != 1 {
138
+		c.Error("Not all endpoints contacted after push", v1Repo, v1Pings)
139
+	}
140
+
141
+	s.d.Cmd("pull", repoName)
142
+	if v1Repo != 3 {
143
+		c.Errorf("Expected v1 repository access after pull")
144
+	}
145
+
146
+}
0 147
new file mode 100644
... ...
@@ -0,0 +1,56 @@
0
+package main
1
+
2
+import (
3
+	"net/http"
4
+	"net/http/httptest"
5
+	"regexp"
6
+	"strings"
7
+	"sync"
8
+
9
+	"github.com/go-check/check"
10
+)
11
+
12
+type handlerFunc func(w http.ResponseWriter, r *http.Request)
13
+
14
+type testRegistry struct {
15
+	server   *httptest.Server
16
+	hostport string
17
+	handlers map[string]handlerFunc
18
+	mu       sync.Mutex
19
+}
20
+
21
+func (tr *testRegistry) registerHandler(path string, h handlerFunc) {
22
+	tr.mu.Lock()
23
+	defer tr.mu.Unlock()
24
+	tr.handlers[path] = h
25
+}
26
+
27
+func newTestRegistry(c *check.C) (*testRegistry, error) {
28
+	testReg := &testRegistry{handlers: make(map[string]handlerFunc)}
29
+
30
+	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
31
+		url := r.URL.String()
32
+
33
+		var matched bool
34
+		var err error
35
+		for re, function := range testReg.handlers {
36
+			matched, err = regexp.MatchString(re, url)
37
+			if err != nil {
38
+				c.Fatalf("Error with handler regexp")
39
+				return
40
+			}
41
+			if matched {
42
+				function(w, r)
43
+				break
44
+			}
45
+		}
46
+
47
+		if !matched {
48
+			c.Fatal("Unable to match", url, "with regexp")
49
+		}
50
+	}))
51
+
52
+	testReg.server = ts
53
+	testReg.hostport = strings.Replace(ts.URL, "http://", "", 1)
54
+	return testReg, nil
55
+}
... ...
@@ -44,6 +44,10 @@ var (
44 44
 	ErrInvalidRepositoryName = errors.New("Invalid repository name (ex: \"registry.domain.tld/myrepos\")")
45 45
 
46 46
 	emptyServiceConfig = NewServiceConfig(nil)
47
+
48
+	// V2Only controls access to legacy registries.  If it is set to true via the
49
+	// command line flag the daemon will not attempt to contact v1 legacy registries
50
+	V2Only = false
47 51
 )
48 52
 
49 53
 // InstallFlags adds command-line options to the top-level flag parser for
... ...
@@ -53,6 +57,7 @@ func (options *Options) InstallFlags(cmd *flag.FlagSet, usageFn func(string) str
53 53
 	cmd.Var(&options.Mirrors, []string{"-registry-mirror"}, usageFn("Preferred Docker registry mirror"))
54 54
 	options.InsecureRegistries = opts.NewListOpts(ValidateIndexName)
55 55
 	cmd.Var(&options.InsecureRegistries, []string{"-insecure-registry"}, usageFn("Enable insecure registry communication"))
56
+	cmd.BoolVar(&V2Only, []string{"-no-legacy-registry"}, false, "Do not contact legacy registries")
56 57
 }
57 58
 
58 59
 type netIPNet net.IPNet
... ...
@@ -42,8 +42,9 @@ func scanForAPIVersion(address string) (string, APIVersion) {
42 42
 	return address, APIVersionUnknown
43 43
 }
44 44
 
45
-// NewEndpoint parses the given address to return a registry endpoint.
46
-func NewEndpoint(index *IndexInfo, metaHeaders http.Header) (*Endpoint, error) {
45
+// NewEndpoint parses the given address to return a registry endpoint.  v can be used to
46
+// specify a specific endpoint version
47
+func NewEndpoint(index *IndexInfo, metaHeaders http.Header, v APIVersion) (*Endpoint, error) {
47 48
 	tlsConfig, err := newTLSConfig(index.Name, index.Secure)
48 49
 	if err != nil {
49 50
 		return nil, err
... ...
@@ -52,6 +53,9 @@ func NewEndpoint(index *IndexInfo, metaHeaders http.Header) (*Endpoint, error) {
52 52
 	if err != nil {
53 53
 		return nil, err
54 54
 	}
55
+	if v != APIVersionUnknown {
56
+		endpoint.Version = v
57
+	}
55 58
 	if err := validateEndpoint(endpoint); err != nil {
56 59
 		return nil, err
57 60
 	}
... ...
@@ -111,11 +115,6 @@ func newEndpoint(address string, tlsConfig *tls.Config, metaHeaders http.Header)
111 111
 	return endpoint, nil
112 112
 }
113 113
 
114
-// GetEndpoint returns a new endpoint with the specified headers
115
-func (repoInfo *RepositoryInfo) GetEndpoint(metaHeaders http.Header) (*Endpoint, error) {
116
-	return NewEndpoint(repoInfo.Index, metaHeaders)
117
-}
118
-
119 114
 // Endpoint stores basic information about a registry endpoint.
120 115
 type Endpoint struct {
121 116
 	client         *http.Client
... ...
@@ -49,6 +49,10 @@ func init() {
49 49
 	httpVersion = append(httpVersion, useragent.VersionInfo{"arch", runtime.GOARCH})
50 50
 
51 51
 	dockerUserAgent = useragent.AppendVersions("", httpVersion...)
52
+
53
+	if runtime.GOOS != "linux" {
54
+		V2Only = true
55
+	}
52 56
 }
53 57
 
54 58
 func newTLSConfig(hostname string, isSecure bool) (*tls.Config, error) {
... ...
@@ -23,7 +23,7 @@ const (
23 23
 
24 24
 func spawnTestRegistrySession(t *testing.T) *Session {
25 25
 	authConfig := &cliconfig.AuthConfig{}
26
-	endpoint, err := NewEndpoint(makeIndex("/v1/"), nil)
26
+	endpoint, err := NewEndpoint(makeIndex("/v1/"), nil, APIVersionUnknown)
27 27
 	if err != nil {
28 28
 		t.Fatal(err)
29 29
 	}
... ...
@@ -50,7 +50,7 @@ func spawnTestRegistrySession(t *testing.T) *Session {
50 50
 
51 51
 func TestPingRegistryEndpoint(t *testing.T) {
52 52
 	testPing := func(index *IndexInfo, expectedStandalone bool, assertMessage string) {
53
-		ep, err := NewEndpoint(index, nil)
53
+		ep, err := NewEndpoint(index, nil, APIVersionUnknown)
54 54
 		if err != nil {
55 55
 			t.Fatal(err)
56 56
 		}
... ...
@@ -70,7 +70,7 @@ func TestPingRegistryEndpoint(t *testing.T) {
70 70
 func TestEndpoint(t *testing.T) {
71 71
 	// Simple wrapper to fail test if err != nil
72 72
 	expandEndpoint := func(index *IndexInfo) *Endpoint {
73
-		endpoint, err := NewEndpoint(index, nil)
73
+		endpoint, err := NewEndpoint(index, nil, APIVersionUnknown)
74 74
 		if err != nil {
75 75
 			t.Fatal(err)
76 76
 		}
... ...
@@ -79,7 +79,7 @@ func TestEndpoint(t *testing.T) {
79 79
 
80 80
 	assertInsecureIndex := func(index *IndexInfo) {
81 81
 		index.Secure = true
82
-		_, err := NewEndpoint(index, nil)
82
+		_, err := NewEndpoint(index, nil, APIVersionUnknown)
83 83
 		assertNotEqual(t, err, nil, index.Name+": Expected error for insecure index")
84 84
 		assertEqual(t, strings.Contains(err.Error(), "insecure-registry"), true, index.Name+": Expected insecure-registry  error for insecure index")
85 85
 		index.Secure = false
... ...
@@ -87,7 +87,7 @@ func TestEndpoint(t *testing.T) {
87 87
 
88 88
 	assertSecureIndex := func(index *IndexInfo) {
89 89
 		index.Secure = true
90
-		_, err := NewEndpoint(index, nil)
90
+		_, err := NewEndpoint(index, nil, APIVersionUnknown)
91 91
 		assertNotEqual(t, err, nil, index.Name+": Expected cert error for secure index")
92 92
 		assertEqual(t, strings.Contains(err.Error(), "certificate signed by unknown authority"), true, index.Name+": Expected cert error for secure index")
93 93
 		index.Secure = false
... ...
@@ -153,7 +153,7 @@ func TestEndpoint(t *testing.T) {
153 153
 	}
154 154
 	for _, address := range badEndpoints {
155 155
 		index.Name = address
156
-		_, err := NewEndpoint(index, nil)
156
+		_, err := NewEndpoint(index, nil, APIVersionUnknown)
157 157
 		checkNotEqual(t, err, nil, "Expected error while expanding bad endpoint")
158 158
 	}
159 159
 }
... ...
@@ -2,15 +2,11 @@ package registry
2 2
 
3 3
 import (
4 4
 	"crypto/tls"
5
-	"fmt"
6 5
 	"net/http"
7 6
 	"net/url"
8
-	"runtime"
9
-	"strings"
10 7
 
11 8
 	"github.com/docker/distribution/registry/client/auth"
12 9
 	"github.com/docker/docker/cliconfig"
13
-	"github.com/docker/docker/pkg/tlsconfig"
14 10
 )
15 11
 
16 12
 // Service is a registry service. It tracks configuration data such as a list
... ...
@@ -40,7 +36,14 @@ func (s *Service) Auth(authConfig *cliconfig.AuthConfig) (string, error) {
40 40
 	if err != nil {
41 41
 		return "", err
42 42
 	}
43
-	endpoint, err := NewEndpoint(index, nil)
43
+
44
+	endpointVersion := APIVersion(APIVersionUnknown)
45
+	if V2Only {
46
+		// Override the endpoint to only attempt a v2 ping
47
+		endpointVersion = APIVersion2
48
+	}
49
+
50
+	endpoint, err := NewEndpoint(index, nil, endpointVersion)
44 51
 	if err != nil {
45 52
 		return "", err
46 53
 	}
... ...
@@ -57,10 +60,11 @@ func (s *Service) Search(term string, authConfig *cliconfig.AuthConfig, headers
57 57
 	}
58 58
 
59 59
 	// *TODO: Search multiple indexes.
60
-	endpoint, err := repoInfo.GetEndpoint(http.Header(headers))
60
+	endpoint, err := NewEndpoint(repoInfo.Index, http.Header(headers), APIVersionUnknown)
61 61
 	if err != nil {
62 62
 		return nil, err
63 63
 	}
64
+
64 65
 	r, err := NewSession(endpoint.client, authConfig, endpoint)
65 66
 	if err != nil {
66 67
 		return nil, err
... ...
@@ -132,97 +136,20 @@ func (s *Service) LookupPushEndpoints(repoName string) (endpoints []APIEndpoint,
132 132
 }
133 133
 
134 134
 func (s *Service) lookupEndpoints(repoName string) (endpoints []APIEndpoint, err error) {
135
-	var cfg = tlsconfig.ServerDefault
136
-	tlsConfig := &cfg
137
-	if strings.HasPrefix(repoName, DefaultNamespace+"/") {
138
-		// v2 mirrors
139
-		for _, mirror := range s.Config.Mirrors {
140
-			mirrorTLSConfig, err := s.tlsConfigForMirror(mirror)
141
-			if err != nil {
142
-				return nil, err
143
-			}
144
-			endpoints = append(endpoints, APIEndpoint{
145
-				URL: mirror,
146
-				// guess mirrors are v2
147
-				Version:      APIVersion2,
148
-				Mirror:       true,
149
-				TrimHostname: true,
150
-				TLSConfig:    mirrorTLSConfig,
151
-			})
152
-		}
153
-		// v2 registry
154
-		endpoints = append(endpoints, APIEndpoint{
155
-			URL:          DefaultV2Registry,
156
-			Version:      APIVersion2,
157
-			Official:     true,
158
-			TrimHostname: true,
159
-			TLSConfig:    tlsConfig,
160
-		})
161
-		if runtime.GOOS == "linux" { // do not inherit legacy API for OSes supported in the future
162
-			// v1 registry
163
-			endpoints = append(endpoints, APIEndpoint{
164
-				URL:          DefaultV1Registry,
165
-				Version:      APIVersion1,
166
-				Official:     true,
167
-				TrimHostname: true,
168
-				TLSConfig:    tlsConfig,
169
-			})
170
-		}
171
-		return endpoints, nil
172
-	}
173
-
174
-	slashIndex := strings.IndexRune(repoName, '/')
175
-	if slashIndex <= 0 {
176
-		return nil, fmt.Errorf("invalid repo name: missing '/':  %s", repoName)
177
-	}
178
-	hostname := repoName[:slashIndex]
179
-
180
-	tlsConfig, err = s.TLSConfig(hostname)
135
+	endpoints, err = s.lookupV2Endpoints(repoName)
181 136
 	if err != nil {
182 137
 		return nil, err
183 138
 	}
184
-	isSecure := !tlsConfig.InsecureSkipVerify
185 139
 
186
-	v2Versions := []auth.APIVersion{
187
-		{
188
-			Type:    "registry",
189
-			Version: "2.0",
190
-		},
191
-	}
192
-	endpoints = []APIEndpoint{
193
-		{
194
-			URL:           "https://" + hostname,
195
-			Version:       APIVersion2,
196
-			TrimHostname:  true,
197
-			TLSConfig:     tlsConfig,
198
-			VersionHeader: DefaultRegistryVersionHeader,
199
-			Versions:      v2Versions,
200
-		},
201
-		{
202
-			URL:          "https://" + hostname,
203
-			Version:      APIVersion1,
204
-			TrimHostname: true,
205
-			TLSConfig:    tlsConfig,
206
-		},
140
+	if V2Only {
141
+		return endpoints, nil
207 142
 	}
208 143
 
209
-	if !isSecure {
210
-		endpoints = append(endpoints, APIEndpoint{
211
-			URL:          "http://" + hostname,
212
-			Version:      APIVersion2,
213
-			TrimHostname: true,
214
-			// used to check if supposed to be secure via InsecureSkipVerify
215
-			TLSConfig:     tlsConfig,
216
-			VersionHeader: DefaultRegistryVersionHeader,
217
-			Versions:      v2Versions,
218
-		}, APIEndpoint{
219
-			URL:          "http://" + hostname,
220
-			Version:      APIVersion1,
221
-			TrimHostname: true,
222
-			// used to check if supposed to be secure via InsecureSkipVerify
223
-			TLSConfig: tlsConfig,
224
-		})
144
+	legacyEndpoints, err := s.lookupV1Endpoints(repoName)
145
+	if err != nil {
146
+		return nil, err
225 147
 	}
148
+	endpoints = append(endpoints, legacyEndpoints...)
226 149
 
227 150
 	return endpoints, nil
228 151
 }
229 152
new file mode 100644
... ...
@@ -0,0 +1,54 @@
0
+package registry
1
+
2
+import (
3
+	"fmt"
4
+	"strings"
5
+
6
+	"github.com/docker/docker/pkg/tlsconfig"
7
+)
8
+
9
+func (s *Service) lookupV1Endpoints(repoName string) (endpoints []APIEndpoint, err error) {
10
+	var cfg = tlsconfig.ServerDefault
11
+	tlsConfig := &cfg
12
+	if strings.HasPrefix(repoName, DefaultNamespace+"/") {
13
+		endpoints = append(endpoints, APIEndpoint{
14
+			URL:          DefaultV1Registry,
15
+			Version:      APIVersion1,
16
+			Official:     true,
17
+			TrimHostname: true,
18
+			TLSConfig:    tlsConfig,
19
+		})
20
+		return endpoints, nil
21
+	}
22
+
23
+	slashIndex := strings.IndexRune(repoName, '/')
24
+	if slashIndex <= 0 {
25
+		return nil, fmt.Errorf("invalid repo name: missing '/':  %s", repoName)
26
+	}
27
+	hostname := repoName[:slashIndex]
28
+
29
+	tlsConfig, err = s.TLSConfig(hostname)
30
+	if err != nil {
31
+		return nil, err
32
+	}
33
+
34
+	endpoints = []APIEndpoint{
35
+		{
36
+			URL:          "https://" + hostname,
37
+			Version:      APIVersion1,
38
+			TrimHostname: true,
39
+			TLSConfig:    tlsConfig,
40
+		},
41
+	}
42
+
43
+	if tlsConfig.InsecureSkipVerify {
44
+		endpoints = append(endpoints, APIEndpoint{ // or this
45
+			URL:          "http://" + hostname,
46
+			Version:      APIVersion1,
47
+			TrimHostname: true,
48
+			// used to check if supposed to be secure via InsecureSkipVerify
49
+			TLSConfig: tlsConfig,
50
+		})
51
+	}
52
+	return endpoints, nil
53
+}
0 54
new file mode 100644
... ...
@@ -0,0 +1,83 @@
0
+package registry
1
+
2
+import (
3
+	"fmt"
4
+	"strings"
5
+
6
+	"github.com/docker/distribution/registry/client/auth"
7
+	"github.com/docker/docker/pkg/tlsconfig"
8
+)
9
+
10
+func (s *Service) lookupV2Endpoints(repoName string) (endpoints []APIEndpoint, err error) {
11
+	var cfg = tlsconfig.ServerDefault
12
+	tlsConfig := &cfg
13
+	if strings.HasPrefix(repoName, DefaultNamespace+"/") {
14
+		// v2 mirrors
15
+		for _, mirror := range s.Config.Mirrors {
16
+			mirrorTLSConfig, err := s.tlsConfigForMirror(mirror)
17
+			if err != nil {
18
+				return nil, err
19
+			}
20
+			endpoints = append(endpoints, APIEndpoint{
21
+				URL: mirror,
22
+				// guess mirrors are v2
23
+				Version:      APIVersion2,
24
+				Mirror:       true,
25
+				TrimHostname: true,
26
+				TLSConfig:    mirrorTLSConfig,
27
+			})
28
+		}
29
+		// v2 registry
30
+		endpoints = append(endpoints, APIEndpoint{
31
+			URL:          DefaultV2Registry,
32
+			Version:      APIVersion2,
33
+			Official:     true,
34
+			TrimHostname: true,
35
+			TLSConfig:    tlsConfig,
36
+		})
37
+
38
+		return endpoints, nil
39
+	}
40
+
41
+	slashIndex := strings.IndexRune(repoName, '/')
42
+	if slashIndex <= 0 {
43
+		return nil, fmt.Errorf("invalid repo name: missing '/':  %s", repoName)
44
+	}
45
+	hostname := repoName[:slashIndex]
46
+
47
+	tlsConfig, err = s.TLSConfig(hostname)
48
+	if err != nil {
49
+		return nil, err
50
+	}
51
+
52
+	v2Versions := []auth.APIVersion{
53
+		{
54
+			Type:    "registry",
55
+			Version: "2.0",
56
+		},
57
+	}
58
+	endpoints = []APIEndpoint{
59
+		{
60
+			URL:           "https://" + hostname,
61
+			Version:       APIVersion2,
62
+			TrimHostname:  true,
63
+			TLSConfig:     tlsConfig,
64
+			VersionHeader: DefaultRegistryVersionHeader,
65
+			Versions:      v2Versions,
66
+		},
67
+	}
68
+
69
+	if tlsConfig.InsecureSkipVerify {
70
+		endpoints = append(endpoints, APIEndpoint{
71
+			URL:          "http://" + hostname,
72
+			Version:      APIVersion2,
73
+			TrimHostname: true,
74
+			// used to check if supposed to be secure via InsecureSkipVerify
75
+			TLSConfig:     tlsConfig,
76
+			VersionHeader: DefaultRegistryVersionHeader,
77
+			Versions:      v2Versions,
78
+		})
79
+	}
80
+
81
+	return endpoints, nil
82
+}