Add the possibility of specifying a subnet for --insecure-registry
| ... | ... |
@@ -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"}, "Enable insecure communication with specified registries (no certificate verification for HTTPS and enable HTTP fallback)")
|
|
| 59 |
+ opts.ListVar(&config.InsecureRegistries, []string{"-insecure-registry"}, "Enable insecure communication with specified registries (no certificate verification for HTTPS and enable HTTP fallback) (e.g., localhost:5000 or 10.20.0.0/16)")
|
|
| 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")
|
| ... | ... |
@@ -68,6 +68,14 @@ func (config *Config) InstallFlags() {
|
| 68 | 68 |
opts.IPListVar(&config.Dns, []string{"#dns", "-dns"}, "Force Docker to use specific DNS servers")
|
| 69 | 69 |
opts.DnsSearchListVar(&config.DnsSearch, []string{"-dns-search"}, "Force Docker to use specific DNS search domains")
|
| 70 | 70 |
opts.MirrorListVar(&config.Mirrors, []string{"-registry-mirror"}, "Specify a preferred Docker registry mirror")
|
| 71 |
+ |
|
| 72 |
+ // Localhost is by default considered as an insecure registry |
|
| 73 |
+ // This is a stop-gap for people who are running a private registry on localhost (especially on Boot2docker). |
|
| 74 |
+ // |
|
| 75 |
+ // TODO: should we deprecate this once it is easier for people to set up a TLS registry or change |
|
| 76 |
+ // daemon flags on boot2docker? |
|
| 77 |
+ // If so, do not forget to check the TODO in TestIsSecure |
|
| 78 |
+ config.InsecureRegistries = append(config.InsecureRegistries, "127.0.0.0/8") |
|
| 71 | 79 |
} |
| 72 | 80 |
|
| 73 | 81 |
func getDefaultNetworkMtu() int {
|
| ... | ... |
@@ -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=[] Enable insecure communication with specified registries (no certificate verification for HTTPS and enable HTTP fallback) |
|
| 73 |
+ --insecure-registry=[] Enable insecure communication with specified registries (disables certificate verification for HTTPS and enables HTTP fallback) (e.g., localhost:5000 or 10.20.0.0/16) |
|
| 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 |
| ... | ... |
@@ -193,24 +193,44 @@ To set the DNS server for all Docker containers, use |
| 193 | 193 |
To set the DNS search domain for all Docker containers, use |
| 194 | 194 |
`docker -d --dns-search example.com`. |
| 195 | 195 |
|
| 196 |
+### Insecure registries |
|
| 197 |
+ |
|
| 198 |
+Docker considers a private registry either secure or insecure. |
|
| 199 |
+In the rest of this section, *registry* is used for *private registry*, and `myregistry:5000` |
|
| 200 |
+is a placeholder example for a private registry. |
|
| 201 |
+ |
|
| 202 |
+A secure registry uses TLS and a copy of its CA certificate is placed on the Docker host at |
|
| 203 |
+`/etc/docker/certs.d/myregistry:5000/ca.crt`. |
|
| 204 |
+An insecure registry is either not using TLS (i.e., listening on plain text HTTP), or is using |
|
| 205 |
+TLS with a CA certificate not known by the Docker daemon. The latter can happen when the |
|
| 206 |
+certificate was not found under `/etc/docker/certs.d/myregistry:5000/`, or if the certificate |
|
| 207 |
+verification failed (i.e., wrong CA). |
|
| 208 |
+ |
|
| 209 |
+By default, Docker assumes all, but local (see local registries below), registries are secure. |
|
| 210 |
+Communicating with an insecure registry is not possible if Docker assumes that registry is secure. |
|
| 211 |
+In order to communicate with an insecure registry, the Docker daemon requires `--insecure-registry` |
|
| 212 |
+in one of the following two forms: |
|
| 213 |
+ |
|
| 214 |
+* `--insecure-registry myregistry:5000` tells the Docker daemon that myregistry:5000 should be considered insecure. |
|
| 215 |
+* `--insecure-registry 10.1.0.0/16` tells the Docker daemon that all registries whose domain resolve to an IP address is part |
|
| 216 |
+of the subnet described by the CIDR syntax, should be considered insecure. |
|
| 217 |
+ |
|
| 218 |
+The flag can be used multiple times to allow multiple registries to be marked as insecure. |
|
| 219 |
+ |
|
| 220 |
+If an insecure registry is not marked as insecure, `docker pull`, `docker push`, and `docker search` |
|
| 221 |
+will result in an error message prompting the user to either secure or pass the `--insecure-registry` |
|
| 222 |
+flag to the Docker daemon as described above. |
|
| 223 |
+ |
|
| 224 |
+Local registries, whose IP address falls in the 127.0.0.0/8 range, are automatically marked as insecure |
|
| 225 |
+as of Docker 1.3.2. It is not recommended to rely on this, as it may change in the future. |
|
| 226 |
+ |
|
| 227 |
+ |
|
| 196 | 228 |
### Miscellaneous options |
| 197 | 229 |
|
| 198 | 230 |
IP masquerading uses address translation to allow containers without a public IP to talk |
| 199 | 231 |
to other machines on the Internet. This may interfere with some network topologies and |
| 200 | 232 |
can be disabled with --ip-masq=false. |
| 201 | 233 |
|
| 202 |
- |
|
| 203 |
-By default, Docker will assume all registries are secured via TLS with certificate verification |
|
| 204 |
-enabled. Prior versions of Docker used an auto fallback if a registry did not support TLS |
|
| 205 |
-(or if the TLS connection failed). This introduced the opportunity for Man In The Middle (MITM) |
|
| 206 |
-attacks, so as of Docker 1.3.1, the user must now specify the `--insecure-registry` daemon flag |
|
| 207 |
-for each insecure registry. An insecure registry is either not using TLS (i.e. plain text HTTP), |
|
| 208 |
-or is using TLS with a CA certificate not known by the Docker daemon (i.e. certification |
|
| 209 |
-verification disabled). For example, if there is a registry listening for HTTP at 127.0.0.1:5000, |
|
| 210 |
-as of Docker 1.3.1 you are required to specify `--insecure-registry 127.0.0.1:5000` when starting |
|
| 211 |
-the Docker daemon. |
|
| 212 |
- |
|
| 213 |
- |
|
| 214 | 234 |
Docker supports softlinks for the Docker data directory |
| 215 | 235 |
(`/var/lib/docker`) and for `/var/lib/docker/tmp`. The `DOCKER_TMPDIR` and the data directory can be set like this: |
| 216 | 236 |
|
| ... | ... |
@@ -12,6 +12,9 @@ import ( |
| 12 | 12 |
log "github.com/Sirupsen/logrus" |
| 13 | 13 |
) |
| 14 | 14 |
|
| 15 |
+// for mocking in unit tests |
|
| 16 |
+var lookupIP = net.LookupIP |
|
| 17 |
+ |
|
| 15 | 18 |
// scans string for api version in the URL path. returns the trimmed hostname, if version found, string and API version. |
| 16 | 19 |
func scanForAPIVersion(hostname string) (string, APIVersion) {
|
| 17 | 20 |
var ( |
| ... | ... |
@@ -79,7 +82,10 @@ func newEndpoint(hostname string, insecureRegistries []string) (*Endpoint, error |
| 79 | 79 |
if err != nil {
|
| 80 | 80 |
return nil, err |
| 81 | 81 |
} |
| 82 |
- endpoint.secure = isSecure(endpoint.URL.Host, insecureRegistries) |
|
| 82 |
+ endpoint.secure, err = isSecure(endpoint.URL.Host, insecureRegistries) |
|
| 83 |
+ if err != nil {
|
|
| 84 |
+ return nil, err |
|
| 85 |
+ } |
|
| 83 | 86 |
return &endpoint, nil |
| 84 | 87 |
} |
| 85 | 88 |
|
| ... | ... |
@@ -152,30 +158,56 @@ func (e Endpoint) Ping() (RegistryInfo, error) {
|
| 152 | 152 |
|
| 153 | 153 |
// isSecure returns false if the provided hostname is part of the list of insecure registries. |
| 154 | 154 |
// Insecure registries accept HTTP and/or accept HTTPS with certificates from unknown CAs. |
| 155 |
-func isSecure(hostname string, insecureRegistries []string) bool {
|
|
| 155 |
+// |
|
| 156 |
+// The list of insecure registries can contain an element with CIDR notation to specify a whole subnet. |
|
| 157 |
+// If the subnet contains one of the IPs of the registry specified by hostname, the latter is considered |
|
| 158 |
+// insecure. |
|
| 159 |
+// |
|
| 160 |
+// hostname should be a URL.Host (`host:port` or `host`) |
|
| 161 |
+func isSecure(hostname string, insecureRegistries []string) (bool, error) {
|
|
| 156 | 162 |
if hostname == IndexServerURL.Host {
|
| 157 |
- return true |
|
| 163 |
+ return true, nil |
|
| 158 | 164 |
} |
| 159 | 165 |
|
| 160 | 166 |
host, _, err := net.SplitHostPort(hostname) |
| 161 |
- |
|
| 162 | 167 |
if err != nil {
|
| 168 |
+ // assume hostname is of the form `host` without the port and go on. |
|
| 163 | 169 |
host = hostname |
| 164 | 170 |
} |
| 165 |
- |
|
| 166 |
- if host == "127.0.0.1" || host == "localhost" {
|
|
| 167 |
- return false |
|
| 168 |
- } |
|
| 169 |
- |
|
| 170 |
- if len(insecureRegistries) == 0 {
|
|
| 171 |
- return true |
|
| 172 |
- } |
|
| 173 |
- |
|
| 174 |
- for _, h := range insecureRegistries {
|
|
| 175 |
- if hostname == h {
|
|
| 176 |
- return false |
|
| 171 |
+ addrs, err := lookupIP(host) |
|
| 172 |
+ if err != nil {
|
|
| 173 |
+ ip := net.ParseIP(host) |
|
| 174 |
+ if ip == nil {
|
|
| 175 |
+ // if resolving `host` fails, error out, since host is to be net.Dial-ed anyway |
|
| 176 |
+ return true, fmt.Errorf("issecure: could not resolve %q: %v", host, err)
|
|
| 177 |
+ } |
|
| 178 |
+ addrs = []net.IP{ip}
|
|
| 179 |
+ } |
|
| 180 |
+ if len(addrs) == 0 {
|
|
| 181 |
+ return true, fmt.Errorf("issecure: could not resolve %q", host)
|
|
| 182 |
+ } |
|
| 183 |
+ |
|
| 184 |
+ for _, addr := range addrs {
|
|
| 185 |
+ for _, r := range insecureRegistries {
|
|
| 186 |
+ // hostname matches insecure registry |
|
| 187 |
+ if hostname == r {
|
|
| 188 |
+ return false, nil |
|
| 189 |
+ } |
|
| 190 |
+ |
|
| 191 |
+ // now assume a CIDR was passed to --insecure-registry |
|
| 192 |
+ _, ipnet, err := net.ParseCIDR(r) |
|
| 193 |
+ if err != nil {
|
|
| 194 |
+ // if could not parse it as a CIDR, even after removing |
|
| 195 |
+ // assume it's not a CIDR and go on with the next candidate |
|
| 196 |
+ continue |
|
| 197 |
+ } |
|
| 198 |
+ |
|
| 199 |
+ // check if the addr falls in the subnet |
|
| 200 |
+ if ipnet.Contains(addr) {
|
|
| 201 |
+ return false, nil |
|
| 202 |
+ } |
|
| 177 | 203 |
} |
| 178 | 204 |
} |
| 179 | 205 |
|
| 180 |
- return true |
|
| 206 |
+ return true, nil |
|
| 181 | 207 |
} |
| ... | ... |
@@ -2,9 +2,11 @@ package registry |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 | 4 |
"encoding/json" |
| 5 |
+ "errors" |
|
| 5 | 6 |
"fmt" |
| 6 | 7 |
"io" |
| 7 | 8 |
"io/ioutil" |
| 9 |
+ "net" |
|
| 8 | 10 |
"net/http" |
| 9 | 11 |
"net/http/httptest" |
| 10 | 12 |
"net/url" |
| ... | ... |
@@ -80,6 +82,11 @@ var ( |
| 80 | 80 |
"latest": "42d718c941f5c532ac049bf0b0ab53f0062f09a03afd4aa4a02c098e46032b9d", |
| 81 | 81 |
}, |
| 82 | 82 |
} |
| 83 |
+ mockHosts = map[string][]net.IP{
|
|
| 84 |
+ "": {net.ParseIP("0.0.0.0")},
|
|
| 85 |
+ "localhost": {net.ParseIP("127.0.0.1"), net.ParseIP("::1")},
|
|
| 86 |
+ "example.com": {net.ParseIP("42.42.42.42")},
|
|
| 87 |
+ } |
|
| 83 | 88 |
) |
| 84 | 89 |
|
| 85 | 90 |
func init() {
|
| ... | ... |
@@ -106,6 +113,25 @@ func init() {
|
| 106 | 106 |
panic(err) |
| 107 | 107 |
} |
| 108 | 108 |
insecureRegistries = []string{URL.Host}
|
| 109 |
+ |
|
| 110 |
+ // override net.LookupIP |
|
| 111 |
+ lookupIP = func(host string) ([]net.IP, error) {
|
|
| 112 |
+ if host == "127.0.0.1" {
|
|
| 113 |
+ // I believe in future Go versions this will fail, so let's fix it later |
|
| 114 |
+ return net.LookupIP(host) |
|
| 115 |
+ } |
|
| 116 |
+ for h, addrs := range mockHosts {
|
|
| 117 |
+ if host == h {
|
|
| 118 |
+ return addrs, nil |
|
| 119 |
+ } |
|
| 120 |
+ for _, addr := range addrs {
|
|
| 121 |
+ if addr.String() == host {
|
|
| 122 |
+ return []net.IP{addr}, nil
|
|
| 123 |
+ } |
|
| 124 |
+ } |
|
| 125 |
+ } |
|
| 126 |
+ return nil, errors.New("lookup: no such host")
|
|
| 127 |
+ } |
|
| 109 | 128 |
} |
| 110 | 129 |
|
| 111 | 130 |
func handlerAccessLog(handler http.Handler) http.Handler {
|
| ... | ... |
@@ -333,19 +333,26 @@ func TestIsSecure(t *testing.T) {
|
| 333 | 333 |
{"localhost:5000", []string{"localhost:5000"}, false},
|
| 334 | 334 |
{"localhost", []string{"example.com"}, false},
|
| 335 | 335 |
{"127.0.0.1:5000", []string{"127.0.0.1:5000"}, false},
|
| 336 |
- {"localhost", []string{}, false},
|
|
| 337 |
- {"localhost:5000", []string{}, false},
|
|
| 338 |
- {"127.0.0.1", []string{}, false},
|
|
| 336 |
+ {"localhost", nil, false},
|
|
| 337 |
+ {"localhost:5000", nil, false},
|
|
| 338 |
+ {"127.0.0.1", nil, false},
|
|
| 339 | 339 |
{"localhost", []string{"example.com"}, false},
|
| 340 | 340 |
{"127.0.0.1", []string{"example.com"}, false},
|
| 341 |
- {"example.com", []string{}, true},
|
|
| 341 |
+ {"example.com", nil, true},
|
|
| 342 | 342 |
{"example.com", []string{"example.com"}, false},
|
| 343 | 343 |
{"127.0.0.1", []string{"example.com"}, false},
|
| 344 | 344 |
{"127.0.0.1:5000", []string{"example.com"}, false},
|
| 345 |
+ {"example.com:5000", []string{"42.42.0.0/16"}, false},
|
|
| 346 |
+ {"example.com", []string{"42.42.0.0/16"}, false},
|
|
| 347 |
+ {"example.com:5000", []string{"42.42.42.42/8"}, false},
|
|
| 348 |
+ {"127.0.0.1:5000", []string{"127.0.0.0/8"}, false},
|
|
| 349 |
+ {"42.42.42.42:5000", []string{"42.1.1.1/8"}, false},
|
|
| 345 | 350 |
} |
| 346 | 351 |
for _, tt := range tests {
|
| 347 |
- if sec := isSecure(tt.addr, tt.insecureRegistries); sec != tt.expected {
|
|
| 348 |
- t.Errorf("isSecure failed for %q %v, expected %v got %v", tt.addr, tt.insecureRegistries, tt.expected, sec)
|
|
| 352 |
+ // TODO: remove this once we remove localhost insecure by default |
|
| 353 |
+ insecureRegistries := append(tt.insecureRegistries, "127.0.0.0/8") |
|
| 354 |
+ if sec, err := isSecure(tt.addr, insecureRegistries); err != nil || sec != tt.expected {
|
|
| 355 |
+ t.Fatalf("isSecure failed for %q %v, expected %v got %v. Error: %v", tt.addr, insecureRegistries, tt.expected, sec, err)
|
|
| 349 | 356 |
} |
| 350 | 357 |
} |
| 351 | 358 |
} |