// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: BUSL-1.1 package types import ( "fmt" "sort" "strings" ) // TLSVersion is a strongly-typed string for TLS versions type TLSVersion string const ( // Error value, excluded from lookup maps TLSVersionInvalid TLSVersion = "TLS_INVALID" // Explicit unspecified zero-value to avoid overwriting parent defaults TLSVersionUnspecified TLSVersion = "" // Explictly allow implementation to select TLS version // May be useful to supercede defaults specified at a higher layer TLSVersionAuto TLSVersion = "TLS_AUTO" _ // Placeholder for SSLv3, hopefully we won't have to add this // TLS versions TLSv1_0 TLSVersion = "TLSv1_0" TLSv1_1 TLSVersion = "TLSv1_1" TLSv1_2 TLSVersion = "TLSv1_2" TLSv1_3 TLSVersion = "TLSv1_3" ) var ( tlsVersions = map[TLSVersion]struct{}{ TLSVersionAuto: {}, TLSv1_0: {}, TLSv1_1: {}, TLSv1_2: {}, TLSv1_3: {}, } // NOTE: This interface is deprecated in favor of tlsVersions // and should be eventually removed in a future release. DeprecatedConsulAgentTLSVersions = map[string]TLSVersion{ "": TLSVersionAuto, "tls10": TLSv1_0, "tls11": TLSv1_1, "tls12": TLSv1_2, "tls13": TLSv1_3, } // NOTE: these currently map to the deprecated config strings to support the // deployment pattern of upgrading servers first. This map should eventually // be removed and any lookups updated to instead use the TLSVersion string // values directly in a future release. ConsulAutoConfigTLSVersionStrings = map[TLSVersion]string{ TLSVersionAuto: "", TLSv1_0: "tls10", TLSv1_1: "tls11", TLSv1_2: "tls12", TLSv1_3: "tls13", } TLSVersionsWithConfigurableCipherSuites = map[TLSVersion]struct{}{ // NOTE: these two are implementation-dependent, but it is not expected that // either Go or Envoy would default to TLS 1.3 as a minimum version in the // near future TLSVersionUnspecified: {}, TLSVersionAuto: {}, TLSv1_0: {}, TLSv1_1: {}, TLSv1_2: {}, } ) func (v *TLSVersion) String() string { return string(*v) } var tlsVersionComparison = map[TLSVersion]uint{ TLSv1_0: 1, TLSv1_1: 2, TLSv1_2: 3, TLSv1_3: 4, } // Will only return true for concrete versions and won't catch // implementation-dependent conflicts with TLSVersionAuto or unspecified values func (a TLSVersion) LessThan(b TLSVersion) (error, bool) { for _, v := range []TLSVersion{a, b} { if _, ok := tlsVersionComparison[v]; !ok { return fmt.Errorf("can't compare implementation-dependent values"), false } } return nil, tlsVersionComparison[a] < tlsVersionComparison[b] } func TLSVersions() string { versions := []string{} for v := range tlsVersions { versions = append(versions, string(v)) } sort.Strings(versions) return strings.Join(versions, ", ") } func ValidateTLSVersion(v TLSVersion) error { if _, ok := tlsVersions[v]; !ok { return fmt.Errorf("no matching TLS version found for %s, please specify one of [%s]", v.String(), TLSVersions()) } return nil } // IANA cipher suite string constants as defined at // https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml // This is the total list of TLS 1.2-style cipher suites // which are currently supported by either Envoy 1.21 or the Consul agent // via Go, and may change as some older suites are removed in future // Envoy releases and Consul drops support for older Envoy versions, // and as supported cipher suites in the Go runtime change. // // The naming convention for cipher suites changed in TLS 1.3 // but constant values should still be globally unqiue. // // Handling validation on distinct sets of TLS 1.3 and TLS 1.2 TLSCipherSuite // constants would be a future exercise if cipher suites for TLS 1.3 ever // become configurable in BoringSSL, Envoy, or other implementation. type TLSCipherSuite string const ( // Cipher suites used by both Envoy and Consul agent TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 = "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256" TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 = "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256" TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 = "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256" TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 = "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256" TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA = "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA" TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA = "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA" TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 = "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384" TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 = "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384" TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA = "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA" TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA = "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA" // Older cipher suites not supported for Consul agent TLS, // will eventually be removed from Envoy defaults TLS_RSA_WITH_AES_128_GCM_SHA256 = "TLS_RSA_WITH_AES_128_GCM_SHA256" TLS_RSA_WITH_AES_128_CBC_SHA = "TLS_RSA_WITH_AES_128_CBC_SHA" TLS_RSA_WITH_AES_256_GCM_SHA384 = "TLS_RSA_WITH_AES_256_GCM_SHA384" TLS_RSA_WITH_AES_256_CBC_SHA = "TLS_RSA_WITH_AES_256_CBC_SHA" ) var ( consulAgentTLSCipherSuites = map[TLSCipherSuite]struct{}{ TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256: {}, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA: {}, TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: {}, TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA: {}, TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384: {}, TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256: {}, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA: {}, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256: {}, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA: {}, TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384: {}, } envoyTLSCipherSuiteStrings = map[TLSCipherSuite]string{ TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: "ECDHE-ECDSA-AES128-GCM-SHA256", TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256: "ECDHE-ECDSA-CHACHA20-POLY1305", TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256: "ECDHE-RSA-AES128-GCM-SHA256", TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256: "ECDHE-RSA-CHACHA20-POLY1305", TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA: "ECDHE-ECDSA-AES128-SHA", TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA: "ECDHE-RSA-AES128-SHA", TLS_RSA_WITH_AES_128_GCM_SHA256: "AES128-GCM-SHA256", TLS_RSA_WITH_AES_128_CBC_SHA: "AES128-SHA", TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384: "ECDHE-ECDSA-AES256-GCM-SHA384", TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384: "ECDHE-RSA-AES256-GCM-SHA384", TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA: "ECDHE-ECDSA-AES256-SHA", TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA: "ECDHE-RSA-AES256-SHA", TLS_RSA_WITH_AES_256_GCM_SHA384: "AES256-GCM-SHA384", TLS_RSA_WITH_AES_256_CBC_SHA: "AES256-SHA", } ) func (c *TLSCipherSuite) String() string { return string(*c) } func ValidateConsulAgentCipherSuites(cipherSuites []TLSCipherSuite) error { var unmatched []string for _, c := range cipherSuites { if _, ok := consulAgentTLSCipherSuites[c]; !ok { unmatched = append(unmatched, c.String()) } } if len(unmatched) > 0 { return fmt.Errorf("no matching Consul Agent TLS cipher suite found for %s", strings.Join(unmatched, ",")) } return nil } func ValidateEnvoyCipherSuites(cipherSuites []TLSCipherSuite) error { var unmatched []string for _, c := range cipherSuites { if _, ok := envoyTLSCipherSuiteStrings[c]; !ok { unmatched = append(unmatched, c.String()) } } if len(unmatched) > 0 { return fmt.Errorf("no matching Envoy TLS cipher suite found for %s", strings.Join(unmatched, ",")) } return nil } func MarshalEnvoyTLSCipherSuiteStrings(cipherSuites []TLSCipherSuite) []string { cipherSuiteStrings := []string{} for _, c := range cipherSuites { if s, ok := envoyTLSCipherSuiteStrings[c]; ok { cipherSuiteStrings = append(cipherSuiteStrings, s) } } return cipherSuiteStrings }