github.com/tschmi5/nomad@v0.11.8/helper/tlsutil/config.go (about)

     1  package tlsutil
     2  
     3  import (
     4  	"crypto/ecdsa"
     5  	"crypto/rsa"
     6  	"crypto/tls"
     7  	"crypto/x509"
     8  	"fmt"
     9  	"io/ioutil"
    10  	"net"
    11  	"strings"
    12  	"time"
    13  
    14  	"github.com/hashicorp/nomad/nomad/structs/config"
    15  )
    16  
    17  // supportedTLSVersions are the current TLS versions that Nomad supports
    18  var supportedTLSVersions = map[string]uint16{
    19  	"tls10": tls.VersionTLS10,
    20  	"tls11": tls.VersionTLS11,
    21  	"tls12": tls.VersionTLS12,
    22  }
    23  
    24  // supportedTLSCiphers are the complete list of TLS ciphers supported by Nomad
    25  var supportedTLSCiphers = map[string]uint16{
    26  	"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305":    tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
    27  	"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305":  tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
    28  	"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256":   tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
    29  	"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
    30  	"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384":   tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
    31  	"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384": tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
    32  	"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256":   tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
    33  	"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA":      tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
    34  	"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
    35  	"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA":    tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
    36  	"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA":      tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
    37  	"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA":    tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
    38  	"TLS_RSA_WITH_AES_128_GCM_SHA256":         tls.TLS_RSA_WITH_AES_128_GCM_SHA256,
    39  	"TLS_RSA_WITH_AES_256_GCM_SHA384":         tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
    40  	"TLS_RSA_WITH_AES_128_CBC_SHA256":         tls.TLS_RSA_WITH_AES_128_CBC_SHA256,
    41  	"TLS_RSA_WITH_AES_128_CBC_SHA":            tls.TLS_RSA_WITH_AES_128_CBC_SHA,
    42  	"TLS_RSA_WITH_AES_256_CBC_SHA":            tls.TLS_RSA_WITH_AES_256_CBC_SHA,
    43  }
    44  
    45  // signatureAlgorithm is the string representation of a signing algorithm
    46  type signatureAlgorithm string
    47  
    48  const (
    49  	rsaStringRepr   signatureAlgorithm = "RSA"
    50  	ecdsaStringRepr signatureAlgorithm = "ECDSA"
    51  )
    52  
    53  // supportedCipherSignatures is the supported cipher suites with their
    54  // corresponding signature algorithm
    55  var supportedCipherSignatures = map[string]signatureAlgorithm{
    56  	"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305":    rsaStringRepr,
    57  	"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305":  ecdsaStringRepr,
    58  	"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256":   rsaStringRepr,
    59  	"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256": ecdsaStringRepr,
    60  	"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384":   rsaStringRepr,
    61  	"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384": ecdsaStringRepr,
    62  	"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256":   rsaStringRepr,
    63  	"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA":      rsaStringRepr,
    64  	"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256": ecdsaStringRepr,
    65  	"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA":    ecdsaStringRepr,
    66  	"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA":      rsaStringRepr,
    67  	"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA":    ecdsaStringRepr,
    68  	"TLS_RSA_WITH_AES_128_GCM_SHA256":         rsaStringRepr,
    69  	"TLS_RSA_WITH_AES_256_GCM_SHA384":         rsaStringRepr,
    70  	"TLS_RSA_WITH_AES_128_CBC_SHA256":         rsaStringRepr,
    71  	"TLS_RSA_WITH_AES_128_CBC_SHA":            rsaStringRepr,
    72  	"TLS_RSA_WITH_AES_256_CBC_SHA":            rsaStringRepr,
    73  }
    74  
    75  // defaultTLSCiphers are the TLS Ciphers that are supported by default
    76  var defaultTLSCiphers = []string{
    77  	"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
    78  	"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
    79  	"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
    80  	"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305",
    81  	"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
    82  	"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
    83  	"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
    84  	"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
    85  	"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
    86  	"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
    87  }
    88  
    89  // RegionSpecificWrapper is used to invoke a static Region and turns a
    90  // RegionWrapper into a Wrapper type.
    91  func RegionSpecificWrapper(region string, tlsWrap RegionWrapper) Wrapper {
    92  	if tlsWrap == nil {
    93  		return nil
    94  	}
    95  	return func(conn net.Conn) (net.Conn, error) {
    96  		return tlsWrap(region, conn)
    97  	}
    98  }
    99  
   100  // RegionWrapper is a function that is used to wrap a non-TLS connection and
   101  // returns an appropriate TLS connection or error. This takes a Region as an
   102  // argument.
   103  type RegionWrapper func(region string, conn net.Conn) (net.Conn, error)
   104  
   105  // Wrapper wraps a connection and enables TLS on it.
   106  type Wrapper func(conn net.Conn) (net.Conn, error)
   107  
   108  // Config used to create tls.Config
   109  type Config struct {
   110  	// VerifyIncoming is used to verify the authenticity of incoming connections.
   111  	// This means that TCP requests are forbidden, only allowing for TLS. TLS connections
   112  	// must match a provided certificate authority. This can be used to force client auth.
   113  	VerifyIncoming bool
   114  
   115  	// VerifyOutgoing is used to verify the authenticity of outgoing connections.
   116  	// This means that TLS requests are used, and TCP requests are not made. TLS connections
   117  	// must match a provided certificate authority. This is used to verify authenticity of
   118  	// server nodes.
   119  	VerifyOutgoing bool
   120  
   121  	// VerifyServerHostname is used to enable hostname verification of servers. This
   122  	// ensures that the certificate presented is valid for server.<datacenter>.<domain>.
   123  	// This prevents a compromised client from being restarted as a server, and then
   124  	// intercepting request traffic as well as being added as a raft peer. This should be
   125  	// enabled by default with VerifyOutgoing, but for legacy reasons we cannot break
   126  	// existing clients.
   127  	VerifyServerHostname bool
   128  
   129  	// CAFile is a path to a certificate authority file. This is used with VerifyIncoming
   130  	// or VerifyOutgoing to verify the TLS connection.
   131  	CAFile string
   132  
   133  	// CertFile is used to provide a TLS certificate that is used for serving TLS connections.
   134  	// Must be provided to serve TLS connections.
   135  	CertFile string
   136  
   137  	// KeyFile is used to provide a TLS key that is used for serving TLS connections.
   138  	// Must be provided to serve TLS connections.
   139  	KeyFile string
   140  
   141  	// KeyLoader dynamically reloads TLS configuration.
   142  	KeyLoader *config.KeyLoader
   143  
   144  	// CipherSuites have a default safe configuration, or operators can override
   145  	// these values for acceptable safe alternatives.
   146  	CipherSuites []uint16
   147  
   148  	// PreferServerCipherSuites controls whether the server selects the
   149  	// client's most preferred ciphersuite, or the server's most preferred
   150  	// ciphersuite. If true then the server's preference, as expressed in
   151  	// the order of elements in CipherSuites, is used.
   152  	PreferServerCipherSuites bool
   153  
   154  	// MinVersion contains the minimum SSL/TLS version that is accepted.
   155  	MinVersion uint16
   156  }
   157  
   158  func NewTLSConfiguration(newConf *config.TLSConfig, verifyIncoming, verifyOutgoing bool) (*Config, error) {
   159  	ciphers, err := ParseCiphers(newConf)
   160  	if err != nil {
   161  		return nil, err
   162  	}
   163  
   164  	minVersion, err := ParseMinVersion(newConf.TLSMinVersion)
   165  	if err != nil {
   166  		return nil, err
   167  	}
   168  
   169  	return &Config{
   170  		VerifyIncoming:           verifyIncoming,
   171  		VerifyOutgoing:           verifyOutgoing,
   172  		VerifyServerHostname:     newConf.VerifyServerHostname,
   173  		CAFile:                   newConf.CAFile,
   174  		CertFile:                 newConf.CertFile,
   175  		KeyFile:                  newConf.KeyFile,
   176  		KeyLoader:                newConf.GetKeyLoader(),
   177  		CipherSuites:             ciphers,
   178  		MinVersion:               minVersion,
   179  		PreferServerCipherSuites: newConf.TLSPreferServerCipherSuites,
   180  	}, nil
   181  }
   182  
   183  // AppendCA opens and parses the CA file and adds the certificates to
   184  // the provided CertPool.
   185  func (c *Config) AppendCA(pool *x509.CertPool) error {
   186  	if c.CAFile == "" {
   187  		return nil
   188  	}
   189  
   190  	// Read the file
   191  	data, err := ioutil.ReadFile(c.CAFile)
   192  	if err != nil {
   193  		return fmt.Errorf("Failed to read CA file: %v", err)
   194  	}
   195  
   196  	// Read certificates and return an error if no valid certificates were
   197  	// found. Unfortunately it is very difficult to return meaningful
   198  	// errors as PEM files are extremely permissive.
   199  	if !pool.AppendCertsFromPEM(data) {
   200  		return fmt.Errorf("Failed to parse any valid certificates in CA file: %s", c.CAFile)
   201  	}
   202  
   203  	return nil
   204  }
   205  
   206  // LoadKeyPair is used to open and parse a certificate and key file
   207  func (c *Config) LoadKeyPair() (*tls.Certificate, error) {
   208  	if c.CertFile == "" || c.KeyFile == "" {
   209  		return nil, nil
   210  	}
   211  
   212  	if c.KeyLoader == nil {
   213  		return nil, fmt.Errorf("No Keyloader object to perform LoadKeyPair")
   214  	}
   215  
   216  	cert, err := c.KeyLoader.LoadKeyPair(c.CertFile, c.KeyFile)
   217  	if err != nil {
   218  		return nil, fmt.Errorf("Failed to load cert/key pair: %v", err)
   219  	}
   220  	return cert, err
   221  }
   222  
   223  // OutgoingTLSConfig generates a TLS configuration for outgoing
   224  // requests. It will return a nil config if this configuration should
   225  // not use TLS for outgoing connections. Provides a callback to
   226  // fetch certificates, allowing for reloading on the fly.
   227  func (c *Config) OutgoingTLSConfig() (*tls.Config, error) {
   228  	// If VerifyServerHostname is true, that implies VerifyOutgoing
   229  	if c.VerifyServerHostname {
   230  		c.VerifyOutgoing = true
   231  	}
   232  	if !c.VerifyOutgoing {
   233  		return nil, nil
   234  	}
   235  	// Create the tlsConfig
   236  	tlsConfig := &tls.Config{
   237  		RootCAs:                  x509.NewCertPool(),
   238  		InsecureSkipVerify:       true,
   239  		CipherSuites:             c.CipherSuites,
   240  		MinVersion:               c.MinVersion,
   241  		PreferServerCipherSuites: c.PreferServerCipherSuites,
   242  	}
   243  	if c.VerifyServerHostname {
   244  		tlsConfig.InsecureSkipVerify = false
   245  	}
   246  
   247  	// Ensure we have a CA if VerifyOutgoing is set
   248  	if c.VerifyOutgoing && c.CAFile == "" {
   249  		return nil, fmt.Errorf("VerifyOutgoing set, and no CA certificate provided!")
   250  	}
   251  
   252  	// Parse the CA cert if any
   253  	err := c.AppendCA(tlsConfig.RootCAs)
   254  	if err != nil {
   255  		return nil, err
   256  	}
   257  
   258  	cert, err := c.LoadKeyPair()
   259  	if err != nil {
   260  		return nil, err
   261  	} else if cert != nil {
   262  		tlsConfig.GetCertificate = c.KeyLoader.GetOutgoingCertificate
   263  		tlsConfig.GetClientCertificate = c.KeyLoader.GetClientCertificate
   264  	}
   265  
   266  	return tlsConfig, nil
   267  }
   268  
   269  // OutgoingTLSWrapper returns a a Wrapper based on the OutgoingTLS
   270  // configuration. If hostname verification is on, the wrapper
   271  // will properly generate the dynamic server name for verification.
   272  func (c *Config) OutgoingTLSWrapper() (RegionWrapper, error) {
   273  	// Get the TLS config
   274  	tlsConfig, err := c.OutgoingTLSConfig()
   275  	if err != nil {
   276  		return nil, err
   277  	}
   278  
   279  	// Check if TLS is not enabled
   280  	if tlsConfig == nil {
   281  		return nil, nil
   282  	}
   283  
   284  	// Generate the wrapper based on hostname verification
   285  	if c.VerifyServerHostname {
   286  		wrapper := func(region string, conn net.Conn) (net.Conn, error) {
   287  			conf := tlsConfig.Clone()
   288  			conf.ServerName = "server." + region + ".nomad"
   289  			return WrapTLSClient(conn, conf)
   290  		}
   291  		return wrapper, nil
   292  	} else {
   293  		wrapper := func(dc string, c net.Conn) (net.Conn, error) {
   294  			return WrapTLSClient(c, tlsConfig)
   295  		}
   296  		return wrapper, nil
   297  	}
   298  
   299  }
   300  
   301  // Wrap a net.Conn into a client tls connection, performing any
   302  // additional verification as needed.
   303  //
   304  // As of go 1.3, crypto/tls only supports either doing no certificate
   305  // verification, or doing full verification including of the peer's
   306  // DNS name. For consul, we want to validate that the certificate is
   307  // signed by a known CA, but because consul doesn't use DNS names for
   308  // node names, we don't verify the certificate DNS names. Since go 1.3
   309  // no longer supports this mode of operation, we have to do it
   310  // manually.
   311  func WrapTLSClient(conn net.Conn, tlsConfig *tls.Config) (net.Conn, error) {
   312  	var err error
   313  	var tlsConn *tls.Conn
   314  
   315  	tlsConn = tls.Client(conn, tlsConfig)
   316  
   317  	// If crypto/tls is doing verification, there's no need to do
   318  	// our own.
   319  	if tlsConfig.InsecureSkipVerify == false {
   320  		return tlsConn, nil
   321  	}
   322  
   323  	if err = tlsConn.Handshake(); err != nil {
   324  		tlsConn.Close()
   325  		return nil, err
   326  	}
   327  
   328  	// The following is lightly-modified from the doFullHandshake
   329  	// method in crypto/tls's handshake_client.go.
   330  	opts := x509.VerifyOptions{
   331  		Roots:         tlsConfig.RootCAs,
   332  		CurrentTime:   time.Now(),
   333  		DNSName:       "",
   334  		Intermediates: x509.NewCertPool(),
   335  	}
   336  
   337  	certs := tlsConn.ConnectionState().PeerCertificates
   338  	for i, cert := range certs {
   339  		if i == 0 {
   340  			continue
   341  		}
   342  		opts.Intermediates.AddCert(cert)
   343  	}
   344  
   345  	_, err = certs[0].Verify(opts)
   346  	if err != nil {
   347  		tlsConn.Close()
   348  		return nil, err
   349  	}
   350  
   351  	return tlsConn, err
   352  }
   353  
   354  // IncomingTLSConfig generates a TLS configuration for incoming requests
   355  func (c *Config) IncomingTLSConfig() (*tls.Config, error) {
   356  	// Create the tlsConfig
   357  	tlsConfig := &tls.Config{
   358  		ClientCAs:                x509.NewCertPool(),
   359  		ClientAuth:               tls.NoClientCert,
   360  		CipherSuites:             c.CipherSuites,
   361  		MinVersion:               c.MinVersion,
   362  		PreferServerCipherSuites: c.PreferServerCipherSuites,
   363  	}
   364  
   365  	// Parse the CA cert if any
   366  	err := c.AppendCA(tlsConfig.ClientCAs)
   367  	if err != nil {
   368  		return nil, err
   369  	}
   370  
   371  	// Add cert/key
   372  	cert, err := c.LoadKeyPair()
   373  	if err != nil {
   374  		return nil, err
   375  	} else if cert != nil {
   376  		tlsConfig.GetCertificate = c.KeyLoader.GetOutgoingCertificate
   377  	}
   378  
   379  	// Check if we require verification
   380  	if c.VerifyIncoming {
   381  		tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
   382  		if c.CAFile == "" {
   383  			return nil, fmt.Errorf("VerifyIncoming set, and no CA certificate provided!")
   384  		}
   385  		if cert == nil {
   386  			return nil, fmt.Errorf("VerifyIncoming set, and no Cert/Key pair provided!")
   387  		}
   388  	}
   389  
   390  	return tlsConfig, nil
   391  }
   392  
   393  // ParseCiphers parses ciphersuites from the comma-separated string into
   394  // recognized slice
   395  func ParseCiphers(tlsConfig *config.TLSConfig) ([]uint16, error) {
   396  	suites := []uint16{}
   397  
   398  	cipherStr := strings.TrimSpace(tlsConfig.TLSCipherSuites)
   399  
   400  	var parsedCiphers []string
   401  	if cipherStr == "" {
   402  		parsedCiphers = defaultTLSCiphers
   403  
   404  	} else {
   405  		parsedCiphers = strings.Split(tlsConfig.TLSCipherSuites, ",")
   406  	}
   407  	for _, cipher := range parsedCiphers {
   408  		c, ok := supportedTLSCiphers[cipher]
   409  		if !ok {
   410  			return suites, fmt.Errorf("unsupported TLS cipher %q", cipher)
   411  		}
   412  		suites = append(suites, c)
   413  	}
   414  
   415  	// Ensure that the specified cipher suite list is supported by the TLS
   416  	// Certificate signature algorithm. This is a check for user error, where a
   417  	// TLS certificate could support RSA but a user has configured a cipher suite
   418  	// list of ciphers where only ECDSA is supported.
   419  	keyLoader := tlsConfig.GetKeyLoader()
   420  
   421  	// Ensure that the keypair has been loaded before continuing
   422  	keyLoader.LoadKeyPair(tlsConfig.CertFile, tlsConfig.KeyFile)
   423  
   424  	if keyLoader.GetCertificate() != nil {
   425  		supportedSignatureAlgorithm, err := getSignatureAlgorithm(keyLoader.GetCertificate())
   426  		if err != nil {
   427  			return []uint16{}, err
   428  		}
   429  
   430  		for _, cipher := range parsedCiphers {
   431  			if supportedCipherSignatures[cipher] == supportedSignatureAlgorithm {
   432  				// Positive case, return the matched cipher suites as the signature
   433  				// algorithm is also supported
   434  				return suites, nil
   435  			}
   436  		}
   437  
   438  		// Negative case, if this is reached it means that none of the specified
   439  		// cipher suites signature algorithms match the signature algorithm
   440  		// for the certificate.
   441  		return []uint16{}, fmt.Errorf("Specified cipher suites don't support the certificate signature algorithm %s, consider adding more cipher suites to match this signature algorithm.", supportedSignatureAlgorithm)
   442  	}
   443  
   444  	// Default in case this function is called but TLS is not actually configured
   445  	// This is only reached if the TLS certificate is nil
   446  	return []uint16{}, nil
   447  }
   448  
   449  // getSignatureAlgorithm returns the signature algorithm for a TLS certificate
   450  // This is determined by examining the type of the certificate's public key,
   451  // as Golang doesn't expose a more straightforward  API which returns this
   452  // type
   453  func getSignatureAlgorithm(tlsCert *tls.Certificate) (signatureAlgorithm, error) {
   454  	privKey := tlsCert.PrivateKey
   455  	switch privKey.(type) {
   456  	case *rsa.PrivateKey:
   457  		return rsaStringRepr, nil
   458  	case *ecdsa.PrivateKey:
   459  		return ecdsaStringRepr, nil
   460  	default:
   461  		return "", fmt.Errorf("Unsupported signature algorithm %T; RSA and ECDSA only are supported.", privKey)
   462  	}
   463  }
   464  
   465  // ParseMinVersion parses the specified minimum TLS version for the Nomad agent
   466  func ParseMinVersion(version string) (uint16, error) {
   467  	if version == "" {
   468  		return supportedTLSVersions["tls12"], nil
   469  	}
   470  
   471  	vers, ok := supportedTLSVersions[version]
   472  	if !ok {
   473  		return 0, fmt.Errorf("unsupported TLS version %q", version)
   474  	}
   475  
   476  	return vers, nil
   477  }
   478  
   479  // ShouldReloadRPCConnections compares two TLS Configurations and determines
   480  // whether they differ such that RPC connections should be reloaded
   481  func ShouldReloadRPCConnections(old, new *config.TLSConfig) (bool, error) {
   482  	var certificateInfoEqual bool
   483  	var rpcInfoEqual bool
   484  
   485  	// If already configured with TLS, compare with the new TLS configuration
   486  	if new != nil {
   487  		var err error
   488  		certificateInfoEqual, err = new.CertificateInfoIsEqual(old)
   489  		if err != nil {
   490  			return false, err
   491  		}
   492  	} else if new == nil && old == nil {
   493  		certificateInfoEqual = true
   494  	}
   495  
   496  	if new != nil && old != nil && new.EnableRPC == old.EnableRPC {
   497  		rpcInfoEqual = true
   498  	}
   499  
   500  	return (!rpcInfoEqual || !certificateInfoEqual), nil
   501  }