diff options
author | Krasimir Angelov <kangelov@gitlab.com> | 2019-05-28 12:46:50 +0300 |
---|---|---|
committer | Nick Thomas <nick@gitlab.com> | 2019-05-28 12:46:50 +0300 |
commit | 1050f11598642b017486fc655561399d3766efb5 (patch) | |
tree | c559fced12a012af3f680512e3869b2e4454176c /internal | |
parent | ef7fff4fa64c9cb3ca57faef3f26fa59f4f51ecb (diff) |
Add config flags to specify TLS versions
Introduce two new configuration options -tls-min-version and
-tls-max-version to control which TLS versions will be supported by the
server. Accepted values are ssl3, tls1.0, tls1.1, tls1.2, and tls1.3.
Closing https://gitlab.com/gitlab-org/gitlab-pages/issues/187
Diffstat (limited to 'internal')
-rw-r--r-- | internal/tlsconfig/tlsconfig.go | 105 | ||||
-rw-r--r-- | internal/tlsconfig/tlsconfig_go1_12.go | 17 | ||||
-rw-r--r-- | internal/tlsconfig/tlsconfig_go1_12_test.go | 42 | ||||
-rw-r--r-- | internal/tlsconfig/tlsconfig_test.go | 71 |
4 files changed, 235 insertions, 0 deletions
diff --git a/internal/tlsconfig/tlsconfig.go b/internal/tlsconfig/tlsconfig.go new file mode 100644 index 00000000..1c9db71e --- /dev/null +++ b/internal/tlsconfig/tlsconfig.go @@ -0,0 +1,105 @@ +package tlsconfig + +import ( + "crypto/tls" + "fmt" + "os" + "sort" + "strings" +) + +// GetCertificateFunc returns the certificate to be used for given domain +type GetCertificateFunc func(*tls.ClientHelloInfo) (*tls.Certificate, error) + +var ( + preferredCipherSuites = []uint16{ + tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, + tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, + tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + } + + // AllTLSVersions has all supported flag values + AllTLSVersions = map[string]uint16{ + "": 0, // Default value in tls.Config + "ssl3": tls.VersionSSL30, + "tls1.0": tls.VersionTLS10, + "tls1.1": tls.VersionTLS11, + "tls1.2": tls.VersionTLS12, + } +) + +// FlagUsage returns string with explanation how to use the CLI flag +func FlagUsage(minOrMax string) string { + versions := []string{} + + for version := range AllTLSVersions { + if version != "" { + versions = append(versions, fmt.Sprintf("%q", version)) + } + } + sort.Strings(versions) + + return fmt.Sprintf("Specifies the "+minOrMax+"imum SSL/TLS version, supported values are %s", strings.Join(versions, ", ")) +} + +// Create returns tls.Config for given app configuration +func Create(cert, key []byte, getCertificate GetCertificateFunc, insecureCiphers bool, tlsMinVersion uint16, tlsMaxVersion uint16) (*tls.Config, error) { + tlsConfig := &tls.Config{GetCertificate: getCertificate} + + err := configureCertificate(tlsConfig, cert, key) + if err != nil { + return nil, err + } + + if !insecureCiphers { + configureTLSCiphers(tlsConfig) + } + + tlsConfig.MinVersion = tlsMinVersion + tlsConfig.MaxVersion = tlsMaxVersion + + return tlsConfig, nil +} + +// ValidateTLSVersions returns error if the provided TLS versions config values are not valid +func ValidateTLSVersions(min, max string) error { + tlsMin, tlsMinOk := AllTLSVersions[min] + tlsMax, tlsMaxOk := AllTLSVersions[max] + + if !tlsMinOk { + return fmt.Errorf("Invalid minimum TLS version: %s", min) + } + if !tlsMaxOk { + return fmt.Errorf("Invalid maximum TLS version: %s", max) + } + if tlsMin > tlsMax && tlsMax > 0 { + return fmt.Errorf("Invalid maximum TLS version: %s; Should be at least %s", max, min) + } + + // At this point values are validated so if we have tls1.3 + // accepted we are on Go 1.12+ so let's enable it too. + if min == "tls1.3" || max == "tls1.3" { + os.Setenv("GODEBUG", os.Getenv("GODEBUG")+",tls13=1") + } + + return nil +} + +func configureCertificate(tlsConfig *tls.Config, cert, key []byte) error { + certificate, err := tls.X509KeyPair(cert, key) + if err != nil { + return err + } + + tlsConfig.Certificates = []tls.Certificate{certificate} + + return nil +} + +func configureTLSCiphers(tlsConfig *tls.Config) { + tlsConfig.PreferServerCipherSuites = true + tlsConfig.CipherSuites = preferredCipherSuites +} diff --git a/internal/tlsconfig/tlsconfig_go1_12.go b/internal/tlsconfig/tlsconfig_go1_12.go new file mode 100644 index 00000000..f92bdf09 --- /dev/null +++ b/internal/tlsconfig/tlsconfig_go1_12.go @@ -0,0 +1,17 @@ +// +build go1.12 + +package tlsconfig + +import ( + "crypto/tls" +) + +func init() { + AllTLSVersions["tls1.3"] = tls.VersionTLS13 + + preferredCipherSuites = append(preferredCipherSuites, + tls.TLS_AES_128_GCM_SHA256, + tls.TLS_AES_256_GCM_SHA384, + tls.TLS_CHACHA20_POLY1305_SHA256, + ) +} diff --git a/internal/tlsconfig/tlsconfig_go1_12_test.go b/internal/tlsconfig/tlsconfig_go1_12_test.go new file mode 100644 index 00000000..56b58c9d --- /dev/null +++ b/internal/tlsconfig/tlsconfig_go1_12_test.go @@ -0,0 +1,42 @@ +// +build go1.12 + +package tlsconfig + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestEnableTLS13(t *testing.T) { + tests := map[string]struct { + tlsMin string + tlsMax string + enableTLS13 bool + }{ + "ask for minimum TLS 1.3": {tlsMin: "tls1.3", tlsMax: "", enableTLS13: true}, + "ask for maximim TLS 1.3": {tlsMin: "", tlsMax: "tls1.3", enableTLS13: true}, + "do not ask for TLS 1.3": {tlsMin: "tls1.2", tlsMax: "tls1.2", enableTLS13: false}, + } + + // Store original GODEBUG value + godebug := os.Getenv("GODEBUG") + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + err := ValidateTLSVersions(tc.tlsMin, tc.tlsMax) + require.NoError(t, err) + + if tc.enableTLS13 { + assert.Regexp(t, "tls13=1", os.Getenv("GODEBUG")) + } else { + assert.NotRegexp(t, "tls13=1", godebug) + } + }) + + // Restore original GODEBUG value + os.Setenv("GODEBUG", godebug) + } +} diff --git a/internal/tlsconfig/tlsconfig_test.go b/internal/tlsconfig/tlsconfig_test.go new file mode 100644 index 00000000..b4cf87ad --- /dev/null +++ b/internal/tlsconfig/tlsconfig_test.go @@ -0,0 +1,71 @@ +package tlsconfig + +import ( + "crypto/tls" + "testing" + + "github.com/stretchr/testify/require" +) + +var cert = []byte(`-----BEGIN CERTIFICATE----- +MIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw +DgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow +EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d +7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B +5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr +BgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1 +NDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l +Wf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc +6MF9+Yw1Yy0t +-----END CERTIFICATE-----`) + +var key = []byte(`-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49 +AwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q +EKTcWGekdmdDPsHloRNtsiCa697B2O9IFA== +-----END EC PRIVATE KEY-----`) + +var getCertificate = func(ch *tls.ClientHelloInfo) (*tls.Certificate, error) { + return nil, nil +} + +func TestValidateTLSVersions(t *testing.T) { + tests := map[string]struct { + tlsMin string + tlsMax string + err string + }{ + "invalid minimum TLS version": {tlsMin: "tls123", tlsMax: "", err: "Invalid minimum TLS version: tls123"}, + "invalid maximum TLS version": {tlsMin: "", tlsMax: "tls123", err: "Invalid maximum TLS version: tls123"}, + "TLS versions conflict": {tlsMin: "tls1.2", tlsMax: "tls1.1", err: "Invalid maximum TLS version: tls1.1; Should be at least tls1.2"}, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + err := ValidateTLSVersions(tc.tlsMin, tc.tlsMax) + require.EqualError(t, err, tc.err) + }) + } +} + +func TestInvalidKeyPair(t *testing.T) { + _, err := Create([]byte(``), []byte(``), getCertificate, false, tls.VersionTLS11, tls.VersionTLS12) + require.EqualError(t, err, "tls: failed to find any PEM data in certificate input") +} + +func TestInsecureCihers(t *testing.T) { + tlsConfig, err := Create(cert, key, getCertificate, true, tls.VersionTLS11, tls.VersionTLS12) + require.NoError(t, err) + require.False(t, tlsConfig.PreferServerCipherSuites) + require.Empty(t, tlsConfig.CipherSuites) +} + +func TestCreate(t *testing.T) { + tlsConfig, err := Create(cert, key, getCertificate, false, tls.VersionTLS11, tls.VersionTLS12) + require.NoError(t, err) + require.IsType(t, getCertificate, tlsConfig.GetCertificate) + require.True(t, tlsConfig.PreferServerCipherSuites) + require.Equal(t, preferredCipherSuites, tlsConfig.CipherSuites) + require.Equal(t, tls.VersionTLS11, tlsConfig.MinVersion) + require.Equal(t, tls.VersionTLS12, tlsConfig.MaxVersion) +} |