From aed97dde1348709c5b1fbcf00387c7fe78fcf57d Mon Sep 17 00:00:00 2001 From: Derek McGowan Date: Wed, 22 Oct 2014 11:07:03 -0700 Subject: [PATCH 1/6] Add trust key creation on client Signed-off-by: Derek McGowan (github: dmcgowan) --- docker/docker.go | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/docker/docker.go b/docker/docker.go index cb780b2443332..05d7becb3bcf2 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -6,6 +6,7 @@ import ( "fmt" "io/ioutil" "os" + "path" "strings" "github.com/docker/docker/api" @@ -15,6 +16,7 @@ import ( flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/reexec" "github.com/docker/docker/utils" + "github.com/docker/libtrust" ) const ( @@ -62,6 +64,23 @@ func main() { } protoAddrParts := strings.SplitN(flHosts[0], "://", 2) + err := os.MkdirAll(path.Dir(*flTrustKey), 0700) + if err != nil { + log.Fatal(err) + } + trustKey, err := libtrust.LoadKeyFile(*flTrustKey) + if err == libtrust.ErrKeyFileDoesNotExist { + trustKey, err = libtrust.GenerateECP256PrivateKey() + if err != nil { + log.Fatalf("Error generating key: %s", err) + } + if err := libtrust.SaveKey(*flTrustKey, trustKey); err != nil { + log.Fatalf("Error saving key file: %s", err) + } + } else if err != nil { + log.Fatalf("Error loading key file: %s", err) + } + var ( cli *client.DockerCli tlsConfig tls.Config @@ -98,9 +117,9 @@ func main() { } if *flTls || *flTlsVerify { - cli = client.NewDockerCli(os.Stdin, os.Stdout, os.Stderr, nil, protoAddrParts[0], protoAddrParts[1], &tlsConfig) + cli = client.NewDockerCli(os.Stdin, os.Stdout, os.Stderr, trustKey, protoAddrParts[0], protoAddrParts[1], &tlsConfig) } else { - cli = client.NewDockerCli(os.Stdin, os.Stdout, os.Stderr, nil, protoAddrParts[0], protoAddrParts[1], nil) + cli = client.NewDockerCli(os.Stdin, os.Stdout, os.Stderr, trustKey, protoAddrParts[0], protoAddrParts[1], nil) } if err := cli.Cmd(flag.Args()...); err != nil { From 2bfeade4ab2d03693a9d35f70c9b31e0076c2a4c Mon Sep 17 00:00:00 2001 From: Derek McGowan Date: Wed, 22 Oct 2014 10:49:23 -0700 Subject: [PATCH 2/6] Add default TLS using engine keys Signed-off-by: Derek McGowan (github: dmcgowan) --- api/server/server.go | 89 ++++++++++++++++++++--- docker/daemon.go | 3 + docker/docker.go | 138 +++++++++++++++++++++++++++++++----- docker/flags.go | 20 +++--- integration/runtime_test.go | 3 + 5 files changed, 215 insertions(+), 38 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index a3edbdc636d14..abc30ff1aec69 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -4,7 +4,6 @@ import ( "bufio" "bytes" "crypto/tls" - "crypto/x509" "encoding/base64" "encoding/json" "expvar" @@ -15,6 +14,7 @@ import ( "net/http" "net/http/pprof" "os" + "path" "strconv" "strings" "syscall" @@ -33,6 +33,7 @@ import ( "github.com/docker/docker/pkg/version" "github.com/docker/docker/registry" "github.com/docker/docker/utils" + "github.com/docker/libtrust" ) var ( @@ -1391,6 +1392,23 @@ func changeGroup(addr string, nameOrGid string) error { return os.Chown(addr, 0, gid) } +// parseAddr parses an address into an array of IPs and domains +func parseAddr(addr string) ([]net.IP, []string, error) { + host, _, err := net.SplitHostPort(addr) + if err != nil { + return nil, nil, err + } + var domains []string + var ips []net.IP + ip := net.ParseIP(host) + if ip != nil { + ips = []net.IP{ip} + } else { + domains = []string{host} + } + return ips, domains, nil +} + // ListenAndServe sets up the required http.Server and gets it listening for // each addr passed in and does protocol specific checking. func ListenAndServe(proto, addr string, job *engine.Job) error { @@ -1428,24 +1446,75 @@ func ListenAndServe(proto, addr string, job *engine.Job) error { return err } - if proto != "unix" && (job.GetenvBool("Tls") || job.GetenvBool("TlsVerify")) { + if proto != "unix" && (!job.GetenvBool("Insecure")) { + trustKeyFile := job.Getenv("TrustKey") + err = os.MkdirAll(path.Dir(trustKeyFile), 0700) + if err != nil { + return fmt.Errorf("Error creating directory: %s", err) + } + trustKey, err := libtrust.LoadKeyFile(trustKeyFile) + if err == libtrust.ErrKeyFileDoesNotExist { + trustKey, err = libtrust.GenerateECP256PrivateKey() + if err != nil { + return fmt.Errorf("Error generating key: %s", err) + } + if err := libtrust.SaveKey(trustKeyFile, trustKey); err != nil { + return fmt.Errorf("Error saving key file: %s", err) + } + } else if err != nil { + return fmt.Errorf("Error loading key file: %s", err) + } + + var cert tls.Certificate tlsCert := job.Getenv("TlsCert") tlsKey := job.Getenv("TlsKey") - cert, err := tls.LoadX509KeyPair(tlsCert, tlsKey) - if err != nil { - return fmt.Errorf("Couldn't load X509 key pair (%s, %s): %s. Key encrypted?", - tlsCert, tlsKey, err) + if tlsCert != "" && tlsKey != "" { + var err error + cert, err = tls.LoadX509KeyPair(tlsCert, tlsKey) + if err != nil { + if !os.IsNotExist(err) { + return fmt.Errorf("Couldn't load X509 key pair (%s, %s): %s. Key encrypted?", + tlsCert, tlsKey, err) + } + ips, domains, err := parseAddr(addr) + if err != nil { + return err + } + // add default docker domain for docker clients to look for + domains = append(domains, "docker") + x509Cert, err := libtrust.GenerateSelfSignedServerCert(trustKey, domains, ips) + if err != nil { + return fmt.Errorf("certificate generation error: %s", err) + } + cert = tls.Certificate{ + Certificate: [][]byte{x509Cert.Raw}, + PrivateKey: trustKey.CryptoPrivateKey(), + Leaf: x509Cert, + } + } + } + tlsConfig := &tls.Config{ NextProtos: []string{"http/1.1"}, Certificates: []tls.Certificate{cert}, // Avoid fallback on insecure SSL protocols MinVersion: tls.VersionTLS10, } + + // Load authorized keys file + clients, err := libtrust.LoadKeySetFile(job.Getenv("TrustClients")) + if err != nil { + return fmt.Errorf("unable to load authorized keys: %s", err) + } + if job.GetenvBool("TlsVerify") { - certPool := x509.NewCertPool() + certPool, poolErr := libtrust.GenerateCACertPool(trustKey, clients) + if poolErr != nil { + return fmt.Errorf("CA pool generation error: %s", poolErr) + } file, err := ioutil.ReadFile(job.Getenv("TlsCa")) - if err != nil { + if err != nil && !os.IsNotExist(err) { return fmt.Errorf("Couldn't read CA certificate: %s", err) } certPool.AppendCertsFromPEM(file) @@ -1459,8 +1528,8 @@ func ListenAndServe(proto, addr string, job *engine.Job) error { // Basic error and sanity checking switch proto { case "tcp": - if !strings.HasPrefix(addr, "127.0.0.1") && !job.GetenvBool("TlsVerify") { - log.Infof("/!\\ DON'T BIND ON ANOTHER IP ADDRESS THAN 127.0.0.1 IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\") + if job.GetenvBool("Insecure") { + log.Infof("/!\\ DON'T BIND INSECURELY ON A TCP ADDRESS IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\") } case "unix": socketGroup := job.Getenv("SocketGroup") diff --git a/docker/daemon.go b/docker/daemon.go index 2f658784723a3..87f52b3670938 100644 --- a/docker/daemon.go +++ b/docker/daemon.go @@ -73,11 +73,14 @@ func mainDaemon() { job.Setenv("Version", dockerversion.VERSION) job.Setenv("SocketGroup", *flSocketGroup) + job.SetenvBool("Insecure", *flInsecure) job.SetenvBool("Tls", *flTls) job.SetenvBool("TlsVerify", *flTlsVerify) job.Setenv("TlsCa", *flCa) job.Setenv("TlsCert", *flCert) job.Setenv("TlsKey", *flKey) + job.Setenv("TrustKey", *flTrustKey) + job.Setenv("TrustClients", *flTrustClients) job.SetenvBool("BufferRequests", true) if err := job.Run(); err != nil { log.Fatal(err) diff --git a/docker/docker.go b/docker/docker.go index 05d7becb3bcf2..36ba428a91f3c 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -1,6 +1,7 @@ package main import ( + "bufio" "crypto/tls" "crypto/x509" "fmt" @@ -8,6 +9,7 @@ import ( "os" "path" "strings" + "time" "github.com/docker/docker/api" "github.com/docker/docker/api/client" @@ -20,10 +22,12 @@ import ( ) const ( - defaultTrustKeyFile = "key.json" - defaultCaFile = "ca.pem" - defaultKeyFile = "key.pem" - defaultCertFile = "cert.pem" + defaultTrustKeyFile = "key.json" + defaultHostKeysFile = "allowed_hosts.json" + defaultClientKeysFile = "authorized_keys.json" + defaultCaFile = "ca.pem" + defaultKeyFile = "key.pem" + defaultCertFile = "cert.pem" ) func main() { @@ -53,6 +57,7 @@ func main() { } flHosts = append(flHosts, defaultHost) } + *flTlsVerify = true if *flDaemon { mainDaemon() @@ -85,41 +90,124 @@ func main() { cli *client.DockerCli tlsConfig tls.Config ) - tlsConfig.InsecureSkipVerify = true + + // Load known hosts + knownHosts, err := libtrust.LoadKeySetFile(*flTrustHosts) + if err != nil { + log.Fatalf("Could not load trusted hosts file: %s", err) + } // If we should verify the server, we need to load a trusted ca if *flTlsVerify { *flTls = true - certPool := x509.NewCertPool() - file, err := ioutil.ReadFile(*flCa) + allowedHosts, err := libtrust.FilterByHosts(knownHosts, protoAddrParts[1], false) + if err != nil { + log.Fatalf("Error filtering hosts: %s", err) + } + certPool, err := libtrust.GenerateCACertPool(trustKey, allowedHosts) if err != nil { - log.Fatalf("Couldn't read ca cert %s: %s", *flCa, err) + log.Fatalf("Could not create CA pool: %s", err) + } + if *flCa != "" { + file, err := ioutil.ReadFile(*flCa) + if err != nil { + if !os.IsNotExist(err) { + log.Fatalf("Couldn't read ca cert %s: %s", *flCa, err) + } else { + tlsConfig.ServerName = "docker" + } + } else { + certPool.AppendCertsFromPEM(file) + } + } else { + tlsConfig.ServerName = "docker" } - certPool.AppendCertsFromPEM(file) tlsConfig.RootCAs = certPool tlsConfig.InsecureSkipVerify = false } // If tls is enabled, try to load and send client certificates if *flTls || *flTlsVerify { - _, errCert := os.Stat(*flCert) - _, errKey := os.Stat(*flKey) - if errCert == nil && errKey == nil { - *flTls = true - cert, err := tls.LoadX509KeyPair(*flCert, *flKey) - if err != nil { + *flTls = true + cert, err := tls.LoadX509KeyPair(*flCert, *flKey) + if err != nil { + if !os.IsNotExist(err) { log.Fatalf("Couldn't load X509 key pair: %s. Key encrypted?", err) + } else { + x509Cert, certErr := libtrust.GenerateSelfSignedClientCert(trustKey) + if certErr != nil { + log.Fatalf("Certificate generation error: %s", certErr) + } + cert = tls.Certificate{ + Certificate: [][]byte{x509Cert.Raw}, + PrivateKey: trustKey.CryptoPrivateKey(), + Leaf: x509Cert, + } + } + } + tlsConfig.Certificates = []tls.Certificate{cert} + } + + if protoAddrParts[0] != "unix" && (!*flInsecure) { + savedInsecure := tlsConfig.InsecureSkipVerify + tlsConfig.InsecureSkipVerify = true + testConn, connErr := tls.Dial(protoAddrParts[0], protoAddrParts[1], &tlsConfig) + if connErr != nil { + log.Fatalf("TLS Handshake error: %s", connErr) + } + opts := x509.VerifyOptions{ + Roots: tlsConfig.RootCAs, + CurrentTime: time.Now(), + DNSName: tlsConfig.ServerName, + Intermediates: x509.NewCertPool(), + } + + certs := testConn.ConnectionState().PeerCertificates + for i, cert := range certs { + if i == 0 { + continue } - tlsConfig.Certificates = []tls.Certificate{cert} + opts.Intermediates.AddCert(cert) } + _, err = certs[0].Verify(opts) + if err != nil { + if _, ok := err.(x509.UnknownAuthorityError); ok { + pubKey, err := libtrust.FromCryptoPublicKey(certs[0].PublicKey) + if err != nil { + log.Fatalf("Error extracting public key from certificate: %s", err) + } + + if promptUnknownKey(pubKey, protoAddrParts[1]) { + pubKey.AddExtendedField("hosts", []string{protoAddrParts[1]}) + err = libtrust.AddKeySetFile(*flTrustHosts, pubKey) + if err != nil { + log.Fatalf("Error saving updated host keys file: %s", err) + } + + ca, err := libtrust.GenerateCACert(trustKey, pubKey) + if err != nil { + log.Fatalf("Error generating CA: %s", err) + } + tlsConfig.RootCAs.AddCert(ca) + } else { + log.Fatalf("Cancelling request due to invalid certificate") + } + } else { + log.Fatalf("TLS verification error: %s", connErr) + } + } + + testConn.Close() + tlsConfig.InsecureSkipVerify = savedInsecure + // Avoid fallback to SSL protocols < TLS1.0 tlsConfig.MinVersion = tls.VersionTLS10 } - if *flTls || *flTlsVerify { - cli = client.NewDockerCli(os.Stdin, os.Stdout, os.Stderr, trustKey, protoAddrParts[0], protoAddrParts[1], &tlsConfig) - } else { + if protoAddrParts[0] == "unix" || *flInsecure { cli = client.NewDockerCli(os.Stdin, os.Stdout, os.Stderr, trustKey, protoAddrParts[0], protoAddrParts[1], nil) + } else { + cli = client.NewDockerCli(os.Stdin, os.Stdout, os.Stderr, trustKey, protoAddrParts[0], protoAddrParts[1], &tlsConfig) } if err := cli.Cmd(flag.Args()...); err != nil { @@ -133,6 +221,18 @@ func main() { } } +func promptUnknownKey(key libtrust.PublicKey, host string) bool { + fmt.Printf("The authenticity of host %q can't be established.\nRemote key ID %s\n", host, key.KeyID()) + fmt.Printf("Are you sure you want to continue connecting (yes/no)? ") + reader := bufio.NewReader(os.Stdin) + line, _, err := reader.ReadLine() + if err != nil { + log.Fatalf("Error reading input: %s", err) + } + input := strings.TrimSpace(strings.ToLower(string(line))) + return input == "yes" || input == "y" +} + func showVersion() { fmt.Printf("Docker version %s, build %s\n", dockerversion.VERSION, dockerversion.GITCOMMIT) } diff --git a/docker/flags.go b/docker/flags.go index 61081ec996313..8ed9a39a75425 100644 --- a/docker/flags.go +++ b/docker/flags.go @@ -28,20 +28,22 @@ var ( flEnableCors = flag.Bool([]string{"#api-enable-cors", "-api-enable-cors"}, false, "Enable CORS headers in the remote API") flTls = flag.Bool([]string{"-tls"}, false, "Use TLS; implied by tls-verify flags") flTlsVerify = flag.Bool([]string{"-tlsverify"}, dockerTlsVerify, "Use TLS and verify the remote (daemon: verify client, client: verify daemon)") + flInsecure = flag.Bool([]string{"-insecure"}, false, "Use insecure non-TLS connections") // these are initialized in init() below since their default values depend on dockerCertPath which isn't fully initialized until init() runs - flTrustKey *string - flCa *string - flCert *string - flKey *string - flHosts []string + flTrustKey *string + flTrustHosts *string + flTrustClients *string + flCa *string + flCert *string + flKey *string + flHosts []string ) func init() { - // placeholder for trust key flag - trustKeyDefault := filepath.Join(dockerCertPath, defaultTrustKeyFile) - flTrustKey = &trustKeyDefault - + flTrustHosts = flag.String([]string{"-allowed-hosts-file"}, filepath.Join(dockerCertPath, defaultHostKeysFile), "Path to file containing allowed hosts") + flTrustClients = flag.String([]string{"-authorized-keys-file"}, filepath.Join(dockerCertPath, defaultClientKeysFile), "Path to file containing authorized keys") + flTrustKey = flag.String([]string{"i", "-identity"}, filepath.Join(dockerCertPath, defaultTrustKeyFile), "Path to libtrust key file") flCa = flag.String([]string{"-tlscacert"}, filepath.Join(dockerCertPath, defaultCaFile), "Trust only remotes providing a certificate signed by the CA given here") flCert = flag.String([]string{"-tlscert"}, filepath.Join(dockerCertPath, defaultCertFile), "Path to TLS certificate file") flKey = flag.String([]string{"-tlskey"}, filepath.Join(dockerCertPath, defaultKeyFile), "Path to TLS key file") diff --git a/integration/runtime_test.go b/integration/runtime_test.go index b17d132f8aa31..ab017561663e8 100644 --- a/integration/runtime_test.go +++ b/integration/runtime_test.go @@ -8,6 +8,7 @@ import ( "net" "net/url" "os" + "path" "path/filepath" "runtime" "strconv" @@ -159,6 +160,7 @@ func spawnGlobalDaemon() { } job := eng.Job("serveapi", listenURL.String()) job.SetenvBool("Logging", true) + job.SetenvBool("Insecure", true) if err := job.Run(); err != nil { log.Fatalf("Unable to spawn the test daemon: %s", err) } @@ -214,6 +216,7 @@ func spawnHttpsDaemon(addr, cacert, cert, key string) *engine.Engine { job.Setenv("TlsCa", cacert) job.Setenv("TlsCert", cert) job.Setenv("TlsKey", key) + job.Setenv("TrustKey", path.Join(unitTestStoreBase, ".docker", "key.json")) if err := job.Run(); err != nil { log.Fatalf("Unable to spawn the test daemon: %s", err) } From dbd502c3343f134ad39a4020b5070a4d0230c403 Mon Sep 17 00:00:00 2001 From: Derek McGowan Date: Thu, 30 Oct 2014 12:03:43 -0700 Subject: [PATCH 3/6] Update https documentation Signed-off-by: Derek McGowan (github: dmcgowan) --- docs/sources/articles/https.md | 84 +++++++++++++++++++++++----------- 1 file changed, 57 insertions(+), 27 deletions(-) diff --git a/docs/sources/articles/https.md b/docs/sources/articles/https.md index c8873bcbe4dd0..33432f113d8c2 100644 --- a/docs/sources/articles/https.md +++ b/docs/sources/articles/https.md @@ -5,11 +5,58 @@ page_keywords: docker, docs, article, example, https, daemon, tls, ca, certifica # Running Docker with https By default, Docker runs via a non-networked Unix socket. It can also -optionally communicate using a HTTP socket. - -If you need Docker to be reachable via the network in a safe manner, you can -enable TLS by specifying the `tlsverify` flag and pointing Docker's -`tlscacert` flag to a trusted CA certificate. +optionally communicate using a HTTP socket. Enabling communication +through HTTP will by default use TLS and require daemon configuration +to allow client connections. + + +There are two different ways of authenticating connections between Docker +client and daemon, both of which use secure TLS connections. + + - **Host-based authentication** uses an authorized keys list on the daemon +to whitelist client connections. The client must also accept the daemon's key +and remember it for future connections. + - **Certificate-based authentication** uses a certificate authority to +authorize connections. Using this method requires additional setup to enable +client authentication. + +The authentication method is selected using the `--auth` flag with values + `host`, `cert`, or `none` . `host` is the default method and `none` +should only be used with caution. + +## Host-based authentication + +Host-based authentication is similar to how SSH does authentication. When +connecting to a daemon for the first time, a client will ask whether a user +trusts a fingerprint of the daemon’s public key. If they do, the public key will +be stored so it does not prompt on subsequent connections. For the daemon +to authenticate the client, each client automatically generates its own +key (~/.docker/key.json) which is presented to the daemon and checked +against a list of keys authorized to connect (~/.docker/authorized_keys.json). + +To enable host-based authentication, add the flag `--auth=host`. The default +identity and authorization files may be overridden through the flags: + + - `--identity` specifies the key file to use. This file contains the client's private +key and its fingerprint is used by the daemon to identify the client. This file +should be secured. + - `--auth-authorized-keys` - specifies the client whitelist. This is a daemon +configuration and should have its write permissions restricted. + - `--auth-allowed-host` - specifies the list of daemon public key fingerprints +which have been approved by the user and the host name associated with +each fingerprint. + +## Certificate-based authentication + +Certificate-based authentication uses TLS certificates provided by a +certificate authority. This is for advanced usage where you may want to +integrate Docker with other TLS-compatible tools or you may already use PKI +within your organisation. You can just get the client to verify the server’s +certificate against a CA, or do full two-way authentication by getting the +server to also verify the client’s certificate. + +To enable certificate-based authentication, add the flag `--auth=cert` and +point the `--tlscacert` flag to a trusted CA certificate. In the daemon mode, it will only allow connections from clients authenticated by a certificate signed by that CA. In the client mode, @@ -24,7 +71,7 @@ it will only connect to servers with a certificate signed by that CA. > Mac OS X comes with a version of OpenSSL that is incompatible with the > certificates that Docker requires. -## Create a CA, server and client keys with OpenSSL +### Create a CA, server and client keys with OpenSSL First, initialize the CA serial file and generate CA private and public keys: @@ -135,34 +182,17 @@ need to provide your client keys, certificates and trusted CA: > daemon, giving them root access to the machine hosting the daemon. Guard > these keys as you would a root password! -## Secure by default - -If you want to secure your Docker client connections by default, you can move -the files to the `.docker` directory in your home directory - and set the -`DOCKER_HOST` and `DOCKER_TLS_VERIFY` variables as well (instead of passing -`-H=tcp://:2376` and `--tlsverify` on every call). - - $ cp ca.pem ~/.docker/ca.pem - $ cp cert.pem ~/.docker/cert.pem - $ cp key.pem ~/.docker/key.pem - $ export DOCKER_HOST=tcp://:2376 - $ export DOCKER_TLS_VERIFY=1 - -Docker will now connect securely by default: - - $ sudo docker ps - -## Other modes +### Other certificate-based modes If you don't want to have complete two-way authentication, you can run Docker in various other modes by mixing the flags. -### Daemon modes +#### Daemon modes - `tlsverify`, `tlscacert`, `tlscert`, `tlskey` set: Authenticate clients - `tls`, `tlscert`, `tlskey`: Do not authenticate clients -### Client modes +#### Client modes - `tls`: Authenticate server based on public/default CA pool - `tlsverify`, `tlscacert`: Authenticate server based on given CA @@ -179,7 +209,7 @@ location using the environment variable `DOCKER_CERT_PATH`. $ export DOCKER_CERT_PATH=${HOME}/.docker/zone1/ $ sudo docker --tlsverify ps -### Connecting to the Secure Docker port using `curl` +#### Connecting to the Secure Docker port using `curl` To use `curl` to make test API requests, you need to use three extra command line flags: From 8e2d2f6afccd327f7a83dc752c4e6613d41fb194 Mon Sep 17 00:00:00 2001 From: Ben Firshman Date: Mon, 3 Nov 2014 11:30:37 +0000 Subject: [PATCH 4/6] Rename host-based auth to identity-based auth Signed-off-by: Ben Firshman --- docs/sources/articles/https.md | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/docs/sources/articles/https.md b/docs/sources/articles/https.md index 33432f113d8c2..dfbf6d60e9f4a 100644 --- a/docs/sources/articles/https.md +++ b/docs/sources/articles/https.md @@ -13,7 +13,7 @@ to allow client connections. There are two different ways of authenticating connections between Docker client and daemon, both of which use secure TLS connections. - - **Host-based authentication** uses an authorized keys list on the daemon + - **Identity-based authentication** uses an authorized keys list on the daemon to whitelist client connections. The client must also accept the daemon's key and remember it for future connections. - **Certificate-based authentication** uses a certificate authority to @@ -21,12 +21,12 @@ authorize connections. Using this method requires additional setup to enable client authentication. The authentication method is selected using the `--auth` flag with values - `host`, `cert`, or `none` . `host` is the default method and `none` + `identity`, `cert`, or `none` . `identity` is the default method and `none` should only be used with caution. -## Host-based authentication +## Identity-based authentication -Host-based authentication is similar to how SSH does authentication. When +Identity-based authentication is similar to how SSH does authentication. When connecting to a daemon for the first time, a client will ask whether a user trusts a fingerprint of the daemon’s public key. If they do, the public key will be stored so it does not prompt on subsequent connections. For the daemon @@ -34,12 +34,13 @@ to authenticate the client, each client automatically generates its own key (~/.docker/key.json) which is presented to the daemon and checked against a list of keys authorized to connect (~/.docker/authorized_keys.json). -To enable host-based authentication, add the flag `--auth=host`. The default -identity and authorization files may be overridden through the flags: +To enable identity-based authentication, add the flag `--auth=identity`. +The default identity and authorization files may be overridden through the +flags: - - `--identity` specifies the key file to use. This file contains the client's private -key and its fingerprint is used by the daemon to identify the client. This file -should be secured. + - `--identity` specifies the key file to use. This file contains the client's +private key and its fingerprint is used by the daemon to identify the client. +This file should be secured. - `--auth-authorized-keys` - specifies the client whitelist. This is a daemon configuration and should have its write permissions restricted. - `--auth-allowed-host` - specifies the list of daemon public key fingerprints From 41ca4050192c427e2481aeac674da3852393c7b8 Mon Sep 17 00:00:00 2001 From: Ben Firshman Date: Mon, 3 Nov 2014 12:11:18 +0000 Subject: [PATCH 5/6] Whitespace fixes Signed-off-by: Ben Firshman --- docs/sources/articles/https.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/sources/articles/https.md b/docs/sources/articles/https.md index dfbf6d60e9f4a..83aed12b52147 100644 --- a/docs/sources/articles/https.md +++ b/docs/sources/articles/https.md @@ -63,13 +63,13 @@ In the daemon mode, it will only allow connections from clients authenticated by a certificate signed by that CA. In the client mode, it will only connect to servers with a certificate signed by that CA. -> **Warning**: +> **Warning**: > Using TLS and managing a CA is an advanced topic. Please familiarize yourself > with OpenSSL, x509 and TLS before using it in production. > **Warning**: > These TLS commands will only generate a working set of certificates on Linux. -> Mac OS X comes with a version of OpenSSL that is incompatible with the +> Mac OS X comes with a version of OpenSSL that is incompatible with the > certificates that Docker requires. ### Create a CA, server and client keys with OpenSSL @@ -176,7 +176,7 @@ need to provide your client keys, certificates and trusted CA: > **Note**: > Docker over TLS should run on TCP port 2376. -> **Warning**: +> **Warning**: > As shown in the example above, you don't have to run the `docker` client > with `sudo` or the `docker` group when you use certificate authentication. > That means anyone with the keys can give any instructions to your Docker From 49286c981a955e609ac7da6d10f40753e605a568 Mon Sep 17 00:00:00 2001 From: Ben Firshman Date: Mon, 3 Nov 2014 12:00:55 +0000 Subject: [PATCH 6/6] Update cert-based auth docs to use new options Signed-off-by: Ben Firshman --- docs/sources/articles/https.md | 54 ++++++++++++++++++---------------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/docs/sources/articles/https.md b/docs/sources/articles/https.md index 83aed12b52147..371d0909b54e4 100644 --- a/docs/sources/articles/https.md +++ b/docs/sources/articles/https.md @@ -63,6 +63,27 @@ In the daemon mode, it will only allow connections from clients authenticated by a certificate signed by that CA. In the client mode, it will only connect to servers with a certificate signed by that CA. +### Client configuration + +To enable certificate-based authentication, use the `--auth=cert` option. By +default, this will use the public CA pool. You want to use a specific CA, +specify its path with the `--auth-ca` option. + +If the server requires client certificate authentication, you can provide this +with the `--auth-cert` and `--auth-key` options. + +### Daemon configuration + +When running the daemon with the `--auth=cert` option, it will serve a TLS +connection that the client can verify against its CA certificate. You must +provide a keypair for the client to check with the `--auth-cert` and +`--auth-key` options. + +If you also want the client to authenticate with a client certificate, you can +pass a CA to check the certificate against with the `--auth-ca` option. + +### Create a CA, server and client keys with OpenSSL + > **Warning**: > Using TLS and managing a CA is an advanced topic. Please familiarize yourself > with OpenSSL, x509 and TLS before using it in production. @@ -72,8 +93,6 @@ it will only connect to servers with a certificate signed by that CA. > Mac OS X comes with a version of OpenSSL that is incompatible with the > certificates that Docker requires. -### Create a CA, server and client keys with OpenSSL - First, initialize the CA serial file and generate CA private and public keys: @@ -164,13 +183,13 @@ Finally, you need to remove the passphrase from the client and server key: Now you can make the Docker daemon only accept connections from clients providing a certificate trusted by our CA: - $ sudo docker -d --tlsverify --tlscacert=ca.pem --tlscert=server-cert.pem --tlskey=server-key.pem \ + $ sudo docker -d --auth=cert --auth-ca=ca.pem --auth-cert=server-cert.pem --auth-key=server-key.pem \ -H=0.0.0.0:2376 To be able to connect to Docker and validate its certificate, you now need to provide your client keys, certificates and trusted CA: - $ sudo docker --tlsverify --tlscacert=ca.pem --tlscert=cert.pem --tlskey=key.pem \ + $ sudo docker --auth=cert --auth-ca=ca.pem --auth-cert=cert.pem --auth-key=key.pem \ -H=dns-name-of-docker-host:2376 version > **Note**: @@ -183,24 +202,7 @@ need to provide your client keys, certificates and trusted CA: > daemon, giving them root access to the machine hosting the daemon. Guard > these keys as you would a root password! -### Other certificate-based modes - -If you don't want to have complete two-way authentication, you can run -Docker in various other modes by mixing the flags. - -#### Daemon modes - - - `tlsverify`, `tlscacert`, `tlscert`, `tlskey` set: Authenticate clients - - `tls`, `tlscert`, `tlskey`: Do not authenticate clients - -#### Client modes - - - `tls`: Authenticate server based on public/default CA pool - - `tlsverify`, `tlscacert`: Authenticate server based on given CA - - `tls`, `tlscert`, `tlskey`: Authenticate with client certificate, do not - authenticate server based on given CA - - `tlsverify`, `tlscacert`, `tlscert`, `tlskey`: Authenticate with client - certificate and authenticate server based on given CA +### Automatic configuration If found, the client will send its client certificate, so you just need to drop your keys into `~/.docker/.pem`. Alternatively, @@ -208,11 +210,11 @@ if you want to store your keys in another location, you can specify that location using the environment variable `DOCKER_CERT_PATH`. $ export DOCKER_CERT_PATH=${HOME}/.docker/zone1/ - $ sudo docker --tlsverify ps + $ sudo docker --auth=cert ps -#### Connecting to the Secure Docker port using `curl` +### Connecting to the Secure Docker port using `curl` -To use `curl` to make test API requests, you need to use three extra command line -flags: +To use `curl` to make test API requests, you need to use three extra command +line flags: $ curl --insecure --cert ~/.docker/cert.pem --key ~/.docker/key.pem https://boot2docker:2376/images/json`