From b4424a1a5012052e2777b96f190f8c1c9da8dc28 Mon Sep 17 00:00:00 2001 From: Atin Malaviya Date: Tue, 18 Nov 2014 11:03:36 -0500 Subject: [PATCH] Moved TLS Config stuff to tlsutil package --- command/agent/config.go | 74 ----------- command/agent/http.go | 17 ++- command/agent/http_test.go | 10 +- command/util_test.go | 11 +- consul/client.go | 12 +- consul/config.go | 166 ----------------------- consul/pool.go | 3 +- consul/raft_rpc.go | 3 +- consul/server.go | 14 +- tlsutil/config.go | 206 +++++++++++++++++++++++++++++ {consul => tlsutil}/config_test.go | 6 +- 11 files changed, 267 insertions(+), 255 deletions(-) create mode 100644 tlsutil/config.go rename {consul => tlsutil}/config_test.go (98%) diff --git a/command/agent/config.go b/command/agent/config.go index de9cb13d7..be5a3d4d0 100644 --- a/command/agent/config.go +++ b/command/agent/config.go @@ -1,13 +1,10 @@ package agent import ( - "crypto/tls" - "crypto/x509" "encoding/base64" "encoding/json" "fmt" "io" - "io/ioutil" "net" "os" "path/filepath" @@ -391,77 +388,6 @@ func (c *Config) ClientListenerAddr(override string, port int) (string, error) { return addr.String(), nil } -// AppendCA opens and parses the CA file and adds the certificates to -// the provided CertPool. -func (c *Config) AppendCA(pool *x509.CertPool) error { - if c.CAFile == "" { - return nil - } - - // Read the file - data, err := ioutil.ReadFile(c.CAFile) - if err != nil { - return fmt.Errorf("Failed to read CA file: %v", err) - } - - if !pool.AppendCertsFromPEM(data) { - return fmt.Errorf("Failed to parse any CA certificates") - } - - return nil -} - -// KeyPair is used to open and parse a certificate and key file -func (c *Config) KeyPair() (*tls.Certificate, error) { - if c.CertFile == "" || c.KeyFile == "" { - return nil, nil - } - cert, err := tls.LoadX509KeyPair(c.CertFile, c.KeyFile) - if err != nil { - return nil, fmt.Errorf("Failed to load cert/key pair: %v", err) - } - return &cert, err -} - -// IncomingTLSConfig generates a TLS configuration for incoming requests -func (c *Config) IncomingTLSConfig() (*tls.Config, error) { - // Create the tlsConfig - tlsConfig := &tls.Config{ - ServerName: c.ServerName, - ClientCAs: x509.NewCertPool(), - ClientAuth: tls.NoClientCert, - } - if tlsConfig.ServerName == "" { - tlsConfig.ServerName = c.NodeName - } - - // Parse the CA cert if any - err := c.AppendCA(tlsConfig.ClientCAs) - if err != nil { - return nil, err - } - - // Add cert/key - cert, err := c.KeyPair() - if err != nil { - return nil, err - } else if cert != nil { - tlsConfig.Certificates = []tls.Certificate{*cert} - } - - // Check if we require verification - if c.VerifyIncoming { - tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert - if c.CAFile == "" { - return nil, fmt.Errorf("VerifyIncoming set, and no CA certificate provided!") - } - if cert == nil { - return nil, fmt.Errorf("VerifyIncoming set, and no Cert/Key pair provided!") - } - } - return tlsConfig, nil -} - // DecodeConfig reads the configuration from the given reader in JSON // format and decodes it into a proper Config structure. func DecodeConfig(r io.Reader) (*Config, error) { diff --git a/command/agent/http.go b/command/agent/http.go index 5d07f23de..79b847a34 100644 --- a/command/agent/http.go +++ b/command/agent/http.go @@ -3,6 +3,7 @@ package agent import ( "crypto/tls" "encoding/json" + "fmt" "io" "log" "net" @@ -13,6 +14,7 @@ import ( "time" "github.com/hashicorp/consul/consul/structs" + "github.com/hashicorp/consul/tlsutil" "github.com/mitchellh/mapstructure" ) @@ -42,7 +44,16 @@ func NewHTTPServers(agent *Agent, config *Config, logOutput io.Writer) ([]*HTTPS return nil, err } - tlsConfig, err = config.IncomingTLSConfig() + tlsConf := &tlsutil.Config{ + VerifyIncoming: config.VerifyIncoming, + VerifyOutgoing: config.VerifyOutgoing, + CAFile: config.CAFile, + CertFile: config.CertFile, + KeyFile: config.KeyFile, + NodeName: config.NodeName, + ServerName: config.ServerName} + + tlsConfig, err = tlsConf.IncomingTLSConfig() if err != nil { return nil, err } @@ -78,13 +89,13 @@ func NewHTTPServers(agent *Agent, config *Config, logOutput io.Writer) ([]*HTTPS if config.Ports.HTTP > 0 { httpAddr, err = config.ClientListener(config.Addresses.HTTP, config.Ports.HTTP) if err != nil { - return nil, err + return nil, fmt.Errorf("Failed to get ClientListener address:port: %v", err) } // Create non-TLS listener list, err = net.Listen("tcp", httpAddr.String()) if err != nil { - return nil, err + return nil, fmt.Errorf("Failed to get Listen on %s: %v", httpAddr.String(), err) } // Create the mux diff --git a/command/agent/http_test.go b/command/agent/http_test.go index d2ee63e7d..c52b822d7 100644 --- a/command/agent/http_test.go +++ b/command/agent/http_test.go @@ -25,12 +25,18 @@ func makeHTTPServer(t *testing.T) (string, *HTTPServer) { if err := os.Mkdir(uiDir, 755); err != nil { t.Fatalf("err: %v", err) } + conf.Addresses.HTTP = "" + conf.Ports.HTTP = agent.config.Ports.HTTP + conf.Ports.HTTPS = -1 addr, _ := agent.config.ClientListener("", agent.config.Ports.HTTP) - server, err := NewHTTPServer(agent, uiDir, true, agent.logOutput, addr.String()) + servers, err := NewHTTPServers(agent, conf, agent.logOutput) if err != nil { t.Fatalf("err: %v", err) } - return dir, server + if servers == nil || len(servers) == 0 { + t.Fatalf(fmt.Sprintf("Could not create HTTP server to listen on: %s", addr.String())) + } + return dir, servers[0] } func encodeReq(obj interface{}) io.ReadCloser { diff --git a/command/util_test.go b/command/util_test.go index 0366f760b..bb0966473 100644 --- a/command/util_test.go +++ b/command/util_test.go @@ -63,19 +63,25 @@ func testAgent(t *testing.T) *agentWrapper { rpc := agent.NewAgentRPC(a, l, mult, lw) + conf.Addresses.HTTP = "127.0.0.1" httpAddr := fmt.Sprintf("127.0.0.1:%d", conf.Ports.HTTP) - http, err := agent.NewHTTPServer(a, "", false, os.Stderr, httpAddr) + http, err := agent.NewHTTPServers(a, conf, os.Stderr) if err != nil { os.RemoveAll(dir) t.Fatalf(fmt.Sprintf("err: %v", err)) } + if http == nil || len(http) == 0 { + os.RemoveAll(dir) + t.Fatalf(fmt.Sprintf("Could not create HTTP server to listen on: %s", httpAddr)) + } + return &agentWrapper{ dir: dir, config: conf, agent: a, rpc: rpc, - http: http, + http: http[0], addr: l.Addr().String(), httpAddr: httpAddr, } @@ -92,6 +98,7 @@ func nextConfig() *agent.Config { conf.Server = true conf.Ports.HTTP = 10000 + 10*idx + conf.Ports.HTTPS = 10400 + 10*idx conf.Ports.RPC = 10100 + 10*idx conf.Ports.SerfLan = 10201 + 10*idx conf.Ports.SerfWan = 10202 + 10*idx diff --git a/consul/client.go b/consul/client.go index 050d147c2..54bd4056a 100644 --- a/consul/client.go +++ b/consul/client.go @@ -4,6 +4,7 @@ import ( "crypto/tls" "fmt" "github.com/hashicorp/consul/consul/structs" + "github.com/hashicorp/consul/tlsutil" "github.com/hashicorp/serf/serf" "log" "math/rand" @@ -93,7 +94,16 @@ func NewClient(config *Config) (*Client, error) { // Create the tlsConfig var tlsConfig *tls.Config var err error - if tlsConfig, err = config.OutgoingTLSConfig(); err != nil { + tlsConf := &tlsutil.Config{ + VerifyIncoming: config.VerifyIncoming, + VerifyOutgoing: config.VerifyOutgoing, + CAFile: config.CAFile, + CertFile: config.CertFile, + KeyFile: config.KeyFile, + NodeName: config.NodeName, + ServerName: config.ServerName} + + if tlsConfig, err = tlsConf.OutgoingTLSConfig(); err != nil { return nil, err } diff --git a/consul/config.go b/consul/config.go index 5404e71e6..e623dcade 100644 --- a/consul/config.go +++ b/consul/config.go @@ -1,11 +1,8 @@ package consul import ( - "crypto/tls" - "crypto/x509" "fmt" "io" - "io/ioutil" "net" "os" "time" @@ -199,169 +196,6 @@ func (c *Config) CheckACL() error { return nil } -// AppendCA opens and parses the CA file and adds the certificates to -// the provided CertPool. -func (c *Config) AppendCA(pool *x509.CertPool) error { - if c.CAFile == "" { - return nil - } - - // Read the file - data, err := ioutil.ReadFile(c.CAFile) - if err != nil { - return fmt.Errorf("Failed to read CA file: %v", err) - } - - if !pool.AppendCertsFromPEM(data) { - return fmt.Errorf("Failed to parse any CA certificates") - } - - return nil -} - -// KeyPair is used to open and parse a certificate and key file -func (c *Config) KeyPair() (*tls.Certificate, error) { - if c.CertFile == "" || c.KeyFile == "" { - return nil, nil - } - cert, err := tls.LoadX509KeyPair(c.CertFile, c.KeyFile) - if err != nil { - return nil, fmt.Errorf("Failed to load cert/key pair: %v", err) - } - return &cert, err -} - -// OutgoingTLSConfig generates a TLS configuration for outgoing -// requests. It will return a nil config if this configuration should -// not use TLS for outgoing connections. -func (c *Config) OutgoingTLSConfig() (*tls.Config, error) { - if !c.VerifyOutgoing { - return nil, nil - } - // Create the tlsConfig - tlsConfig := &tls.Config{ - RootCAs: x509.NewCertPool(), - InsecureSkipVerify: true, - } - if c.ServerName != "" { - tlsConfig.ServerName = c.ServerName - tlsConfig.InsecureSkipVerify = false - } - - // Ensure we have a CA if VerifyOutgoing is set - if c.VerifyOutgoing && c.CAFile == "" { - return nil, fmt.Errorf("VerifyOutgoing set, and no CA certificate provided!") - } - - // Parse the CA cert if any - err := c.AppendCA(tlsConfig.RootCAs) - if err != nil { - return nil, err - } - - // Add cert/key - cert, err := c.KeyPair() - if err != nil { - return nil, err - } else if cert != nil { - tlsConfig.Certificates = []tls.Certificate{*cert} - } - - return tlsConfig, nil -} - -// Wrap a net.Conn into a client tls connection, performing any -// additional verification as needed. -// -// As of go 1.3, crypto/tls only supports either doing no certificate -// verification, or doing full verification including of the peer's -// DNS name. For consul, we want to validate that the certificate is -// signed by a known CA, but because consul doesn't use DNS names for -// node names, we don't verify the certificate DNS names. Since go 1.3 -// no longer supports this mode of operation, we have to do it -// manually. -func wrapTLSClient(conn net.Conn, tlsConfig *tls.Config) (net.Conn, error) { - var err error - var tlsConn *tls.Conn - - tlsConn = tls.Client(conn, tlsConfig) - - // If crypto/tls is doing verification, there's no need to do - // our own. - if tlsConfig.InsecureSkipVerify == false { - return tlsConn, nil - } - - if err = tlsConn.Handshake(); err != nil { - tlsConn.Close() - return nil, err - } - - // The following is lightly-modified from the doFullHandshake - // method in crypto/tls's handshake_client.go. - opts := x509.VerifyOptions{ - Roots: tlsConfig.RootCAs, - CurrentTime: time.Now(), - DNSName: "", - Intermediates: x509.NewCertPool(), - } - - certs := tlsConn.ConnectionState().PeerCertificates - for i, cert := range certs { - if i == 0 { - continue - } - opts.Intermediates.AddCert(cert) - } - - _, err = certs[0].Verify(opts) - if err != nil { - tlsConn.Close() - return nil, err - } - - return tlsConn, err -} - -// IncomingTLSConfig generates a TLS configuration for incoming requests -func (c *Config) IncomingTLSConfig() (*tls.Config, error) { - // Create the tlsConfig - tlsConfig := &tls.Config{ - ServerName: c.ServerName, - ClientCAs: x509.NewCertPool(), - ClientAuth: tls.NoClientCert, - } - if tlsConfig.ServerName == "" { - tlsConfig.ServerName = c.NodeName - } - - // Parse the CA cert if any - err := c.AppendCA(tlsConfig.ClientCAs) - if err != nil { - return nil, err - } - - // Add cert/key - cert, err := c.KeyPair() - if err != nil { - return nil, err - } else if cert != nil { - tlsConfig.Certificates = []tls.Certificate{*cert} - } - - // Check if we require verification - if c.VerifyIncoming { - tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert - if c.CAFile == "" { - return nil, fmt.Errorf("VerifyIncoming set, and no CA certificate provided!") - } - if cert == nil { - return nil, fmt.Errorf("VerifyIncoming set, and no Cert/Key pair provided!") - } - } - return tlsConfig, nil -} - // DefaultConfig is used to return a sane default configuration func DefaultConfig() *Config { hostname, err := os.Hostname() diff --git a/consul/pool.go b/consul/pool.go index 91fe035f2..4ab75fbc6 100644 --- a/consul/pool.go +++ b/consul/pool.go @@ -11,6 +11,7 @@ import ( "sync/atomic" "time" + "github.com/hashicorp/consul/tlsutil" "github.com/hashicorp/go-msgpack/codec" "github.com/hashicorp/yamux" "github.com/inconshreveable/muxado" @@ -222,7 +223,7 @@ func (p *ConnPool) getNewConn(addr net.Addr, version int) (*Conn, error) { } // Wrap the connection in a TLS client - tlsConn, err := wrapTLSClient(conn, p.tlsConfig) + tlsConn, err := tlsutil.WrapTLSClient(conn, p.tlsConfig) if err != nil { conn.Close() return nil, err diff --git a/consul/raft_rpc.go b/consul/raft_rpc.go index 1024cd987..e0ee4c68e 100644 --- a/consul/raft_rpc.go +++ b/consul/raft_rpc.go @@ -3,6 +3,7 @@ package consul import ( "crypto/tls" "fmt" + "github.com/hashicorp/consul/tlsutil" "net" "sync" "time" @@ -94,7 +95,7 @@ func (l *RaftLayer) Dial(address string, timeout time.Duration) (net.Conn, error } // Wrap the connection in a TLS client - conn, err = wrapTLSClient(conn, l.tlsConfig) + conn, err = tlsutil.WrapTLSClient(conn, l.tlsConfig) if err != nil { return nil, err } diff --git a/consul/server.go b/consul/server.go index 789db198e..4b6aa3a93 100644 --- a/consul/server.go +++ b/consul/server.go @@ -16,6 +16,7 @@ import ( "time" "github.com/hashicorp/consul/acl" + "github.com/hashicorp/consul/tlsutil" "github.com/hashicorp/golang-lru" "github.com/hashicorp/raft" "github.com/hashicorp/raft-mdb" @@ -168,13 +169,22 @@ func NewServer(config *Config) (*Server, error) { } // Create the tlsConfig for outgoing connections - tlsConfig, err := config.OutgoingTLSConfig() + tlsConf := &tlsutil.Config{ + VerifyIncoming: config.VerifyIncoming, + VerifyOutgoing: config.VerifyOutgoing, + CAFile: config.CAFile, + CertFile: config.CertFile, + KeyFile: config.KeyFile, + NodeName: config.NodeName, + ServerName: config.ServerName} + + tlsConfig, err := tlsConf.OutgoingTLSConfig() if err != nil { return nil, err } // Get the incoming tls config - incomingTLS, err := config.IncomingTLSConfig() + incomingTLS, err := tlsConf.IncomingTLSConfig() if err != nil { return nil, err } diff --git a/tlsutil/config.go b/tlsutil/config.go new file mode 100644 index 000000000..ab781de13 --- /dev/null +++ b/tlsutil/config.go @@ -0,0 +1,206 @@ +package tlsutil + +import ( + "crypto/tls" + "crypto/x509" + "fmt" + "io/ioutil" + "net" + "time" +) + +// Config used to create tls.Config +type Config struct { + // VerifyIncoming is used to verify the authenticity of incoming connections. + // This means that TCP requests are forbidden, only allowing for TLS. TLS connections + // must match a provided certificate authority. This can be used to force client auth. + VerifyIncoming bool + + // VerifyOutgoing is used to verify the authenticity of outgoing connections. + // This means that TLS requests are used, and TCP requests are not made. TLS connections + // must match a provided certificate authority. This is used to verify authenticity of + // server nodes. + VerifyOutgoing bool + + // CAFile is a path to a certificate authority file. This is used with VerifyIncoming + // or VerifyOutgoing to verify the TLS connection. + CAFile string + + // CertFile is used to provide a TLS certificate that is used for serving TLS connections. + // Must be provided to serve TLS connections. + CertFile string + + // KeyFile is used to provide a TLS key that is used for serving TLS connections. + // Must be provided to serve TLS connections. + KeyFile string + + // Node name is the name we use to advertise. Defaults to hostname. + NodeName string + + // ServerName is used with the TLS certificate to ensure the name we + // provide matches the certificate + ServerName string +} + +// AppendCA opens and parses the CA file and adds the certificates to +// the provided CertPool. +func (c *Config) AppendCA(pool *x509.CertPool) error { + if c.CAFile == "" { + return nil + } + + // Read the file + data, err := ioutil.ReadFile(c.CAFile) + if err != nil { + return fmt.Errorf("Failed to read CA file: %v", err) + } + + if !pool.AppendCertsFromPEM(data) { + return fmt.Errorf("Failed to parse any CA certificates") + } + + return nil +} + +// KeyPair is used to open and parse a certificate and key file +func (c *Config) KeyPair() (*tls.Certificate, error) { + if c.CertFile == "" || c.KeyFile == "" { + return nil, nil + } + cert, err := tls.LoadX509KeyPair(c.CertFile, c.KeyFile) + if err != nil { + return nil, fmt.Errorf("Failed to load cert/key pair: %v", err) + } + return &cert, err +} + +// OutgoingTLSConfig generates a TLS configuration for outgoing +// requests. It will return a nil config if this configuration should +// not use TLS for outgoing connections. +func (c *Config) OutgoingTLSConfig() (*tls.Config, error) { + if !c.VerifyOutgoing { + return nil, nil + } + // Create the tlsConfig + tlsConfig := &tls.Config{ + RootCAs: x509.NewCertPool(), + InsecureSkipVerify: true, + } + if c.ServerName != "" { + tlsConfig.ServerName = c.ServerName + tlsConfig.InsecureSkipVerify = false + } + + // Ensure we have a CA if VerifyOutgoing is set + if c.VerifyOutgoing && c.CAFile == "" { + return nil, fmt.Errorf("VerifyOutgoing set, and no CA certificate provided!") + } + + // Parse the CA cert if any + err := c.AppendCA(tlsConfig.RootCAs) + if err != nil { + return nil, err + } + + // Add cert/key + cert, err := c.KeyPair() + if err != nil { + return nil, err + } else if cert != nil { + tlsConfig.Certificates = []tls.Certificate{*cert} + } + + return tlsConfig, nil +} + +// Wrap a net.Conn into a client tls connection, performing any +// additional verification as needed. +// +// As of go 1.3, crypto/tls only supports either doing no certificate +// verification, or doing full verification including of the peer's +// DNS name. For consul, we want to validate that the certificate is +// signed by a known CA, but because consul doesn't use DNS names for +// node names, we don't verify the certificate DNS names. Since go 1.3 +// no longer supports this mode of operation, we have to do it +// manually. +func WrapTLSClient(conn net.Conn, tlsConfig *tls.Config) (net.Conn, error) { + var err error + var tlsConn *tls.Conn + + tlsConn = tls.Client(conn, tlsConfig) + + // If crypto/tls is doing verification, there's no need to do + // our own. + if tlsConfig.InsecureSkipVerify == false { + return tlsConn, nil + } + + if err = tlsConn.Handshake(); err != nil { + tlsConn.Close() + return nil, err + } + + // The following is lightly-modified from the doFullHandshake + // method in crypto/tls's handshake_client.go. + opts := x509.VerifyOptions{ + Roots: tlsConfig.RootCAs, + CurrentTime: time.Now(), + DNSName: "", + Intermediates: x509.NewCertPool(), + } + + certs := tlsConn.ConnectionState().PeerCertificates + for i, cert := range certs { + if i == 0 { + continue + } + opts.Intermediates.AddCert(cert) + } + + _, err = certs[0].Verify(opts) + if err != nil { + tlsConn.Close() + return nil, err + } + + return tlsConn, err +} + +// IncomingTLSConfig generates a TLS configuration for incoming requests +func (c *Config) IncomingTLSConfig() (*tls.Config, error) { + // Create the tlsConfig + tlsConfig := &tls.Config{ + ServerName: c.ServerName, + ClientCAs: x509.NewCertPool(), + ClientAuth: tls.NoClientCert, + } + if tlsConfig.ServerName == "" { + tlsConfig.ServerName = c.NodeName + } + + // Parse the CA cert if any + err := c.AppendCA(tlsConfig.ClientCAs) + if err != nil { + return nil, err + } + + // Add cert/key + cert, err := c.KeyPair() + if err != nil { + return nil, err + } else if cert != nil { + tlsConfig.Certificates = []tls.Certificate{*cert} + } + + // Check if we require verification + if c.VerifyIncoming { + tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert + if c.CAFile == "" { + return nil, fmt.Errorf("VerifyIncoming set, and no CA certificate provided!") + } + if cert == nil { + return nil, fmt.Errorf("VerifyIncoming set, and no Cert/Key pair provided!") + } + } + return tlsConfig, nil +} diff --git a/consul/config_test.go b/tlsutil/config_test.go similarity index 98% rename from consul/config_test.go rename to tlsutil/config_test.go index 1007ffba7..150fddccd 100644 --- a/consul/config_test.go +++ b/tlsutil/config_test.go @@ -1,4 +1,4 @@ -package consul +package tlsutil import ( "crypto/tls" @@ -204,7 +204,7 @@ func TestConfig_wrapTLS_OK(t *testing.T) { t.Fatalf("OutgoingTLSConfig err: %v", err) } - tlsClient, err := wrapTLSClient(client, clientConfig) + tlsClient, err := WrapTLSClient(client, clientConfig) if err != nil { t.Fatalf("wrapTLS err: %v", err) } else { @@ -237,7 +237,7 @@ func TestConfig_wrapTLS_BadCert(t *testing.T) { t.Fatalf("OutgoingTLSConfig err: %v", err) } - tlsClient, err := wrapTLSClient(client, clientTLSConfig) + tlsClient, err := WrapTLSClient(client, clientTLSConfig) if err == nil { t.Fatalf("wrapTLS no err") }