Skip to content

Commit

Permalink
Do not verify certificate when using --insecure-registry on an HTTPS …
Browse files Browse the repository at this point in the history
…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
  • Loading branch information
tiborvass committed Oct 30, 2014
1 parent afade42 commit 6a1ff02
Show file tree
Hide file tree
Showing 9 changed files with 114 additions and 113 deletions.
2 changes: 1 addition & 1 deletion daemon/config.go
Expand Up @@ -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.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)")
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.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")
Expand Down
20 changes: 10 additions & 10 deletions docs/sources/reference/commandline/cli.md
Expand Up @@ -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
-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
--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-forward=true Enable net.ipv4.ip_forward
--ip-masq=true Enable IP masquerading for bridge's IP range
Expand Down Expand Up @@ -195,16 +195,16 @@ to other machines on the Internet. This may interfere with some network topologi
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
(`/var/lib/docker`) and for `/var/lib/docker/tmp`. The `DOCKER_TMPDIR` and the data directory can be set like this:
Expand Down
2 changes: 1 addition & 1 deletion graph/tags_unit_test.go
Expand Up @@ -53,7 +53,7 @@ func mkTestTagStore(root string, t *testing.T) *TagStore {
if err != nil {
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 {
t.Fatal(err)
}
Expand Down
49 changes: 39 additions & 10 deletions registry/endpoint.go
Expand Up @@ -2,7 +2,6 @@ package registry

import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
Expand Down Expand Up @@ -34,27 +33,40 @@ func scanForAPIVersion(hostname string) (string, APIVersion) {
return hostname, DefaultAPIVersion
}

func NewEndpoint(hostname string) (*Endpoint, error) {
endpoint, err := newEndpoint(hostname)
func NewEndpoint(hostname string, secure bool) (*Endpoint, error) {
endpoint, err := newEndpoint(hostname, secure)
if err != nil {
return nil, err
}

// Try HTTPS ping to registry
endpoint.URL.Scheme = "https"
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"

if secure {
// 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"
if _, err = endpoint.Ping(); err != nil {
return nil, errors.New("Invalid Registry endpoint: " + err.Error())
_, 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
}
func newEndpoint(hostname string) (*Endpoint, error) {
func newEndpoint(hostname string, secure bool) (*Endpoint, error) {
var (
endpoint Endpoint
endpoint = Endpoint{secure: secure}
trimmedHostname string
err error
)
Expand All @@ -72,6 +84,7 @@ func newEndpoint(hostname string) (*Endpoint, error) {
type Endpoint struct {
URL *url.URL
Version APIVersion
secure bool
}

// Get the formated URL for the root of this registry Endpoint
Expand All @@ -95,7 +108,7 @@ func (e Endpoint) Ping() (RegistryInfo, error) {
return RegistryInfo{Standalone: false}, err
}

resp, _, err := doRequest(req, nil, ConnectTimeout)
resp, _, err := doRequest(req, nil, ConnectTimeout, e.secure)
if err != nil {
return RegistryInfo{Standalone: false}, err
}
Expand Down Expand Up @@ -134,3 +147,19 @@ func (e Endpoint) Ping() (RegistryInfo, error) {
log.Debugf("RegistryInfo.Standalone: %t", info.Standalone)
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
}
2 changes: 1 addition & 1 deletion registry/endpoint_test.go
Expand Up @@ -12,7 +12,7 @@ func TestEndpointParse(t *testing.T) {
{"0.0.0.0:5000", "https://0.0.0.0:5000/v1/"},
}
for _, td := range testData {
e, err := newEndpoint(td.str)
e, err := newEndpoint(td.str, true)
if err != nil {
t.Errorf("%q: %s", td.str, err)
}
Expand Down
141 changes: 55 additions & 86 deletions registry/registry.go
Expand Up @@ -14,6 +14,7 @@ import (
"strings"
"time"

log "github.com/Sirupsen/logrus"
"github.com/docker/docker/utils"
)

Expand All @@ -35,7 +36,7 @@ const (
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{
RootCAs: roots,
// Avoid fallback to SSL protocols < TLS1.0
Expand All @@ -46,6 +47,10 @@ func newClient(jar http.CookieJar, roots *x509.CertPool, cert *tls.Certificate,
tlsConfig.Certificates = append(tlsConfig.Certificates, *cert)
}

if !secure {
tlsConfig.InsecureSkipVerify = true
}

httpTransport := &http.Transport{
DisableKeepAlives: true,
Proxy: http.ProxyFromEnvironment,
Expand Down Expand Up @@ -86,69 +91,76 @@ 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) {
hasFile := func(files []os.FileInfo, name string) bool {
for _, f := range files {
if f.Name() == name {
return true
}
}
return false
}

hostDir := path.Join("/etc/docker/certs.d", req.URL.Host)
fs, err := ioutil.ReadDir(hostDir)
if err != nil && !os.IsNotExist(err) {
return nil, nil, err
}

func doRequest(req *http.Request, jar http.CookieJar, timeout TimeoutType, secure bool) (*http.Response, *http.Client, error) {
var (
pool *x509.CertPool
certs []*tls.Certificate
)

for _, f := range fs {
if strings.HasSuffix(f.Name(), ".crt") {
if pool == nil {
pool = x509.NewCertPool()
}
data, err := ioutil.ReadFile(path.Join(hostDir, f.Name()))
if err != nil {
return nil, nil, err
if secure && req.URL.Scheme == "https" {
hasFile := func(files []os.FileInfo, name string) bool {
for _, f := range files {
if f.Name() == name {
return true
}
}
pool.AppendCertsFromPEM(data)
return false
}

hostDir := path.Join("/etc/docker/certs.d", req.URL.Host)
log.Debugf("hostDir: %s", hostDir)
fs, err := ioutil.ReadDir(hostDir)
if err != nil && !os.IsNotExist(err) {
return nil, nil, err
}
if strings.HasSuffix(f.Name(), ".cert") {
certName := f.Name()
keyName := certName[:len(certName)-5] + ".key"
if !hasFile(fs, keyName) {
return nil, nil, fmt.Errorf("Missing key %s for certificate %s", keyName, certName)

for _, f := range fs {
if strings.HasSuffix(f.Name(), ".crt") {
if pool == nil {
pool = x509.NewCertPool()
}
log.Debugf("crt: %s", hostDir+"/"+f.Name())
data, err := ioutil.ReadFile(path.Join(hostDir, f.Name()))
if err != nil {
return nil, nil, err
}
pool.AppendCertsFromPEM(data)
}
cert, err := tls.LoadX509KeyPair(path.Join(hostDir, certName), path.Join(hostDir, keyName))
if err != nil {
return nil, nil, err
if strings.HasSuffix(f.Name(), ".cert") {
certName := f.Name()
keyName := certName[:len(certName)-5] + ".key"
log.Debugf("cert: %s", hostDir+"/"+f.Name())
if !hasFile(fs, keyName) {
return nil, nil, fmt.Errorf("Missing key %s for certificate %s", keyName, certName)
}
cert, err := tls.LoadX509KeyPair(path.Join(hostDir, certName), path.Join(hostDir, keyName))
if err != nil {
return nil, nil, err
}
certs = append(certs, &cert)
}
certs = append(certs, &cert)
}
if strings.HasSuffix(f.Name(), ".key") {
keyName := f.Name()
certName := keyName[:len(keyName)-4] + ".cert"
if !hasFile(fs, certName) {
return nil, nil, fmt.Errorf("Missing certificate %s for key %s", certName, keyName)
if strings.HasSuffix(f.Name(), ".key") {
keyName := f.Name()
certName := keyName[:len(keyName)-4] + ".cert"
log.Debugf("key: %s", hostDir+"/"+f.Name())
if !hasFile(fs, certName) {
return nil, nil, fmt.Errorf("Missing certificate %s for key %s", certName, keyName)
}
}
}
}

if len(certs) == 0 {
client := newClient(jar, pool, nil, timeout)
client := newClient(jar, pool, nil, timeout, secure)
res, err := client.Do(req)
if err != nil {
return nil, nil, err
}
return res, client, nil
}

for i, cert := range certs {
client := newClient(jar, pool, cert, timeout)
client := newClient(jar, pool, cert, timeout, secure)
res, err := client.Do(req)
// If this is the last cert, otherwise, continue to next cert if 403 or 5xx
if i == len(certs)-1 || err == nil &&
Expand Down Expand Up @@ -213,49 +225,6 @@ func ResolveRepositoryName(reposName string) (string, string, error) {
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 {
var (
trusteds = []string{"docker.com", "docker.io"}
Expand Down
4 changes: 2 additions & 2 deletions registry/registry_test.go
Expand Up @@ -21,7 +21,7 @@ const (

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

func TestPingRegistryEndpoint(t *testing.T) {
ep, err := NewEndpoint(makeURL("/v1/"))
ep, err := NewEndpoint(makeURL("/v1/"), false)
if err != nil {
t.Fatal(err)
}
Expand Down
5 changes: 4 additions & 1 deletion registry/service.go
Expand Up @@ -89,7 +89,10 @@ func (s *Service) Search(job *engine.Job) engine.Status {
if err != nil {
return job.Error(err)
}
endpoint, err := NewEndpoint(hostname)

secure := IsSecure(hostname, s.insecureRegistries)

endpoint, err := NewEndpoint(hostname, secure)
if err != nil {
return job.Error(err)
}
Expand Down
2 changes: 1 addition & 1 deletion registry/session.go
Expand Up @@ -65,7 +65,7 @@ func NewSession(authConfig *AuthConfig, factory *utils.HTTPRequestFactory, endpo
}

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.
Expand Down

0 comments on commit 6a1ff02

Please sign in to comment.