diff --git a/docs/full-configuration.md b/docs/full-configuration.md index 6ddacfb2..f1f125d1 100644 --- a/docs/full-configuration.md +++ b/docs/full-configuration.md @@ -182,6 +182,7 @@ The configuration file contains the following sections: - `proxy_allowed`, list of IP addresses and IP ranges allowed to set client IP proxy header such as `X-Forwarded-For`. Any client IP proxy headers, if set on requests from a connection address not in this list, will be silently ignored. Default: empty. - `client_ip_proxy_header`, string. Defines the allowed client IP proxy header such as `X-Forwarded-For`, `X-Real-IP` etc. Default: empty - `client_ip_header_depth`, integer. Some client IP headers such as `X-Forwarded-For` can contain multiple IP address, this setting define the position to trust starting from the right. For example if we have: `10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1` and the depth is `0`, SFTPGo will use `13.0.0.1` as client IP, if depth is `1`, `12.0.0.1` will be used and so on. Default: `0`. + - `disable_www_auth_header`, boolean. Set to `true` to not add the WWW-Authenticate header after an authentication failure, only the `401` status code will be sent. Default: `false`. - `certificate_file`, string. Certificate for WebDAV over HTTPS. This can be an absolute path or a path relative to the config dir. - `certificate_key_file`, string. Private key matching the above certificate. This can be an absolute path or a path relative to the config dir. A certificate and a private key are required to enable HTTPS connections. Certificate and key files can be reloaded on demand sending a `SIGHUP` signal on Unix based systems and a `paramchange` request to the running service on Windows. - `ca_certificates`, list of strings. Set of root certificate authorities to be used to verify client certificates. diff --git a/internal/config/config.go b/internal/config/config.go index bee2500a..5dde3d50 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -81,18 +81,19 @@ var ( Debug: false, } defaultWebDAVDBinding = webdavd.Binding{ - Address: "", - Port: 0, - EnableHTTPS: false, - CertificateFile: "", - CertificateKeyFile: "", - MinTLSVersion: 12, - ClientAuthType: 0, - TLSCipherSuites: nil, - Prefix: "", - ProxyAllowed: nil, - ClientIPProxyHeader: "", - ClientIPHeaderDepth: 0, + Address: "", + Port: 0, + EnableHTTPS: false, + CertificateFile: "", + CertificateKeyFile: "", + MinTLSVersion: 12, + ClientAuthType: 0, + TLSCipherSuites: nil, + Prefix: "", + ProxyAllowed: nil, + ClientIPProxyHeader: "", + ClientIPHeaderDepth: 0, + DisableWWWAuthHeader: false, } defaultHTTPDBinding = httpd.Binding{ Address: "", @@ -1193,6 +1194,12 @@ func getWebDAVDBindingFromEnv(idx int) { isSet = true } + enableHTTPS, ok := lookupBoolFromEnv(fmt.Sprintf("SFTPGO_WEBDAVD__BINDINGS__%v__ENABLE_HTTPS", idx)) + if ok { + binding.EnableHTTPS = enableHTTPS + isSet = true + } + certificateFile, ok := os.LookupEnv(fmt.Sprintf("SFTPGO_WEBDAVD__BINDINGS__%v__CERTIFICATE_FILE", idx)) if ok { binding.CertificateFile = certificateFile @@ -1205,12 +1212,6 @@ func getWebDAVDBindingFromEnv(idx int) { isSet = true } - enableHTTPS, ok := lookupBoolFromEnv(fmt.Sprintf("SFTPGO_WEBDAVD__BINDINGS__%v__ENABLE_HTTPS", idx)) - if ok { - binding.EnableHTTPS = enableHTTPS - isSet = true - } - tlsVer, ok := lookupIntFromEnv(fmt.Sprintf("SFTPGO_WEBDAVD__BINDINGS__%v__MIN_TLS_VERSION", idx)) if ok { binding.MinTLSVersion = int(tlsVer) @@ -1229,13 +1230,19 @@ func getWebDAVDBindingFromEnv(idx int) { isSet = true } + prefix, ok := os.LookupEnv(fmt.Sprintf("SFTPGO_WEBDAVD__BINDINGS__%v__PREFIX", idx)) + if ok { + binding.Prefix = prefix + isSet = true + } + if getWebDAVDBindingProxyConfigsFromEnv(idx, &binding) { isSet = true } - prefix, ok := os.LookupEnv(fmt.Sprintf("SFTPGO_WEBDAVD__BINDINGS__%v__PREFIX", idx)) + disableWWWAuth, ok := lookupBoolFromEnv(fmt.Sprintf("SFTPGO_WEBDAVD__BINDINGS__%v__DISABLE_WWW_AUTH_HEADER", idx)) if ok { - binding.Prefix = prefix + binding.DisableWWWAuthHeader = disableWWWAuth isSet = true } diff --git a/internal/config/config_test.go b/internal/config/config_test.go index e0f6de1c..85660681 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -943,6 +943,7 @@ func TestWebDAVBindingsFromEnv(t *testing.T) { os.Setenv("SFTPGO_WEBDAVD__BINDINGS__2__PREFIX", "/dav2") os.Setenv("SFTPGO_WEBDAVD__BINDINGS__2__CERTIFICATE_FILE", "webdav.crt") os.Setenv("SFTPGO_WEBDAVD__BINDINGS__2__CERTIFICATE_KEY_FILE", "webdav.key") + os.Setenv("SFTPGO_WEBDAVD__BINDINGS__2__DISABLE_WWW_AUTH_HEADER", "1") t.Cleanup(func() { os.Unsetenv("SFTPGO_WEBDAVD__BINDINGS__1__ADDRESS") @@ -960,6 +961,7 @@ func TestWebDAVBindingsFromEnv(t *testing.T) { os.Unsetenv("SFTPGO_WEBDAVD__BINDINGS__2__PREFIX") os.Unsetenv("SFTPGO_WEBDAVD__BINDINGS__2__CERTIFICATE_FILE") os.Unsetenv("SFTPGO_WEBDAVD__BINDINGS__2__CERTIFICATE_KEY_FILE") + os.Unsetenv("SFTPGO_WEBDAVD__BINDINGS__2__DISABLE_WWW_AUTH_HEADER") }) err := config.LoadConfig(configDir, "") @@ -973,6 +975,7 @@ func TestWebDAVBindingsFromEnv(t *testing.T) { require.Len(t, bindings[0].TLSCipherSuites, 0) require.Empty(t, bindings[0].Prefix) require.Equal(t, 0, bindings[0].ClientIPHeaderDepth) + require.False(t, bindings[0].DisableWWWAuthHeader) require.Equal(t, 8000, bindings[1].Port) require.Equal(t, "127.0.0.1", bindings[1].Address) require.False(t, bindings[1].EnableHTTPS) @@ -984,6 +987,7 @@ func TestWebDAVBindingsFromEnv(t *testing.T) { require.Equal(t, "X-Forwarded-For", bindings[1].ClientIPProxyHeader) require.Equal(t, 2, bindings[1].ClientIPHeaderDepth) require.Empty(t, bindings[1].Prefix) + require.False(t, bindings[1].DisableWWWAuthHeader) require.Equal(t, 9000, bindings[2].Port) require.Equal(t, "127.0.1.1", bindings[2].Address) require.True(t, bindings[2].EnableHTTPS) @@ -994,6 +998,7 @@ func TestWebDAVBindingsFromEnv(t *testing.T) { require.Equal(t, "webdav.crt", bindings[2].CertificateFile) require.Equal(t, "webdav.key", bindings[2].CertificateKeyFile) require.Equal(t, 0, bindings[2].ClientIPHeaderDepth) + require.True(t, bindings[2].DisableWWWAuthHeader) } func TestHTTPDBindingsFromEnv(t *testing.T) { diff --git a/internal/webdavd/server.go b/internal/webdavd/server.go index 7e933a70..3f0e1ab8 100644 --- a/internal/webdavd/server.go +++ b/internal/webdavd/server.go @@ -189,7 +189,9 @@ func (s *webDavServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { user, isCached, lockSystem, loginMethod, err := s.authenticate(r, ipAddr) if err != nil { updateLoginMetrics(&user, ipAddr, loginMethod, err) - w.Header().Set("WWW-Authenticate", "Basic realm=\"SFTPGo WebDAV\"") + if !s.binding.DisableWWWAuthHeader { + w.Header().Set("WWW-Authenticate", "Basic realm=\"SFTPGo WebDAV\"") + } http.Error(w, fmt.Sprintf("Authentication error: %v", err), http.StatusUnauthorized) return } diff --git a/internal/webdavd/webdavd.go b/internal/webdavd/webdavd.go index a0c054f7..a3b8320e 100644 --- a/internal/webdavd/webdavd.go +++ b/internal/webdavd/webdavd.go @@ -122,7 +122,10 @@ type Binding struct { // "10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1" and the depth is 0, SFTPGo will use "13.0.0.1" // as client IP, if depth is 1, "12.0.0.1" will be used and so on ClientIPHeaderDepth int `json:"client_ip_header_depth" mapstructure:"client_ip_header_depth"` - allowHeadersFrom []func(net.IP) bool + // Do not add the WWW-Authenticate header after an authentication error, + // only the 401 status code will be sent + DisableWWWAuthHeader bool `json:"disable_www_auth_header" mapstructure:"disable_www_auth_header"` + allowHeadersFrom []func(net.IP) bool } func (b *Binding) parseAllowedProxy() error { diff --git a/sftpgo.json b/sftpgo.json index a8560ede..f631b662 100644 --- a/sftpgo.json +++ b/sftpgo.json @@ -151,7 +151,8 @@ "prefix": "", "proxy_allowed": [], "client_ip_proxy_header": "", - "client_ip_header_depth": 0 + "client_ip_header_depth": 0, + "disable_www_auth_header": false } ], "certificate_file": "",