1package tlsutil 2 3import ( 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 18var 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 25var 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 46type signatureAlgorithm string 47 48const ( 49 rsaStringRepr signatureAlgorithm = "RSA" 50 ecdsaStringRepr signatureAlgorithm = "ECDSA" 51) 52 53// supportedCipherSignatures is the supported cipher suites with their 54// corresponding signature algorithm 55var 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 76var 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. 91func 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. 103type RegionWrapper func(region string, conn net.Conn) (net.Conn, error) 104 105// Wrapper wraps a connection and enables TLS on it. 106type Wrapper func(conn net.Conn) (net.Conn, error) 107 108// Config used to create tls.Config 109type 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 158func 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. 185func (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 207func (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. 227func (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. 272func (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. 311func WrapTLSClient(conn net.Conn, tlsConfig *tls.Config) (net.Conn, error) { 312 tlsConn := tls.Client(conn, tlsConfig) 313 314 // If crypto/tls is doing verification, there's no need to do 315 // our own. 316 if !tlsConfig.InsecureSkipVerify { 317 return tlsConn, nil 318 } 319 320 if err := tlsConn.Handshake(); err != nil { 321 tlsConn.Close() 322 return nil, err 323 } 324 325 // The following is lightly-modified from the doFullHandshake 326 // method in crypto/tls's handshake_client.go. 327 opts := x509.VerifyOptions{ 328 Roots: tlsConfig.RootCAs, 329 CurrentTime: time.Now(), 330 DNSName: "", 331 Intermediates: x509.NewCertPool(), 332 } 333 334 certs := tlsConn.ConnectionState().PeerCertificates 335 for i, cert := range certs { 336 if i == 0 { 337 continue 338 } 339 opts.Intermediates.AddCert(cert) 340 } 341 342 _, err := certs[0].Verify(opts) 343 if err != nil { 344 tlsConn.Close() 345 return nil, err 346 } 347 348 return tlsConn, nil 349} 350 351// IncomingTLSConfig generates a TLS configuration for incoming requests 352func (c *Config) IncomingTLSConfig() (*tls.Config, error) { 353 // Create the tlsConfig 354 tlsConfig := &tls.Config{ 355 ClientCAs: x509.NewCertPool(), 356 ClientAuth: tls.NoClientCert, 357 CipherSuites: c.CipherSuites, 358 MinVersion: c.MinVersion, 359 PreferServerCipherSuites: c.PreferServerCipherSuites, 360 } 361 362 // Parse the CA cert if any 363 err := c.AppendCA(tlsConfig.ClientCAs) 364 if err != nil { 365 return nil, err 366 } 367 368 // Add cert/key 369 cert, err := c.LoadKeyPair() 370 if err != nil { 371 return nil, err 372 } else if cert != nil { 373 tlsConfig.GetCertificate = c.KeyLoader.GetOutgoingCertificate 374 } 375 376 // Check if we require verification 377 if c.VerifyIncoming { 378 tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert 379 if c.CAFile == "" { 380 return nil, fmt.Errorf("VerifyIncoming set, and no CA certificate provided!") 381 } 382 if cert == nil { 383 return nil, fmt.Errorf("VerifyIncoming set, and no Cert/Key pair provided!") 384 } 385 } 386 387 return tlsConfig, nil 388} 389 390// ParseCiphers parses ciphersuites from the comma-separated string into 391// recognized slice 392func ParseCiphers(tlsConfig *config.TLSConfig) ([]uint16, error) { 393 suites := []uint16{} 394 395 cipherStr := strings.TrimSpace(tlsConfig.TLSCipherSuites) 396 397 var parsedCiphers []string 398 if cipherStr == "" { 399 parsedCiphers = defaultTLSCiphers 400 401 } else { 402 parsedCiphers = strings.Split(tlsConfig.TLSCipherSuites, ",") 403 } 404 for _, cipher := range parsedCiphers { 405 c, ok := supportedTLSCiphers[cipher] 406 if !ok { 407 return suites, fmt.Errorf("unsupported TLS cipher %q", cipher) 408 } 409 suites = append(suites, c) 410 } 411 412 // Ensure that the specified cipher suite list is supported by the TLS 413 // Certificate signature algorithm. This is a check for user error, where a 414 // TLS certificate could support RSA but a user has configured a cipher suite 415 // list of ciphers where only ECDSA is supported. 416 keyLoader := tlsConfig.GetKeyLoader() 417 418 // Ensure that the keypair has been loaded before continuing 419 keyLoader.LoadKeyPair(tlsConfig.CertFile, tlsConfig.KeyFile) 420 421 if keyLoader.GetCertificate() != nil { 422 supportedSignatureAlgorithm, err := getSignatureAlgorithm(keyLoader.GetCertificate()) 423 if err != nil { 424 return []uint16{}, err 425 } 426 427 for _, cipher := range parsedCiphers { 428 if supportedCipherSignatures[cipher] == supportedSignatureAlgorithm { 429 // Positive case, return the matched cipher suites as the signature 430 // algorithm is also supported 431 return suites, nil 432 } 433 } 434 435 // Negative case, if this is reached it means that none of the specified 436 // cipher suites signature algorithms match the signature algorithm 437 // for the certificate. 438 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) 439 } 440 441 // Default in case this function is called but TLS is not actually configured 442 // This is only reached if the TLS certificate is nil 443 return []uint16{}, nil 444} 445 446// getSignatureAlgorithm returns the signature algorithm for a TLS certificate 447// This is determined by examining the type of the certificate's public key, 448// as Golang doesn't expose a more straightforward API which returns this 449// type 450func getSignatureAlgorithm(tlsCert *tls.Certificate) (signatureAlgorithm, error) { 451 privKey := tlsCert.PrivateKey 452 switch privKey.(type) { 453 case *rsa.PrivateKey: 454 return rsaStringRepr, nil 455 case *ecdsa.PrivateKey: 456 return ecdsaStringRepr, nil 457 default: 458 return "", fmt.Errorf("Unsupported signature algorithm %T; RSA and ECDSA only are supported.", privKey) 459 } 460} 461 462// ParseMinVersion parses the specified minimum TLS version for the Nomad agent 463func ParseMinVersion(version string) (uint16, error) { 464 if version == "" { 465 return supportedTLSVersions["tls12"], nil 466 } 467 468 vers, ok := supportedTLSVersions[version] 469 if !ok { 470 return 0, fmt.Errorf("unsupported TLS version %q", version) 471 } 472 473 return vers, nil 474} 475 476// ShouldReloadRPCConnections compares two TLS Configurations and determines 477// whether they differ such that RPC connections should be reloaded 478func ShouldReloadRPCConnections(old, new *config.TLSConfig) (bool, error) { 479 var certificateInfoEqual bool 480 var rpcInfoEqual bool 481 482 // If already configured with TLS, compare with the new TLS configuration 483 if new != nil { 484 var err error 485 certificateInfoEqual, err = new.CertificateInfoIsEqual(old) 486 if err != nil { 487 return false, err 488 } 489 } else if new == nil && old == nil { 490 certificateInfoEqual = true 491 } 492 493 if new != nil && old != nil && new.EnableRPC == old.EnableRPC { 494 rpcInfoEqual = true 495 } 496 497 return (!rpcInfoEqual || !certificateInfoEqual), nil 498} 499