types: add types/tls.go for strongly-typed TLS versions and cipher suites (#11645)
types: add TLS constants types: distinguish between human and Envoy serialization for TLSVersion constants types: add DeprecatedAgentTLSVersions for backwards compatibility types: add methods for printing TLSVersion as strings types: add TLSVersionInvalid error value types: add a basic test for TLSVersion comparison types: add TLS cihper suite mapping using IANA constant names and values types: adding ConsulAutoConfigTLSVersionStrings changelog: add entry for TLSVersion and TLSCipherSuite types types: initialize TLSVerison constants starting at zero types: remove TLSVersionInvalid < 0 test types: update note for ConsulAutoConfigTLSVersionStrings types: programmatically invert TLSCipherSuites for HumanTLSCipherSuiteStrings lookup map Co-authored-by: Dan Upton <daniel@floppy.co> types: add test for TLSVersion zero-value types: remove unused EnvoyTLSVersionStrings types: implement MarshalJSON for TLSVersion types: implement TLSVersionUnspecified as zero value types: delegate TLS.MarshalJSON to json.Marshal, use ConsulConfigTLSVersionStrings as default String() values Co-authored-by: Dan Upton <daniel@floppy.co>
This commit is contained in:
parent
42a528f163
commit
78a008daf6
|
@ -0,0 +1,3 @@
|
||||||
|
```release-note:improvement
|
||||||
|
types: add TLSVersion and TLSCipherSuite
|
||||||
|
```
|
|
@ -0,0 +1,185 @@
|
||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TLSVersion is a strongly-typed int used for relative comparison
|
||||||
|
// (minimum, maximum, greater than, less than) of TLS versions
|
||||||
|
type TLSVersion int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Error value, excluded from lookup maps
|
||||||
|
TLSVersionInvalid TLSVersion = iota - 1
|
||||||
|
|
||||||
|
// Explicit unspecified zero-value to avoid overwriting parent defaults
|
||||||
|
TLSVersionUnspecified
|
||||||
|
|
||||||
|
// Explictly allow implementation to select TLS version
|
||||||
|
// May be useful to supercede defaults specified at a higher layer
|
||||||
|
TLSVersionAuto
|
||||||
|
|
||||||
|
_ // Placeholder for SSLv3, hopefully we won't have to add this
|
||||||
|
|
||||||
|
// TLS versions
|
||||||
|
TLSv1_0
|
||||||
|
TLSv1_1
|
||||||
|
TLSv1_2
|
||||||
|
TLSv1_3
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
TLSVersions = map[string]TLSVersion{
|
||||||
|
"TLS_AUTO": TLSVersionAuto,
|
||||||
|
"TLSv1_0": TLSv1_0,
|
||||||
|
"TLSv1_1": TLSv1_1,
|
||||||
|
"TLSv1_2": TLSv1_2,
|
||||||
|
"TLSv1_3": 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,
|
||||||
|
}
|
||||||
|
HumanTLSVersionStrings = map[TLSVersion]string{
|
||||||
|
TLSVersionAuto: "Allow implementation to select TLS version",
|
||||||
|
TLSv1_0: "TLS 1.0",
|
||||||
|
TLSv1_1: "TLS 1.1",
|
||||||
|
TLSv1_2: "TLS 1.2",
|
||||||
|
TLSv1_3: "TLS 1.3",
|
||||||
|
}
|
||||||
|
ConsulConfigTLSVersionStrings = func() map[TLSVersion]string {
|
||||||
|
inverted := make(map[TLSVersion]string, len(TLSVersions))
|
||||||
|
for k, v := range TLSVersions {
|
||||||
|
inverted[v] = k
|
||||||
|
}
|
||||||
|
return inverted
|
||||||
|
}()
|
||||||
|
// 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 use ConsulConfigTLSVersionStrings
|
||||||
|
// with newer config strings instead in a future release.
|
||||||
|
ConsulAutoConfigTLSVersionStrings = map[TLSVersion]string{
|
||||||
|
TLSVersionAuto: "",
|
||||||
|
TLSv1_0: "tls10",
|
||||||
|
TLSv1_1: "tls11",
|
||||||
|
TLSv1_2: "tls12",
|
||||||
|
TLSv1_3: "tls13",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (v TLSVersion) String() string {
|
||||||
|
return ConsulConfigTLSVersionStrings[v]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v TLSVersion) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(v.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *TLSVersion) UnmarshalJSON(bytes []byte) error {
|
||||||
|
versionStr := string(bytes)
|
||||||
|
|
||||||
|
if n := len(versionStr); n > 1 && versionStr[0] == '"' && versionStr[n-1] == '"' {
|
||||||
|
versionStr = versionStr[1 : n-1] // trim surrounding quotes
|
||||||
|
}
|
||||||
|
|
||||||
|
if version, ok := TLSVersions[versionStr]; ok {
|
||||||
|
*v = version
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
*v = TLSVersionInvalid
|
||||||
|
return fmt.Errorf("no matching TLS Version found for %s", versionStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IANA cipher suite constants and values 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 a subset of 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 uint16
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Envoy cipher suites also used by Consul agent
|
||||||
|
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 TLSCipherSuite = 0xc02b
|
||||||
|
TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 = 0xcca9 // Not used by Consul agent yet
|
||||||
|
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 = 0xc02f
|
||||||
|
TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 = 0xcca8 // Not used by Consul agent yet
|
||||||
|
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA = 0xc009
|
||||||
|
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA = 0xc013
|
||||||
|
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 = 0xc02c
|
||||||
|
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 = 0xc030
|
||||||
|
TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA = 0xc00a
|
||||||
|
TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA = 0xc014
|
||||||
|
|
||||||
|
// Older cipher suites not supported for Consul agent TLS, will eventually be removed from Envoy defaults
|
||||||
|
TLS_RSA_WITH_AES_128_GCM_SHA256 = 0x009c
|
||||||
|
TLS_RSA_WITH_AES_128_CBC_SHA = 0x002f
|
||||||
|
TLS_RSA_WITH_AES_256_GCM_SHA384 = 0x009d
|
||||||
|
TLS_RSA_WITH_AES_256_CBC_SHA = 0x0035
|
||||||
|
|
||||||
|
// Additional cipher suites used by Consul agent but not Envoy
|
||||||
|
// TODO: these are both explicitly listed as insecure and disabled in the Go source, should they be removed?
|
||||||
|
// https://cs.opensource.google/go/go/+/refs/tags/go1.17.3:src/crypto/tls/cipher_suites.go;l=329-330
|
||||||
|
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 = 0x0023
|
||||||
|
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 = 0xc027
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
TLSCipherSuites = map[string]TLSCipherSuite{
|
||||||
|
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256": TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
|
||||||
|
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA": TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
|
||||||
|
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256": TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||||
|
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA": TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
|
||||||
|
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384": TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
||||||
|
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256": TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
|
||||||
|
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA": TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
|
||||||
|
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256": TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||||
|
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA": TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
|
||||||
|
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384": TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||||||
|
|
||||||
|
"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,
|
||||||
|
|
||||||
|
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256": TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
|
||||||
|
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256": TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
|
||||||
|
}
|
||||||
|
HumanTLSCipherSuiteStrings = func() map[TLSCipherSuite]string {
|
||||||
|
inverted := make(map[TLSCipherSuite]string, len(TLSCipherSuites))
|
||||||
|
for k, v := range TLSCipherSuites {
|
||||||
|
inverted[v] = k
|
||||||
|
}
|
||||||
|
return inverted
|
||||||
|
}()
|
||||||
|
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",
|
||||||
|
}
|
||||||
|
)
|
|
@ -0,0 +1,49 @@
|
||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTLSVersion_PartialEq(t *testing.T) {
|
||||||
|
require.Greater(t, TLSv1_3, TLSv1_2)
|
||||||
|
require.Greater(t, TLSv1_2, TLSv1_1)
|
||||||
|
require.Greater(t, TLSv1_1, TLSv1_0)
|
||||||
|
|
||||||
|
require.Less(t, TLSv1_2, TLSv1_3)
|
||||||
|
require.Less(t, TLSv1_1, TLSv1_2)
|
||||||
|
require.Less(t, TLSv1_0, TLSv1_1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTLSVersion_Invalid(t *testing.T) {
|
||||||
|
var zeroValue TLSVersion
|
||||||
|
require.NotEqual(t, TLSVersionInvalid, zeroValue)
|
||||||
|
require.NotEqual(t, TLSVersionInvalid, TLSVersionUnspecified)
|
||||||
|
require.NotEqual(t, TLSVersionInvalid, TLSVersionAuto)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTLSVersion_Zero(t *testing.T) {
|
||||||
|
var zeroValue TLSVersion
|
||||||
|
require.Equal(t, TLSVersionUnspecified, zeroValue)
|
||||||
|
require.NotEqual(t, TLSVersionUnspecified, TLSVersionInvalid)
|
||||||
|
require.NotEqual(t, TLSVersionUnspecified, TLSVersionAuto)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTLSVersion_ToJSON(t *testing.T) {
|
||||||
|
var tlsVersion TLSVersion
|
||||||
|
err := tlsVersion.UnmarshalJSON([]byte(`"foo"`))
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Equal(t, tlsVersion, TLSVersionInvalid)
|
||||||
|
|
||||||
|
for str, version := range TLSVersions {
|
||||||
|
versionJSON, err := json.Marshal(version)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, versionJSON, []byte(`"`+str+`"`))
|
||||||
|
|
||||||
|
err = tlsVersion.UnmarshalJSON([]byte(`"` + str + `"`))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, tlsVersion, version)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue