github.com/blixtra/nomad@v0.7.2-0.20171221000451-da9a1d7bb050/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  // AppendCA opens and parses the CA file and adds the certificates to
    71  // the provided CertPool.
    72  func (c *Config) AppendCA(pool *x509.CertPool) error {
    73  	if c.CAFile == "" {
    74  		return nil
    75  	}
    76  
    77  	// Read the file
    78  	data, err := ioutil.ReadFile(c.CAFile)
    79  	if err != nil {
    80  		return fmt.Errorf("Failed to read CA file: %v", err)
    81  	}
    82  
    83  	if !pool.AppendCertsFromPEM(data) {
    84  		return fmt.Errorf("Failed to parse any CA certificates")
    85  	}
    86  
    87  	return nil
    88  }
    89  
    90  // LoadKeyPair is used to open and parse a certificate and key file
    91  func (c *Config) LoadKeyPair() (*tls.Certificate, error) {
    92  	if c.CertFile == "" || c.KeyFile == "" {
    93  		return nil, nil
    94  	}
    95  
    96  	if c.KeyLoader == nil {
    97  		return nil, fmt.Errorf("No Keyloader object to perform LoadKeyPair")
    98  	}
    99  
   100  	cert, err := c.KeyLoader.LoadKeyPair(c.CertFile, c.KeyFile)
   101  	if err != nil {
   102  		return nil, fmt.Errorf("Failed to load cert/key pair: %v", err)
   103  	}
   104  	return cert, err
   105  }
   106  
   107  // OutgoingTLSConfig generates a TLS configuration for outgoing
   108  // requests. It will return a nil config if this configuration should
   109  // not use TLS for outgoing connections. Provides a callback to
   110  // fetch certificates, allowing for reloading on the fly.
   111  func (c *Config) OutgoingTLSConfig() (*tls.Config, error) {
   112  	// If VerifyServerHostname is true, that implies VerifyOutgoing
   113  	if c.VerifyServerHostname {
   114  		c.VerifyOutgoing = true
   115  	}
   116  	if !c.VerifyOutgoing {
   117  		return nil, nil
   118  	}
   119  	// Create the tlsConfig
   120  	tlsConfig := &tls.Config{
   121  		RootCAs:            x509.NewCertPool(),
   122  		InsecureSkipVerify: true,
   123  	}
   124  	if c.VerifyServerHostname {
   125  		tlsConfig.InsecureSkipVerify = false
   126  	}
   127  
   128  	// Ensure we have a CA if VerifyOutgoing is set
   129  	if c.VerifyOutgoing && c.CAFile == "" {
   130  		return nil, fmt.Errorf("VerifyOutgoing set, and no CA certificate provided!")
   131  	}
   132  
   133  	// Parse the CA cert if any
   134  	err := c.AppendCA(tlsConfig.RootCAs)
   135  	if err != nil {
   136  		return nil, err
   137  	}
   138  
   139  	cert, err := c.LoadKeyPair()
   140  	if err != nil {
   141  		return nil, err
   142  	} else if cert != nil {
   143  		tlsConfig.GetCertificate = c.KeyLoader.GetOutgoingCertificate
   144  		tlsConfig.GetClientCertificate = c.KeyLoader.GetClientCertificate
   145  	}
   146  
   147  	return tlsConfig, nil
   148  }
   149  
   150  // OutgoingTLSWrapper returns a a Wrapper based on the OutgoingTLS
   151  // configuration. If hostname verification is on, the wrapper
   152  // will properly generate the dynamic server name for verification.
   153  func (c *Config) OutgoingTLSWrapper() (RegionWrapper, error) {
   154  	// Get the TLS config
   155  	tlsConfig, err := c.OutgoingTLSConfig()
   156  	if err != nil {
   157  		return nil, err
   158  	}
   159  
   160  	// Check if TLS is not enabled
   161  	if tlsConfig == nil {
   162  		return nil, nil
   163  	}
   164  
   165  	// Generate the wrapper based on hostname verification
   166  	if c.VerifyServerHostname {
   167  		wrapper := func(region string, conn net.Conn) (net.Conn, error) {
   168  			conf := tlsConfig.Clone()
   169  			conf.ServerName = "server." + region + ".nomad"
   170  			return WrapTLSClient(conn, conf)
   171  		}
   172  		return wrapper, nil
   173  	} else {
   174  		wrapper := func(dc string, c net.Conn) (net.Conn, error) {
   175  			return WrapTLSClient(c, tlsConfig)
   176  		}
   177  		return wrapper, nil
   178  	}
   179  
   180  }
   181  
   182  // Wrap a net.Conn into a client tls connection, performing any
   183  // additional verification as needed.
   184  //
   185  // As of go 1.3, crypto/tls only supports either doing no certificate
   186  // verification, or doing full verification including of the peer's
   187  // DNS name. For consul, we want to validate that the certificate is
   188  // signed by a known CA, but because consul doesn't use DNS names for
   189  // node names, we don't verify the certificate DNS names. Since go 1.3
   190  // no longer supports this mode of operation, we have to do it
   191  // manually.
   192  func WrapTLSClient(conn net.Conn, tlsConfig *tls.Config) (net.Conn, error) {
   193  	var err error
   194  	var tlsConn *tls.Conn
   195  
   196  	tlsConn = tls.Client(conn, tlsConfig)
   197  
   198  	// If crypto/tls is doing verification, there's no need to do
   199  	// our own.
   200  	if tlsConfig.InsecureSkipVerify == false {
   201  		return tlsConn, nil
   202  	}
   203  
   204  	if err = tlsConn.Handshake(); err != nil {
   205  		tlsConn.Close()
   206  		return nil, err
   207  	}
   208  
   209  	// The following is lightly-modified from the doFullHandshake
   210  	// method in crypto/tls's handshake_client.go.
   211  	opts := x509.VerifyOptions{
   212  		Roots:         tlsConfig.RootCAs,
   213  		CurrentTime:   time.Now(),
   214  		DNSName:       "",
   215  		Intermediates: x509.NewCertPool(),
   216  	}
   217  
   218  	certs := tlsConn.ConnectionState().PeerCertificates
   219  	for i, cert := range certs {
   220  		if i == 0 {
   221  			continue
   222  		}
   223  		opts.Intermediates.AddCert(cert)
   224  	}
   225  
   226  	_, err = certs[0].Verify(opts)
   227  	if err != nil {
   228  		tlsConn.Close()
   229  		return nil, err
   230  	}
   231  
   232  	return tlsConn, err
   233  }
   234  
   235  // IncomingTLSConfig generates a TLS configuration for incoming requests
   236  func (c *Config) IncomingTLSConfig() (*tls.Config, error) {
   237  	// Create the tlsConfig
   238  	tlsConfig := &tls.Config{
   239  		ClientCAs:  x509.NewCertPool(),
   240  		ClientAuth: tls.NoClientCert,
   241  	}
   242  
   243  	// Parse the CA cert if any
   244  	err := c.AppendCA(tlsConfig.ClientCAs)
   245  	if err != nil {
   246  		return nil, err
   247  	}
   248  
   249  	// Add cert/key
   250  	cert, err := c.LoadKeyPair()
   251  	if err != nil {
   252  		return nil, err
   253  	} else if cert != nil {
   254  		tlsConfig.GetCertificate = c.KeyLoader.GetOutgoingCertificate
   255  	}
   256  
   257  	// Check if we require verification
   258  	if c.VerifyIncoming {
   259  		tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
   260  		if c.CAFile == "" {
   261  			return nil, fmt.Errorf("VerifyIncoming set, and no CA certificate provided!")
   262  		}
   263  		if cert == nil {
   264  			return nil, fmt.Errorf("VerifyIncoming set, and no Cert/Key pair provided!")
   265  		}
   266  	}
   267  
   268  	return tlsConfig, nil
   269  }