From f87e566df9c790938be2c6e5a64f7b58ea0cefb9 Mon Sep 17 00:00:00 2001 From: Julien Pivotto Date: Wed, 13 May 2020 20:26:01 +0200 Subject: [PATCH] tls: enable the selection of more TLS settings (#1695) tls: enable the selection of more TLS settings * Rename `tls_config` to `tls_server_config`. * Add new http server config with HTTP/2 enabled by default. Signed-off-by: Julien Pivotto --- https/README.md | 38 ++++- .../tls_config_auth_clientCAs_invalid.bad.yml | 2 +- .../tls_config_auth_clientCAs_missing.bad.yml | 2 +- .../tls_config_auth_user_list_invalid.bad.yml | 2 +- https/testdata/tls_config_junk_key.yml | 2 +- https/testdata/tls_config_noAuth.bad.yml | 2 +- .../tls_config_noAuth.good.blocking.yml | 2 +- https/testdata/tls_config_noAuth.good.yml | 2 +- .../tls_config_noAuth_allCiphers.good.yml | 26 +++ .../tls_config_noAuth_allCurves.good.yml | 10 ++ .../tls_config_noAuth_certPath_empty.bad.yml | 2 +- ...tls_config_noAuth_certPath_invalid.bad.yml | 2 +- ...nfig_noAuth_certPath_keyPath_empty.bad.yml | 2 +- ...ig_noAuth_certPath_keyPath_invalid.bad.yml | 2 +- .../tls_config_noAuth_inventedCiphers.bad.yml | 8 + .../tls_config_noAuth_inventedCurves.bad.yml | 7 + .../tls_config_noAuth_keyPath_empty.bad.yml | 2 +- .../tls_config_noAuth_keyPath_invalid.bad.yml | 2 +- .../tls_config_noAuth_noHTTP2.good.yml | 10 ++ .../tls_config_noAuth_noHTTP2Cipher.bad.yml | 8 + .../tls_config_noAuth_someCiphers.good.yml | 10 ++ ...config_noAuth_someCiphers_noOrder.good.yml | 11 ++ .../tls_config_noAuth_someCurves.good.yml | 8 + .../tls_config_noAuth_wrongTLSVersion.bad.yml | 6 + https/testdata/tls_config_users.good.yml | 2 +- https/tls_config.go | 159 ++++++++++++++++-- https/tls_config_test.go | 144 +++++++++++++++- 27 files changed, 433 insertions(+), 40 deletions(-) create mode 100644 https/testdata/tls_config_noAuth_allCiphers.good.yml create mode 100644 https/testdata/tls_config_noAuth_allCurves.good.yml create mode 100644 https/testdata/tls_config_noAuth_inventedCiphers.bad.yml create mode 100644 https/testdata/tls_config_noAuth_inventedCurves.bad.yml create mode 100644 https/testdata/tls_config_noAuth_noHTTP2.good.yml create mode 100644 https/testdata/tls_config_noAuth_noHTTP2Cipher.bad.yml create mode 100644 https/testdata/tls_config_noAuth_someCiphers.good.yml create mode 100644 https/testdata/tls_config_noAuth_someCiphers_noOrder.good.yml create mode 100644 https/testdata/tls_config_noAuth_someCurves.good.yml create mode 100644 https/testdata/tls_config_noAuth_wrongTLSVersion.bad.yml diff --git a/https/README.md b/https/README.md index 70b321ea..dd0fa405 100644 --- a/https/README.md +++ b/https/README.md @@ -14,18 +14,48 @@ The config file should be written in YAML format, and is reloaded on each connec ## Sample Config ``` -tls_config: - # Certificate and key files for server to use to authenticate to client +tls_server_config: + # Certificate and key files for server to use to authenticate to client. cert_file: key_file: - # Server policy for client authentication. Maps to ClientAuth Policies + # Server policy for client authentication. Maps to ClientAuth Policies. # For more detail on clientAuth options: [ClientAuthType](https://golang.org/pkg/crypto/tls/#ClientAuthType) [ client_auth_type: | default = "NoClientCert" ] - # CA certificate for client certificate authentication to the server + # CA certificate for client certificate authentication to the server. [ client_ca_file: ] + # Minimum TLS version that is acceptable. + [ min_version: | default = "TLS12" ] + + # Maximum TLS version that is acceptable. + [ max_version: | default = "TLS13" ] + + # List of supported cipher suites for TLS versions up to TLS 1.2. If empty, + # Go default cipher suites are used. Available cipher suites are documented + # in the go documentation: + # https://golang.org/pkg/crypto/tls/#pkg-constants + [ cipher_suites: + [ - ] ] + + # prefer_server_cipher_suites controls whether the server selects the + # client's most preferred ciphersuite, or the server's most preferred + # ciphersuite. If true then the server's preference, as expressed in + # the order of elements in cipher_suites, is used. + [ prefer_server_cipher_suites: | default = true ] + + # Elliptic curves that will be used in an ECDHE handshake, in preference + # order. Available curves are documented in the go documentation: + # https://golang.org/pkg/crypto/tls/#CurveID + [ curve_preferences: + [ - ] ] + +http_server_config: + # Enable HTTP/2 support. Note that HTTP/2 is only supported with TLS. + # This can not be changed on the fly. + [ http2: | default = true ] + # List of usernames and hashed passwords that have full access to the web # server via basic authentication. If empty, no basic authentication is # required. Passwords are hashed with bcrypt. diff --git a/https/testdata/tls_config_auth_clientCAs_invalid.bad.yml b/https/testdata/tls_config_auth_clientCAs_invalid.bad.yml index 742889fb..91ec7068 100644 --- a/https/testdata/tls_config_auth_clientCAs_invalid.bad.yml +++ b/https/testdata/tls_config_auth_clientCAs_invalid.bad.yml @@ -1,4 +1,4 @@ -tls_config : +tls_server_config : cert_file : "testdata/server.crt" key_file : "testdata/server.key" client_ca_file : "somefile" \ No newline at end of file diff --git a/https/testdata/tls_config_auth_clientCAs_missing.bad.yml b/https/testdata/tls_config_auth_clientCAs_missing.bad.yml index 5f172a96..fea2a67f 100644 --- a/https/testdata/tls_config_auth_clientCAs_missing.bad.yml +++ b/https/testdata/tls_config_auth_clientCAs_missing.bad.yml @@ -1,4 +1,4 @@ -tls_config : +tls_server_config : cert_file : "testdata/server.crt" key_file : "testdata/server.key" client_auth_type : "RequireAndVerifyClientCert" \ No newline at end of file diff --git a/https/testdata/tls_config_auth_user_list_invalid.bad.yml b/https/testdata/tls_config_auth_user_list_invalid.bad.yml index 90c1d959..73245731 100644 --- a/https/testdata/tls_config_auth_user_list_invalid.bad.yml +++ b/https/testdata/tls_config_auth_user_list_invalid.bad.yml @@ -1,4 +1,4 @@ -tls_config : +tls_server_config : cert_file : "testdata/server.crt" key_file : "testdata/server.key" basic_auth_users: diff --git a/https/testdata/tls_config_junk_key.yml b/https/testdata/tls_config_junk_key.yml index acb2cc3d..77f55346 100644 --- a/https/testdata/tls_config_junk_key.yml +++ b/https/testdata/tls_config_junk_key.yml @@ -1,2 +1,2 @@ -tls_config : +tls_server_config : cert_filse: "testdata/server.crt" diff --git a/https/testdata/tls_config_noAuth.bad.yml b/https/testdata/tls_config_noAuth.bad.yml index afba2771..f3091806 100644 --- a/https/testdata/tls_config_noAuth.bad.yml +++ b/https/testdata/tls_config_noAuth.bad.yml @@ -1,4 +1,4 @@ -tls_config : +tls_server_config : cert_file : "testdata/server.crt" key_file : "testdata/server.key" client_ca_file : "testdata/tls-ca-chain.pem" diff --git a/https/testdata/tls_config_noAuth.good.blocking.yml b/https/testdata/tls_config_noAuth.good.blocking.yml index 3a214242..43e47ca8 100644 --- a/https/testdata/tls_config_noAuth.good.blocking.yml +++ b/https/testdata/tls_config_noAuth.good.blocking.yml @@ -1,4 +1,4 @@ -tls_config : +tls_server_config : cert_file : "testdata/server.crt" key_file : "testdata/server.key" client_auth_type : "RequireAndVerifyClientCert" diff --git a/https/testdata/tls_config_noAuth.good.yml b/https/testdata/tls_config_noAuth.good.yml index d762d8e6..33b6a680 100644 --- a/https/testdata/tls_config_noAuth.good.yml +++ b/https/testdata/tls_config_noAuth.good.yml @@ -1,4 +1,4 @@ -tls_config : +tls_server_config : cert_file : "testdata/server.crt" key_file : "testdata/server.key" client_auth_type : "VerifyClientCertIfGiven" diff --git a/https/testdata/tls_config_noAuth_allCiphers.good.yml b/https/testdata/tls_config_noAuth_allCiphers.good.yml new file mode 100644 index 00000000..e16aec15 --- /dev/null +++ b/https/testdata/tls_config_noAuth_allCiphers.good.yml @@ -0,0 +1,26 @@ +tls_server_config : + cert_file : "testdata/server.crt" + key_file : "testdata/server.key" + client_auth_type : "VerifyClientCertIfGiven" + client_ca_file : "testdata/tls-ca-chain.pem" + cipher_suites: + - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 + - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 + - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 + - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 + - TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 + - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 + - TLS_AES_128_GCM_SHA256 + - TLS_AES_256_GCM_SHA384 + - TLS_CHACHA20_POLY1305_SHA256 + - TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA + - TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA + - TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA + - TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA + - TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA + - TLS_RSA_WITH_3DES_EDE_CBC_SHA + - TLS_RSA_WITH_AES_128_CBC_SHA + - TLS_RSA_WITH_AES_256_CBC_SHA + - TLS_RSA_WITH_AES_128_GCM_SHA256 + - TLS_RSA_WITH_AES_256_GCM_SHA384 + diff --git a/https/testdata/tls_config_noAuth_allCurves.good.yml b/https/testdata/tls_config_noAuth_allCurves.good.yml new file mode 100644 index 00000000..e727402a --- /dev/null +++ b/https/testdata/tls_config_noAuth_allCurves.good.yml @@ -0,0 +1,10 @@ +tls_server_config : + cert_file : "testdata/server.crt" + key_file : "testdata/server.key" + client_auth_type : "VerifyClientCertIfGiven" + client_ca_file : "testdata/tls-ca-chain.pem" + curve_preferences: + - CurveP256 + - CurveP384 + - CurveP521 + - X25519 diff --git a/https/testdata/tls_config_noAuth_certPath_empty.bad.yml b/https/testdata/tls_config_noAuth_certPath_empty.bad.yml index f7aaa942..b9739c04 100644 --- a/https/testdata/tls_config_noAuth_certPath_empty.bad.yml +++ b/https/testdata/tls_config_noAuth_certPath_empty.bad.yml @@ -1,3 +1,3 @@ -tls_config : +tls_server_config : cert_file : "" key_file : "testdata/server.key" \ No newline at end of file diff --git a/https/testdata/tls_config_noAuth_certPath_invalid.bad.yml b/https/testdata/tls_config_noAuth_certPath_invalid.bad.yml index 09344f94..b2f46d93 100644 --- a/https/testdata/tls_config_noAuth_certPath_invalid.bad.yml +++ b/https/testdata/tls_config_noAuth_certPath_invalid.bad.yml @@ -1,3 +1,3 @@ -tls_config : +tls_server_config : cert_file : "somefile" key_file : "testdata/server.key" \ No newline at end of file diff --git a/https/testdata/tls_config_noAuth_certPath_keyPath_empty.bad.yml b/https/testdata/tls_config_noAuth_certPath_keyPath_empty.bad.yml index 1511b5a7..4e366adf 100644 --- a/https/testdata/tls_config_noAuth_certPath_keyPath_empty.bad.yml +++ b/https/testdata/tls_config_noAuth_certPath_keyPath_empty.bad.yml @@ -1,4 +1,4 @@ -tls_config : +tls_server_config : cert_file : "" key_file : "" client_auth_type: "x" diff --git a/https/testdata/tls_config_noAuth_certPath_keyPath_invalid.bad.yml b/https/testdata/tls_config_noAuth_certPath_keyPath_invalid.bad.yml index 972e4574..ab0a262e 100644 --- a/https/testdata/tls_config_noAuth_certPath_keyPath_invalid.bad.yml +++ b/https/testdata/tls_config_noAuth_certPath_keyPath_invalid.bad.yml @@ -1,3 +1,3 @@ -tls_config : +tls_server_config : cert_file : "somefile" key_file : "somefile" \ No newline at end of file diff --git a/https/testdata/tls_config_noAuth_inventedCiphers.bad.yml b/https/testdata/tls_config_noAuth_inventedCiphers.bad.yml new file mode 100644 index 00000000..1c5b28e1 --- /dev/null +++ b/https/testdata/tls_config_noAuth_inventedCiphers.bad.yml @@ -0,0 +1,8 @@ +tls_server_config : + cert_file : "testdata/server.crt" + key_file : "testdata/server.key" + client_auth_type : "VerifyClientCertIfGiven" + client_ca_file : "testdata/tls-ca-chain.pem" + cipher_suites: + - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA2048 + diff --git a/https/testdata/tls_config_noAuth_inventedCurves.bad.yml b/https/testdata/tls_config_noAuth_inventedCurves.bad.yml new file mode 100644 index 00000000..16de7381 --- /dev/null +++ b/https/testdata/tls_config_noAuth_inventedCurves.bad.yml @@ -0,0 +1,7 @@ +tls_server_config : + cert_file : "testdata/server.crt" + key_file : "testdata/server.key" + client_auth_type : "VerifyClientCertIfGiven" + client_ca_file : "testdata/tls-ca-chain.pem" + curve_preferences: + - CurveP257 diff --git a/https/testdata/tls_config_noAuth_keyPath_empty.bad.yml b/https/testdata/tls_config_noAuth_keyPath_empty.bad.yml index 87a52652..d9970298 100644 --- a/https/testdata/tls_config_noAuth_keyPath_empty.bad.yml +++ b/https/testdata/tls_config_noAuth_keyPath_empty.bad.yml @@ -1,3 +1,3 @@ -tls_config : +tls_server_config : cert_file : "testdata/server.crt" key_file : "" \ No newline at end of file diff --git a/https/testdata/tls_config_noAuth_keyPath_invalid.bad.yml b/https/testdata/tls_config_noAuth_keyPath_invalid.bad.yml index b3985f37..2b9d37f7 100644 --- a/https/testdata/tls_config_noAuth_keyPath_invalid.bad.yml +++ b/https/testdata/tls_config_noAuth_keyPath_invalid.bad.yml @@ -1,3 +1,3 @@ -tls_config : +tls_server_config : cert_file : "testdata/server.cert" key_file : "somefile" \ No newline at end of file diff --git a/https/testdata/tls_config_noAuth_noHTTP2.good.yml b/https/testdata/tls_config_noAuth_noHTTP2.good.yml new file mode 100644 index 00000000..d962c3d0 --- /dev/null +++ b/https/testdata/tls_config_noAuth_noHTTP2.good.yml @@ -0,0 +1,10 @@ +tls_server_config : + cert_file : "testdata/server.crt" + key_file : "testdata/server.key" + client_auth_type : "VerifyClientCertIfGiven" + client_ca_file : "testdata/tls-ca-chain.pem" + cipher_suites: + - TLS_RSA_WITH_AES_128_CBC_SHA + max_version: TLS12 +http_server_config: + http2: false diff --git a/https/testdata/tls_config_noAuth_noHTTP2Cipher.bad.yml b/https/testdata/tls_config_noAuth_noHTTP2Cipher.bad.yml new file mode 100644 index 00000000..2d6723a7 --- /dev/null +++ b/https/testdata/tls_config_noAuth_noHTTP2Cipher.bad.yml @@ -0,0 +1,8 @@ +tls_server_config : + cert_file : "testdata/server.crt" + key_file : "testdata/server.key" + client_auth_type : "VerifyClientCertIfGiven" + client_ca_file : "testdata/tls-ca-chain.pem" + cipher_suites: + - TLS_RSA_WITH_AES_128_CBC_SHA + max_version: TLS12 diff --git a/https/testdata/tls_config_noAuth_someCiphers.good.yml b/https/testdata/tls_config_noAuth_someCiphers.good.yml new file mode 100644 index 00000000..aae1e658 --- /dev/null +++ b/https/testdata/tls_config_noAuth_someCiphers.good.yml @@ -0,0 +1,10 @@ +tls_server_config : + cert_file : "testdata/server.crt" + key_file : "testdata/server.key" + client_auth_type : "VerifyClientCertIfGiven" + client_ca_file : "testdata/tls-ca-chain.pem" + cipher_suites: + - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 + - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 + min_version: TLS12 + max_version: TLS12 diff --git a/https/testdata/tls_config_noAuth_someCiphers_noOrder.good.yml b/https/testdata/tls_config_noAuth_someCiphers_noOrder.good.yml new file mode 100644 index 00000000..d21c6be0 --- /dev/null +++ b/https/testdata/tls_config_noAuth_someCiphers_noOrder.good.yml @@ -0,0 +1,11 @@ +tls_server_config : + cert_file : "testdata/server.crt" + key_file : "testdata/server.key" + client_auth_type : "VerifyClientCertIfGiven" + client_ca_file : "testdata/tls-ca-chain.pem" + cipher_suites: + - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 + - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 + prefer_server_cipher_suites: false + min_version: TLS12 + max_version: TLS12 diff --git a/https/testdata/tls_config_noAuth_someCurves.good.yml b/https/testdata/tls_config_noAuth_someCurves.good.yml new file mode 100644 index 00000000..2e860fc8 --- /dev/null +++ b/https/testdata/tls_config_noAuth_someCurves.good.yml @@ -0,0 +1,8 @@ +tls_server_config : + cert_file : "testdata/server.crt" + key_file : "testdata/server.key" + client_auth_type : "VerifyClientCertIfGiven" + client_ca_file : "testdata/tls-ca-chain.pem" + min_version: TLS13 + curve_preferences: + - CurveP521 diff --git a/https/testdata/tls_config_noAuth_wrongTLSVersion.bad.yml b/https/testdata/tls_config_noAuth_wrongTLSVersion.bad.yml new file mode 100644 index 00000000..51a0d6a6 --- /dev/null +++ b/https/testdata/tls_config_noAuth_wrongTLSVersion.bad.yml @@ -0,0 +1,6 @@ +tls_server_config : + cert_file : "testdata/server.crt" + key_file : "testdata/server.key" + client_auth_type : "VerifyClientCertIfGiven" + client_ca_file : "testdata/tls-ca-chain.pem" + min_version: TLS111 diff --git a/https/testdata/tls_config_users.good.yml b/https/testdata/tls_config_users.good.yml index 278177d0..8c686fc1 100644 --- a/https/testdata/tls_config_users.good.yml +++ b/https/testdata/tls_config_users.good.yml @@ -1,4 +1,4 @@ -tls_config : +tls_server_config : cert_file : "testdata/server.crt" key_file : "testdata/server.key" basic_auth_users: diff --git a/https/tls_config.go b/https/tls_config.go index 44e57e9b..e7cc6321 100644 --- a/https/tls_config.go +++ b/https/tls_config.go @@ -17,6 +17,7 @@ package https import ( "crypto/tls" "crypto/x509" + "fmt" "io/ioutil" "net/http" @@ -32,15 +33,25 @@ var ( ) type Config struct { - TLSConfig TLSStruct `yaml:"tls_config"` - Users map[string]config_util.Secret `yaml:"basic_auth_users"` + TLSConfig TLSStruct `yaml:"tls_server_config"` + HTTPConfig HTTPStruct `yaml:"http_server_config"` + Users map[string]config_util.Secret `yaml:"basic_auth_users"` } type TLSStruct struct { - TLSCertPath string `yaml:"cert_file"` - TLSKeyPath string `yaml:"key_file"` - ClientAuth string `yaml:"client_auth_type"` - ClientCAs string `yaml:"client_ca_file"` + TLSCertPath string `yaml:"cert_file"` + TLSKeyPath string `yaml:"key_file"` + ClientAuth string `yaml:"client_auth_type"` + ClientCAs string `yaml:"client_ca_file"` + CipherSuites []cipher `yaml:"cipher_suites"` + CurvePreferences []curve `yaml:"curve_preferences"` + MinVersion tlsVersion `yaml:"min_version"` + MaxVersion tlsVersion `yaml:"max_version"` + PreferServerCipherSuites bool `yaml:"prefer_server_cipher_suites"` +} + +type HTTPStruct struct { + HTTP2 bool `yaml:"http2"` } func getConfig(configPath string) (*Config, error) { @@ -48,7 +59,14 @@ func getConfig(configPath string) (*Config, error) { if err != nil { return nil, err } - c := &Config{} + c := &Config{ + TLSConfig: TLSStruct{ + MinVersion: tls.VersionTLS12, + MaxVersion: tls.VersionTLS13, + PreferServerCipherSuites: true, + }, + HTTPConfig: HTTPStruct{HTTP2: true}, + } err = yaml.UnmarshalStrict(content, c) return c, err } @@ -70,12 +88,11 @@ func ConfigToTLSConfig(c *TLSStruct) (*tls.Config, error) { if c.TLSCertPath == "" { return nil, errors.New("missing cert_file") } + if c.TLSKeyPath == "" { return nil, errors.New("missing key_file") } - cfg := &tls.Config{ - MinVersion: tls.VersionTLS12, - } + loadCert := func() (*tls.Certificate, error) { cert, err := tls.LoadX509KeyPair(c.TLSCertPath, c.TLSKeyPath) if err != nil { @@ -83,14 +100,38 @@ func ConfigToTLSConfig(c *TLSStruct) (*tls.Config, error) { } return &cert, nil } + // Confirm that certificate and key paths are valid. if _, err := loadCert(); err != nil { return nil, err } + + cfg := &tls.Config{ + MinVersion: (uint16)(c.MinVersion), + MaxVersion: (uint16)(c.MaxVersion), + PreferServerCipherSuites: c.PreferServerCipherSuites, + } + cfg.GetCertificate = func(*tls.ClientHelloInfo) (*tls.Certificate, error) { return loadCert() } + var cf []uint16 + for _, c := range c.CipherSuites { + cf = append(cf, (uint16)(c)) + } + if len(cf) > 0 { + cfg.CipherSuites = cf + } + + var cp []tls.CurveID + for _, c := range c.CurvePreferences { + cp = append(cp, (tls.CurveID)(c)) + } + if len(cp) > 0 { + cfg.CurvePreferences = cp + } + if c.ClientCAs != "" { clientCAPool := x509.NewCertPool() clientCAFile, err := ioutil.ReadFile(c.ClientCAs) @@ -126,7 +167,7 @@ func ConfigToTLSConfig(c *TLSStruct) (*tls.Config, error) { // Listen starts the server on the given address. If tlsConfigPath isn't empty the server connection will be started using TLS. func Listen(server *http.Server, tlsConfigPath string, logger log.Logger) error { if tlsConfigPath == "" { - level.Info(logger).Log("msg", "TLS is disabled and it cannot be enabled on the fly.") + level.Info(logger).Log("msg", "TLS is disabled and it cannot be enabled on the fly.", "http2", false) return server.ListenAndServe() } @@ -145,14 +186,21 @@ func Listen(server *http.Server, tlsConfigPath string, logger log.Logger) error handler: handler, } - config, err := getTLSConfig(tlsConfigPath) + c, err := getConfig(tlsConfigPath) + if err != nil { + return err + } + config, err := ConfigToTLSConfig(&c.TLSConfig) switch err { case nil: + if !c.HTTPConfig.HTTP2 { + server.TLSNextProto = make(map[string]func(*http.Server, *tls.Conn, http.Handler)) + } // Valid TLS config. - level.Info(logger).Log("msg", "TLS is enabled and it cannot be disabled on the fly.") + level.Info(logger).Log("msg", "TLS is enabled and it cannot be disabled on the fly.", "http2", c.HTTPConfig.HTTP2) case errNoTLSConfig: // No TLS config, back to plain HTTP. - level.Info(logger).Log("msg", "TLS is disabled and it cannot be enabled on the fly.") + level.Info(logger).Log("msg", "TLS is disabled and it cannot be enabled on the fly.", "http2", false) return server.ListenAndServe() default: // Invalid TLS config. @@ -168,3 +216,86 @@ func Listen(server *http.Server, tlsConfigPath string, logger log.Logger) error } return server.ListenAndServeTLS("", "") } + +type cipher uint16 + +func (c *cipher) UnmarshalYAML(unmarshal func(interface{}) error) error { + var s string + err := unmarshal((*string)(&s)) + if err != nil { + return err + } + for _, cs := range tls.CipherSuites() { + if cs.Name == s { + *c = (cipher)(cs.ID) + return nil + } + } + return errors.New("unknown cipher: " + s) +} + +func (c cipher) MarshalYAML() (interface{}, error) { + return tls.CipherSuiteName((uint16)(c)), nil +} + +type curve tls.CurveID + +var curves = map[string]curve{ + "CurveP256": (curve)(tls.CurveP256), + "CurveP384": (curve)(tls.CurveP384), + "CurveP521": (curve)(tls.CurveP521), + "X25519": (curve)(tls.X25519), +} + +func (c *curve) UnmarshalYAML(unmarshal func(interface{}) error) error { + var s string + err := unmarshal((*string)(&s)) + if err != nil { + return err + } + if curveid, ok := curves[s]; ok { + *c = curveid + return nil + } + return errors.New("unknown curve: " + s) +} + +func (c *curve) MarshalYAML() (interface{}, error) { + for s, curveid := range curves { + if *c == curveid { + return s, nil + } + } + return fmt.Sprintf("%v", c), nil +} + +type tlsVersion uint16 + +var tlsVersions = map[string]tlsVersion{ + "TLS13": (tlsVersion)(tls.VersionTLS13), + "TLS12": (tlsVersion)(tls.VersionTLS12), + "TLS11": (tlsVersion)(tls.VersionTLS11), + "TLS10": (tlsVersion)(tls.VersionTLS10), +} + +func (tv *tlsVersion) UnmarshalYAML(unmarshal func(interface{}) error) error { + var s string + err := unmarshal((*string)(&s)) + if err != nil { + return err + } + if v, ok := tlsVersions[s]; ok { + *tv = v + return nil + } + return errors.New("unknown TLS version: " + s) +} + +func (tv *tlsVersion) MarshalYAML() (interface{}, error) { + for s, v := range tlsVersions { + if *tv == v { + return s, nil + } + } + return fmt.Sprintf("%v", tv), nil +} diff --git a/https/tls_config_test.go b/https/tls_config_test.go index 07f412a5..ccc7a992 100644 --- a/https/tls_config_test.go +++ b/https/tls_config_test.go @@ -11,6 +11,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +// +build go1.14 + package https import ( @@ -45,6 +47,12 @@ var ( "Bad password": regexp.MustCompile(`hashedSecret too short to be a bcrypted password`), "Unauthorized": regexp.MustCompile(`Unauthorized`), "Forbidden": regexp.MustCompile(`Forbidden`), + "Handshake failure": regexp.MustCompile(`handshake failure`), + "Unknown cipher": regexp.MustCompile(`unknown cipher`), + "Unknown curve": regexp.MustCompile(`unknown curve`), + "Unknown TLS version": regexp.MustCompile(`unknown TLS version`), + "No HTTP2 cipher": regexp.MustCompile(`TLSConfig.CipherSuites is missing an HTTP/2-required`), + "Incompatible TLS version": regexp.MustCompile(`protocol version not supported`), } ) @@ -65,14 +73,18 @@ func getPort() string { } type TestInputs struct { - Name string - Server func() *http.Server - UseNilServer bool - YAMLConfigPath string - ExpectedError *regexp.Regexp - UseTLSClient bool - Username string - Password string + Name string + Server func() *http.Server + UseNilServer bool + YAMLConfigPath string + ExpectedError *regexp.Regexp + UseTLSClient bool + ClientMaxTLSVersion uint16 + CipherSuites []uint16 + ActualCipher uint16 + CurvePreferences []tls.CurveID + Username string + Password string } func TestYAMLFiles(t *testing.T) { @@ -142,6 +154,21 @@ func TestYAMLFiles(t *testing.T) { YAMLConfigPath: "testdata/tls_config_auth_user_list_invalid.bad.yml", ExpectedError: ErrorMap["Bad password"], }, + { + Name: `invalid config yml (bad cipher)`, + YAMLConfigPath: "testdata/tls_config_noAuth_inventedCiphers.bad.yml", + ExpectedError: ErrorMap["Unknown cipher"], + }, + { + Name: `invalid config yml (bad curves)`, + YAMLConfigPath: "testdata/tls_config_noAuth_inventedCurves.bad.yml", + ExpectedError: ErrorMap["Unknown curve"], + }, + { + Name: `invalid config yml (bad TLS version)`, + YAMLConfigPath: "testdata/tls_config_noAuth_wrongTLSVersion.bad.yml", + ExpectedError: ErrorMap["Unknown TLS version"], + }, } for _, testInputs := range testTables { t.Run(testInputs.Name, testInputs.Test) @@ -172,6 +199,87 @@ func TestServerBehaviour(t *testing.T) { UseTLSClient: true, ExpectedError: nil, }, + { + Name: `valid tls config yml with TLS 1.1 client`, + YAMLConfigPath: "testdata/tls_config_noAuth.good.yml", + UseTLSClient: true, + ClientMaxTLSVersion: tls.VersionTLS11, + ExpectedError: ErrorMap["Incompatible TLS version"], + }, + { + Name: `valid tls config yml with all ciphers`, + YAMLConfigPath: "testdata/tls_config_noAuth_allCiphers.good.yml", + UseTLSClient: true, + ExpectedError: nil, + }, + { + Name: `valid tls config yml with some ciphers`, + YAMLConfigPath: "testdata/tls_config_noAuth_someCiphers.good.yml", + UseTLSClient: true, + CipherSuites: []uint16{tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, + ExpectedError: nil, + }, + { + Name: `valid tls config yml with no common cipher`, + YAMLConfigPath: "testdata/tls_config_noAuth_someCiphers.good.yml", + UseTLSClient: true, + CipherSuites: []uint16{tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA}, + ExpectedError: ErrorMap["Handshake failure"], + }, + { + Name: `valid tls config yml with multiple client ciphers`, + YAMLConfigPath: "testdata/tls_config_noAuth_someCiphers.good.yml", + UseTLSClient: true, + CipherSuites: []uint16{ + tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + }, + ActualCipher: tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + ExpectedError: nil, + }, + { + Name: `valid tls config yml with multiple client ciphers, client chooses cipher`, + YAMLConfigPath: "testdata/tls_config_noAuth_someCiphers_noOrder.good.yml", + UseTLSClient: true, + CipherSuites: []uint16{ + tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + }, + ActualCipher: tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + ExpectedError: nil, + }, + { + Name: `valid tls config yml with all curves`, + YAMLConfigPath: "testdata/tls_config_noAuth_allCurves.good.yml", + UseTLSClient: true, + ExpectedError: nil, + }, + { + Name: `valid tls config yml with some curves`, + YAMLConfigPath: "testdata/tls_config_noAuth_someCurves.good.yml", + UseTLSClient: true, + CurvePreferences: []tls.CurveID{tls.CurveP521}, + ExpectedError: nil, + }, + { + Name: `valid tls config yml with no common curves`, + YAMLConfigPath: "testdata/tls_config_noAuth_someCurves.good.yml", + UseTLSClient: true, + CurvePreferences: []tls.CurveID{tls.CurveP384}, + ExpectedError: ErrorMap["Handshake failure"], + }, + { + Name: `valid tls config yml with non-http2 ciphers`, + YAMLConfigPath: "testdata/tls_config_noAuth_noHTTP2.good.yml", + UseTLSClient: true, + ExpectedError: nil, + }, + { + Name: `valid tls config yml with non-http2 ciphers but http2 enabled`, + YAMLConfigPath: "testdata/tls_config_noAuth_noHTTP2Cipher.bad.yml", + UseTLSClient: true, + ExpectedError: ErrorMap["No HTTP2 cipher"], + }, } for _, testInputs := range testTables { t.Run(testInputs.Name, testInputs.Test) @@ -297,6 +405,14 @@ func (test *TestInputs) Test(t *testing.T) { var proto string if test.UseTLSClient { client = getTLSClient() + t := client.Transport.(*http.Transport) + t.TLSClientConfig.MaxVersion = test.ClientMaxTLSVersion + if len(test.CipherSuites) > 0 { + t.TLSClientConfig.CipherSuites = test.CipherSuites + } + if len(test.CurvePreferences) > 0 { + t.TLSClientConfig.CurvePreferences = test.CurvePreferences + } proto = "https" } else { client = http.DefaultClient @@ -318,6 +434,18 @@ func (test *TestInputs) Test(t *testing.T) { recordConnectionError(err) return } + + if test.ActualCipher != 0 { + if r.TLS.CipherSuite != test.ActualCipher { + recordConnectionError( + fmt.Errorf("bad cipher suite selected. Expected: %s, got: %s", + tls.CipherSuiteName(r.TLS.CipherSuite), + tls.CipherSuiteName(test.ActualCipher), + ), + ) + } + } + body, err := ioutil.ReadAll(r.Body) if err != nil { recordConnectionError(err)