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