lib/httpserver: added tlsCipherSuites flag (#2468)

* lib/httpserver: added tlsCipherSuites flag

* lib/httpserver: compare lower case strings

* lib/httpserver: use EqualFold

* lib/httpserver: used flagutil.NewArray, supported only strings cipher suites

* lib/httpserver: updated flag description, added flag to documentation

* Update lib/httpserver/httpserver.go

Co-authored-by: Aliaksandr Valialkin <valyala@victoriametrics.com>
This commit is contained in:
Dmytro Kozlov 2022-04-16 15:07:07 +03:00 committed by Aliaksandr Valialkin
parent c50e48a74c
commit 26ae50ec26
No known key found for this signature in database
GPG Key ID: A72BEC6CD3D0DED1
3 changed files with 108 additions and 5 deletions

View File

@ -30,9 +30,10 @@ import (
)
var (
tlsEnable = flag.Bool("tls", false, "Whether to enable TLS (aka HTTPS) for incoming requests. -tlsCertFile and -tlsKeyFile must be set if -tls is set")
tlsCertFile = flag.String("tlsCertFile", "", "Path to file with TLS certificate. Used only if -tls is set. Prefer ECDSA certs instead of RSA certs as RSA certs are slower. The provided certificate file is automatically re-read every second, so it can be dynamically updated")
tlsKeyFile = flag.String("tlsKeyFile", "", "Path to file with TLS key. Used only if -tls is set. The provided key file is automatically re-read every second, so it can be dynamically updated")
tlsEnable = flag.Bool("tls", false, "Whether to enable TLS (aka HTTPS) for incoming requests. -tlsCertFile and -tlsKeyFile must be set if -tls is set")
tlsCertFile = flag.String("tlsCertFile", "", "Path to file with TLS certificate. Used only if -tls is set. Prefer ECDSA certs instead of RSA certs as RSA certs are slower. The provided certificate file is automatically re-read every second, so it can be dynamically updated")
tlsKeyFile = flag.String("tlsKeyFile", "", "Path to file with TLS key. Used only if -tls is set. The provided key file is automatically re-read every second, so it can be dynamically updated")
tlsCipherSuites = flagutil.NewArray("tlsCipherSuites", "Cipher suites names for TLS encryption. For example, TLS_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_AES_256_CBC_SHA. Used only if -tls flag is set")
pathPrefix = flag.String("http.pathPrefix", "", "An optional prefix to add to all the paths handled by http server. For example, if '-http.pathPrefix=/foo/bar' is set, "+
"then all the http requests will be handled on '/foo/bar/*' paths. This may be useful for proxied requests. "+
@ -88,7 +89,7 @@ func Serve(addr string, rh RequestHandler) {
logger.Infof("pprof handlers are exposed at %s://%s/debug/pprof/", scheme, hostAddr)
var tlsConfig *tls.Config
if *tlsEnable {
tc, err := netutil.GetServerTLSConfig("", *tlsCertFile, *tlsKeyFile)
tc, err := netutil.GetServerTLSConfig("", *tlsCertFile, *tlsKeyFile, *tlsCipherSuites)
if err != nil {
logger.Fatalf("cannot load TLS cert from -tlsCertFile=%q, -tlsKeyFile=%q: %s", *tlsCertFile, *tlsKeyFile, err)
}

View File

@ -4,6 +4,7 @@ import (
"crypto/tls"
"crypto/x509"
"fmt"
"strings"
"sync"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fasttime"
@ -11,7 +12,7 @@ import (
)
// GetServerTLSConfig returns TLS config for the server with possible client verification (mTLS) if tlsCAFile isn't empty.
func GetServerTLSConfig(tlsCAFile, tlsCertFile, tlsKeyFile string) (*tls.Config, error) {
func GetServerTLSConfig(tlsCAFile, tlsCertFile, tlsKeyFile string, tlsCipherSuites []string) (*tls.Config, error) {
var certLock sync.Mutex
var certDeadline uint64
var cert *tls.Certificate
@ -19,6 +20,10 @@ func GetServerTLSConfig(tlsCAFile, tlsCertFile, tlsKeyFile string) (*tls.Config,
if err != nil {
return nil, fmt.Errorf("cannot load TLS cert from certFile=%q, keyFile=%q: %w", tlsCertFile, tlsKeyFile, err)
}
cipherSuites, err := collectCipherSuites(tlsCipherSuites)
if err != nil {
return nil, fmt.Errorf("cannot use TLS cipher suites from tlsCipherSuites=%q: %w", tlsCipherSuites, err)
}
cert = &c
cfg := &tls.Config{
MinVersion: tls.VersionTLS12,
@ -36,6 +41,7 @@ func GetServerTLSConfig(tlsCAFile, tlsCertFile, tlsKeyFile string) (*tls.Config,
}
return cert, nil
},
CipherSuites: cipherSuites,
}
if tlsCAFile != "" {
// Enable mTLS ( https://en.wikipedia.org/wiki/Mutual_authentication#mTLS )
@ -52,3 +58,21 @@ func GetServerTLSConfig(tlsCAFile, tlsCertFile, tlsKeyFile string) (*tls.Config,
}
return cfg, nil
}
func collectCipherSuites(definedCipherSuites []string) ([]uint16, error) {
var cipherSuites []uint16
supportedCipherSuites := tls.CipherSuites()
supportedCipherSuitesMap := make(map[string]uint16, len(supportedCipherSuites))
for _, scf := range supportedCipherSuites {
supportedCipherSuitesMap[strings.ToLower(scf.Name)] = scf.ID
}
for _, gotSuite := range definedCipherSuites {
id, ok := supportedCipherSuitesMap[strings.ToLower(gotSuite)]
if !ok {
return nil, fmt.Errorf("got unsupported cipher suite name: %s", gotSuite)
}
cipherSuites = append(cipherSuites, id)
}
return cipherSuites, nil
}

78
lib/netutil/tls_test.go Normal file
View File

@ -0,0 +1,78 @@
package netutil
import (
"reflect"
"testing"
)
func Test_validateCipherSuites(t *testing.T) {
type args struct {
definedCipherSuites []string
}
tests := []struct {
name string
args args
want []uint16
wantErr bool
}{
{
name: "empty cipher suites",
args: args{definedCipherSuites: []string{}},
want: nil,
},
{
name: "got wrong string",
args: args{definedCipherSuites: []string{"word"}},
want: nil,
wantErr: true,
},
{
name: "got wrong number",
args: args{definedCipherSuites: []string{"123"}},
want: nil,
wantErr: true,
},
{
name: "got correct string cipher suite",
args: args{definedCipherSuites: []string{"TLS_RSA_WITH_AES_128_CBC_SHA", "TLS_RSA_WITH_AES_256_CBC_SHA"}},
want: []uint16{0x2f, 0x35},
wantErr: false,
},
{
name: "got correct string with different cases (upper and lower) cipher suite",
args: args{definedCipherSuites: []string{"tls_rsa_with_aes_128_cbc_sha", "TLS_RSA_WITH_AES_256_CBC_SHA"}},
want: []uint16{0x2f, 0x35},
wantErr: false,
},
{
name: "got correct number cipher suite",
args: args{definedCipherSuites: []string{"0x2f", "0x35"}},
want: nil,
wantErr: true,
},
{
name: "got insecure number cipher suite",
args: args{definedCipherSuites: []string{"0x0005", "0x000a"}},
want: nil,
wantErr: true,
},
{
name: "got insecure string cipher suite",
args: args{definedCipherSuites: []string{"TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", "TLS_ECDHE_RSA_WITH_RC4_128_SHA"}},
want: nil,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := collectCipherSuites(tt.args.definedCipherSuites)
if (err != nil) != tt.wantErr {
t.Errorf("collectCipherSuites() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("validateCipherSuites() got = %v, want %v", got, tt.want)
}
})
}
}