github.com/djenriquez/nomad-1@v0.8.1/helper/tlsutil/config.go (about)

     1  package tlsutil
     2  
     3  import (
     4  	"crypto/tls"
     5  	"crypto/x509"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"net"
     9  	"time"
    10  
    11  	"github.com/hashicorp/nomad/nomad/structs/config"
    12  )
    13  
    14  // RegionSpecificWrapper is used to invoke a static Region and turns a
    15  // RegionWrapper into a Wrapper type.
    16  func RegionSpecificWrapper(region string, tlsWrap RegionWrapper) Wrapper {
    17  	if tlsWrap == nil {
    18  		return nil
    19  	}
    20  	return func(conn net.Conn) (net.Conn, error) {
    21  		return tlsWrap(region, conn)
    22  	}
    23  }
    24  
    25  // RegionWrapper is a function that is used to wrap a non-TLS connection and
    26  // returns an appropriate TLS connection or error. This takes a Region as an
    27  // argument.
    28  type RegionWrapper func(region string, conn net.Conn) (net.Conn, error)
    29  
    30  // Wrapper wraps a connection and enables TLS on it.
    31  type Wrapper func(conn net.Conn) (net.Conn, error)
    32  
    33  // Config used to create tls.Config
    34  type Config struct {
    35  	// VerifyIncoming is used to verify the authenticity of incoming connections.
    36  	// This means that TCP requests are forbidden, only allowing for TLS. TLS connections
    37  	// must match a provided certificate authority. This can be used to force client auth.
    38  	VerifyIncoming bool
    39  
    40  	// VerifyOutgoing is used to verify the authenticity of outgoing connections.
    41  	// This means that TLS requests are used, and TCP requests are not made. TLS connections
    42  	// must match a provided certificate authority. This is used to verify authenticity of
    43  	// server nodes.
    44  	VerifyOutgoing bool
    45  
    46  	// VerifyServerHostname is used to enable hostname verification of servers. This
    47  	// ensures that the certificate presented is valid for server.<datacenter>.<domain>.
    48  	// This prevents a compromised client from being restarted as a server, and then
    49  	// intercepting request traffic as well as being added as a raft peer. This should be
    50  	// enabled by default with VerifyOutgoing, but for legacy reasons we cannot break
    51  	// existing clients.
    52  	VerifyServerHostname bool
    53  
    54  	// CAFile is a path to a certificate authority file. This is used with VerifyIncoming
    55  	// or VerifyOutgoing to verify the TLS connection.
    56  	CAFile string
    57  
    58  	// CertFile is used to provide a TLS certificate that is used for serving TLS connections.
    59  	// Must be provided to serve TLS connections.
    60  	CertFile string
    61  
    62  	// KeyFile is used to provide a TLS key that is used for serving TLS connections.
    63  	// Must be provided to serve TLS connections.
    64  	KeyFile string
    65  
    66  	// KeyLoader dynamically reloads TLS configuration.
    67  	KeyLoader *config.KeyLoader
    68  }
    69  
    70  func NewTLSConfiguration(newConf *config.TLSConfig) *Config {
    71  	return &Config{
    72  		VerifyIncoming:       true,
    73  		VerifyOutgoing:       true,
    74  		VerifyServerHostname: newConf.VerifyServerHostname,
    75  		CAFile:               newConf.CAFile,
    76  		CertFile:             newConf.CertFile,
    77  		KeyFile:              newConf.KeyFile,
    78  		KeyLoader:            newConf.GetKeyLoader(),
    79  	}
    80  }
    81  
    82  // AppendCA opens and parses the CA file and adds the certificates to
    83  // the provided CertPool.
    84  func (c *Config) AppendCA(pool *x509.CertPool) error {
    85  	if c.CAFile == "" {
    86  		return nil
    87  	}
    88  
    89  	// Read the file
    90  	data, err := ioutil.ReadFile(c.CAFile)
    91  	if err != nil {
    92  		return fmt.Errorf("Failed to read CA file: %v", err)
    93  	}
    94  
    95  	if !pool.AppendCertsFromPEM(data) {
    96  		return fmt.Errorf("Failed to parse any CA certificates")
    97  	}
    98  
    99  	return nil
   100  }
   101  
   102  // LoadKeyPair is used to open and parse a certificate and key file
   103  func (c *Config) LoadKeyPair() (*tls.Certificate, error) {
   104  	if c.CertFile == "" || c.KeyFile == "" {
   105  		return nil, nil
   106  	}
   107  
   108  	if c.KeyLoader == nil {
   109  		return nil, fmt.Errorf("No Keyloader object to perform LoadKeyPair")
   110  	}
   111  
   112  	cert, err := c.KeyLoader.LoadKeyPair(c.CertFile, c.KeyFile)
   113  	if err != nil {
   114  		return nil, fmt.Errorf("Failed to load cert/key pair: %v", err)
   115  	}
   116  	return cert, err
   117  }
   118  
   119  // OutgoingTLSConfig generates a TLS configuration for outgoing
   120  // requests. It will return a nil config if this configuration should
   121  // not use TLS for outgoing connections. Provides a callback to
   122  // fetch certificates, allowing for reloading on the fly.
   123  func (c *Config) OutgoingTLSConfig() (*tls.Config, error) {
   124  	// If VerifyServerHostname is true, that implies VerifyOutgoing
   125  	if c.VerifyServerHostname {
   126  		c.VerifyOutgoing = true
   127  	}
   128  	if !c.VerifyOutgoing {
   129  		return nil, nil
   130  	}
   131  	// Create the tlsConfig
   132  	tlsConfig := &tls.Config{
   133  		RootCAs:            x509.NewCertPool(),
   134  		InsecureSkipVerify: true,
   135  	}
   136  	if c.VerifyServerHostname {
   137  		tlsConfig.InsecureSkipVerify = false
   138  	}
   139  
   140  	// Ensure we have a CA if VerifyOutgoing is set
   141  	if c.VerifyOutgoing && c.CAFile == "" {
   142  		return nil, fmt.Errorf("VerifyOutgoing set, and no CA certificate provided!")
   143  	}
   144  
   145  	// Parse the CA cert if any
   146  	err := c.AppendCA(tlsConfig.RootCAs)
   147  	if err != nil {
   148  		return nil, err
   149  	}
   150  
   151  	cert, err := c.LoadKeyPair()
   152  	if err != nil {
   153  		return nil, err
   154  	} else if cert != nil {
   155  		tlsConfig.GetCertificate = c.KeyLoader.GetOutgoingCertificate
   156  		tlsConfig.GetClientCertificate = c.KeyLoader.GetClientCertificate
   157  	}
   158  
   159  	return tlsConfig, nil
   160  }
   161  
   162  // OutgoingTLSWrapper returns a a Wrapper based on the OutgoingTLS
   163  // configuration. If hostname verification is on, the wrapper
   164  // will properly generate the dynamic server name for verification.
   165  func (c *Config) OutgoingTLSWrapper() (RegionWrapper, error) {
   166  	// Get the TLS config
   167  	tlsConfig, err := c.OutgoingTLSConfig()
   168  	if err != nil {
   169  		return nil, err
   170  	}
   171  
   172  	// Check if TLS is not enabled
   173  	if tlsConfig == nil {
   174  		return nil, nil
   175  	}
   176  
   177  	// Generate the wrapper based on hostname verification
   178  	if c.VerifyServerHostname {
   179  		wrapper := func(region string, conn net.Conn) (net.Conn, error) {
   180  			conf := tlsConfig.Clone()
   181  			conf.ServerName = "server." + region + ".nomad"
   182  			return WrapTLSClient(conn, conf)
   183  		}
   184  		return wrapper, nil
   185  	} else {
   186  		wrapper := func(dc string, c net.Conn) (net.Conn, error) {
   187  			return WrapTLSClient(c, tlsConfig)
   188  		}
   189  		return wrapper, nil
   190  	}
   191  
   192  }
   193  
   194  // Wrap a net.Conn into a client tls connection, performing any
   195  // additional verification as needed.
   196  //
   197  // As of go 1.3, crypto/tls only supports either doing no certificate
   198  // verification, or doing full verification including of the peer's
   199  // DNS name. For consul, we want to validate that the certificate is
   200  // signed by a known CA, but because consul doesn't use DNS names for
   201  // node names, we don't verify the certificate DNS names. Since go 1.3
   202  // no longer supports this mode of operation, we have to do it
   203  // manually.
   204  func WrapTLSClient(conn net.Conn, tlsConfig *tls.Config) (net.Conn, error) {
   205  	var err error
   206  	var tlsConn *tls.Conn
   207  
   208  	tlsConn = tls.Client(conn, tlsConfig)
   209  
   210  	// If crypto/tls is doing verification, there's no need to do
   211  	// our own.
   212  	if tlsConfig.InsecureSkipVerify == false {
   213  		return tlsConn, nil
   214  	}
   215  
   216  	if err = tlsConn.Handshake(); err != nil {
   217  		tlsConn.Close()
   218  		return nil, err
   219  	}
   220  
   221  	// The following is lightly-modified from the doFullHandshake
   222  	// method in crypto/tls's handshake_client.go.
   223  	opts := x509.VerifyOptions{
   224  		Roots:         tlsConfig.RootCAs,
   225  		CurrentTime:   time.Now(),
   226  		DNSName:       "",
   227  		Intermediates: x509.NewCertPool(),
   228  	}
   229  
   230  	certs := tlsConn.ConnectionState().PeerCertificates
   231  	for i, cert := range certs {
   232  		if i == 0 {
   233  			continue
   234  		}
   235  		opts.Intermediates.AddCert(cert)
   236  	}
   237  
   238  	_, err = certs[0].Verify(opts)
   239  	if err != nil {
   240  		tlsConn.Close()
   241  		return nil, err
   242  	}
   243  
   244  	return tlsConn, err
   245  }
   246  
   247  // IncomingTLSConfig generates a TLS configuration for incoming requests
   248  func (c *Config) IncomingTLSConfig() (*tls.Config, error) {
   249  	// Create the tlsConfig
   250  	tlsConfig := &tls.Config{
   251  		ClientCAs:  x509.NewCertPool(),
   252  		ClientAuth: tls.NoClientCert,
   253  	}
   254  
   255  	// Parse the CA cert if any
   256  	err := c.AppendCA(tlsConfig.ClientCAs)
   257  	if err != nil {
   258  		return nil, err
   259  	}
   260  
   261  	// Add cert/key
   262  	cert, err := c.LoadKeyPair()
   263  	if err != nil {
   264  		return nil, err
   265  	} else if cert != nil {
   266  		tlsConfig.GetCertificate = c.KeyLoader.GetOutgoingCertificate
   267  	}
   268  
   269  	// Check if we require verification
   270  	if c.VerifyIncoming {
   271  		tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
   272  		if c.CAFile == "" {
   273  			return nil, fmt.Errorf("VerifyIncoming set, and no CA certificate provided!")
   274  		}
   275  		if cert == nil {
   276  			return nil, fmt.Errorf("VerifyIncoming set, and no Cert/Key pair provided!")
   277  		}
   278  	}
   279  
   280  	return tlsConfig, nil
   281  }