1
0
Fork 0
mirror of https://github.com/moby/moby.git synced 2022-11-09 12:21:53 -05:00

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
This commit is contained in:
Tibor Vass 2014-10-10 23:22:12 -04:00
parent afade4236d
commit 6a1ff022b0
9 changed files with 116 additions and 115 deletions

View file

@ -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"}, "Make these registries use http") opts.ListVar(&config.InsecureRegistries, []string{"-insecure-registry"}, "Enable insecure communication with specified registries (no certificate verification for HTTPS and enable HTTP fallback)")
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")

View file

@ -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=[] Make these registries use http --insecure-registry=[] Enable insecure communication with specified registries (no certificate verification for HTTPS and enable HTTP fallback)
--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
@ -195,16 +195,16 @@ to other machines on the Internet. This may interfere with some network topologi
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.
By default docker will assume all registries are securied via TLS. Prior versions
of docker used an auto fallback if a registry did not support TLS. This introduces
the opportunity for MITM attacks so in Docker 1.2 the user must specify `--insecure-registries`
when starting the Docker daemon to state which registries are not using TLS and to communicate
with these registries via plain text. If you are running a local registry over plain text
on `127.0.0.1:5000` you will be required to specify `--insecure-registries 127.0.0.1:500`
when starting the docker daemon to be able to push and pull images to that registry.
No automatic fallback will happen after Docker 1.2 to detect if a registry is using
HTTP or HTTPS.
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:

View file

