diff --git a/.drone.yml b/.drone.yml index 53e00e0fdae92..204e01549f9fc 100755 --- a/.drone.yml +++ b/.drone.yml @@ -7,6 +7,7 @@ script: # Setup the DockerInDocker environment. - hack/dind # Tests relying on StartWithBusybox make Drone time out. + - rm integration-cli/docker_cli_auth_test.go - rm integration-cli/docker_cli_daemon_test.go - rm integration-cli/docker_cli_exec_test.go # Validate and test. diff --git a/api/client/auth.go b/api/client/auth.go new file mode 100644 index 0000000000000..a2124048c796d --- /dev/null +++ b/api/client/auth.go @@ -0,0 +1,159 @@ +package client + +import ( + "bufio" + "crypto/tls" + "crypto/x509" + "fmt" + "io/ioutil" + "log" + "os" + "strings" + "time" + + "github.com/docker/libtrust" +) + +// NewIdentityAuthTLSConfig creates a tls.Config for the client to use for +// libtrust identity authentication +func NewIdentityAuthTLSConfig(trustKey libtrust.PrivateKey, knownHostsPath, proto, addr string) (*tls.Config, error) { + tlsConfig := createTLSConfig() + + // Load known hosts + knownHosts, err := libtrust.LoadKeySetFile(knownHostsPath) + if err != nil { + return nil, fmt.Errorf("Could not load trusted hosts file: %s", err) + } + + // Generate CA pool from known hosts + allowedHosts, err := libtrust.FilterByHosts(knownHosts, addr, false) + if err != nil { + return nil, fmt.Errorf("Error filtering hosts: %s", err) + } + certPool, err := libtrust.GenerateCACertPool(trustKey, allowedHosts) + if err != nil { + return nil, fmt.Errorf("Could not create CA pool: %s", err) + } + tlsConfig.ServerName = "docker" + tlsConfig.RootCAs = certPool + + // Generate client cert from trust key + x509Cert, err := libtrust.GenerateSelfSignedClientCert(trustKey) + if err != nil { + return nil, fmt.Errorf("Certificate generation error: %s", err) + } + tlsConfig.Certificates = []tls.Certificate{{ + Certificate: [][]byte{x509Cert.Raw}, + PrivateKey: trustKey.CryptoPrivateKey(), + Leaf: x509Cert, + }} + + // Connect to server to see if it is a known host + tlsConfig.InsecureSkipVerify = true + testConn, err := tls.Dial(proto, addr, tlsConfig) + if err != nil { + return nil, fmt.Errorf("TLS Handshake error: %s", err) + } + 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 + } + 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 { + return nil, fmt.Errorf("Error extracting public key from certificate: %s", err) + } + + // If server is not a known host, prompt user to ask whether it should + // be trusted and add to the known hosts file + if promptUnknownKey(pubKey, addr) { + pubKey.AddExtendedField("hosts", []string{addr}) + err = libtrust.AddKeySetFile(knownHostsPath, pubKey) + if err != nil { + return nil, fmt.Errorf("Error saving updated host keys file: %s", err) + } + + ca, err := libtrust.GenerateCACert(trustKey, pubKey) + if err != nil { + return nil, fmt.Errorf("Error generating CA: %s", err) + } + tlsConfig.RootCAs.AddCert(ca) + } else { + return nil, fmt.Errorf("Cancelling request due to invalid certificate") + } + } else { + return nil, fmt.Errorf("TLS verification error: %s", err) + } + } + + testConn.Close() + tlsConfig.InsecureSkipVerify = false + + return tlsConfig, nil +} + +// NewCertAuthTLSConfig creates a tls.Config for the client to use for +// certificate authentication +func NewCertAuthTLSConfig(caPath, certPath, keyPath string) (*tls.Config, error) { + tlsConfig := createTLSConfig() + + // Verify the server against a CA certificate? + if caPath != "" { + certPool := x509.NewCertPool() + file, err := ioutil.ReadFile(caPath) + if err != nil { + return nil, fmt.Errorf("Couldn't read ca cert %s: %s", caPath, err) + } + certPool.AppendCertsFromPEM(file) + tlsConfig.RootCAs = certPool + } else { + tlsConfig.InsecureSkipVerify = true + } + + // Try to load and send client certificates + if certPath != "" && keyPath != "" { + _, errCert := os.Stat(certPath) + _, errKey := os.Stat(keyPath) + if errCert == nil && errKey == nil { + cert, err := tls.LoadX509KeyPair(certPath, keyPath) + if err != nil { + return nil, fmt.Errorf("Couldn't load X509 key pair: %s. Key encrypted?", err) + } + tlsConfig.Certificates = []tls.Certificate{cert} + } + } + return tlsConfig, nil +} + +// createTLSConfig creates the base tls.Config used by auth methods with some +// sensible defaults +func createTLSConfig() *tls.Config { + return &tls.Config{ + // Avoid fallback to SSL protocols < TLS1.0 + MinVersion: tls.VersionTLS10, + } +} + +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" +} diff --git a/api/common.go b/api/common.go index 3a46a8a5237a2..07aed0499d134 100644 --- a/api/common.go +++ b/api/common.go @@ -4,14 +4,14 @@ import ( "fmt" "mime" "os" - "path" + "path/filepath" "strings" log "github.com/Sirupsen/logrus" "github.com/docker/docker/engine" "github.com/docker/docker/pkg/parsers" "github.com/docker/docker/pkg/version" - "github.com/docker/docker/vendor/src/github.com/docker/libtrust" + "github.com/docker/libtrust" ) const ( @@ -54,7 +54,7 @@ func MatchesContentType(contentType, expectedType string) bool { // LoadOrCreateTrustKey attempts to load the libtrust key at the given path, // otherwise generates a new one func LoadOrCreateTrustKey(trustKeyPath string) (libtrust.PrivateKey, error) { - err := os.MkdirAll(path.Dir(trustKeyPath), 0700) + err := os.MkdirAll(filepath.Dir(trustKeyPath), 0700) if err != nil { return nil, err } @@ -67,6 +67,11 @@ func LoadOrCreateTrustKey(trustKeyPath string) (libtrust.PrivateKey, error) { if err := libtrust.SaveKey(trustKeyPath, trustKey); err != nil { return nil, fmt.Errorf("Error saving key file: %s", err) } + dir, file := filepath.Split(trustKeyPath) + // Save public key + if err := libtrust.SavePublicKey(filepath.Join(dir, "public-"+file), trustKey.PublicKey()); err != nil { + return nil, fmt.Errorf("Error saving public key file: %s", err) + } } else if err != nil { return nil, fmt.Errorf("Error loading key file: %s", err) } diff --git a/api/server/auth.go b/api/server/auth.go new file mode 100644 index 0000000000000..75c292ecf75f8 --- /dev/null +++ b/api/server/auth.go @@ -0,0 +1,177 @@ +package server + +import ( + "crypto/tls" + "crypto/x509" + "fmt" + "io/ioutil" + "net" + "os" + "path" + "sync" + + "github.com/docker/libtrust" +) + +// ClientKeyManager manages client keys on the filesystem +type ClientKeyManager struct { + key libtrust.PrivateKey + clientFile string + clientDir string + + clientLock sync.RWMutex + clients []libtrust.PublicKey + + configLock sync.Mutex + configs []*tls.Config +} + +// NewClientKeyManager loads a new manager from a set of key files +// and managed by the given private key. +func NewClientKeyManager(trustKey libtrust.PrivateKey, clientFile, clientDir string) (*ClientKeyManager, error) { + m := &ClientKeyManager{ + key: trustKey, + clientFile: clientFile, + clientDir: clientDir, + } + if err := m.loadKeys(); err != nil { + return nil, err + } + // TODO Start watching file and directory + + return m, nil +} +func (c *ClientKeyManager) loadKeys() error { + // Load authorized keys file + var clients []libtrust.PublicKey + if c.clientFile != "" { + fileClients, err := libtrust.LoadKeySetFile(c.clientFile) + if err != nil { + return fmt.Errorf("unable to load authorized keys: %s", err) + } + clients = fileClients + } + + // Add clients from authorized keys directory + files, err := ioutil.ReadDir(c.clientDir) + if err != nil && !os.IsNotExist(err) { + return fmt.Errorf("unable to open authorized keys directory: %s", err) + } + for _, f := range files { + if !f.IsDir() { + publicKey, err := libtrust.LoadPublicKeyFile(path.Join(c.clientDir, f.Name())) + if err != nil { + return fmt.Errorf("unable to load authorized key file: %s", err) + } + clients = append(clients, publicKey) + } + } + + c.clientLock.Lock() + c.clients = clients + c.clientLock.Unlock() + + return nil +} + +// RegisterTLSConfig registers a tls configuration to manager +// such that any changes to the keys may be reflected in +// the tls client CA pool +func (c *ClientKeyManager) RegisterTLSConfig(tlsConfig *tls.Config) error { + c.clientLock.RLock() + certPool, err := libtrust.GenerateCACertPool(c.key, c.clients) + if err != nil { + return fmt.Errorf("CA pool generation error: %s", err) + } + c.clientLock.RUnlock() + + tlsConfig.ClientCAs = certPool + + c.configLock.Lock() + c.configs = append(c.configs, tlsConfig) + c.configLock.Unlock() + + return nil +} + +// NewIdentityAuthTLSConfig creates a tls.Config for the server to use for +// libtrust identity authentication +func NewIdentityAuthTLSConfig(trustKey libtrust.PrivateKey, clients *ClientKeyManager, addr string) (*tls.Config, error) { + tlsConfig := createTLSConfig() + + tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert + if err := clients.RegisterTLSConfig(tlsConfig); err != nil { + return nil, err + } + + // Generate cert + ips, domains, err := parseAddr(addr) + if err != nil { + return nil, 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 nil, fmt.Errorf("certificate generation error: %s", err) + } + tlsConfig.Certificates = []tls.Certificate{{ + Certificate: [][]byte{x509Cert.Raw}, + PrivateKey: trustKey.CryptoPrivateKey(), + Leaf: x509Cert, + }} + + return tlsConfig, nil +} + +// NewCertAuthTLSConfig creates a tls.Config for the server to use for +// certificate authentication +func NewCertAuthTLSConfig(caPath, certPath, keyPath string) (*tls.Config, error) { + tlsConfig := createTLSConfig() + + cert, err := tls.LoadX509KeyPair(certPath, keyPath) + if err != nil { + return nil, fmt.Errorf("Couldn't load X509 key pair (%s, %s): %s. Key encrypted?", certPath, keyPath, err) + } + tlsConfig.Certificates = []tls.Certificate{cert} + + // Verify client certificates against a CA? + if caPath != "" { + certPool := x509.NewCertPool() + file, err := ioutil.ReadFile(caPath) + if err != nil { + return nil, fmt.Errorf("Couldn't read CA certificate: %s", err) + } + certPool.AppendCertsFromPEM(file) + + tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert + tlsConfig.ClientCAs = certPool + } + + return tlsConfig, nil +} + +func createTLSConfig() *tls.Config { + return &tls.Config{ + NextProtos: []string{"http/1.1"}, + // Avoid fallback on insecure SSL protocols + MinVersion: tls.VersionTLS10, + } +} + +// 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 +} diff --git a/api/server/server.go b/api/server/server.go index b3cf0603bb9dd..3595b16aa8c0b 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -3,7 +3,7 @@ package server import ( "bufio" "bytes" - + "crypto/tls" "encoding/base64" "encoding/json" "expvar" @@ -18,9 +18,6 @@ import ( "strings" "syscall" - "crypto/tls" - "crypto/x509" - "code.google.com/p/go.net/websocket" "github.com/docker/libcontainer/user" "github.com/gorilla/mux" @@ -1408,33 +1405,6 @@ func lookupGidByName(nameOrGid string) (int, error) { return -1, fmt.Errorf("Group %s not found", nameOrGid) } -func setupTls(cert, key, ca string, l net.Listener) (net.Listener, error) { - tlsCert, err := tls.LoadX509KeyPair(cert, key) - if err != nil { - return nil, fmt.Errorf("Couldn't load X509 key pair (%s, %s): %s. Key encrypted?", - cert, key, err) - } - tlsConfig := &tls.Config{ - NextProtos: []string{"http/1.1"}, - Certificates: []tls.Certificate{tlsCert}, - // Avoid fallback on insecure SSL protocols - MinVersion: tls.VersionTLS10, - } - - if ca != "" { - certPool := x509.NewCertPool() - file, err := ioutil.ReadFile(ca) - if err != nil { - return nil, fmt.Errorf("Couldn't read CA certificate: %s", err) - } - certPool.AppendCertsFromPEM(file) - tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert - tlsConfig.ClientCAs = certPool - } - - return tls.NewListener(l, tlsConfig), nil -} - func newListener(proto, addr string, bufferRequests bool) (net.Listener, error) { if bufferRequests { return listenbuffer.NewListenBuffer(proto, addr, activationLock) @@ -1497,10 +1467,6 @@ func setupUnixHttp(addr string, job *engine.Job) (*HttpServer, error) { } func setupTcpHttp(addr string, job *engine.Job) (*HttpServer, error) { - 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 /!\\") - } - r, err := createRouter(job.Eng, job.GetenvBool("Logging"), job.GetenvBool("EnableCors"), job.Getenv("Version")) if err != nil { return nil, err @@ -1511,16 +1477,37 @@ func setupTcpHttp(addr string, job *engine.Job) (*HttpServer, error) { return nil, err } - if job.GetenvBool("Tls") || job.GetenvBool("TlsVerify") { - var tlsCa string - if job.GetenvBool("TlsVerify") { - tlsCa = job.Getenv("TlsCa") + var tlsConfig *tls.Config + + switch job.Getenv("Auth") { + case "identity": + trustKey, err := api.LoadOrCreateTrustKey(job.Getenv("TrustKey")) + if err != nil { + return nil, err } - l, err = setupTls(job.Getenv("TlsCert"), job.Getenv("TlsKey"), tlsCa, l) + manager, err := NewClientKeyManager(trustKey, job.Getenv("TrustClients"), job.Getenv("TrustDir")) if err != nil { return nil, err } + if tlsConfig, err = NewIdentityAuthTLSConfig(trustKey, manager, addr); err != nil { + return nil, fmt.Errorf("Error creating TLS config: %s", err) + } + case "cert": + if tlsConfig, err = NewCertAuthTLSConfig(job.Getenv("AuthCa"), job.Getenv("AuthCert"), job.Getenv("AuthKey")); err != nil { + return nil, fmt.Errorf("Error creating TLS config: %s", err) + } + case "none": + tlsConfig = nil + default: + return nil, fmt.Errorf("Unknown auth method: %s", job.Getenv("Auth")) + } + + if tlsConfig == nil { + log.Infof("/!\\ DON'T BIND INSECURELY ON A TCP ADDRESS IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\") + } else { + l = tls.NewListener(l, tlsConfig) } + return &HttpServer{&http.Server{Addr: addr, Handler: r}, l}, nil } diff --git a/docker/daemon.go b/docker/daemon.go index 3128f7ee55cf7..0e6c68754e760 100644 --- a/docker/daemon.go +++ b/docker/daemon.go @@ -82,11 +82,12 @@ func mainDaemon() { job.Setenv("Version", dockerversion.VERSION) job.Setenv("SocketGroup", *flSocketGroup) - job.SetenvBool("Tls", *flTls) - job.SetenvBool("TlsVerify", *flTlsVerify) - job.Setenv("TlsCa", *flCa) - job.Setenv("TlsCert", *flCert) - job.Setenv("TlsKey", *flKey) + job.Setenv("Auth", *flAuth) + job.Setenv("AuthCa", *flAuthCa) + job.Setenv("AuthCert", *flAuthCert) + job.Setenv("AuthKey", *flAuthKey) + job.Setenv("TrustKey", *flTrustKey) + job.Setenv("TrustDir", *flTrustDir) job.SetenvBool("BufferRequests", true) if err := job.Run(); err != nil { log.Fatal(err) diff --git a/docker/docker.go b/docker/docker.go index 3137f5c99fc46..c89e341c50ccd 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -2,9 +2,7 @@ package main import ( "crypto/tls" - "crypto/x509" "fmt" - "io/ioutil" "os" "strings" @@ -17,13 +15,6 @@ import ( "github.com/docker/docker/utils" ) -const ( - defaultTrustKeyFile = "key.json" - defaultCaFile = "ca.pem" - defaultKeyFile = "key.pem" - defaultCertFile = "cert.pem" -) - func main() { if reexec.Init() { return @@ -54,6 +45,29 @@ func main() { initLogging(log.DebugLevel) } + // Backwards compatibility for deprecated --tls and --tlsverify options + if *flTls || flag.IsSet("-tlsverify") { + *flAuth = "cert" + + // Backwards compatibility for --tlscacert + if *flCa != "" { + *flAuthCa = *flCa + } + // Backwards compatibility for --tlscert + if *flCert != "" { + *flAuthCert = *flCert + } + // Backwards compatibility for --tlskey + if *flKey != "" { + *flAuthKey = *flKey + } + + // Only verify against a CA if --tlsverify is set + if !*flTlsVerify { + *flAuthCa = "" + } + } + if len(flHosts) == 0 { defaultHost := os.Getenv("DOCKER_HOST") if defaultHost == "" || *flDaemon { @@ -76,52 +90,33 @@ func main() { log.Fatal("Please specify only one -H") } protoAddrParts := strings.SplitN(flHosts[0], "://", 2) + proto, addr := protoAddrParts[0], protoAddrParts[1] - var ( - cli *client.DockerCli - tlsConfig tls.Config - ) - tlsConfig.InsecureSkipVerify = true - - // Regardless of whether the user sets it to true or false, if they - // specify --tlsverify at all then we need to turn on tls - if flag.IsSet("-tlsverify") { - *flTls = true + trustKey, err := api.LoadOrCreateTrustKey(*flTrustKey) + if err != nil { + log.Fatal(err) } - // If we should verify the server, we need to load a trusted ca - if *flTlsVerify { - certPool := x509.NewCertPool() - file, err := ioutil.ReadFile(*flCa) - if err != nil { - log.Fatalf("Couldn't read ca cert %s: %s", *flCa, err) - } - certPool.AppendCertsFromPEM(file) - tlsConfig.RootCAs = certPool - tlsConfig.InsecureSkipVerify = false - } + var tlsConfig *tls.Config - // 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 { - log.Fatalf("Couldn't load X509 key pair: %s. Key encrypted?", err) + if proto != "unix" { + switch *flAuth { + case "identity": + if tlsConfig, err = client.NewIdentityAuthTLSConfig(trustKey, *flTrustHosts, proto, addr); err != nil { + log.Fatal(err) + } + case "cert": + if tlsConfig, err = client.NewCertAuthTLSConfig(*flAuthCa, *flAuthCert, *flAuthKey); err != nil { + log.Fatal(err) } - tlsConfig.Certificates = []tls.Certificate{cert} + case "none": + tlsConfig = nil + default: + log.Fatalf("Unknown auth method: %s", *flAuth) } - // Avoid fallback to SSL protocols < TLS1.0 - tlsConfig.MinVersion = tls.VersionTLS10 } - if *flTls || *flTlsVerify { - cli = client.NewDockerCli(os.Stdin, os.Stdout, os.Stderr, nil, 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, proto, addr, tlsConfig) if err := cli.Cmd(flag.Args()...); err != nil { if sterr, ok := err.(*utils.StatusError); ok { diff --git a/docker/flags.go b/docker/flags.go index 6601b4fe8a9d4..f1b732de23d34 100644 --- a/docker/flags.go +++ b/docker/flags.go @@ -10,17 +10,26 @@ import ( flag "github.com/docker/docker/pkg/mflag" ) +const ( + defaultTrustKeyFile = "key.json" + defaultHostKeysFile = "known-hosts.json" + defaultClientKeysDir = "authorized-keys.d" + defaultCaFile = "ca.pem" + defaultKeyFile = "key.pem" + defaultCertFile = "cert.pem" +) + var ( + dockerAuth = os.Getenv("DOCKER_AUTH") + dockerAuthCert = os.Getenv("DOCKER_AUTH_CERT") + dockerAuthKey = os.Getenv("DOCKER_AUTH_KEY") + dockerAuthCa = os.Getenv("DOCKER_AUTH_CA") + + // Deprecated TLS environment variables dockerCertPath = os.Getenv("DOCKER_CERT_PATH") dockerTlsVerify = os.Getenv("DOCKER_TLS_VERIFY") != "" ) -func init() { - if dockerCertPath == "" { - dockerCertPath = filepath.Join(getHomeDir(), ".docker") - } -} - func getHomeDir() string { if runtime.GOOS == "windows" { return os.Getenv("USERPROFILE") @@ -35,27 +44,46 @@ var ( flSocketGroup = flag.String([]string{"G", "-group"}, "docker", "Group to assign the unix socket specified by -H when running in daemon mode\nuse '' (the empty string) to disable setting of a group") flLogLevel = flag.String([]string{"l", "-log-level"}, "info", "Set the logging level") 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 --tlsverify flag") - flTlsVerify = flag.Bool([]string{"-tlsverify"}, dockerTlsVerify, "Use TLS and verify the remote (daemon: verify client, client: verify daemon)") + flAuthCa = flag.String([]string{"-auth-ca"}, dockerAuthCa, "CA to verify remotes against in cert auth mode") + flAuthCert = flag.String([]string{"-auth-cert"}, dockerAuthCert, "Certificate to present to remote in cert auth mode") + flAuthKey = flag.String([]string{"-auth-key"}, dockerAuthKey, "Private key for cert auth") + // Deprecated TLS options + flTls = flag.Bool([]string{"#-tls"}, false, "Use TLS; implied by --tlsverify=true") + flTlsVerify = flag.Bool([]string{"#-tlsverify"}, dockerTlsVerify, "Use TLS and verify the remote (daemon: verify client, client: verify daemon)") // 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 + flAuth *string + flTrustKey *string + flTrustHosts *string + flTrustDir *string + flCa *string + flCert *string + flKey *string + flHosts []string ) func init() { - // placeholder for trust key flag - trustKeyDefault := filepath.Join(dockerCertPath, defaultTrustKeyFile) - flTrustKey = &trustKeyDefault + dockerHome := filepath.Join(getHomeDir(), ".docker") + + if dockerAuth == "" { + dockerAuth = "none" + } + if dockerCertPath == "" { + dockerCertPath = dockerHome + } + + flAuth = flag.String([]string{"-auth"}, dockerAuth, "Method used to authenticate the connection between client and daemon. Possible methods: identity, cert, none") + flTrustHosts = flag.String([]string{"-auth-known-hosts"}, filepath.Join(dockerHome, defaultHostKeysFile), "Path to file containing known hosts for identity auth") + flTrustDir = flag.String([]string{"-auth-authorized-dir"}, filepath.Join(dockerHome, defaultClientKeysDir), "Path to directory containing authorized public key files for identity auth") + flTrustKey = flag.String([]string{"i", "-identity"}, filepath.Join(dockerHome, 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") opts.HostListVar(&flHosts, []string{"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.") + // Deprecated TLS options + flCa = flag.String([]string{"#-tlscacert"}, filepath.Join(dockerCertPath, defaultCaFile), "Deprecated: Trust only remotes providing a certificate signed by the CA given here") + flCert = flag.String([]string{"#-tlscert"}, filepath.Join(dockerCertPath, defaultCertFile), "Deprecated: Path to TLS certificate file") + flKey = flag.String([]string{"#-tlskey"}, filepath.Join(dockerCertPath, defaultKeyFile), "Deprecated: Path to TLS key file") + flag.Usage = func() { fmt.Fprint(os.Stderr, "Usage: docker [OPTIONS] COMMAND [arg...]\n\nA self-sufficient runtime for linux containers.\n\nOptions:\n") diff --git a/docs/man/docker.1.md b/docs/man/docker.1.md index c8d28b2c238a9..0970dc7653584 100644 --- a/docs/man/docker.1.md +++ b/docs/man/docker.1.md @@ -34,6 +34,9 @@ unix://[/path/to/socket] to use. **--api-enable-cors**=*true*|*false* Enable CORS headers in the remote API. Default is false. +**--auth**=identity|cert|none + Set authentication method for tcp socket. Default is `none`. + **-b**="" Attach containers to a pre\-existing network bridge; use 'none' to disable container networking diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index f5ea845e95754..10fb386c41a85 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -85,7 +85,7 @@ pages: - ['articles/basics.md', 'Articles', 'Docker basics'] - ['articles/networking.md', 'Articles', 'Advanced networking'] - ['articles/security.md', 'Articles', 'Security'] -- ['articles/https.md', 'Articles', 'Running Docker with HTTPS'] +- ['articles/authentication.md', 'Articles', 'Authenticating to the Docker daemon'] - ['articles/host_integration.md', 'Articles', 'Automatically starting containers'] - ['articles/baseimages.md', 'Articles', 'Creating a base image'] - ['articles/dockerfile_best-practices.md', 'Articles', 'Best practices for writing Dockerfiles'] diff --git a/docs/s3_website.json b/docs/s3_website.json index 224ba816e28c4..f79de4cc9f474 100644 --- a/docs/s3_website.json +++ b/docs/s3_website.json @@ -26,7 +26,8 @@ { "Condition": { "KeyPrefixEquals": "use/host_integration/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "articles/host_integration/" } }, { "Condition": { "KeyPrefixEquals": "docker-io/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "docker-hub/" } }, { "Condition": { "KeyPrefixEquals": "examples/cfengine_process_management/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "articles/cfengine_process_management/" } }, - { "Condition": { "KeyPrefixEquals": "examples/https/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "articles/https/" } }, + { "Condition": { "KeyPrefixEquals": "examples/https/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "articles/authentication/" } }, + { "Condition": { "KeyPrefixEquals": "articles/https/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "articles/authentication/" } }, { "Condition": { "KeyPrefixEquals": "examples/ambassador_pattern_linking/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "articles/ambassador_pattern_linking/" } }, { "Condition": { "KeyPrefixEquals": "examples/using_supervisord/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "articles/using_supervisord/" } }, { "Condition": { "KeyPrefixEquals": "reference/api/registry_index_spec/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "reference/api/hub_registry_spec/" } }, diff --git a/docs/sources/articles/https.md b/docs/sources/articles/authentication.md similarity index 53% rename from docs/sources/articles/https.md rename to docs/sources/articles/authentication.md index c8873bcbe4dd0..73c996c9e25e5 100644 --- a/docs/sources/articles/https.md +++ b/docs/sources/articles/authentication.md @@ -1,31 +1,112 @@ -page_title: Running Docker with HTTPS -page_description: How to setup and run Docker with HTTPS -page_keywords: docker, docs, article, example, https, daemon, tls, ca, certificate +page_title: Authenticating to the Docker daemon +page_description: How to setup the Docker daemon with encryption and authentication +page_keywords: docker, docs, article, example, https, daemon, tls, ca, certificate, authentication # Running Docker with https By default, Docker runs via a non-networked Unix socket. It can also -optionally communicate using a HTTP socket. +optionally communicate using a HTTP socket. Enabling communication +through HTTP will by default use TLS and require daemon configuration +to allow client connections. -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. + +There are two different ways of authenticating connections between Docker +client and daemon, both of which use secure TLS connections. + + - **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 +authorize connections. Using this method requires additional setup to enable +client authentication. + +The authentication method is selected using the `--auth` flag with values + `identity`, `cert`, or `none` . The `none` method is currently the default but +`identity` will become the default in a future version. + +> **Note**: +> The `--tls` and `--tlsverify` options in Docker 1.3 and earlier have +> been replaced by the `--auth=cert` option. The old options have been +> deprecated. + +## Identity-based authentication + +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 +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.d/`). +Every public key file in the authorized key directory represents a client which +is authorized to connect using that key. + +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. + - `--auth-authorized-keys` - specifies the directory containing the client +public key files to whitelist. This is a daemon configuration and the directory +should have its write permissions restricted. + - `--auth-known-hosts` - specifies the list of daemon public key fingerprints +which have been approved by the user and the host name associated with +each fingerprint. + +To setup a new client connection, copy the `~/.docker/public-key.json` +file on the client machine to the `~/.docker/authorized-keys.d/` directory on +the daemon machine. The copied file should keep the same suffix (e.g. `.json`, +`.jwk` or `.pem`) but otherwise the name may be changed to something which +meaningfully identities the client to the user. + +## Certificate-based authentication + +Certificate-based authentication uses TLS certificates provided by a +Certificate Authority (CA). 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 `--auth-ca` 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, it will only connect to servers with a certificate signed by that CA. -> **Warning**: +### 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. > **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 - First, initialize the CA serial file and generate CA private and public keys: @@ -116,60 +197,26 @@ 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**: > 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 > 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 - -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, @@ -177,11 +224,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` -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` diff --git a/docs/sources/articles/certificates.md b/docs/sources/articles/certificates.md index e031676402f0d..807e100b0a5aa 100644 --- a/docs/sources/articles/certificates.md +++ b/docs/sources/articles/certificates.md @@ -4,9 +4,10 @@ page_keywords: Usage, registry, repository, client, root, certificate, docker, a # Using certificates for repository client verification -In [Running Docker with HTTPS](/articles/https), you learned that, by default, -Docker runs via a non-networked Unix socket and TLS must be enabled in order -to have the Docker client and the daemon communicate securely over HTTPS. +In [Authenticating to the Docker daemon](/articles/authentication), you learned +that, by default, Docker runs via a non-networked Unix socket and authentication +must be enabled in order to have the Docker client and the daemon communicate +securely. Now, you will see how to allow the Docker registry (i.e., *a server*) to verify that the Docker daemon (i.e., *a client*) has the right to access the @@ -45,15 +46,15 @@ Our example is set up like this: ## Creating the client certificates You will use OpenSSL's `genrsa` and `req` commands to first generate an RSA -key and then use the key to create the certificate request. +key and then use the key to create the certificate request. $ openssl genrsa -out client.key 1024 $ openssl req -new -x509 -text -key client.key -out client.cert -> **Warning:**: +> **Warning:**: > Using TLS and managing a CA is an advanced topic. > You should be familiar with OpenSSL, x509, and TLS before -> attempting to use them in production. +> attempting to use them in production. > **Warning:** > These TLS commands will only generate a working set of certificates on Linux. diff --git a/docs/sources/articles/security.md b/docs/sources/articles/security.md index 12f7b350ec185..0cd26ddfa484e 100644 --- a/docs/sources/articles/security.md +++ b/docs/sources/articles/security.md @@ -110,7 +110,7 @@ However, if you do that, being aware of the above mentioned security implication, you should ensure that it will be reachable only from a trusted network or VPN; or protected with e.g., `stunnel` and client SSL certificates. You can also secure them with [HTTPS and -certificates](/articles/https/). +certificates](/articles/authentication/). Recent improvements in Linux namespaces will soon allow to run full-featured containers without root privileges, thanks to the new user diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index 07cc578eb2f5a..d218fa8106a89 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -84,11 +84,14 @@ expect an integer, and they can only be specified once. -s, --storage-driver="" Force the Docker runtime to use a specific storage driver --selinux-enabled=false Enable selinux support. SELinux does not presently support the BTRFS storage driver --storage-opt=[] Set storage driver options - --tls=false Use TLS; implied by --tlsverify flag - --tlscacert="/home/sven/.docker/ca.pem" Trust only remotes providing a certificate signed by the CA given here - --tlscert="/home/sven/.docker/cert.pem" Path to TLS certificate file - --tlskey="/home/sven/.docker/key.pem" Path to TLS key file - --tlsverify=false Use TLS and verify the remote (daemon: verify client, client: verify daemon) + --auth="none" Set authentication method when using tcp socket host value in -H or --host (none, identity, cert) + --auth-ca="" Path to CA certificate when using `cert` auth, only remotes providing a certificate signed by this CA are trusted + --auth-cert="" Path to TLS certificate file when using `cert` auth + --auth-key="" Path to TLS key file when using `cert` auth + --auth-authorized-keys="" Path to authorized keys directory containing public key files to whitelist when using `identity` auth + defaults to "~/.docker/authorized-keys.d/" + --auth-known-hosts="" Path to known hosts file containing list of daemon public key fingerprints when using `identity` auth + defaults to "~/.docker/known-hosts.json" -v, --version=false Print version information and quit Options with [] may be specified multiple times. @@ -111,7 +114,7 @@ requiring either `root` permission, or `docker` group membership. If you need to access the Docker daemon remotely, you need to enable the `tcp` Socket. Beware that the default setup provides un-encrypted and un-authenticated direct access to the Docker daemon - and should be secured either using the -[built in HTTPS encrypted socket](/articles/https/), or by putting a secure web +[built in authentication](/articles/authentication/), or by putting a secure web proxy in front of it. You can listen on port `2375` on all network interfaces with `-H tcp://0.0.0.0:2375`, or on a particular network interface using its IP address: `-H tcp://192.168.59.103:2375`. It is conventional to use port `2375` @@ -145,12 +148,15 @@ the `-H` flag for the client. $ sudo docker ps # both are equal -Setting the `DOCKER_TLS_VERIFY` environment variable to any value other than the empty -string is equivalent to setting the `--tlsverify` flag. The following are equivalent: +Setting the `DOCKER_AUTH` environment variable to any value other than the empty +string is equivalent to setting the `--auth` flag. The environment variables +`DOCKER_AUTH_CA`, `DOCKER_AUTH_CERT` and `DOCKER_AUTH_KEY` also correspond with +flags of the same name. The following are equivalent: - $ sudo docker --tlsverify ps + $ sudo docker --auth=cert --auth-ca=ca.pem ps # or - $ export DOCKER_TLS_VERIFY=1 + $ export DOCKER_AUTH=cert + $ export DOCKER_AUTH_CA=ca.pem $ sudo docker ps ### Daemon storage-driver option @@ -214,7 +220,7 @@ 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: +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 @@ -1663,4 +1669,3 @@ both Docker client and daemon. Usage: docker wait CONTAINER [CONTAINER...] Block until a container stops, then print its exit code. - diff --git a/docs/sources/release-notes.md b/docs/sources/release-notes.md index b1b3b2bfdf44e..71e018b35c065 100644 --- a/docs/sources/release-notes.md +++ b/docs/sources/release-notes.md @@ -90,7 +90,7 @@ line reference](/reference/commandline/cli/#run). * You can now set a `DOCKER_TLS_VERIFY` environment variable to secure connections by default (rather than having to pass the `--tlsverify` flag on -every call). For more information, see the [https guide](/articles/https). +every call). * Three security issues have been addressed in this release: [CVE-2014-5280, CVE-2014-5270, and diff --git a/integration-cli/docker_cli_auth_test.go b/integration-cli/docker_cli_auth_test.go new file mode 100644 index 0000000000000..3318fdf3292b5 --- /dev/null +++ b/integration-cli/docker_cli_auth_test.go @@ -0,0 +1,440 @@ +package main + +import ( + "os/exec" + "strings" + "testing" +) + +const testDaemonURL = "tcp://localhost:4272" + +func TestAuthIdentityWithGoodKeys(t *testing.T) { + d := NewDaemon(t) + if err := d.Start( + "-H", testDaemonURL, + "--auth", "identity", + "--identity", "fixtures/https/private-key-1.json", + "--auth-authorized-keys", "fixtures/https/public-key-2.json", + ); err != nil { + t.Fatalf("Could not start daemon: %v", err) + } + defer d.Stop() + + // ensure basic connection + cmd := exec.Command( + dockerBinary, + "-H", testDaemonURL, + "--auth", "identity", + "--identity", "fixtures/https/private-key-2.json", + "--auth-known-hosts", "fixtures/https/public-key-1.json", + "info", + ) + out, _, err := runCommandWithOutput(cmd) + if err != nil { + t.Fatalf("failed to execute command: %s, %v", out, err) + } + + logDone("auth - identity mode with good keys") +} + +func TestAuthIdentityWithBadServerKey(t *testing.T) { + d := NewDaemon(t) + if err := d.Start( + "-H", testDaemonURL, + "--auth", "identity", + "--identity", "fixtures/https/private-key-3.json", + "--auth-authorized-keys", "fixtures/https/public-key-2.json", + ); err != nil { + t.Fatalf("Could not start daemon: %v", err) + } + defer d.Stop() + + // ensure basic connection + cmd := exec.Command( + dockerBinary, + "-H", testDaemonURL, + "--auth", "identity", + "--identity", "fixtures/https/private-key-2.json", + "--auth-known-hosts", "fixtures/https/public-key-1.json", + "info", + ) + out, _, err := runCommandWithOutput(cmd) + if err == nil { + t.Fatalf("command should have failed: %s", out) + } + if !strings.Contains(out, "The authenticity of host \"localhost:4272\" can't be established.") { + t.Errorf("command output should have contained 'The authenticity of host \"localhost:4272\" can't be established.': %s", out) + } + + logDone("auth - identity mode with bad server key") +} + +func TestAuthIdentityWithBadClientKey(t *testing.T) { + d := NewDaemon(t) + if err := d.Start( + "-H", testDaemonURL, + "--auth", "identity", + "--identity", "fixtures/https/private-key-1.json", + "--auth-authorized-keys", "fixtures/https/public-key-2.json", + ); err != nil { + t.Fatalf("Could not start daemon: %v", err) + } + defer d.Stop() + + // ensure basic connection + cmd := exec.Command( + dockerBinary, + "-H", testDaemonURL, + "--auth", "identity", + "--identity", "fixtures/https/private-key-3.json", + "--auth-known-hosts", "fixtures/https/public-key-1.json", + "info", + ) + out, _, err := runCommandWithOutput(cmd) + if err == nil { + t.Fatalf("command should have failed: %s", out) + } + if !strings.Contains(out, "remote error: bad certificate") { + t.Errorf("command output should have contained 'remote error: bad certificate': %s", out) + } + + logDone("auth - identity mode with bad client key") +} + +func TestAuthCert(t *testing.T) { + d := NewDaemon(t) + if err := d.Start( + "-H", testDaemonURL, + "--auth", "cert", + "--auth-cert", "fixtures/https/server-cert.pem", + "--auth-key", "fixtures/https/server-key.pem", + ); err != nil { + t.Fatalf("Could not start daemon: %v", err) + } + defer d.Stop() + + // ensure basic TLS connection + cmd := exec.Command( + dockerBinary, + "-H", testDaemonURL, + "--auth", "cert", + "info", + ) + out, _, err := runCommandWithOutput(cmd) + if err != nil { + t.Fatalf("failed to execute command: %s, %v", out, err) + } + + // ensure verification with CA works + cmd = exec.Command( + dockerBinary, + "-H", testDaemonURL, + "--auth", "cert", + "--auth-ca", "fixtures/https/ca.pem", + "info", + ) + out, _, err = runCommandWithOutput(cmd) + if err != nil { + t.Fatalf("failed to execute command: %s, %v", out, err) + } + + // ensure connecting to server without TLS enabled fails + cmd = exec.Command( + dockerBinary, + "-H", testDaemonURL, + "--auth", "none", + "info", + ) + out, _, err = runCommandWithOutput(cmd) + if err == nil { + t.Fatalf("command with --auth=none should have failed: %s", out) + } + if !strings.Contains(out, "malformed HTTP response") { + t.Errorf("command output should have contained 'malformed HTTP response': %s", out) + } + + logDone("auth - cert mode, client verifying server") +} + +func TestAuthCertWithBadCert(t *testing.T) { + d := NewDaemon(t) + if err := d.Start( + "-H", testDaemonURL, + "--auth", "cert", + "--auth-cert", "fixtures/https/server-rogue-cert.pem", + "--auth-key", "fixtures/https/server-rogue-key.pem", + ); err != nil { + t.Fatalf("Could not start daemon: %v", err) + } + defer d.Stop() + + // ensure verification fails + cmd := exec.Command( + dockerBinary, + "-H", testDaemonURL, + "--auth", "cert", + "--auth-ca", "fixtures/https/ca.pem", + "info", + ) + out, _, err := runCommandWithOutput(cmd) + if err == nil { + t.Fatalf("command without cert and key should have failed: %s", out) + } + if !strings.Contains(out, "certificate signed by unknown authority") { + t.Errorf("command output should have contained 'certificate signed by unknown authority': %s", out) + } + + logDone("auth - cert mode, client rejects bad certificate") +} + +func TestAuthCertClientCert(t *testing.T) { + d := NewDaemon(t) + if err := d.Start( + "-H", testDaemonURL, + "--auth", "cert", + "--auth-cert", "fixtures/https/server-cert.pem", + "--auth-key", "fixtures/https/server-key.pem", + "--auth-ca", "fixtures/https/ca.pem", + ); err != nil { + t.Fatalf("Could not start daemon: %v", err) + } + defer d.Stop() + + // ensure basic client cert works + cmd := exec.Command( + dockerBinary, + "-H", testDaemonURL, + "--auth", "cert", + "--auth-cert", "fixtures/https/client-cert.pem", + "--auth-key", "fixtures/https/client-key.pem", + "info", + ) + out, _, err := runCommandWithOutput(cmd) + if err != nil { + t.Fatalf("failed to execute command: %s, %v", out, err) + } + + // ensure client cert with --tlsverify works + cmd = exec.Command( + dockerBinary, + "-H", testDaemonURL, + "--auth", "cert", + "--auth-cert", "fixtures/https/client-cert.pem", + "--auth-key", "fixtures/https/client-key.pem", + "--auth-ca", "fixtures/https/ca.pem", + "info", + ) + out, _, err = runCommandWithOutput(cmd) + if err != nil { + t.Fatalf("failed to execute command: %s, %v", out, err) + } + + // ensure not passing certs fails + cmd = exec.Command( + dockerBinary, + "-H", testDaemonURL, + "--auth", "cert", + "info", + ) + out, _, err = runCommandWithOutput(cmd) + if err == nil { + t.Fatalf("command should have failed: %s", out) + } + if !strings.Contains(out, "bad certificate") { + t.Errorf("command output should have contained 'bad certificate': %s", out) + } + + // ensure passing incorrect certs fails + cmd = exec.Command( + dockerBinary, + "-H", testDaemonURL, + "--auth", "cert", + "--auth-cert", "fixtures/https/client-rogue-cert.pem", + "--auth-key", "fixtures/https/client-rogue-key.pem", + "info", + ) + out, _, err = runCommandWithOutput(cmd) + if err == nil { + t.Fatalf("command should have failed: %s", out) + } + if !strings.Contains(out, "bad certificate") { + t.Errorf("command output should have contained 'bad certificate': %s", out) + } + + logDone("auth - cert mode, client certificates") +} + +func TestAuthTLS(t *testing.T) { + d := NewDaemon(t) + if err := d.Start( + "-H", testDaemonURL, + "--tls", + "--tlscert", "fixtures/https/server-cert.pem", + "--tlskey", "fixtures/https/server-key.pem", + ); err != nil { + t.Fatalf("Could not start daemon: %v", err) + } + defer d.Stop() + + // ensure basic TLS connection + cmd := exec.Command( + dockerBinary, + "-H", testDaemonURL, + "--tls", + "info", + ) + out, _, err := runCommandWithOutput(cmd) + if err != nil { + t.Fatalf("failed to execute command: %s, %v", out, err) + } + + // ensure basic TLS connection is triggered with --tlsverify=false + cmd = exec.Command( + dockerBinary, + "-H", testDaemonURL, + "--tlsverify=false", + "info", + ) + out, _, err = runCommandWithOutput(cmd) + if err != nil { + t.Fatalf("failed to execute command: %s, %v", out, err) + } + + // ensure verification with CA works + cmd = exec.Command( + dockerBinary, + "-H", testDaemonURL, + "--tlsverify", + "--tlscacert", "fixtures/https/ca.pem", + "info", + ) + out, _, err = runCommandWithOutput(cmd) + if err != nil { + t.Fatalf("failed to execute command: %s, %v", out, err) + } + + // ensure connecting to server without TLS enabled fails + cmd = exec.Command( + dockerBinary, + "-H", testDaemonURL, + "--auth", "none", + "info", + ) + out, _, err = runCommandWithOutput(cmd) + if err == nil { + t.Fatalf("command without --tls should have failed: %s", out) + } + if !strings.Contains(out, "malformed HTTP response") { + t.Errorf("command output should have contained 'malformed HTTP response': %s", out) + } + + logDone("auth - client verifying server with TLS options") +} + +func TestAuthTLSVerifyWithBadCert(t *testing.T) { + d := NewDaemon(t) + if err := d.Start( + "-H", testDaemonURL, + "--tls", + "--tlscert", "fixtures/https/server-rogue-cert.pem", + "--tlskey", "fixtures/https/server-rogue-key.pem", + ); err != nil { + t.Fatalf("Could not start daemon: %v", err) + } + defer d.Stop() + + // ensure verification fails + cmd := exec.Command( + dockerBinary, + "-H", testDaemonURL, + "--tlsverify", + "--tlscacert", "fixtures/https/ca.pem", + "info", + ) + out, _, err := runCommandWithOutput(cmd) + if err == nil { + t.Fatalf("command without cert and key should have failed: %s", out) + } + if !strings.Contains(out, "certificate signed by unknown authority") { + t.Errorf("command output should have contained 'certificate signed by unknown authority': %s", out) + } + + logDone("auth - client rejects bad certificate with TLS options") +} + +func TestAuthTLSClientCert(t *testing.T) { + d := NewDaemon(t) + if err := d.Start( + "-H", testDaemonURL, + "--tlsverify", + "--tlscert", "fixtures/https/server-cert.pem", + "--tlskey", "fixtures/https/server-key.pem", + "--tlscacert", "fixtures/https/ca.pem", + ); err != nil { + t.Fatalf("Could not start daemon: %v", err) + } + defer d.Stop() + + // ensure basic client cert works + cmd := exec.Command( + dockerBinary, + "-H", testDaemonURL, + "--tls", + "--tlscert", "fixtures/https/client-cert.pem", + "--tlskey", "fixtures/https/client-key.pem", + "info", + ) + out, _, err := runCommandWithOutput(cmd) + if err != nil { + t.Fatalf("failed to execute command: %s, %v", out, err) + } + + // ensure client cert with --tlsverify works + cmd = exec.Command( + dockerBinary, + "-H", testDaemonURL, + "--tlsverify", + "--tlscert", "fixtures/https/client-cert.pem", + "--tlskey", "fixtures/https/client-key.pem", + "--tlscacert", "fixtures/https/ca.pem", + "info", + ) + out, _, err = runCommandWithOutput(cmd) + if err != nil { + t.Fatalf("failed to execute command: %s, %v", out, err) + } + + // ensure not passing certs fails + cmd = exec.Command( + dockerBinary, + "-H", testDaemonURL, + "--tls", + "info", + ) + out, _, err = runCommandWithOutput(cmd) + if err == nil { + t.Fatalf("command should have failed: %s", out) + } + if !strings.Contains(out, "bad certificate") { + t.Errorf("command output should have contained 'bad certificate': %s", out) + } + + // ensure passing incorrect certs fails + cmd = exec.Command( + dockerBinary, + "-H", testDaemonURL, + "--tls", + "--tlscert", "fixtures/https/client-rogue-cert.pem", + "--tlskey", "fixtures/https/client-rogue-key.pem", + "info", + ) + out, _, err = runCommandWithOutput(cmd) + if err == nil { + t.Fatalf("command should have failed: %s", out) + } + if !strings.Contains(out, "bad certificate") { + t.Errorf("command output should have contained 'bad certificate': %s", out) + } + + logDone("auth - client certificates with TLS options") +} diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index 9546af0014c78..ed9dcf472055c 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -2688,28 +2688,3 @@ func TestContainerNetworkMode(t *testing.T) { logDone("run - container shared network namespace") } - -func TestRunTLSverify(t *testing.T) { - cmd := exec.Command(dockerBinary, "ps") - out, ec, err := runCommandWithOutput(cmd) - if err != nil || ec != 0 { - t.Fatalf("Should have worked: %v:\n%v", err, out) - } - - // Regardless of whether we specify true or false we need to - // test to make sure tls is turned on if --tlsverify is specified at all - - cmd = exec.Command(dockerBinary, "--tlsverify=false", "ps") - out, ec, err = runCommandWithOutput(cmd) - if err == nil || ec == 0 || !strings.Contains(out, "trying to connect") { - t.Fatalf("Should have failed: \nec:%v\nout:%v\nerr:%v", ec, out, err) - } - - cmd = exec.Command(dockerBinary, "--tlsverify=true", "ps") - out, ec, err = runCommandWithOutput(cmd) - if err == nil || ec == 0 || !strings.Contains(out, "cert") { - t.Fatalf("Should have failed: \nec:%v\nout:%v\nerr:%v", ec, out, err) - } - - logDone("run - verify tls is set for --tlsverify") -} diff --git a/integration-cli/docker_utils.go b/integration-cli/docker_utils.go index ba1a0b1306b17..87549c5f6477e 100644 --- a/integration-cli/docker_utils.go +++ b/integration-cli/docker_utils.go @@ -154,6 +154,12 @@ func (d *Daemon) Start(arg ...string) error { d.t.Log("daemon started") return nil + case <-d.wait: + output, err := ioutil.ReadFile(d.logFile.Name()) + if err != nil { + return fmt.Errorf("Daemon exited before it start serving requests. Error reading output from daemon: %s", err) + } + return fmt.Errorf("Daemon exited before it started serving requests. Output:\n%s", output) } } } diff --git a/integration/fixtures/https/ca.pem b/integration-cli/fixtures/https/ca.pem similarity index 100% rename from integration/fixtures/https/ca.pem rename to integration-cli/fixtures/https/ca.pem diff --git a/integration/fixtures/https/client-cert.pem b/integration-cli/fixtures/https/client-cert.pem similarity index 100% rename from integration/fixtures/https/client-cert.pem rename to integration-cli/fixtures/https/client-cert.pem diff --git a/integration/fixtures/https/client-key.pem b/integration-cli/fixtures/https/client-key.pem similarity index 100% rename from integration/fixtures/https/client-key.pem rename to integration-cli/fixtures/https/client-key.pem diff --git a/integration/fixtures/https/client-rogue-cert.pem b/integration-cli/fixtures/https/client-rogue-cert.pem similarity index 100% rename from integration/fixtures/https/client-rogue-cert.pem rename to integration-cli/fixtures/https/client-rogue-cert.pem diff --git a/integration/fixtures/https/client-rogue-key.pem b/integration-cli/fixtures/https/client-rogue-key.pem similarity index 100% rename from integration/fixtures/https/client-rogue-key.pem rename to integration-cli/fixtures/https/client-rogue-key.pem diff --git a/integration-cli/fixtures/https/private-key-1.json b/integration-cli/fixtures/https/private-key-1.json new file mode 100644 index 0000000000000..c686fa52ca2c0 --- /dev/null +++ b/integration-cli/fixtures/https/private-key-1.json @@ -0,0 +1,8 @@ +{ + "crv": "P-256", + "d": "AITQyfsSpfi4d6ewvcs7-MsVct5EIXnJ8ZT_2iNlv70", + "kid": "FDTR:VRAS:HWMP:MVLO:XVN4:OQRA:QWUS:PMZX:ANM5:6AB3:IZVG:MELL", + "kty": "EC", + "x": "qt6kkQOC8GCGPxjHPaHkvbou0gX_4YXWzA2ExvmM7co", + "y": "QIOEwDF1pbSrcC0XIAHx9QeZG7qBX0rS5AhGtw-5X4Y" +} \ No newline at end of file diff --git a/integration-cli/fixtures/https/private-key-2.json b/integration-cli/fixtures/https/private-key-2.json new file mode 100644 index 0000000000000..02b2aeeed65b7 --- /dev/null +++ b/integration-cli/fixtures/https/private-key-2.json @@ -0,0 +1,8 @@ +{ + "crv": "P-256", + "d": "7n2qxPU2_4CuFQKSoSlObO3IyAjN0tCuhJUPhv5uqHo", + "kid": "2IUQ:R3EY:7DDM:TAIV:653R:QQVO:W5OZ:DEMO:XBHS:4ET7:6ZFO:D4UP", + "kty": "EC", + "x": "0rgsH0zVAY61DgSACt7FwGiAsvHNQaWL6028Sg-5NUM", + "y": "kU58U2i97SVN8ETuXd87aMkKfroLYY5ICKiiyDlNwfI" +} \ No newline at end of file diff --git a/integration-cli/fixtures/https/private-key-3.json b/integration-cli/fixtures/https/private-key-3.json new file mode 100644 index 0000000000000..6eded791d5336 --- /dev/null +++ b/integration-cli/fixtures/https/private-key-3.json @@ -0,0 +1,8 @@ +{ + "crv": "P-256", + "d": "yonHc66-w9m0bhHkNRKAsc94OQDgYWx_A9ynHB0IA8Q", + "kid": "4DFB:Z4JA:GT3U:ZJ52:6MWJ:I75B:UROI:2IUY:EAIA:PUIH:VOP4:MZP5", + "kty": "EC", + "x": "OdLw0U3X9kXz3bKBEG_0JZZglNRGk-QAsVl_zDHay7c", + "y": "Jfh6okMAYdsDcO8JNK1Rrisp_beHID4wq7O8rp3F0K0" +} \ No newline at end of file diff --git a/integration-cli/fixtures/https/public-key-1.json b/integration-cli/fixtures/https/public-key-1.json new file mode 100644 index 0000000000000..e4f25dae28b30 --- /dev/null +++ b/integration-cli/fixtures/https/public-key-1.json @@ -0,0 +1,12 @@ +{ + "keys": [ + { + "hosts": ["localhost:4272"], + "crv": "P-256", + "kid": "FDTR:VRAS:HWMP:MVLO:XVN4:OQRA:QWUS:PMZX:ANM5:6AB3:IZVG:MELL", + "kty": "EC", + "x": "qt6kkQOC8GCGPxjHPaHkvbou0gX_4YXWzA2ExvmM7co", + "y": "QIOEwDF1pbSrcC0XIAHx9QeZG7qBX0rS5AhGtw-5X4Y" + } + ] +} diff --git a/integration-cli/fixtures/https/public-key-2.json b/integration-cli/fixtures/https/public-key-2.json new file mode 100644 index 0000000000000..ce71d2dbb86e4 --- /dev/null +++ b/integration-cli/fixtures/https/public-key-2.json @@ -0,0 +1,12 @@ +{ + "keys": [ + { + "hosts": ["localhost:4272"], + "crv": "P-256", + "kid": "2IUQ:R3EY:7DDM:TAIV:653R:QQVO:W5OZ:DEMO:XBHS:4ET7:6ZFO:D4UP", + "kty": "EC", + "x": "0rgsH0zVAY61DgSACt7FwGiAsvHNQaWL6028Sg-5NUM", + "y": "kU58U2i97SVN8ETuXd87aMkKfroLYY5ICKiiyDlNwfI" + } + ] +} diff --git a/integration-cli/fixtures/https/public-key-3.json b/integration-cli/fixtures/https/public-key-3.json new file mode 100644 index 0000000000000..eb48b38eb916c --- /dev/null +++ b/integration-cli/fixtures/https/public-key-3.json @@ -0,0 +1,12 @@ +{ + "keys": [ + { + "hosts": ["localhost:4272"], + "crv": "P-256", + "kid": "4DFB:Z4JA:GT3U:ZJ52:6MWJ:I75B:UROI:2IUY:EAIA:PUIH:VOP4:MZP5", + "kty": "EC", + "x": "OdLw0U3X9kXz3bKBEG_0JZZglNRGk-QAsVl_zDHay7c", + "y": "Jfh6okMAYdsDcO8JNK1Rrisp_beHID4wq7O8rp3F0K0" + } + ] +} diff --git a/integration/fixtures/https/server-cert.pem b/integration-cli/fixtures/https/server-cert.pem similarity index 100% rename from integration/fixtures/https/server-cert.pem rename to integration-cli/fixtures/https/server-cert.pem diff --git a/integration/fixtures/https/server-key.pem b/integration-cli/fixtures/https/server-key.pem similarity index 100% rename from integration/fixtures/https/server-key.pem rename to integration-cli/fixtures/https/server-key.pem diff --git a/integration/fixtures/https/server-rogue-cert.pem b/integration-cli/fixtures/https/server-rogue-cert.pem similarity index 100% rename from integration/fixtures/https/server-rogue-cert.pem rename to integration-cli/fixtures/https/server-rogue-cert.pem diff --git a/integration/fixtures/https/server-rogue-key.pem b/integration-cli/fixtures/https/server-rogue-key.pem similarity index 100% rename from integration/fixtures/https/server-rogue-key.pem rename to integration-cli/fixtures/https/server-rogue-key.pem diff --git a/integration/https_test.go b/integration/https_test.go deleted file mode 100644 index 0705dc812fe42..0000000000000 --- a/integration/https_test.go +++ /dev/null @@ -1,97 +0,0 @@ -package docker - -import ( - "crypto/tls" - "crypto/x509" - "io/ioutil" - "strings" - "testing" - "time" - - "github.com/docker/docker/api/client" - "github.com/docker/libtrust" -) - -const ( - errBadCertificate = "remote error: bad certificate" - errCaUnknown = "x509: certificate signed by unknown authority" -) - -func getTlsConfig(certFile, keyFile string, t *testing.T) *tls.Config { - certPool := x509.NewCertPool() - file, err := ioutil.ReadFile("fixtures/https/ca.pem") - if err != nil { - t.Fatal(err) - } - certPool.AppendCertsFromPEM(file) - - cert, err := tls.LoadX509KeyPair("fixtures/https/"+certFile, "fixtures/https/"+keyFile) - if err != nil { - t.Fatalf("Couldn't load X509 key pair: %s", err) - } - tlsConfig := &tls.Config{ - RootCAs: certPool, - Certificates: []tls.Certificate{cert}, - } - return tlsConfig -} - -// TestHttpsInfo connects via two-way authenticated HTTPS to the info endpoint -func TestHttpsInfo(t *testing.T) { - key, err := libtrust.GenerateECP256PrivateKey() - if err != nil { - t.Fatal(err) - } - cli := client.NewDockerCli(nil, ioutil.Discard, ioutil.Discard, key, testDaemonProto, - testDaemonHttpsAddr, getTlsConfig("client-cert.pem", "client-key.pem", t)) - - setTimeout(t, "Reading command output time out", 10*time.Second, func() { - if err := cli.CmdInfo(); err != nil { - t.Fatal(err) - } - }) -} - -// TestHttpsInfoRogueCert connects via two-way authenticated HTTPS to the info endpoint -// by using a rogue client certificate and checks that it fails with the expected error. -func TestHttpsInfoRogueCert(t *testing.T) { - key, err := libtrust.GenerateECP256PrivateKey() - if err != nil { - t.Fatal(err) - } - cli := client.NewDockerCli(nil, ioutil.Discard, ioutil.Discard, key, testDaemonProto, - testDaemonHttpsAddr, getTlsConfig("client-rogue-cert.pem", "client-rogue-key.pem", t)) - - setTimeout(t, "Reading command output time out", 10*time.Second, func() { - err := cli.CmdInfo() - if err == nil { - t.Fatal("Expected error but got nil") - } - if !strings.Contains(err.Error(), errBadCertificate) { - t.Fatalf("Expected error: %s, got instead: %s", errBadCertificate, err) - } - }) -} - -// TestHttpsInfoRogueServerCert connects via two-way authenticated HTTPS to the info endpoint -// which provides a rogue server certificate and checks that it fails with the expected error -func TestHttpsInfoRogueServerCert(t *testing.T) { - key, err := libtrust.GenerateECP256PrivateKey() - if err != nil { - t.Fatal(err) - } - cli := client.NewDockerCli(nil, ioutil.Discard, ioutil.Discard, key, testDaemonProto, - testDaemonRogueHttpsAddr, getTlsConfig("client-cert.pem", "client-key.pem", t)) - - setTimeout(t, "Reading command output time out", 10*time.Second, func() { - err := cli.CmdInfo() - if err == nil { - t.Fatal("Expected error but got nil") - } - - if !strings.Contains(err.Error(), errCaUnknown) { - t.Fatalf("Expected error: %s, got instead: %s", errBadCertificate, err) - } - - }) -} diff --git a/integration/runtime_test.go b/integration/runtime_test.go index d173af1f7fd88..c5ce84b340be8 100644 --- a/integration/runtime_test.go +++ b/integration/runtime_test.go @@ -29,27 +29,22 @@ import ( ) const ( - unitTestImageName = "docker-test-image" - unitTestImageID = "83599e29c455eb719f77d799bc7c51521b9551972f5a850d7ad265bc1b5292f6" // 1.0 - unitTestImageIDShort = "83599e29c455" - unitTestNetworkBridge = "testdockbr0" - unitTestStoreBase = "/var/lib/docker/unit-tests" - unitTestDockerTmpdir = "/var/lib/docker/tmp" - testDaemonAddr = "127.0.0.1:4270" - testDaemonProto = "tcp" - testDaemonHttpsProto = "tcp" - testDaemonHttpsAddr = "localhost:4271" - testDaemonRogueHttpsAddr = "localhost:4272" + unitTestImageName = "docker-test-image" + unitTestImageID = "83599e29c455eb719f77d799bc7c51521b9551972f5a850d7ad265bc1b5292f6" // 1.0 + unitTestImageIDShort = "83599e29c455" + unitTestNetworkBridge = "testdockbr0" + unitTestStoreBase = "/var/lib/docker/unit-tests" + unitTestDockerTmpdir = "/var/lib/docker/tmp" + testDaemonAddr = "127.0.0.1:4270" + testDaemonProto = "tcp" ) var ( // FIXME: globalDaemon is deprecated by globalEngine. All tests should be converted. - globalDaemon *daemon.Daemon - globalEngine *engine.Engine - globalHttpsEngine *engine.Engine - globalRogueHttpsEngine *engine.Engine - startFds int - startGoroutines int + globalDaemon *daemon.Daemon + globalEngine *engine.Engine + startFds int + startGoroutines int ) // FIXME: nuke() is deprecated by Daemon.Nuke() @@ -121,8 +116,6 @@ func init() { // Create the "global daemon" with a long-running daemons for integration tests spawnGlobalDaemon() - spawnLegitHttpsDaemon() - spawnRogueHttpsDaemon() startFds, startGoroutines = utils.GetTotalUsedFds(), runtime.NumGoroutine() } @@ -160,6 +153,7 @@ func spawnGlobalDaemon() { } job := eng.Job("serveapi", listenURL.String()) job.SetenvBool("Logging", true) + job.Setenv("Auth", "none") if err := job.Run(); err != nil { log.Fatalf("Unable to spawn the test daemon: %s", err) } @@ -174,61 +168,6 @@ func spawnGlobalDaemon() { } } -func spawnLegitHttpsDaemon() { - if globalHttpsEngine != nil { - return - } - globalHttpsEngine = spawnHttpsDaemon(testDaemonHttpsAddr, "fixtures/https/ca.pem", - "fixtures/https/server-cert.pem", "fixtures/https/server-key.pem") -} - -func spawnRogueHttpsDaemon() { - if globalRogueHttpsEngine != nil { - return - } - globalRogueHttpsEngine = spawnHttpsDaemon(testDaemonRogueHttpsAddr, "fixtures/https/ca.pem", - "fixtures/https/server-rogue-cert.pem", "fixtures/https/server-rogue-key.pem") -} - -func spawnHttpsDaemon(addr, cacert, cert, key string) *engine.Engine { - t := std_log.New(os.Stderr, "", 0) - root, err := newTestDirectory(unitTestStoreBase) - if err != nil { - t.Fatal(err) - } - // FIXME: here we don't use NewTestEngine because it configures the daemon with Autorestart=false, - // and we want to set it to true. - - eng := newTestEngine(t, true, root) - - // Spawn a Daemon - go func() { - log.Debugf("Spawning https daemon for integration tests") - listenURL := &url.URL{ - Scheme: testDaemonHttpsProto, - Host: addr, - } - job := eng.Job("serveapi", listenURL.String()) - job.SetenvBool("Logging", true) - job.SetenvBool("Tls", true) - job.SetenvBool("TlsVerify", true) - job.Setenv("TlsCa", cacert) - job.Setenv("TlsCert", cert) - job.Setenv("TlsKey", key) - if err := job.Run(); err != nil { - log.Fatalf("Unable to spawn the test daemon: %s", err) - } - }() - - // Give some time to ListenAndServer to actually start - time.Sleep(time.Second) - - if err := eng.Job("acceptconnections").Run(); err != nil { - log.Fatalf("Unable to accept connections for test api: %s", err) - } - return eng -} - // FIXME: test that ImagePull(json=true) send correct json output func GetTestImage(daemon *daemon.Daemon) *image.Image {