Browse code

Merge pull request #13636 from tiborvass/refactor-tls

Refactor TLS code with a new `tlsconfig` package

Arnaud Porterie authored on 2015/06/06 02:16:24
Showing 6 changed files
... ...
@@ -1,6 +1,7 @@
1 1
 package server
2 2
 
3 3
 import (
4
+	"crypto/tls"
4 5
 	"encoding/base64"
5 6
 	"encoding/json"
6 7
 	"fmt"
... ...
@@ -44,11 +45,7 @@ type ServerConfig struct {
44 44
 	CorsHeaders string
45 45
 	Version     string
46 46
 	SocketGroup string
47
-	Tls         bool
48
-	TlsVerify   bool
49
-	TlsCa       string
50
-	TlsCert     string
51
-	TlsKey      string
47
+	TLSConfig   *tls.Config
52 48
 }
53 49
 
54 50
 type Server struct {
... ...
@@ -1435,22 +1432,15 @@ func (s *Server) ping(version version.Version, w http.ResponseWriter, r *http.Re
1435 1435
 }
1436 1436
 
1437 1437
 func (s *Server) initTcpSocket(addr string) (l net.Listener, err error) {
1438
-	if !s.cfg.TlsVerify {
1438
+	if s.cfg.TLSConfig == nil || s.cfg.TLSConfig.ClientAuth != tls.RequireAndVerifyClientCert {
1439 1439
 		logrus.Warn("/!\\ DON'T BIND ON ANY IP ADDRESS WITHOUT setting -tlsverify IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\")
1440 1440
 	}
1441
-
1442
-	var c *sockets.TlsConfig
1443
-	if s.cfg.Tls || s.cfg.TlsVerify {
1444
-		c = sockets.NewTlsConfig(s.cfg.TlsCert, s.cfg.TlsKey, s.cfg.TlsCa, s.cfg.TlsVerify)
1445
-	}
1446
-
1447
-	if l, err = sockets.NewTcpSocket(addr, c, s.start); err != nil {
1441
+	if l, err = sockets.NewTcpSocket(addr, s.cfg.TLSConfig, s.start); err != nil {
1448 1442
 		return nil, err
1449 1443
 	}
1450 1444
 	if err := allocateDaemonPort(addr); err != nil {
1451 1445
 		return nil, err
1452 1446
 	}
1453
-
1454 1447
 	return
1455 1448
 }
1456 1449
 
... ...
@@ -3,6 +3,7 @@
3 3
 package main
4 4
 
5 5
 import (
6
+	"crypto/tls"
6 7
 	"fmt"
7 8
 	"io"
8 9
 	"os"
... ...
@@ -21,6 +22,7 @@ import (
21 21
 	"github.com/docker/docker/pkg/signal"
22 22
 	"github.com/docker/docker/pkg/system"
23 23
 	"github.com/docker/docker/pkg/timeutils"
24
+	"github.com/docker/docker/pkg/tlsconfig"
24 25
 	"github.com/docker/docker/registry"
25 26
 	"github.com/docker/docker/utils"
26 27
 )
... ...
@@ -112,11 +114,17 @@ func mainDaemon() {
112 112
 		CorsHeaders: daemonCfg.CorsHeaders,
113 113
 		Version:     dockerversion.VERSION,
114 114
 		SocketGroup: daemonCfg.SocketGroup,
115
-		Tls:         *flTls,
116
-		TlsVerify:   *flTlsVerify,
117
-		TlsCa:       *flCa,
118
-		TlsCert:     *flCert,
119
-		TlsKey:      *flKey,
115
+	}
116
+
117
+	if *flTls {
118
+		if *flTlsVerify {
119
+			tlsOptions.ClientAuth = tls.RequireAndVerifyClientCert
120
+		}
121
+		tlsConfig, err := tlsconfig.Server(tlsOptions)
122
+		if err != nil {
123
+			logrus.Fatal(err)
124
+		}
125
+		serverConfig.TLSConfig = tlsConfig
120 126
 	}
121 127
 
122 128
 	api := apiserver.New(serverConfig)
... ...
@@ -2,9 +2,7 @@ package main
2 2
 
3 3
 import (
4 4
 	"crypto/tls"
5
-	"crypto/x509"
6 5
 	"fmt"
7
-	"io/ioutil"
8 6
 	"os"
9 7
 	"runtime"
10 8
 	"strings"
... ...
@@ -16,6 +14,7 @@ import (
16 16
 	flag "github.com/docker/docker/pkg/mflag"
17 17
 	"github.com/docker/docker/pkg/reexec"
18 18
 	"github.com/docker/docker/pkg/term"
19
+	"github.com/docker/docker/pkg/tlsconfig"
19 20
 	"github.com/docker/docker/utils"
20 21
 )
21 22
 
... ...
@@ -85,6 +84,12 @@ func main() {
85 85
 
86 86
 	setDefaultConfFlag(flTrustKey, defaultTrustKeyFile)
87 87
 
88
+	// Regardless of whether the user sets it to true or false, if they
89
+	// specify --tlsverify at all then we need to turn on tls
90
+	if flag.IsSet("-tlsverify") {
91
+		*flTls = true
92
+	}
93
+
88 94
 	if *flDaemon {
89 95
 		if *flHelp {
90 96
 			flag.Usage()
... ...
@@ -94,59 +99,35 @@ func main() {
94 94
 		return
95 95
 	}
96 96
 
97
+	// From here on, we assume we're a client, not a server.
98
+
97 99
 	if len(flHosts) > 1 {
98 100
 		fmt.Fprintf(os.Stderr, "Please specify only one -H")
99 101
 		os.Exit(0)
100 102
 	}
101 103
 	protoAddrParts := strings.SplitN(flHosts[0], "://", 2)
102 104
 
103
-	var (
104
-		cli       *client.DockerCli
105
-		tlsConfig tls.Config
106
-	)
107
-	tlsConfig.InsecureSkipVerify = true
108
-
109
-	// Regardless of whether the user sets it to true or false, if they
110
-	// specify --tlsverify at all then we need to turn on tls
111
-	if flag.IsSet("-tlsverify") {
112
-		*flTls = true
113
-	}
114
-
115
-	// If we should verify the server, we need to load a trusted ca
116
-	if *flTlsVerify {
117
-		certPool := x509.NewCertPool()
118
-		file, err := ioutil.ReadFile(*flCa)
119
-		if err != nil {
120
-			fmt.Fprintf(os.Stderr, "Couldn't read ca cert %s: %s\n", *flCa, err)
121
-			os.Exit(1)
105
+	var tlsConfig *tls.Config
106
+	if *flTls {
107
+		tlsOptions.InsecureSkipVerify = !*flTlsVerify
108
+		if !flag.IsSet("-tlscert") {
109
+			if _, err := os.Stat(tlsOptions.CertFile); os.IsNotExist(err) {
110
+				tlsOptions.CertFile = ""
111
+			}
122 112
 		}
123
-		certPool.AppendCertsFromPEM(file)
124
-		tlsConfig.RootCAs = certPool
125
-		tlsConfig.InsecureSkipVerify = false
126
-	}
127
-
128
-	// If tls is enabled, try to load and send client certificates
129
-	if *flTls || *flTlsVerify {
130
-		_, errCert := os.Stat(*flCert)
131
-		_, errKey := os.Stat(*flKey)
132
-		if errCert == nil && errKey == nil {
133
-			*flTls = true
134
-			cert, err := tls.LoadX509KeyPair(*flCert, *flKey)
135
-			if err != nil {
136
-				fmt.Fprintf(os.Stderr, "Couldn't load X509 key pair: %q. Make sure the key is encrypted\n", err)
137
-				os.Exit(1)
113
+		if !flag.IsSet("-tlskey") {
114
+			if _, err := os.Stat(tlsOptions.KeyFile); os.IsNotExist(err) {
115
+				tlsOptions.KeyFile = ""
138 116
 			}
139
-			tlsConfig.Certificates = []tls.Certificate{cert}
140 117
 		}
141
-		// Avoid fallback to SSL protocols < TLS1.0
142
-		tlsConfig.MinVersion = tls.VersionTLS10
143
-	}
144
-
145
-	if *flTls || *flTlsVerify {
146
-		cli = client.NewDockerCli(stdin, stdout, stderr, *flTrustKey, protoAddrParts[0], protoAddrParts[1], &tlsConfig)
147
-	} else {
148
-		cli = client.NewDockerCli(stdin, stdout, stderr, *flTrustKey, protoAddrParts[0], protoAddrParts[1], nil)
118
+		var err error
119
+		tlsConfig, err = tlsconfig.Client(tlsOptions)
120
+		if err != nil {
121
+			fmt.Fprintln(stderr, err)
122
+			os.Exit(1)
123
+		}
149 124
 	}
125
+	cli := client.NewDockerCli(stdin, stdout, stderr, *flTrustKey, protoAddrParts[0], protoAddrParts[1], tlsConfig)
150 126
 
151 127
 	if err := cli.Cmd(flag.Args()...); err != nil {
152 128
 		if sterr, ok := err.(client.StatusError); ok {
... ...
@@ -10,6 +10,7 @@ import (
10 10
 	"github.com/docker/docker/opts"
11 11
 	"github.com/docker/docker/pkg/homedir"
12 12
 	flag "github.com/docker/docker/pkg/mflag"
13
+	"github.com/docker/docker/pkg/tlsconfig"
13 14
 )
14 15
 
15 16
 type command struct {
... ...
@@ -94,10 +95,8 @@ var (
94 94
 	flTlsVerify = flag.Bool([]string{"-tlsverify"}, dockerTlsVerify, "Use TLS and verify the remote")
95 95
 
96 96
 	// these are initialized in init() below since their default values depend on dockerCertPath which isn't fully initialized until init() runs
97
+	tlsOptions tlsconfig.Options
97 98
 	flTrustKey *string
98
-	flCa       *string
99
-	flCert     *string
100
-	flKey      *string
101 99
 	flHosts    []string
102 100
 )
103 101
 
... ...
@@ -116,9 +115,9 @@ func init() {
116 116
 	// TODO use flag flag.String([]string{"i", "-identity"}, "", "Path to libtrust key file")
117 117
 	flTrustKey = &placeholderTrustKey
118 118
 
119
-	flCa = flag.String([]string{"-tlscacert"}, filepath.Join(dockerCertPath, defaultCaFile), "Trust certs signed only by this CA")
120
-	flCert = flag.String([]string{"-tlscert"}, filepath.Join(dockerCertPath, defaultCertFile), "Path to TLS certificate file")
121
-	flKey = flag.String([]string{"-tlskey"}, filepath.Join(dockerCertPath, defaultKeyFile), "Path to TLS key file")
119
+	flag.StringVar(&tlsOptions.CAFile, []string{"-tlscacert"}, filepath.Join(dockerCertPath, defaultCaFile), "Trust certs signed only by this CA")
120
+	flag.StringVar(&tlsOptions.CertFile, []string{"-tlscert"}, filepath.Join(dockerCertPath, defaultCertFile), "Path to TLS certificate file")
121
+	flag.StringVar(&tlsOptions.KeyFile, []string{"-tlskey"}, filepath.Join(dockerCertPath, defaultKeyFile), "Path to TLS key file")
122 122
 	opts.HostListVar(&flHosts, []string{"H", "-host"}, "Daemon socket(s) to connect to")
123 123
 
124 124
 	flag.Usage = func() {
... ...
@@ -2,68 +2,19 @@ package sockets
2 2
 
3 3
 import (
4 4
 	"crypto/tls"
5
-	"crypto/x509"
6
-	"fmt"
7
-	"io/ioutil"
8 5
 	"net"
9
-	"os"
10 6
 
11 7
 	"github.com/docker/docker/pkg/listenbuffer"
12 8
 )
13 9
 
14
-type TlsConfig struct {
15
-	CA          string
16
-	Certificate string
17
-	Key         string
18
-	Verify      bool
19
-}
20
-
21
-func NewTlsConfig(tlsCert, tlsKey, tlsCA string, verify bool) *TlsConfig {
22
-	return &TlsConfig{
23
-		Verify:      verify,
24
-		Certificate: tlsCert,
25
-		Key:         tlsKey,
26
-		CA:          tlsCA,
27
-	}
28
-}
29
-
30
-func NewTcpSocket(addr string, config *TlsConfig, activate <-chan struct{}) (net.Listener, error) {
10
+func NewTcpSocket(addr string, tlsConfig *tls.Config, activate <-chan struct{}) (net.Listener, error) {
31 11
 	l, err := listenbuffer.NewListenBuffer("tcp", addr, activate)
32 12
 	if err != nil {
33 13
 		return nil, err
34 14
 	}
35
-	if config != nil {
36
-		if l, err = setupTls(l, config); err != nil {
37
-			return nil, err
38
-		}
15
+	if tlsConfig != nil {
16
+		tlsConfig.NextProtos = []string{"http/1.1"}
17
+		l = tls.NewListener(l, tlsConfig)
39 18
 	}
40 19
 	return l, nil
41 20
 }
42
-
43
-func setupTls(l net.Listener, config *TlsConfig) (net.Listener, error) {
44
-	tlsCert, err := tls.LoadX509KeyPair(config.Certificate, config.Key)
45
-	if err != nil {
46
-		if os.IsNotExist(err) {
47
-			return nil, fmt.Errorf("Could not load X509 key pair (%s, %s): %v", config.Certificate, config.Key, err)
48
-		}
49
-		return nil, fmt.Errorf("Error reading X509 key pair (%s, %s): %q. Make sure the key is encrypted.",
50
-			config.Certificate, config.Key, err)
51
-	}
52
-	tlsConfig := &tls.Config{
53
-		NextProtos:   []string{"http/1.1"},
54
-		Certificates: []tls.Certificate{tlsCert},
55
-		// Avoid fallback on insecure SSL protocols
56
-		MinVersion: tls.VersionTLS10,
57
-	}
58
-	if config.CA != "" {
59
-		certPool := x509.NewCertPool()
60
-		file, err := ioutil.ReadFile(config.CA)
61
-		if err != nil {
62
-			return nil, fmt.Errorf("Could not read CA certificate: %v", err)
63
-		}
64
-		certPool.AppendCertsFromPEM(file)
65
-		tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
66
-		tlsConfig.ClientCAs = certPool
67
-	}
68
-	return tls.NewListener(l, tlsConfig), nil
69
-}
70 21
new file mode 100644
... ...
@@ -0,0 +1,107 @@
0
+// Package tlsconfig provides primitives to retrieve secure-enough TLS configurations for both clients and servers.
1
+//
2
+// As a reminder from https://golang.org/pkg/crypto/tls/#Config:
3
+//	A Config structure is used to configure a TLS client or server. After one has been passed to a TLS function it must not be modified.
4
+//	A Config may be reused; the tls package will also not modify it.
5
+package tlsconfig
6
+
7
+import (
8
+	"crypto/tls"
9
+	"crypto/x509"
10
+	"fmt"
11
+	"io/ioutil"
12
+	"os"
13
+
14
+	"github.com/Sirupsen/logrus"
15
+)
16
+
17
+// Options represents the information needed to create client and server TLS configurations.
18
+type Options struct {
19
+	InsecureSkipVerify bool
20
+	ClientAuth         tls.ClientAuthType
21
+	CAFile             string
22
+	CertFile           string
23
+	KeyFile            string
24
+}
25
+
26
+// Default is a secure-enough TLS configuration.
27
+var Default = tls.Config{
28
+	// Avoid fallback to SSL protocols < TLS1.0
29
+	MinVersion:               tls.VersionTLS10,
30
+	PreferServerCipherSuites: true,
31
+	CipherSuites: []uint16{
32
+		tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
33
+		tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
34
+		tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
35
+		tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
36
+		tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
37
+		tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
38
+		tls.TLS_RSA_WITH_AES_128_CBC_SHA,
39
+		tls.TLS_RSA_WITH_AES_256_CBC_SHA,
40
+	},
41
+}
42
+
43
+// certPool returns an X.509 certificate pool from `caFile`, the certificate file.
44
+func certPool(caFile string) (*x509.CertPool, error) {
45
+	// If we should verify the server, we need to load a trusted ca
46
+	certPool := x509.NewCertPool()
47
+	pem, err := ioutil.ReadFile(caFile)
48
+	if err != nil {
49
+		return nil, fmt.Errorf("Could not read CA certificate %s: %v", caFile, err)
50
+	}
51
+	if !certPool.AppendCertsFromPEM(pem) {
52
+		return nil, fmt.Errorf("failed to append certificates from PEM file: %s", caFile)
53
+	}
54
+	s := certPool.Subjects()
55
+	subjects := make([]string, len(s))
56
+	for i, subject := range s {
57
+		subjects[i] = string(subject)
58
+	}
59
+	logrus.Debugf("Trusting certs with subjects: %v", subjects)
60
+	return certPool, nil
61
+}
62
+
63
+// Client returns a TLS configuration meant to be used by a client.
64
+func Client(options Options) (*tls.Config, error) {
65
+	tlsConfig := Default
66
+	tlsConfig.InsecureSkipVerify = options.InsecureSkipVerify
67
+	if !options.InsecureSkipVerify {
68
+		CAs, err := certPool(options.CAFile)
69
+		if err != nil {
70
+			return nil, err
71
+		}
72
+		tlsConfig.RootCAs = CAs
73
+	}
74
+
75
+	if options.CertFile != "" && options.KeyFile != "" {
76
+		tlsCert, err := tls.LoadX509KeyPair(options.CertFile, options.KeyFile)
77
+		if err != nil {
78
+			return nil, fmt.Errorf("Could not load X509 key pair: %v. Make sure the key is not encrypted", err)
79
+		}
80
+		tlsConfig.Certificates = []tls.Certificate{tlsCert}
81
+	}
82
+
83
+	return &tlsConfig, nil
84
+}
85
+
86
+// Server returns a TLS configuration meant to be used by a server.
87
+func Server(options Options) (*tls.Config, error) {
88
+	tlsConfig := Default
89
+	tlsConfig.ClientAuth = options.ClientAuth
90
+	tlsCert, err := tls.LoadX509KeyPair(options.CertFile, options.KeyFile)
91
+	if err != nil {
92
+		if os.IsNotExist(err) {
93
+			return nil, fmt.Errorf("Could not load X509 key pair (%s, %s): %v", options.CertFile, options.KeyFile, err)
94
+		}
95
+		return nil, fmt.Errorf("Error reading X509 key pair (%s, %s): %v. Make sure the key is not encrypted.", options.CertFile, options.KeyFile, err)
96
+	}
97
+	tlsConfig.Certificates = []tls.Certificate{tlsCert}
98
+	if options.ClientAuth >= tls.VerifyClientCertIfGiven {
99
+		CAs, err := certPool(options.CAFile)
100
+		if err != nil {
101
+			return nil, err
102
+		}
103
+		tlsConfig.ClientCAs = CAs
104
+	}
105
+	return &tlsConfig, nil
106
+}