From 3ebdfa9b2db2f4a8acc1ef052c80d61778ac4eb2 Mon Sep 17 00:00:00 2001 From: Nicola Murino Date: Wed, 7 Sep 2022 14:31:50 +0200 Subject: [PATCH] data providers: allow to disable SNI for TLS connections Signed-off-by: Nicola Murino --- docs/full-configuration.md | 1 + go.mod | 6 +-- go.sum | 11 ++-- internal/config/config.go | 2 + internal/dataprovider/dataprovider.go | 5 ++ internal/dataprovider/mysql.go | 73 +++++++++++++++------------ internal/dataprovider/pgsql.go | 3 ++ sftpgo.json | 1 + 8 files changed, 63 insertions(+), 39 deletions(-) diff --git a/docs/full-configuration.md b/docs/full-configuration.md index f74c8a7d..382d1d45 100644 --- a/docs/full-configuration.md +++ b/docs/full-configuration.md @@ -211,6 +211,7 @@ The configuration file contains the following sections: - `password`, string. Database password. Leave empty for drivers `sqlite`, `bolt` and `memory` - `sslmode`, integer. Used for drivers `mysql` and `postgresql`. 0 disable TLS connections, 1 require TLS, 2 set TLS mode to `verify-ca` for driver `postgresql` and `skip-verify` for driver `mysql`, 3 set TLS mode to `verify-full` for driver `postgresql` and `preferred` for driver `mysql` - `root_cert`, string. Path to the root certificate authority used to verify that the server certificate was signed by a trusted CA + - `disable_sni`, boolean. Allows to opt out Server Name Indication (SNI) for TLS connections. Default: `false` - `client_cert`, string. Path to the client certificate for two-way TLS authentication - `client_key`,string. Path to the client key for two-way TLS authentication - `connection_string`, string. Provide a custom database connection string. If not empty, this connection string will be used instead of building one using the previous parameters. Leave empty for drivers `bolt` and `memory` diff --git a/go.mod b/go.mod index 1d0aacac..9bc2e1b9 100644 --- a/go.mod +++ b/go.mod @@ -37,7 +37,7 @@ require ( github.com/jlaffaye/ftp v0.0.0-20201112195030-9aae4d151126 github.com/klauspost/compress v1.15.9 github.com/lestrrat-go/jwx v1.2.25 - github.com/lib/pq v1.10.6 + github.com/lib/pq v1.10.7 github.com/lithammer/shortuuid/v3 v3.0.7 github.com/mattn/go-sqlite3 v1.14.15 github.com/mhale/smtpd v0.8.0 @@ -68,9 +68,9 @@ require ( golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094 - golang.org/x/sys v0.0.0-20220906135438-9e1f76180b77 + golang.org/x/sys v0.0.0-20220907062415-87db552b00fd golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 - google.golang.org/api v0.94.0 + google.golang.org/api v0.95.0 gopkg.in/natefinch/lumberjack.v2 v2.0.0 ) diff --git a/go.sum b/go.sum index 5159f48e..9470fab1 100644 --- a/go.sum +++ b/go.sum @@ -589,8 +589,9 @@ github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lib/pq v1.10.6 h1:jbk+ZieJ0D7EVGJYpL9QTz7/YW6UHbmdnZWYyK5cdBs= github.com/lib/pq v1.10.6/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= +github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lithammer/shortuuid/v3 v3.0.7 h1:trX0KTHy4Pbwo/6ia8fscyHoGA+mf1jWbPJVuvyJQQ8= github.com/lithammer/shortuuid/v3 v3.0.7/go.mod h1:vMk8ke37EmiewwolSO1NLW8vP4ZaKlRuDIi8tWWmAts= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= @@ -977,8 +978,8 @@ golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220906135438-9e1f76180b77 h1:C1tElbkWrsSkn3IRl1GCW/gETw1TywWIPgwZtXTZbYg= -golang.org/x/sys v0.0.0-20220906135438-9e1f76180b77/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220907062415-87db552b00fd h1:AZeIEzg+8RCELJYq8w+ODLVxFgLMMigSwO/ffKPEd9U= +golang.org/x/sys v0.0.0-20220907062415-87db552b00fd/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -1119,8 +1120,8 @@ google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69 google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw= google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg= google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o= -google.golang.org/api v0.94.0 h1:KtKM9ru3nzQioV1HLlUf1cR7vMYJIpgls5VhAYQXIwA= -google.golang.org/api v0.94.0/go.mod h1:eADj+UBuxkh5zlrSntJghuNeg8HwQ1w5lTKkuqaETEI= +google.golang.org/api v0.95.0 h1:d1c24AAS01DYqXreBeuVV7ewY/U8Mnhh47pwtsgVtYg= +google.golang.org/api v0.95.0/go.mod h1:eADj+UBuxkh5zlrSntJghuNeg8HwQ1w5lTKkuqaETEI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= diff --git a/internal/config/config.go b/internal/config/config.go index 5dde3d50..e73fcb6f 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -318,6 +318,7 @@ func Init() { ConnectionString: "", SQLTablesPrefix: "", SSLMode: 0, + DisableSNI: false, RootCert: "", ClientCert: "", ClientKey: "", @@ -1927,6 +1928,7 @@ func setViperDefaults() { viper.SetDefault("data_provider.username", globalConf.ProviderConf.Username) viper.SetDefault("data_provider.password", globalConf.ProviderConf.Password) viper.SetDefault("data_provider.sslmode", globalConf.ProviderConf.SSLMode) + viper.SetDefault("data_provider.disable_sni", globalConf.ProviderConf.DisableSNI) viper.SetDefault("data_provider.root_cert", globalConf.ProviderConf.RootCert) viper.SetDefault("data_provider.client_cert", globalConf.ProviderConf.ClientCert) viper.SetDefault("data_provider.client_key", globalConf.ProviderConf.ClientKey) diff --git a/internal/dataprovider/dataprovider.go b/internal/dataprovider/dataprovider.go index 53d2f13a..6834ca3b 100644 --- a/internal/dataprovider/dataprovider.go +++ b/internal/dataprovider/dataprovider.go @@ -330,6 +330,8 @@ type Config struct { // 2 set ssl mode to verify-ca for driver postgresql and skip-verify for driver mysql. // 3 set ssl mode to verify-full for driver postgresql and preferred for driver mysql. SSLMode int `json:"sslmode" mapstructure:"sslmode"` + // Used for drivers mysql and postgresql. Set to true to disable SNI + DisableSNI bool `json:"disable_sni" mapstructure:"disable_sni"` // Path to the root certificate authority used to verify that the server certificate was signed by a trusted CA RootCert string `json:"root_cert" mapstructure:"root_cert"` // Path to the client certificate for two-way TLS authentication @@ -493,6 +495,9 @@ func (c *Config) IsDefenderSupported() bool { } func (c *Config) requireCustomTLSForMySQL() bool { + if config.DisableSNI { + return config.SSLMode != 0 + } if config.RootCert != "" && util.IsFileInputValid(config.RootCert) { return config.SSLMode != 0 } diff --git a/internal/dataprovider/mysql.go b/internal/dataprovider/mysql.go index 02da73f6..c0640fdd 100644 --- a/internal/dataprovider/mysql.go +++ b/internal/dataprovider/mysql.go @@ -25,6 +25,7 @@ import ( "errors" "fmt" "os" + "path/filepath" "strings" "time" @@ -219,37 +220,8 @@ func getMySQLConnectionString(redactedPwd bool) (string, error) { } sslMode := getSSLMode() if sslMode == "custom" && !redactedPwd { - tlsConfig := &tls.Config{} - if config.RootCert != "" { - rootCAs, err := x509.SystemCertPool() - if err != nil { - rootCAs = x509.NewCertPool() - } - rootCrt, err := os.ReadFile(config.RootCert) - if err != nil { - return "", fmt.Errorf("unable to load root certificate %#v: %v", config.RootCert, err) - } - if !rootCAs.AppendCertsFromPEM(rootCrt) { - return "", fmt.Errorf("unable to parse root certificate %#v", config.RootCert) - } - tlsConfig.RootCAs = rootCAs - } - if config.ClientCert != "" && config.ClientKey != "" { - clientCert := make([]tls.Certificate, 0, 1) - tlsCert, err := tls.LoadX509KeyPair(config.ClientCert, config.ClientKey) - if err != nil { - return "", fmt.Errorf("unable to load key pair %#v, %#v: %v", config.ClientCert, config.ClientKey, err) - } - clientCert = append(clientCert, tlsCert) - tlsConfig.Certificates = clientCert - } - if config.SSLMode == 2 { - tlsConfig.InsecureSkipVerify = true - } - providerLog(logger.LevelInfo, "registering custom TLS config, root cert %#v, client cert %#v, client key %#v", - config.RootCert, config.ClientCert, config.ClientKey) - if err := mysql.RegisterTLSConfig("custom", tlsConfig); err != nil { - return "", fmt.Errorf("unable to register tls config: %v", err) + if err := registerMySQLCustomTLSConfig(); err != nil { + return "", err } } connectionString = fmt.Sprintf("%v:%v@tcp([%v]:%v)/%v?charset=utf8mb4&interpolateParams=true&timeout=10s&parseTime=true&tls=%v&writeTimeout=60s&readTimeout=60s", @@ -260,6 +232,45 @@ func getMySQLConnectionString(redactedPwd bool) (string, error) { return connectionString, nil } +func registerMySQLCustomTLSConfig() error { + tlsConfig := &tls.Config{} + if config.RootCert != "" { + rootCAs, err := x509.SystemCertPool() + if err != nil { + rootCAs = x509.NewCertPool() + } + rootCrt, err := os.ReadFile(config.RootCert) + if err != nil { + return fmt.Errorf("unable to load root certificate %#v: %v", config.RootCert, err) + } + if !rootCAs.AppendCertsFromPEM(rootCrt) { + return fmt.Errorf("unable to parse root certificate %#v", config.RootCert) + } + tlsConfig.RootCAs = rootCAs + } + if config.ClientCert != "" && config.ClientKey != "" { + clientCert := make([]tls.Certificate, 0, 1) + tlsCert, err := tls.LoadX509KeyPair(config.ClientCert, config.ClientKey) + if err != nil { + return fmt.Errorf("unable to load key pair %#v, %#v: %v", config.ClientCert, config.ClientKey, err) + } + clientCert = append(clientCert, tlsCert) + tlsConfig.Certificates = clientCert + } + if config.SSLMode == 2 || config.SSLMode == 3 { + tlsConfig.InsecureSkipVerify = true + } + if !filepath.IsAbs(config.Host) && !config.DisableSNI { + tlsConfig.ServerName = config.Host + } + providerLog(logger.LevelInfo, "registering custom TLS config, root cert %#v, client cert %#v, client key %#v, disable SNI? %v", + config.RootCert, config.ClientCert, config.ClientKey, config.DisableSNI) + if err := mysql.RegisterTLSConfig("custom", tlsConfig); err != nil { + return fmt.Errorf("unable to register tls config: %v", err) + } + return nil +} + func (p *MySQLProvider) checkAvailability() error { return sqlCommonCheckAvailability(p.dbHandle) } diff --git a/internal/dataprovider/pgsql.go b/internal/dataprovider/pgsql.go index 51a6fb5d..492085ee 100644 --- a/internal/dataprovider/pgsql.go +++ b/internal/dataprovider/pgsql.go @@ -231,6 +231,9 @@ func getPGSQLConnectionString(redactedPwd bool) string { if config.ClientCert != "" && config.ClientKey != "" { connectionString += fmt.Sprintf(" sslcert='%v' sslkey='%v'", config.ClientCert, config.ClientKey) } + if config.DisableSNI { + connectionString += " sslsni=0" + } } else { connectionString = config.ConnectionString } diff --git a/sftpgo.json b/sftpgo.json index f631b662..2daab3f4 100644 --- a/sftpgo.json +++ b/sftpgo.json @@ -190,6 +190,7 @@ "username": "", "password": "", "sslmode": 0, + "disable_sni": false, "root_cert": "", "client_cert": "", "client_key": "",