Merge pull request #9100 from tiborvass/insecure-registry-cidr

Add the possibility of specifying a subnet for --insecure-registry
This commit is contained in:
Tibor Vass 2014-11-14 13:45:48 -08:00
Родитель 25643f8932 5937663a08
Коммит 36503981f0
5 изменённых файлов: 127 добавлений и 34 удалений

Просмотреть файл

@ -56,7 +56,7 @@ func (config *Config) InstallFlags() {
flag.StringVar(&config.BridgeIP, []string{"#bip", "-bip"}, "", "Use this CIDR notation address for the network bridge's IP, not compatible with -b") flag.StringVar(&config.BridgeIP, []string{"#bip", "-bip"}, "", "Use this CIDR notation address for the network bridge's IP, not compatible with -b")
flag.StringVar(&config.BridgeIface, []string{"b", "-bridge"}, "", "Attach containers to a pre-existing network bridge\nuse 'none' to disable container networking") flag.StringVar(&config.BridgeIface, []string{"b", "-bridge"}, "", "Attach containers to a pre-existing network bridge\nuse 'none' to disable container networking")
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)") 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)")
opts.ListVar(&config.InsecureRegistries, []string{"-insecure-registry"}, "Enable insecure communication with specified registries (no certificate verification for HTTPS and enable HTTP fallback)") 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)")
flag.BoolVar(&config.InterContainerCommunication, []string{"#icc", "-icc"}, true, "Enable inter-container communication") flag.BoolVar(&config.InterContainerCommunication, []string{"#icc", "-icc"}, true, "Enable inter-container communication")
flag.StringVar(&config.GraphDriver, []string{"s", "-storage-driver"}, "", "Force the Docker runtime to use a specific storage driver") flag.StringVar(&config.GraphDriver, []string{"s", "-storage-driver"}, "", "Force the Docker runtime to use a specific storage driver")
flag.StringVar(&config.ExecDriver, []string{"e", "-exec-driver"}, "native", "Force the Docker runtime to use a specific exec driver") 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() {
opts.IPListVar(&config.Dns, []string{"#dns", "-dns"}, "Force Docker to use specific DNS servers") opts.IPListVar(&config.Dns, []string{"#dns", "-dns"}, "Force Docker to use specific DNS servers")
opts.DnsSearchListVar(&config.DnsSearch, []string{"-dns-search"}, "Force Docker to use specific DNS search domains") opts.DnsSearchListVar(&config.DnsSearch, []string{"-dns-search"}, "Force Docker to use specific DNS search domains")
opts.MirrorListVar(&config.Mirrors, []string{"-registry-mirror"}, "Specify a preferred Docker registry mirror") opts.MirrorListVar(&config.Mirrors, []string{"-registry-mirror"}, "Specify a preferred Docker registry mirror")
// Localhost is by default considered as an insecure registry
// This is a stop-gap for people who are running a private registry on localhost (especially on Boot2docker).
//
// TODO: should we deprecate this once it is easier for people to set up a TLS registry or change
// daemon flags on boot2docker?
// If so, do not forget to check the TODO in TestIsSecure
config.InsecureRegistries = append(config.InsecureRegistries, "127.0.0.0/8")
} }
func getDefaultNetworkMtu() int { func getDefaultNetworkMtu() int {

Просмотреть файл

@ -70,7 +70,7 @@ expect an integer, and they can only be specified once.
-g, --graph="/var/lib/docker" Path to use as the root of the Docker runtime -g, --graph="/var/lib/docker" Path to use as the root of the Docker runtime
-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. -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.
--icc=true Enable inter-container communication --icc=true Enable inter-container communication
--insecure-registry=[] Enable insecure communication with specified registries (no certificate verification for HTTPS and enable HTTP fallback) --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)
--ip=0.0.0.0 Default IP address to use when binding container ports --ip=0.0.0.0 Default IP address to use when binding container ports
--ip-forward=true Enable net.ipv4.ip_forward --ip-forward=true Enable net.ipv4.ip_forward
--ip-masq=true Enable IP masquerading for bridge's IP range --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
To set the DNS search domain for all Docker containers, use To set the DNS search domain for all Docker containers, use
`docker -d --dns-search example.com`. `docker -d --dns-search example.com`.
### Insecure registries
Docker considers a private registry either secure or insecure.
In the rest of this section, *registry* is used for *private registry*, and `myregistry:5000`
is a placeholder example for a private registry.
A secure registry uses TLS and a copy of its CA certificate is placed on the Docker host at
`/etc/docker/certs.d/myregistry:5000/ca.crt`.
An insecure registry is either not using TLS (i.e., listening on plain text HTTP), or is using
TLS with a CA certificate not known by the Docker daemon. The latter can happen when the
certificate was not found under `/etc/docker/certs.d/myregistry:5000/`, or if the certificate
verification failed (i.e., wrong CA).
By default, Docker assumes all, but local (see local registries below), registries are secure.
Communicating with an insecure registry is not possible if Docker assumes that registry is secure.
In order to communicate with an insecure registry, the Docker daemon requires `--insecure-registry`
in one of the following two forms:
* `--insecure-registry myregistry:5000` tells the Docker daemon that myregistry:5000 should be considered insecure.
* `--insecure-registry 10.1.0.0/16` tells the Docker daemon that all registries whose domain resolve to an IP address is part
of the subnet described by the CIDR syntax, should be considered insecure.
The flag can be used multiple times to allow multiple registries to be marked as insecure.
If an insecure registry is not marked as insecure, `docker pull`, `docker push`, and `docker search`
will result in an error message prompting the user to either secure or pass the `--insecure-registry`
flag to the Docker daemon as described above.
Local registries, whose IP address falls in the 127.0.0.0/8 range, are automatically marked as insecure
as of Docker 1.3.2. It is not recommended to rely on this, as it may change in the future.
### Miscellaneous options ### Miscellaneous options
IP masquerading uses address translation to allow containers without a public IP to talk IP masquerading uses address translation to allow containers without a public IP to talk
to other machines on the Internet. This may interfere with some network topologies and to other machines on the Internet. This may interfere with some network topologies and
can be disabled with --ip-masq=false. can be disabled with --ip-masq=false.
By default, Docker will assume all registries are secured via TLS with certificate verification
enabled. Prior versions of Docker used an auto fallback if a registry did not support TLS
(or if the TLS connection failed). This introduced the opportunity for Man In The Middle (MITM)
attacks, so as of Docker 1.3.1, the user must now specify the `--insecure-registry` daemon flag
for each insecure registry. An insecure registry is either not using TLS (i.e. plain text HTTP),
or is using TLS with a CA certificate not known by the Docker daemon (i.e. certification
verification disabled). For example, if there is a registry listening for HTTP at 127.0.0.1:5000,
as of Docker 1.3.1 you are required to specify `--insecure-registry 127.0.0.1:5000` when starting
the Docker daemon.
Docker supports softlinks for the Docker data directory Docker supports softlinks for the Docker data directory
(`/var/lib/docker`) and for `/var/lib/docker/tmp`. The `DOCKER_TMPDIR` and the data directory can be set like this: (`/var/lib/docker`) and for `/var/lib/docker/tmp`. The `DOCKER_TMPDIR` and the data directory can be set like this:

Просмотреть файл

@ -12,6 +12,9 @@ import (
log "github.com/Sirupsen/logrus" log "github.com/Sirupsen/logrus"
) )
// for mocking in unit tests
var lookupIP = net.LookupIP
// scans string for api version in the URL path. returns the trimmed hostname, if version found, string and API version. // scans string for api version in the URL path. returns the trimmed hostname, if version found, string and API version.
func scanForAPIVersion(hostname string) (string, APIVersion) { func scanForAPIVersion(hostname string) (string, APIVersion) {
var ( var (
@ -79,7 +82,10 @@ func newEndpoint(hostname string, insecureRegistries []string) (*Endpoint, error
if err != nil { if err != nil {
return nil, err return nil, err
} }
endpoint.secure = isSecure(endpoint.URL.Host, insecureRegistries) endpoint.secure, err = isSecure(endpoint.URL.Host, insecureRegistries)
if err != nil {
return nil, err
}
return &endpoint, nil return &endpoint, nil
} }
@ -152,30 +158,56 @@ func (e Endpoint) Ping() (RegistryInfo, error) {
// isSecure returns false if the provided hostname is part of the list of insecure registries. // isSecure returns false if the provided hostname is part of the list of insecure registries.
// Insecure registries accept HTTP and/or accept HTTPS with certificates from unknown CAs. // Insecure registries accept HTTP and/or accept HTTPS with certificates from unknown CAs.
func isSecure(hostname string, insecureRegistries []string) bool { //
// The list of insecure registries can contain an element with CIDR notation to specify a whole subnet.
// If the subnet contains one of the IPs of the registry specified by hostname, the latter is considered
// insecure.
//
// hostname should be a URL.Host (`host:port` or `host`)
func isSecure(hostname string, insecureRegistries []string) (bool, error) {
if hostname == IndexServerURL.Host { if hostname == IndexServerURL.Host {
return true return true, nil
} }
host, _, err := net.SplitHostPort(hostname) host, _, err := net.SplitHostPort(hostname)
if err != nil { if err != nil {
// assume hostname is of the form `host` without the port and go on.
host = hostname host = hostname
} }
addrs, err := lookupIP(host)
if host == "127.0.0.1" || host == "localhost" { if err != nil {
return false ip := net.ParseIP(host)
if ip == nil {
// if resolving `host` fails, error out, since host is to be net.Dial-ed anyway
return true, fmt.Errorf("issecure: could not resolve %q: %v", host, err)
}
addrs = []net.IP{ip}
}
if len(addrs) == 0 {
return true, fmt.Errorf("issecure: could not resolve %q", host)
} }
if len(insecureRegistries) == 0 { for _, addr := range addrs {
return true for _, r := range insecureRegistries {
// hostname matches insecure registry
if hostname == r {
return false, nil
} }
for _, h := range insecureRegistries { // now assume a CIDR was passed to --insecure-registry
if hostname == h { _, ipnet, err := net.ParseCIDR(r)
return false if err != nil {
// if could not parse it as a CIDR, even after removing
// assume it's not a CIDR and go on with the next candidate
continue
}
// check if the addr falls in the subnet
if ipnet.Contains(addr) {
return false, nil
}
} }
} }
return true return true, nil
} }

Просмотреть файл

@ -2,9 +2,11 @@ package registry
import ( import (
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"net"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"net/url" "net/url"
@ -80,6 +82,11 @@ var (
"latest": "42d718c941f5c532ac049bf0b0ab53f0062f09a03afd4aa4a02c098e46032b9d", "latest": "42d718c941f5c532ac049bf0b0ab53f0062f09a03afd4aa4a02c098e46032b9d",
}, },
} }
mockHosts = map[string][]net.IP{
"": {net.ParseIP("0.0.0.0")},
"localhost": {net.ParseIP("127.0.0.1"), net.ParseIP("::1")},
"example.com": {net.ParseIP("42.42.42.42")},
}
) )
func init() { func init() {
@ -106,6 +113,25 @@ func init() {
panic(err) panic(err)
} }
insecureRegistries = []string{URL.Host} insecureRegistries = []string{URL.Host}
// override net.LookupIP
lookupIP = func(host string) ([]net.IP, error) {
if host == "127.0.0.1" {
// I believe in future Go versions this will fail, so let's fix it later
return net.LookupIP(host)
}
for h, addrs := range mockHosts {
if host == h {
return addrs, nil
}
for _, addr := range addrs {
if addr.String() == host {
return []net.IP{addr}, nil
}
}
}
return nil, errors.New("lookup: no such host")
}
} }
func handlerAccessLog(handler http.Handler) http.Handler { func handlerAccessLog(handler http.Handler) http.Handler {

Просмотреть файл

@ -333,19 +333,26 @@ func TestIsSecure(t *testing.T) {
{"localhost:5000", []string{"localhost:5000"}, false}, {"localhost:5000", []string{"localhost:5000"}, false},
{"localhost", []string{"example.com"}, false}, {"localhost", []string{"example.com"}, false},
{"127.0.0.1:5000", []string{"127.0.0.1:5000"}, false}, {"127.0.0.1:5000", []string{"127.0.0.1:5000"}, false},
{"localhost", []string{}, false}, {"localhost", nil, false},
{"localhost:5000", []string{}, false}, {"localhost:5000", nil, false},
{"127.0.0.1", []string{}, false}, {"127.0.0.1", nil, false},
{"localhost", []string{"example.com"}, false}, {"localhost", []string{"example.com"}, false},
{"127.0.0.1", []string{"example.com"}, false}, {"127.0.0.1", []string{"example.com"}, false},
{"example.com", []string{}, true}, {"example.com", nil, true},
{"example.com", []string{"example.com"}, false}, {"example.com", []string{"example.com"}, false},
{"127.0.0.1", []string{"example.com"}, false}, {"127.0.0.1", []string{"example.com"}, false},
{"127.0.0.1:5000", []string{"example.com"}, false}, {"127.0.0.1:5000", []string{"example.com"}, false},
{"example.com:5000", []string{"42.42.0.0/16"}, false},
{"example.com", []string{"42.42.0.0/16"}, false},
{"example.com:5000", []string{"42.42.42.42/8"}, false},
{"127.0.0.1:5000", []string{"127.0.0.0/8"}, false},
{"42.42.42.42:5000", []string{"42.1.1.1/8"}, false},
} }
for _, tt := range tests { for _, tt := range tests {
if sec := isSecure(tt.addr, tt.insecureRegistries); sec != tt.expected { // TODO: remove this once we remove localhost insecure by default
t.Errorf("isSecure failed for %q %v, expected %v got %v", tt.addr, tt.insecureRegistries, tt.expected, sec) insecureRegistries := append(tt.insecureRegistries, "127.0.0.0/8")
if sec, err := isSecure(tt.addr, insecureRegistries); err != nil || sec != tt.expected {
t.Fatalf("isSecure failed for %q %v, expected %v got %v. Error: %v", tt.addr, insecureRegistries, tt.expected, sec, err)
} }
} }
} }