@ -53,7 +53,7 @@ func mkTestTagStore(root string, t *testing.T) *TagStore {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
store, err := NewTagStore(path.Join(root, "tags"), graph, nil) store, err := NewTagStore(path.Join(root, "tags"), graph, nil, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View file

@ -2,7 +2,6 @@ package registry
import ( import (
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
@ -34,27 +33,40 @@ func scanForAPIVersion(hostname string) (string, APIVersion) {
return hostname, DefaultAPIVersion return hostname, DefaultAPIVersion
} }
func NewEndpoint(hostname string) (*Endpoint, error) { func NewEndpoint(hostname string, secure bool) (*Endpoint, error) {
endpoint, err := newEndpoint(hostname) endpoint, err := newEndpoint(hostname, secure)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Try HTTPS ping to registry
endpoint.URL.Scheme = "https" endpoint.URL.Scheme = "https"
if _, err := endpoint.Ping(); err != nil { if _, err := endpoint.Ping(); err != nil {
log.Debugf("Registry %s does not work (%s), falling back to http", endpoint, err)
// TODO: Check if http fallback is enabled //TODO: triggering highland build can be done there without "failing"
endpoint.URL.Scheme = "http"
if _, err = endpoint.Ping(); err != nil { if secure {
return nil, errors.New("Invalid Registry endpoint: " + err.Error()) // If registry is secure and HTTPS failed, show user the error and tell them about `--insecure-registry`
// in case that's what they need. DO NOT accept unknown CA certificates, and DO NOT fallback to HTTP.
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)
} }
// If registry is insecure and HTTPS failed, fallback to HTTP.
log.Debugf("Error from registry %q marked as insecure: %v. Insecurely falling back to HTTP", endpoint, err)
endpoint.URL.Scheme = "http"
_, err2 := endpoint.Ping()
if err2 == nil {
return endpoint, nil
}
return nil, fmt.Errorf("Invalid registry endpoint %q. HTTPS attempt: %v. HTTP attempt: %v", endpoint, err, err2)
} }
return endpoint, nil return endpoint, nil
} }
func newEndpoint(hostname string) (*Endpoint, error) { func newEndpoint(hostname string, secure bool) (*Endpoint, error) {
var ( var (
endpoint Endpoint endpoint = Endpoint{secure: secure}
trimmedHostname string trimmedHostname string
err error err error
) )
@ -72,6 +84,7 @@ func newEndpoint(hostname string) (*Endpoint, error) {
type Endpoint struct { type Endpoint struct {
URL *url.URL URL *url.URL
Version APIVersion Version APIVersion
secure bool
} }
// Get the formated URL for the root of this registry Endpoint // Get the formated URL for the root of this registry Endpoint
@ -95,7 +108,7 @@ func (e Endpoint) Ping() (RegistryInfo, error) {
return RegistryInfo{Standalone: false}, err return RegistryInfo{Standalone: false}, err
} }
resp, _, err := doRequest(req, nil, ConnectTimeout) resp, _, err := doRequest(req, nil, ConnectTimeout, e.secure)
if err != nil { if err != nil {
return RegistryInfo{Standalone: false}, err return RegistryInfo{Standalone: false}, err
} }
@ -134,3 +147,19 @@ func (e Endpoint) Ping() (RegistryInfo, error) {
log.Debugf("RegistryInfo.Standalone: %t", info.Standalone) log.Debugf("RegistryInfo.Standalone: %t", info.Standalone)
return info, nil return info, nil
} }
// 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.
func IsSecure(hostname string, insecureRegistries []string) bool {
if hostname == IndexServerAddress() {
return true
}
for _, h := range insecureRegistries {
if hostname == h {
return false
}
}
return true
}

View file

@ -12,7 +12,7 @@ func TestEndpointParse(t *testing.T) {
{"0.0.0.0:5000", "https://0.0.0.0:5000/v1/"}, {"0.0.0.0:5000", "https://0.0.0.0:5000/v1/"},
} }
for _, td := range testData { for _, td := range testData {
e, err := newEndpoint(td.str) e, err := newEndpoint(td.str, true)
if err != nil { if err != nil {
t.Errorf("%q: %s", td.str, err) t.Errorf("%q: %s", td.str, err)
} }

View file

@ -14,6 +14,7 @@ import (
"strings" "strings"
"time" "time"
log "github.com/Sirupsen/logrus"
"github.com/docker/docker/utils" "github.com/docker/docker/utils"
) )
@ -35,7 +36,7 @@ const (
ConnectTimeout ConnectTimeout
) )
func newClient(jar http.CookieJar, roots *x509.CertPool, cert *tls.Certificate, timeout TimeoutType) *http.Client { func newClient(jar http.CookieJar, roots *x509.CertPool, cert *tls.Certificate, timeout TimeoutType, secure bool) *http.Client {
tlsConfig := tls.Config{ tlsConfig := tls.Config{
RootCAs: roots, RootCAs: roots,
// Avoid fallback to SSL protocols < TLS1.0 // Avoid fallback to SSL protocols < TLS1.0
@ -46,6 +47,10 @@ func newClient(jar http.CookieJar, roots *x509.CertPool, cert *tls.Certificate,
tlsConfig.Certificates = append(tlsConfig.Certificates, *cert) tlsConfig.Certificates = append(tlsConfig.Certificates, *cert)
} }
if !secure {
tlsConfig.InsecureSkipVerify = true
}
httpTransport := &http.Transport{ httpTransport := &http.Transport{
DisableKeepAlives: true, DisableKeepAlives: true,
Proxy: http.ProxyFromEnvironment, Proxy: http.ProxyFromEnvironment,
@ -86,7 +91,13 @@ func newClient(jar http.CookieJar, roots *x509.CertPool, cert *tls.Certificate,
} }
} }
func doRequest(req *http.Request, jar http.CookieJar, timeout TimeoutType) (*http.Response, *http.Client, error) { func doRequest(req *http.Request, jar http.CookieJar, timeout TimeoutType, secure bool) (*http.Response, *http.Client, error) {
var (
pool *x509.CertPool
certs []*tls.Certificate
)
if secure && req.URL.Scheme == "https" {
hasFile := func(files []os.FileInfo, name string) bool { hasFile := func(files []os.FileInfo, name string) bool {
for _, f := range files { for _, f := range files {
if f.Name() == name { if f.Name() == name {
@ -97,21 +108,18 @@ func doRequest(req *http.Request, jar http.CookieJar, timeout TimeoutType) (*htt
} }
hostDir := path.Join("/etc/docker/certs.d", req.URL.Host) hostDir := path.Join("/etc/docker/certs.d", req.URL.Host)
log.Debugf("hostDir: %s", hostDir)
fs, err := ioutil.ReadDir(hostDir) fs, err := ioutil.ReadDir(hostDir)
if err != nil && !os.IsNotExist(err) { if err != nil && !os.IsNotExist(err) {
return nil, nil, err return nil, nil, err
} }
var (
pool *x509.CertPool
certs []*tls.Certificate
)
for _, f := range fs { for _, f := range fs {
if strings.HasSuffix(f.Name(), ".crt") { if strings.HasSuffix(f.Name(), ".crt") {
if pool == nil { if pool == nil {
pool = x509.NewCertPool() pool = x509.NewCertPool()
} }
log.Debugf("crt: %s", hostDir+"/"+f.Name())
data, err := ioutil.ReadFile(path.Join(hostDir, f.Name())) data, err := ioutil.ReadFile(path.Join(hostDir, f.Name()))
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
@ -121,6 +129,7 @@ func doRequest(req *http.Request, jar http.CookieJar, timeout TimeoutType) (*htt
if strings.HasSuffix(f.Name(), ".cert") { if strings.HasSuffix(f.Name(), ".cert") {
certName := f.Name() certName := f.Name()
keyName := certName[:len(certName)-5] + ".key" keyName := certName[:len(certName)-5] + ".key"
log.Debugf("cert: %s", hostDir+"/"+f.Name())
if !hasFile(fs, keyName) { if !hasFile(fs, keyName) {
return nil, nil, fmt.Errorf("Missing key %s for certificate %s", keyName, certName) return nil, nil, fmt.Errorf("Missing key %s for certificate %s", keyName, certName)
} }
@ -133,22 +142,25 @@ func doRequest(req *http.Request, jar http.CookieJar, timeout TimeoutType) (*htt
if strings.HasSuffix(f.Name(), ".key") { if strings.HasSuffix(f.Name(), ".key") {
keyName := f.Name() keyName := f.Name()
certName := keyName[:len(keyName)-4] + ".cert" certName := keyName[:len(keyName)-4] + ".cert"
log.Debugf("key: %s", hostDir+"/"+f.Name())
if !hasFile(fs, certName) { if !hasFile(fs, certName) {
return nil, nil, fmt.Errorf("Missing certificate %s for key %s", certName, keyName) return nil, nil, fmt.Errorf("Missing certificate %s for key %s", certName, keyName)
} }
} }
} }
}
if len(certs) == 0 { if len(certs) == 0 {
client := newClient(jar, pool, nil, timeout) client := newClient(jar, pool, nil, timeout, secure)
res, err := client.Do(req) res, err := client.Do(req)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
return res, client, nil return res, client, nil
} }
for i, cert := range certs { for i, cert := range certs {
client := newClient(jar, pool, cert, timeout) client := newClient(jar, pool, cert, timeout, secure)
res, err := client.Do(req) res, err := client.Do(req)
// If this is the last cert, otherwise, continue to next cert if 403 or 5xx // If this is the last cert, otherwise, continue to next cert if 403 or 5xx
if i == len(certs)-1 || err == nil && if i == len(certs)-1 || err == nil &&
@ -213,49 +225,6 @@ func ResolveRepositoryName(reposName string) (string, string, error) {
return hostname, reposName, nil return hostname, reposName, nil
} }
// this method expands the registry name as used in the prefix of a repo
// to a full url. if it already is a url, there will be no change.
func ExpandAndVerifyRegistryUrl(hostname string, secure bool) (string, error) {
if hostname == IndexServerAddress() {
return hostname, nil
}
endpoint := fmt.Sprintf("http://%s/v1/", hostname)
if secure {
endpoint = fmt.Sprintf("https://%s/v1/", hostname)
}
if _, oerr := pingRegistryEndpoint(endpoint); oerr != nil {
//TODO: triggering highland build can be done there without "failing"
err := fmt.Errorf("Invalid registry endpoint '%s': %s ", endpoint, oerr)
if secure {
err = fmt.Errorf("%s. If this private registry supports only HTTP, please add `--insecure-registry %s` to the daemon's arguments.", oerr, hostname)
}
return "", err
}
return endpoint, nil
}
// this method verifies if the provided hostname is part of the list of
// insecure registries and returns false if HTTP should be used
func IsSecure(hostname string, insecureRegistries []string) bool {
if hostname == IndexServerAddress() {
return true
}
for _, h := range insecureRegistries {
if hostname == h {
return false
}
}
return true
}
func trustedLocation(req *http.Request) bool { func trustedLocation(req *http.Request) bool {
var ( var (
trusteds = []string{"docker.com", "docker.io"} trusteds = []string{"docker.com", "docker.io"}

View file

@ -21,7 +21,7 @@ const (
func spawnTestRegistrySession(t *testing.T) *Session { func spawnTestRegistrySession(t *testing.T) *Session {
authConfig := &AuthConfig{} authConfig := &AuthConfig{}
endpoint, err := NewEndpoint(makeURL("/v1/")) endpoint, err := NewEndpoint(makeURL("/v1/"), false)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -33,7 +33,7 @@ func spawnTestRegistrySession(t *testing.T) *Session {
} }
func TestPingRegistryEndpoint(t *testing.T) { func TestPingRegistryEndpoint(t *testing.T) {
ep, err := NewEndpoint(makeURL("/v1/")) ep, err := NewEndpoint(makeURL("/v1/"), false)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View file

@ -89,7 +89,10 @@ func (s *Service) Search(job *engine.Job) engine.Status {
if err != nil { if err != nil {
return job.Error(err) return job.Error(err)
} }
endpoint, err := NewEndpoint(hostname)
secure := IsSecure(hostname, s.insecureRegistries)
endpoint, err := NewEndpoint(hostname, secure)
if err != nil { if err != nil {
return job.Error(err) return job.Error(err)
} }

View file

@ -65,7 +65,7 @@ func NewSession(authConfig *AuthConfig, factory *utils.HTTPRequestFactory, endpo
} }
func (r *Session) doRequest(req *http.Request) (*http.Response, *http.Client, error) { func (r *Session) doRequest(req *http.Request) (*http.Response, *http.Client, error) {
return doRequest(req, r.jar, r.timeout) return doRequest(req, r.jar, r.timeout, r.indexEndpoint.secure)
} }
// Retrieve the history of a given image from the Registry. // Retrieve the history of a given image from the Registry.