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