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 }