Browse code

Do not verify certificate when using --insecure-registry on an HTTPS registry

Signed-off-by: Tibor Vass <teabee89@gmail.com>

Conflicts:
registry/registry.go
registry/registry_test.go
registry/service.go
registry/session.go

Conflicts:
registry/endpoint.go
registry/registry.go

Tibor Vass authored on 2014/10/11 12:22:12
Showing 9 changed files
... ...
@@ -56,7 +56,7 @@ func (config *Config) InstallFlags() {
56 56
 	flag.StringVar(&config.BridgeIP, []string{"#bip", "-bip"}, "", "Use this CIDR notation address for the network bridge's IP, not compatible with -b")
57 57
 	flag.StringVar(&config.BridgeIface, []string{"b", "-bridge"}, "", "Attach containers to a pre-existing network bridge\nuse 'none' to disable container networking")
58 58
 	flag.StringVar(&config.FixedCIDR, []string{"-fixed-cidr"}, "", "IPv4 subnet for fixed IPs (ex: 10.20.0.0/16)\nthis subnet must be nested in the bridge subnet (which is defined by -b or --bip)")
59
-	opts.ListVar(&config.InsecureRegistries, []string{"-insecure-registry"}, "Make these registries use http")
59
+	opts.ListVar(&config.InsecureRegistries, []string{"-insecure-registry"}, "Enable insecure communication with specified registries (no certificate verification for HTTPS and enable HTTP fallback)")
60 60
 	flag.BoolVar(&config.InterContainerCommunication, []string{"#icc", "-icc"}, true, "Enable inter-container communication")
61 61
 	flag.StringVar(&config.GraphDriver, []string{"s", "-storage-driver"}, "", "Force the Docker runtime to use a specific storage driver")
62 62
 	flag.StringVar(&config.ExecDriver, []string{"e", "-exec-driver"}, "native", "Force the Docker runtime to use a specific exec driver")
... ...
@@ -70,7 +70,7 @@ expect an integer, and they can only be specified once.
70 70
       -g, --graph="/var/lib/docker"              Path to use as the root of the Docker runtime
71 71
       -H, --host=[]                              The socket(s) to bind to in daemon mode or connect to in client mode, specified using one or more tcp://host:port, unix:///path/to/socket, fd://* or fd://socketfd.
72 72
       --icc=true                                 Enable inter-container communication
73
-      --insecure-registry=[]                     Make these registries use http
73
+      --insecure-registry=[]                     Enable insecure communication with specified registries (no certificate verification for HTTPS and enable HTTP fallback)
74 74
       --ip=0.0.0.0                               Default IP address to use when binding container ports
75 75
       --ip-forward=true                          Enable net.ipv4.ip_forward
76 76
       --ip-masq=true                             Enable IP masquerading for bridge's IP range
... ...
@@ -195,16 +195,16 @@ to other machines on the Internet. This may interfere with some network topologi
195 195
 can be disabled with --ip-masq=false.
196 196
 
197 197
 
198
+By default, Docker will assume all registries are secured via TLS with certificate verification
199
+enabled. Prior versions of Docker used an auto fallback if a registry did not support TLS
200
+(or if the TLS connection failed). This introduced the opportunity for Man In The Middle (MITM)
201
+attacks, so as of Docker 1.3.1, the user must now specify the `--insecure-registry` daemon flag
202
+for each insecure registry. An insecure registry is either not using TLS (i.e. plain text HTTP),
203
+or is using TLS with a CA certificate not known by the Docker daemon (i.e. certification
204
+verification disabled). For example, if there is a registry listening for HTTP at 127.0.0.1:5000,
205
+as of Docker 1.3.1 you are required to specify `--insecure-registry 127.0.0.1:5000` when starting
206
+the Docker daemon.
198 207
 
199
-By default docker will assume all registries are securied via TLS.  Prior versions
200
-of docker used an auto fallback if a registry did not support TLS.  This introduces
201
-the opportunity for MITM attacks so in Docker 1.2 the user must specify `--insecure-registries` 
202
-when starting the Docker daemon to state which registries are not using TLS and to communicate
203
-with these registries via plain text.  If you are running a local registry over plain text
204
-on `127.0.0.1:5000` you will be required to specify `--insecure-registries 127.0.0.1:500` 
205
-when starting the docker daemon to be able to push and pull images to that registry.
206
-No automatic fallback will happen after Docker 1.2 to detect if a registry is using
207
-HTTP or HTTPS.
208 208
 
209 209
 Docker supports softlinks for the Docker data directory
210 210
 (`/var/lib/docker`) and for `/var/lib/docker/tmp`. The `DOCKER_TMPDIR` and the data directory can be set like this:
... ...
@@ -53,7 +53,7 @@ func mkTestTagStore(root string, t *testing.T) *TagStore {
53 53
 	if err != nil {
54 54
 		t.Fatal(err)
55 55
 	}
56
-	store, err := NewTagStore(path.Join(root, "tags"), graph, nil)
56
+	store, err := NewTagStore(path.Join(root, "tags"), graph, nil, nil)
57 57
 	if err != nil {
58 58
 		t.Fatal(err)
59 59
 	}
... ...
@@ -2,7 +2,6 @@ package registry
2 2
 
3 3
 import (
4 4
 	"encoding/json"
5
-	"errors"
6 5
 	"fmt"
7 6
 	"io/ioutil"
8 7
 	"net/http"
... ...
@@ -34,27 +33,40 @@ func scanForAPIVersion(hostname string) (string, APIVersion) {
34 34
 	return hostname, DefaultAPIVersion
35 35
 }
36 36
 
37
-func NewEndpoint(hostname string) (*Endpoint, error) {
38
-	endpoint, err := newEndpoint(hostname)
37
+func NewEndpoint(hostname string, secure bool) (*Endpoint, error) {
38
+	endpoint, err := newEndpoint(hostname, secure)
39 39
 	if err != nil {
40 40
 		return nil, err
41 41
 	}
42 42
 
43
+	// Try HTTPS ping to registry
43 44
 	endpoint.URL.Scheme = "https"
44 45
 	if _, err := endpoint.Ping(); err != nil {
45
-		log.Debugf("Registry %s does not work (%s), falling back to http", endpoint, err)
46
-		// TODO: Check if http fallback is enabled
46
+
47
+		//TODO: triggering highland build can be done there without "failing"
48
+
49
+		if secure {
50
+			// If registry is secure and HTTPS failed, show user the error and tell them about `--insecure-registry`
51
+			// in case that's what they need. DO NOT accept unknown CA certificates, and DO NOT fallback to HTTP.
52
+			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)
53
+		}
54
+
55
+		// If registry is insecure and HTTPS failed, fallback to HTTP.
56
+		log.Debugf("Error from registry %q marked as insecure: %v. Insecurely falling back to HTTP", endpoint, err)
47 57
 		endpoint.URL.Scheme = "http"
48
-		if _, err = endpoint.Ping(); err != nil {
49
-			return nil, errors.New("Invalid Registry endpoint: " + err.Error())
58
+		_, err2 := endpoint.Ping()
59
+		if err2 == nil {
60
+			return endpoint, nil
50 61
 		}
62
+
63
+		return nil, fmt.Errorf("Invalid registry endpoint %q. HTTPS attempt: %v. HTTP attempt: %v", endpoint, err, err2)
51 64
 	}
52 65
 
53 66
 	return endpoint, nil
54 67
 }
55
-func newEndpoint(hostname string) (*Endpoint, error) {
68
+func newEndpoint(hostname string, secure bool) (*Endpoint, error) {
56 69
 	var (
57
-		endpoint        Endpoint
70
+		endpoint        = Endpoint{secure: secure}
58 71
 		trimmedHostname string
59 72
 		err             error
60 73
 	)
... ...
@@ -72,6 +84,7 @@ func newEndpoint(hostname string) (*Endpoint, error) {
72 72
 type Endpoint struct {
73 73
 	URL     *url.URL
74 74
 	Version APIVersion
75
+	secure  bool
75 76
 }
76 77
 
77 78
 // Get the formated URL for the root of this registry Endpoint
... ...
@@ -95,7 +108,7 @@ func (e Endpoint) Ping() (RegistryInfo, error) {
95 95
 		return RegistryInfo{Standalone: false}, err
96 96
 	}
97 97
 
98
-	resp, _, err := doRequest(req, nil, ConnectTimeout)
98
+	resp, _, err := doRequest(req, nil, ConnectTimeout, e.secure)
99 99
 	if err != nil {
100 100
 		return RegistryInfo{Standalone: false}, err
101 101
 	}
... ...
@@ -134,3 +147,19 @@ func (e Endpoint) Ping() (RegistryInfo, error) {
134 134
 	log.Debugf("RegistryInfo.Standalone: %t", info.Standalone)
135 135
 	return info, nil
136 136
 }
137
+
138
+// IsSecure returns false if the provided hostname is part of the list of insecure registries.
139
+// Insecure registries accept HTTP and/or accept HTTPS with certificates from unknown CAs.
140
+func IsSecure(hostname string, insecureRegistries []string) bool {
141
+	if hostname == IndexServerAddress() {
142
+		return true
143
+	}
144
+
145
+	for _, h := range insecureRegistries {
146
+		if hostname == h {
147
+			return false
148
+		}
149
+	}
150
+
151
+	return true
152
+}
... ...
@@ -12,7 +12,7 @@ func TestEndpointParse(t *testing.T) {
12 12
 		{"0.0.0.0:5000", "https://0.0.0.0:5000/v1/"},
13 13
 	}
14 14
 	for _, td := range testData {
15
-		e, err := newEndpoint(td.str)
15
+		e, err := newEndpoint(td.str, true)
16 16
 		if err != nil {
17 17
 			t.Errorf("%q: %s", td.str, err)
18 18
 		}
... ...
@@ -14,6 +14,7 @@ import (
14 14
 	"strings"
15 15
 	"time"
16 16
 
17
+	log "github.com/Sirupsen/logrus"
17 18
 	"github.com/docker/docker/utils"
18 19
 )
19 20
 
... ...
@@ -35,7 +36,7 @@ const (
35 35
 	ConnectTimeout
36 36
 )
37 37
 
38
-func newClient(jar http.CookieJar, roots *x509.CertPool, cert *tls.Certificate, timeout TimeoutType) *http.Client {
38
+func newClient(jar http.CookieJar, roots *x509.CertPool, cert *tls.Certificate, timeout TimeoutType, secure bool) *http.Client {
39 39
 	tlsConfig := tls.Config{
40 40
 		RootCAs: roots,
41 41
 		// Avoid fallback to SSL protocols < TLS1.0
... ...
@@ -46,6 +47,10 @@ func newClient(jar http.CookieJar, roots *x509.CertPool, cert *tls.Certificate,
46 46
 		tlsConfig.Certificates = append(tlsConfig.Certificates, *cert)
47 47
 	}
48 48
 
49
+	if !secure {
50
+		tlsConfig.InsecureSkipVerify = true
51
+	}
52
+
49 53
 	httpTransport := &http.Transport{
50 54
 		DisableKeepAlives: true,
51 55
 		Proxy:             http.ProxyFromEnvironment,
... ...
@@ -86,69 +91,76 @@ func newClient(jar http.CookieJar, roots *x509.CertPool, cert *tls.Certificate,
86 86
 	}
87 87
 }
88 88
 
89
-func doRequest(req *http.Request, jar http.CookieJar, timeout TimeoutType) (*http.Response, *http.Client, error) {
90
-	hasFile := func(files []os.FileInfo, name string) bool {
91
-		for _, f := range files {
92
-			if f.Name() == name {
93
-				return true
94
-			}
95
-		}
96
-		return false
97
-	}
98
-
99
-	hostDir := path.Join("/etc/docker/certs.d", req.URL.Host)
100
-	fs, err := ioutil.ReadDir(hostDir)
101
-	if err != nil && !os.IsNotExist(err) {
102
-		return nil, nil, err
103
-	}
104
-
89
+func doRequest(req *http.Request, jar http.CookieJar, timeout TimeoutType, secure bool) (*http.Response, *http.Client, error) {
105 90
 	var (
106 91
 		pool  *x509.CertPool
107 92
 		certs []*tls.Certificate
108 93
 	)
109 94
 
110
-	for _, f := range fs {
111
-		if strings.HasSuffix(f.Name(), ".crt") {
112
-			if pool == nil {
113
-				pool = x509.NewCertPool()
114
-			}
115
-			data, err := ioutil.ReadFile(path.Join(hostDir, f.Name()))
116
-			if err != nil {
117
-				return nil, nil, err
95
+	if secure && req.URL.Scheme == "https" {
96
+		hasFile := func(files []os.FileInfo, name string) bool {
97
+			for _, f := range files {
98
+				if f.Name() == name {
99
+					return true
100
+				}
118 101
 			}
119
-			pool.AppendCertsFromPEM(data)
102
+			return false
103
+		}
104
+
105
+		hostDir := path.Join("/etc/docker/certs.d", req.URL.Host)
106
+		log.Debugf("hostDir: %s", hostDir)
107
+		fs, err := ioutil.ReadDir(hostDir)
108
+		if err != nil && !os.IsNotExist(err) {
109
+			return nil, nil, err
120 110
 		}
121
-		if strings.HasSuffix(f.Name(), ".cert") {
122
-			certName := f.Name()
123
-			keyName := certName[:len(certName)-5] + ".key"
124
-			if !hasFile(fs, keyName) {
125
-				return nil, nil, fmt.Errorf("Missing key %s for certificate %s", keyName, certName)
111
+
112
+		for _, f := range fs {
113
+			if strings.HasSuffix(f.Name(), ".crt") {
114
+				if pool == nil {
115
+					pool = x509.NewCertPool()
116
+				}
117
+				log.Debugf("crt: %s", hostDir+"/"+f.Name())
118
+				data, err := ioutil.ReadFile(path.Join(hostDir, f.Name()))
119
+				if err != nil {
120
+					return nil, nil, err
121
+				}
122
+				pool.AppendCertsFromPEM(data)
126 123
 			}
127
-			cert, err := tls.LoadX509KeyPair(path.Join(hostDir, certName), path.Join(hostDir, keyName))
128
-			if err != nil {
129
-				return nil, nil, err
124
+			if strings.HasSuffix(f.Name(), ".cert") {
125
+				certName := f.Name()
126
+				keyName := certName[:len(certName)-5] + ".key"
127
+				log.Debugf("cert: %s", hostDir+"/"+f.Name())
128
+				if !hasFile(fs, keyName) {
129
+					return nil, nil, fmt.Errorf("Missing key %s for certificate %s", keyName, certName)
130
+				}
131
+				cert, err := tls.LoadX509KeyPair(path.Join(hostDir, certName), path.Join(hostDir, keyName))
132
+				if err != nil {
133
+					return nil, nil, err
134
+				}
135
+				certs = append(certs, &cert)
130 136
 			}
131
-			certs = append(certs, &cert)
132
-		}
133
-		if strings.HasSuffix(f.Name(), ".key") {
134
-			keyName := f.Name()
135
-			certName := keyName[:len(keyName)-4] + ".cert"
136
-			if !hasFile(fs, certName) {
137
-				return nil, nil, fmt.Errorf("Missing certificate %s for key %s", certName, keyName)
137
+			if strings.HasSuffix(f.Name(), ".key") {
138
+				keyName := f.Name()
139
+				certName := keyName[:len(keyName)-4] + ".cert"
140
+				log.Debugf("key: %s", hostDir+"/"+f.Name())
141
+				if !hasFile(fs, certName) {
142
+					return nil, nil, fmt.Errorf("Missing certificate %s for key %s", certName, keyName)
143
+				}
138 144
 			}
139 145
 		}
140 146
 	}
141 147
 
142 148
 	if len(certs) == 0 {
143
-		client := newClient(jar, pool, nil, timeout)
149
+		client := newClient(jar, pool, nil, timeout, secure)
144 150
 		res, err := client.Do(req)
145 151
 		if err != nil {
146 152
 			return nil, nil, err
147 153
 		}
148 154
 		return res, client, nil
149 155
 	}
156
+
150 157
 	for i, cert := range certs {
151
-		client := newClient(jar, pool, cert, timeout)
158
+		client := newClient(jar, pool, cert, timeout, secure)
152 159
 		res, err := client.Do(req)
153 160
 		// If this is the last cert, otherwise, continue to next cert if 403 or 5xx
154 161
 		if i == len(certs)-1 || err == nil &&
... ...
@@ -213,49 +225,6 @@ func ResolveRepositoryName(reposName string) (string, string, error) {
213 213
 	return hostname, reposName, nil
214 214
 }
215 215
 
216
-// this method expands the registry name as used in the prefix of a repo
217
-// to a full url. if it already is a url, there will be no change.
218
-func ExpandAndVerifyRegistryUrl(hostname string, secure bool) (string, error) {
219
-	if hostname == IndexServerAddress() {
220
-		return hostname, nil
221
-	}
222
-
223
-	endpoint := fmt.Sprintf("http://%s/v1/", hostname)
224
-
225
-	if secure {
226
-		endpoint = fmt.Sprintf("https://%s/v1/", hostname)
227
-	}
228
-
229
-	if _, oerr := pingRegistryEndpoint(endpoint); oerr != nil {
230
-		//TODO: triggering highland build can be done there without "failing"
231
-		err := fmt.Errorf("Invalid registry endpoint '%s': %s ", endpoint, oerr)
232
-
233
-		if secure {
234
-			err = fmt.Errorf("%s. If this private registry supports only HTTP, please add `--insecure-registry %s` to the daemon's arguments.", oerr, hostname)
235
-		}
236
-
237
-		return "", err
238
-	}
239
-
240
-	return endpoint, nil
241
-}
242
-
243
-// this method verifies if the provided hostname is part of the list of
244
-// insecure registries and returns false if HTTP should be used
245
-func IsSecure(hostname string, insecureRegistries []string) bool {
246
-	if hostname == IndexServerAddress() {
247
-		return true
248
-	}
249
-
250
-	for _, h := range insecureRegistries {
251
-		if hostname == h {
252
-			return false
253
-		}
254
-	}
255
-
256
-	return true
257
-}
258
-
259 216
 func trustedLocation(req *http.Request) bool {
260 217
 	var (
261 218
 		trusteds = []string{"docker.com", "docker.io"}
... ...
@@ -21,7 +21,7 @@ const (
21 21
 
22 22
 func spawnTestRegistrySession(t *testing.T) *Session {
23 23
 	authConfig := &AuthConfig{}
24
-	endpoint, err := NewEndpoint(makeURL("/v1/"))
24
+	endpoint, err := NewEndpoint(makeURL("/v1/"), false)
25 25
 	if err != nil {
26 26
 		t.Fatal(err)
27 27
 	}
... ...
@@ -33,7 +33,7 @@ func spawnTestRegistrySession(t *testing.T) *Session {
33 33
 }
34 34
 
35 35
 func TestPingRegistryEndpoint(t *testing.T) {
36
-	ep, err := NewEndpoint(makeURL("/v1/"))
36
+	ep, err := NewEndpoint(makeURL("/v1/"), false)
37 37
 	if err != nil {
38 38
 		t.Fatal(err)
39 39
 	}
... ...
@@ -89,7 +89,10 @@ func (s *Service) Search(job *engine.Job) engine.Status {
89 89
 	if err != nil {
90 90
 		return job.Error(err)
91 91
 	}
92
-	endpoint, err := NewEndpoint(hostname)
92
+
93
+	secure := IsSecure(hostname, s.insecureRegistries)
94
+
95
+	endpoint, err := NewEndpoint(hostname, secure)
93 96
 	if err != nil {
94 97
 		return job.Error(err)
95 98
 	}
... ...
@@ -65,7 +65,7 @@ func NewSession(authConfig *AuthConfig, factory *utils.HTTPRequestFactory, endpo
65 65
 }
66 66
 
67 67
 func (r *Session) doRequest(req *http.Request) (*http.Response, *http.Client, error) {
68
-	return doRequest(req, r.jar, r.timeout)
68
+	return doRequest(req, r.jar, r.timeout, r.indexEndpoint.secure)
69 69
 }
70 70
 
71 71
 // Retrieve the history of a given image from the Registry.