httpd/webdav: allow to configure trusted proxy header and depth
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
This commit is contained in:
parent
32da923dfe
commit
f6b11c2d01
11 changed files with 173 additions and 72 deletions
|
@ -67,16 +67,18 @@ var (
|
|||
Debug: false,
|
||||
}
|
||||
defaultWebDAVDBinding = webdavd.Binding{
|
||||
Address: "",
|
||||
Port: 0,
|
||||
EnableHTTPS: false,
|
||||
CertificateFile: "",
|
||||
CertificateKeyFile: "",
|
||||
MinTLSVersion: 12,
|
||||
ClientAuthType: 0,
|
||||
TLSCipherSuites: nil,
|
||||
Prefix: "",
|
||||
ProxyAllowed: nil,
|
||||
Address: "",
|
||||
Port: 0,
|
||||
EnableHTTPS: false,
|
||||
CertificateFile: "",
|
||||
CertificateKeyFile: "",
|
||||
MinTLSVersion: 12,
|
||||
ClientAuthType: 0,
|
||||
TLSCipherSuites: nil,
|
||||
Prefix: "",
|
||||
ProxyAllowed: nil,
|
||||
ClientIPProxyHeader: "",
|
||||
ClientIPHeaderDepth: 0,
|
||||
}
|
||||
defaultHTTPDBinding = httpd.Binding{
|
||||
Address: "",
|
||||
|
@ -90,6 +92,8 @@ var (
|
|||
ClientAuthType: 0,
|
||||
TLSCipherSuites: nil,
|
||||
ProxyAllowed: nil,
|
||||
ClientIPProxyHeader: "",
|
||||
ClientIPHeaderDepth: 0,
|
||||
HideLoginURL: 0,
|
||||
RenderOpenAPI: true,
|
||||
WebClientIntegrations: nil,
|
||||
|
@ -1126,6 +1130,30 @@ func applyFTPDBindingFromEnv(idx int, isSet bool, binding ftpd.Binding) {
|
|||
}
|
||||
}
|
||||
|
||||
func getWebDAVDBindingProxyConfigsFromEnv(idx int, binding *webdavd.Binding) bool {
|
||||
isSet := false
|
||||
|
||||
proxyAllowed, ok := lookupStringListFromEnv(fmt.Sprintf("SFTPGO_WEBDAVD__BINDINGS__%v__PROXY_ALLOWED", idx))
|
||||
if ok {
|
||||
binding.ProxyAllowed = proxyAllowed
|
||||
isSet = true
|
||||
}
|
||||
|
||||
clientIPProxyHeader, ok := os.LookupEnv(fmt.Sprintf("SFTPGO_WEBDAVD__BINDINGS__%v__CLIENT_IP_PROXY_HEADER", idx))
|
||||
if ok {
|
||||
binding.ClientIPProxyHeader = clientIPProxyHeader
|
||||
isSet = true
|
||||
}
|
||||
|
||||
clientIPHeaderDepth, ok := lookupIntFromEnv(fmt.Sprintf("SFTPGO_WEBDAVD__BINDINGS__%v__CLIENT_IP_HEADER_DEPTH", idx))
|
||||
if ok {
|
||||
binding.ClientIPHeaderDepth = int(clientIPHeaderDepth)
|
||||
isSet = true
|
||||
}
|
||||
|
||||
return isSet
|
||||
}
|
||||
|
||||
func getWebDAVDBindingFromEnv(idx int) {
|
||||
binding := webdavd.Binding{
|
||||
MinTLSVersion: 12,
|
||||
|
@ -1184,9 +1212,7 @@ func getWebDAVDBindingFromEnv(idx int) {
|
|||
isSet = true
|
||||
}
|
||||
|
||||
proxyAllowed, ok := lookupStringListFromEnv(fmt.Sprintf("SFTPGO_WEBDAVD__BINDINGS__%v__PROXY_ALLOWED", idx))
|
||||
if ok {
|
||||
binding.ProxyAllowed = proxyAllowed
|
||||
if getWebDAVDBindingProxyConfigsFromEnv(idx, &binding) {
|
||||
isSet = true
|
||||
}
|
||||
|
||||
|
@ -1519,6 +1545,30 @@ func getHTTPDNestedObjectsFromEnv(idx int, binding *httpd.Binding) bool {
|
|||
return isSet
|
||||
}
|
||||
|
||||
func getHTTPDBindingProxyConfigsFromEnv(idx int, binding *httpd.Binding) bool {
|
||||
isSet := false
|
||||
|
||||
proxyAllowed, ok := lookupStringListFromEnv(fmt.Sprintf("SFTPGO_HTTPD__BINDINGS__%v__PROXY_ALLOWED", idx))
|
||||
if ok {
|
||||
binding.ProxyAllowed = proxyAllowed
|
||||
isSet = true
|
||||
}
|
||||
|
||||
clientIPProxyHeader, ok := os.LookupEnv(fmt.Sprintf("SFTPGO_HTTPD__BINDINGS__%v__CLIENT_IP_PROXY_HEADER", idx))
|
||||
if ok {
|
||||
binding.ClientIPProxyHeader = clientIPProxyHeader
|
||||
isSet = true
|
||||
}
|
||||
|
||||
clientIPHeaderDepth, ok := lookupIntFromEnv(fmt.Sprintf("SFTPGO_HTTPD__BINDINGS__%v__CLIENT_IP_HEADER_DEPTH", idx))
|
||||
if ok {
|
||||
binding.ClientIPHeaderDepth = int(clientIPHeaderDepth)
|
||||
isSet = true
|
||||
}
|
||||
|
||||
return isSet
|
||||
}
|
||||
|
||||
func getHTTPDBindingFromEnv(idx int) {
|
||||
binding := getDefaultHTTPBinding(idx)
|
||||
isSet := false
|
||||
|
@ -1589,9 +1639,7 @@ func getHTTPDBindingFromEnv(idx int) {
|
|||
isSet = true
|
||||
}
|
||||
|
||||
proxyAllowed, ok := lookupStringListFromEnv(fmt.Sprintf("SFTPGO_HTTPD__BINDINGS__%v__PROXY_ALLOWED", idx))
|
||||
if ok {
|
||||
binding.ProxyAllowed = proxyAllowed
|
||||
if getHTTPDBindingProxyConfigsFromEnv(idx, &binding) {
|
||||
isSet = true
|
||||
}
|
||||
|
||||
|
|
|
@ -837,6 +837,8 @@ func TestWebDAVBindingsFromEnv(t *testing.T) {
|
|||
os.Setenv("SFTPGO_WEBDAVD__BINDINGS__1__ENABLE_HTTPS", "0")
|
||||
os.Setenv("SFTPGO_WEBDAVD__BINDINGS__1__TLS_CIPHER_SUITES", "TLS_RSA_WITH_AES_128_CBC_SHA ")
|
||||
os.Setenv("SFTPGO_WEBDAVD__BINDINGS__1__PROXY_ALLOWED", "192.168.10.1")
|
||||
os.Setenv("SFTPGO_WEBDAVD__BINDINGS__1__CLIENT_IP_PROXY_HEADER", "X-Forwarded-For")
|
||||
os.Setenv("SFTPGO_WEBDAVD__BINDINGS__1__CLIENT_IP_HEADER_DEPTH", "2")
|
||||
os.Setenv("SFTPGO_WEBDAVD__BINDINGS__2__ADDRESS", "127.0.1.1")
|
||||
os.Setenv("SFTPGO_WEBDAVD__BINDINGS__2__PORT", "9000")
|
||||
os.Setenv("SFTPGO_WEBDAVD__BINDINGS__2__ENABLE_HTTPS", "1")
|
||||
|
@ -852,6 +854,8 @@ func TestWebDAVBindingsFromEnv(t *testing.T) {
|
|||
os.Unsetenv("SFTPGO_WEBDAVD__BINDINGS__1__ENABLE_HTTPS")
|
||||
os.Unsetenv("SFTPGO_WEBDAVD__BINDINGS__1__TLS_CIPHER_SUITES")
|
||||
os.Unsetenv("SFTPGO_WEBDAVD__BINDINGS__1__PROXY_ALLOWED")
|
||||
os.Unsetenv("SFTPGO_WEBDAVD__BINDINGS__1__CLIENT_IP_PROXY_HEADER")
|
||||
os.Unsetenv("SFTPGO_WEBDAVD__BINDINGS__1__CLIENT_IP_HEADER_DEPTH")
|
||||
os.Unsetenv("SFTPGO_WEBDAVD__BINDINGS__2__ADDRESS")
|
||||
os.Unsetenv("SFTPGO_WEBDAVD__BINDINGS__2__PORT")
|
||||
os.Unsetenv("SFTPGO_WEBDAVD__BINDINGS__2__ENABLE_HTTPS")
|
||||
|
@ -873,6 +877,7 @@ func TestWebDAVBindingsFromEnv(t *testing.T) {
|
|||
require.Equal(t, 12, bindings[0].MinTLSVersion)
|
||||
require.Len(t, bindings[0].TLSCipherSuites, 0)
|
||||
require.Empty(t, bindings[0].Prefix)
|
||||
require.Equal(t, 0, bindings[0].ClientIPHeaderDepth)
|
||||
require.Equal(t, 8000, bindings[1].Port)
|
||||
require.Equal(t, "127.0.0.1", bindings[1].Address)
|
||||
require.False(t, bindings[1].EnableHTTPS)
|
||||
|
@ -881,6 +886,8 @@ func TestWebDAVBindingsFromEnv(t *testing.T) {
|
|||
require.Len(t, bindings[1].TLSCipherSuites, 1)
|
||||
require.Equal(t, "TLS_RSA_WITH_AES_128_CBC_SHA", bindings[1].TLSCipherSuites[0])
|
||||
require.Equal(t, "192.168.10.1", bindings[1].ProxyAllowed[0])
|
||||
require.Equal(t, "X-Forwarded-For", bindings[1].ClientIPProxyHeader)
|
||||
require.Equal(t, 2, bindings[1].ClientIPHeaderDepth)
|
||||
require.Empty(t, bindings[1].Prefix)
|
||||
require.Equal(t, 9000, bindings[2].Port)
|
||||
require.Equal(t, "127.0.1.1", bindings[2].Address)
|
||||
|
@ -891,6 +898,7 @@ func TestWebDAVBindingsFromEnv(t *testing.T) {
|
|||
require.Equal(t, "/dav2", bindings[2].Prefix)
|
||||
require.Equal(t, "webdav.crt", bindings[2].CertificateFile)
|
||||
require.Equal(t, "webdav.key", bindings[2].CertificateKeyFile)
|
||||
require.Equal(t, 0, bindings[2].ClientIPHeaderDepth)
|
||||
}
|
||||
|
||||
func TestHTTPDBindingsFromEnv(t *testing.T) {
|
||||
|
@ -917,6 +925,8 @@ func TestHTTPDBindingsFromEnv(t *testing.T) {
|
|||
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__CLIENT_AUTH_TYPE", "1")
|
||||
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__TLS_CIPHER_SUITES", " TLS_AES_256_GCM_SHA384 , TLS_CHACHA20_POLY1305_SHA256")
|
||||
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__PROXY_ALLOWED", " 192.168.9.1 , 172.16.25.0/24")
|
||||
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__CLIENT_IP_PROXY_HEADER", "X-Real-IP")
|
||||
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__CLIENT_IP_HEADER_DEPTH", "2")
|
||||
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__HIDE_LOGIN_URL", "3")
|
||||
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__WEB_CLIENT_INTEGRATIONS__1__URL", "http://127.0.0.1/")
|
||||
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__WEB_CLIENT_INTEGRATIONS__1__FILE_EXTENSIONS", ".pdf, .txt")
|
||||
|
@ -979,6 +989,8 @@ func TestHTTPDBindingsFromEnv(t *testing.T) {
|
|||
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__CLIENT_AUTH_TYPE")
|
||||
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__TLS_CIPHER_SUITES")
|
||||
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__PROXY_ALLOWED")
|
||||
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__CLIENT_IP_PROXY_HEADER")
|
||||
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__CLIENT_IP_HEADER_DEPTH")
|
||||
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__HIDE_LOGIN_URL")
|
||||
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__WEB_CLIENT_INTEGRATIONS__1__URL")
|
||||
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__WEB_CLIENT_INTEGRATIONS__1__FILE_EXTENSIONS")
|
||||
|
@ -1038,6 +1050,7 @@ func TestHTTPDBindingsFromEnv(t *testing.T) {
|
|||
require.Equal(t, "TLS_AES_128_GCM_SHA256", bindings[0].TLSCipherSuites[0])
|
||||
require.Equal(t, 0, bindings[0].HideLoginURL)
|
||||
require.False(t, bindings[0].Security.Enabled)
|
||||
require.Equal(t, 0, bindings[0].ClientIPHeaderDepth)
|
||||
require.Equal(t, 8000, bindings[1].Port)
|
||||
require.Equal(t, "127.0.0.1", bindings[1].Address)
|
||||
require.False(t, bindings[1].EnableHTTPS)
|
||||
|
@ -1051,6 +1064,7 @@ func TestHTTPDBindingsFromEnv(t *testing.T) {
|
|||
require.False(t, bindings[1].Security.Enabled)
|
||||
require.Equal(t, "Web Admin", bindings[1].Branding.WebAdmin.Name)
|
||||
require.Equal(t, "WebClient", bindings[1].Branding.WebClient.ShortName)
|
||||
require.Equal(t, 0, bindings[1].ClientIPHeaderDepth)
|
||||
require.Equal(t, 9000, bindings[2].Port)
|
||||
require.Equal(t, "127.0.1.1", bindings[2].Address)
|
||||
require.True(t, bindings[2].EnableHTTPS)
|
||||
|
@ -1065,6 +1079,8 @@ func TestHTTPDBindingsFromEnv(t *testing.T) {
|
|||
require.Len(t, bindings[2].ProxyAllowed, 2)
|
||||
require.Equal(t, "192.168.9.1", bindings[2].ProxyAllowed[0])
|
||||
require.Equal(t, "172.16.25.0/24", bindings[2].ProxyAllowed[1])
|
||||
require.Equal(t, "X-Real-IP", bindings[2].ClientIPProxyHeader)
|
||||
require.Equal(t, 2, bindings[2].ClientIPHeaderDepth)
|
||||
require.Equal(t, 3, bindings[2].HideLoginURL)
|
||||
require.Len(t, bindings[2].WebClientIntegrations, 1)
|
||||
require.Equal(t, "http://127.0.0.1/", bindings[2].WebClientIntegrations[0].URL)
|
||||
|
|
|
@ -179,7 +179,9 @@ The configuration file contains the following sections:
|
|||
- `client_auth_type`, integer. Set to `1` to require a client certificate and verify it. Set to `2` to request a client certificate during the TLS handshake and verify it if given, in this mode the client is allowed not to send a certificate. At least one certification authority must be defined in order to verify client certificates. If no certification authority is defined, this setting is ignored. Default: 0.
|
||||
- `tls_cipher_suites`, list of strings. List of supported cipher suites for TLS version 1.2. If empty, a default list of secure cipher suites is used, with a preference order based on hardware performance. Note that TLS 1.3 ciphersuites are not configurable. The supported ciphersuites names are defined [here](https://github.com/golang/go/blob/master/src/crypto/tls/cipher_suites.go#L52). Any invalid name will be silently ignored. The order matters, the ciphers listed first will be the preferred ones. Default: empty.
|
||||
- `prefix`, string. Prefix for WebDAV resources, if empty WebDAV resources will be available at the `/` URI. If defined it must be an absolute URI, for example `/dav`. Default: "".
|
||||
- `proxy_allowed`, list of IP addresses and IP ranges allowed to set `X-Forwarded-For`, `X-Real-IP`, `CF-Connecting-IP`, `True-Client-IP` headers. Any of the indicated headers, if set on requests from a connection address not in this list, will be silently ignored. Default: empty.
|
||||
- `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`.
|
||||
- `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.
|
||||
|
@ -262,8 +264,10 @@ The configuration file contains the following sections:
|
|||
- `certificate_key_file`, string. Binding specific private key matching the above certificate. This can be an absolute path or a path relative to the config dir. If not set the global ones will be used, if any.
|
||||
- `min_tls_version`, integer. Defines the minimum version of TLS to be enabled. `12` means TLS 1.2 (and therefore TLS 1.2 and TLS 1.3 will be enabled),`13` means TLS 1.3. Default: `12`.
|
||||
- `client_auth_type`, integer. Set to `1` to require client certificate authentication in addition to JWT/Web authentication. You need to define at least a certificate authority for this to work. Default: 0.
|
||||
- `tls_cipher_suites`, list of strings. List of supported cipher suites for TLS version 1.2. If empty, a default list of secure cipher suites is used, with a preference order based on hardware performance. Note that TLS 1.3 ciphersuites are not configurable. The supported ciphersuites names are defined [here](https://github.com/golang/go/blob/master/src/crypto/tls/cipher_suites.go#L52). Any invalid name will be silently ignored. The order matters, the ciphers listed first will be the preferred ones. Default: blank.
|
||||
- `proxy_allowed`, list of IP addresses and IP ranges allowed to set `X-Forwarded-For`, `X-Real-IP`, `X-Forwarded-Proto`, `CF-Connecting-IP`, `True-Client-IP` and any other headers defined in the `security` section. Any of the indicated headers, if set on requests from a connection address not in this list, will be silently ignored. Default: blank.
|
||||
- `tls_cipher_suites`, list of strings. List of supported cipher suites for TLS version 1.2. If empty, a default list of secure cipher suites is used, with a preference order based on hardware performance. Note that TLS 1.3 ciphersuites are not configurable. The supported ciphersuites names are defined [here](https://github.com/golang/go/blob/master/src/crypto/tls/cipher_suites.go#L52). Any invalid name will be silently ignored. The order matters, the ciphers listed first will be the preferred ones. Default: empty.
|
||||
- `proxy_allowed`, list of IP addresses and IP ranges allowed to set client IP proxy header such as `X-Forwarded-For`, `X-Real-IP` and any other headers defined in the `security` section. Any of the indicated 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`.
|
||||
- `hide_login_url`, integer. If both web admin and web client are enabled each login page will show a link to the other one. This setting allows to hide this link. 0 means that the login links are displayed on both admin and client login page. This is the default. 1 means that the login link to the web client login page is hidden on admin login page. 2 means that the login link to the web admin login page is hidden on client login page. The flags can be combined, for example 3 will disable both login links.
|
||||
- `render_openapi`, boolean. Set to `false` to disable serving of the OpenAPI schema and renderer. Default `true`.
|
||||
- `web_client_integrations`, list of struct. The SFTPGo web client allows to send the files with the specified extensions to the configured URL using the [postMessage API](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage). This way you can integrate your own file viewer or editor. Take a look at the commentented example [here](../examples/webclient-integrations/test.html) to understand how to use this feature. Each struct has the following fields:
|
||||
|
|
|
@ -417,9 +417,16 @@ type Binding struct {
|
|||
// any invalid name will be silently ignored.
|
||||
// The order matters, the ciphers listed first will be the preferred ones.
|
||||
TLSCipherSuites []string `json:"tls_cipher_suites" mapstructure:"tls_cipher_suites"`
|
||||
// List of IP addresses and IP ranges allowed to set X-Forwarded-For, X-Real-IP,
|
||||
// X-Forwarded-Proto headers.
|
||||
// List of IP addresses and IP ranges allowed to set client IP proxy headers and
|
||||
// X-Forwarded-Proto header.
|
||||
ProxyAllowed []string `json:"proxy_allowed" mapstructure:"proxy_allowed"`
|
||||
// Allowed client IP proxy header such as "X-Forwarded-For", "X-Real-IP"
|
||||
ClientIPProxyHeader string `json:"client_ip_proxy_header" mapstructure:"client_ip_proxy_header"`
|
||||
// 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
|
||||
ClientIPHeaderDepth int `json:"client_ip_header_depth" mapstructure:"client_ip_header_depth"`
|
||||
// If both web admin and web client are enabled each login page will show a link
|
||||
// to the other one. This setting allows to hide this link:
|
||||
// - 0 login links are displayed on both admin and client login page. This is the default
|
||||
|
|
|
@ -1551,11 +1551,12 @@ func TestProxyHeaders(t *testing.T) {
|
|||
testIP := "10.29.1.9"
|
||||
validForwardedFor := "172.19.2.6"
|
||||
b := Binding{
|
||||
Address: "",
|
||||
Port: 8080,
|
||||
EnableWebAdmin: true,
|
||||
EnableWebClient: false,
|
||||
ProxyAllowed: []string{testIP, "10.8.0.0/30"},
|
||||
Address: "",
|
||||
Port: 8080,
|
||||
EnableWebAdmin: true,
|
||||
EnableWebClient: false,
|
||||
ProxyAllowed: []string{testIP, "10.8.0.0/30"},
|
||||
ClientIPProxyHeader: "x-forwarded-for",
|
||||
}
|
||||
err = b.parseAllowedProxy()
|
||||
assert.NoError(t, err)
|
||||
|
|
|
@ -977,7 +977,7 @@ func (s *httpdServer) checkConnection(next http.Handler) http.Handler {
|
|||
if ip != nil {
|
||||
for _, allow := range s.binding.allowHeadersFrom {
|
||||
if allow(ip) {
|
||||
parsedIP := util.GetRealIP(r)
|
||||
parsedIP := util.GetRealIP(r, s.binding.ClientIPProxyHeader, s.binding.ClientIPHeaderDepth)
|
||||
if parsedIP != "" {
|
||||
ipAddr = parsedIP
|
||||
r.RemoteAddr = ipAddr
|
||||
|
|
|
@ -149,7 +149,9 @@
|
|||
"client_auth_type": 0,
|
||||
"tls_cipher_suites": [],
|
||||
"prefix": "",
|
||||
"proxy_allowed": []
|
||||
"proxy_allowed": [],
|
||||
"client_ip_proxy_header": "",
|
||||
"client_ip_header_depth": 0
|
||||
}
|
||||
],
|
||||
"certificate_file": "",
|
||||
|
@ -251,6 +253,8 @@
|
|||
"client_auth_type": 0,
|
||||
"tls_cipher_suites": [],
|
||||
"proxy_allowed": [],
|
||||
"client_ip_proxy_header": "",
|
||||
"client_ip_header_depth": 0,
|
||||
"hide_login_url": 0,
|
||||
"render_openapi": true,
|
||||
"web_client_integrations": [],
|
||||
|
|
44
util/util.go
44
util/util.go
|
@ -41,11 +41,7 @@ const (
|
|||
)
|
||||
|
||||
var (
|
||||
xForwardedFor = http.CanonicalHeaderKey("X-Forwarded-For")
|
||||
xRealIP = http.CanonicalHeaderKey("X-Real-IP")
|
||||
cfConnectingIP = http.CanonicalHeaderKey("CF-Connecting-IP")
|
||||
trueClientIP = http.CanonicalHeaderKey("True-Client-IP")
|
||||
emailRegex = regexp.MustCompile("^(?:(?:(?:(?:[a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+(?:\\.([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+)*)|(?:(?:\\x22)(?:(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(?:\\x20|\\x09)+)?(?:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}]))))*(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(\\x20|\\x09)+)?(?:\\x22))))@(?:(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.)+(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.?$")
|
||||
emailRegex = regexp.MustCompile("^(?:(?:(?:(?:[a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+(?:\\.([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+)*)|(?:(?:\\x22)(?:(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(?:\\x20|\\x09)+)?(?:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}]))))*(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(\\x20|\\x09)+)?(?:\\x22))))@(?:(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.)+(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.?$")
|
||||
)
|
||||
|
||||
// Contains reports whether v is present in elems.
|
||||
|
@ -536,27 +532,29 @@ func GetSSHPublicKeyAsString(pubKey []byte) (string, error) {
|
|||
|
||||
// GetRealIP returns the ip address as result of parsing either the
|
||||
// X-Real-IP header or the X-Forwarded-For header
|
||||
func GetRealIP(r *http.Request) string {
|
||||
var ip string
|
||||
|
||||
if clientIP := r.Header.Get(trueClientIP); clientIP != "" {
|
||||
ip = clientIP
|
||||
} else if xrip := r.Header.Get(xRealIP); xrip != "" {
|
||||
ip = xrip
|
||||
} else if clientIP := r.Header.Get(cfConnectingIP); clientIP != "" {
|
||||
ip = clientIP
|
||||
} else if xff := r.Header.Get(xForwardedFor); xff != "" {
|
||||
i := strings.Index(xff, ",")
|
||||
if i == -1 {
|
||||
i = len(xff)
|
||||
}
|
||||
ip = strings.TrimSpace(xff[:i])
|
||||
}
|
||||
if ip == "" || net.ParseIP(ip) == nil {
|
||||
func GetRealIP(r *http.Request, header string, depth int) string {
|
||||
if header == "" {
|
||||
return ""
|
||||
}
|
||||
var ipAddresses []string
|
||||
|
||||
return ip
|
||||
for _, h := range r.Header.Values(header) {
|
||||
for _, ipStr := range strings.Split(h, ",") {
|
||||
ipStr = strings.TrimSpace(ipStr)
|
||||
ipAddresses = append(ipAddresses, ipStr)
|
||||
}
|
||||
}
|
||||
|
||||
idx := len(ipAddresses) - 1 - depth
|
||||
if idx >= 0 {
|
||||
ip := strings.TrimSpace(ipAddresses[idx])
|
||||
if ip == "" || net.ParseIP(ip) == nil {
|
||||
return ""
|
||||
}
|
||||
return ip
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetHTTPLocalAddress returns the local address for an http.Request
|
||||
|
|
|
@ -435,19 +435,26 @@ func TestRemoteAddress(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
assert.Empty(t, req.RemoteAddr)
|
||||
|
||||
req.Header.Set("True-Client-IP", remoteAddr1)
|
||||
ip := util.GetRealIP(req)
|
||||
trueClientIP := "True-Client-IP"
|
||||
cfConnectingIP := "CF-Connecting-IP"
|
||||
xff := "X-Forwarded-For"
|
||||
xRealIP := "X-Real-IP"
|
||||
|
||||
req.Header.Set(trueClientIP, remoteAddr1)
|
||||
ip := util.GetRealIP(req, trueClientIP, 0)
|
||||
assert.Equal(t, remoteAddr1, ip)
|
||||
req.Header.Del("True-Client-IP")
|
||||
req.Header.Set("CF-Connecting-IP", remoteAddr1)
|
||||
ip = util.GetRealIP(req)
|
||||
ip = util.GetRealIP(req, trueClientIP, 2)
|
||||
assert.Empty(t, ip)
|
||||
req.Header.Del(trueClientIP)
|
||||
req.Header.Set(cfConnectingIP, remoteAddr1)
|
||||
ip = util.GetRealIP(req, cfConnectingIP, 0)
|
||||
assert.Equal(t, remoteAddr1, ip)
|
||||
req.Header.Del("CF-Connecting-IP")
|
||||
req.Header.Set("X-Forwarded-For", remoteAddr1)
|
||||
ip = util.GetRealIP(req)
|
||||
req.Header.Del(cfConnectingIP)
|
||||
req.Header.Set(xff, remoteAddr1)
|
||||
ip = util.GetRealIP(req, xff, 0)
|
||||
assert.Equal(t, remoteAddr1, ip)
|
||||
// this will be ignored, remoteAddr1 is not allowed to se this header
|
||||
req.Header.Set("X-Forwarded-For", remoteAddr2)
|
||||
req.Header.Set(xff, remoteAddr2)
|
||||
req.RemoteAddr = remoteAddr1
|
||||
ip = server.checkRemoteAddress(req)
|
||||
assert.Equal(t, remoteAddr1, ip)
|
||||
|
@ -455,32 +462,41 @@ func TestRemoteAddress(t *testing.T) {
|
|||
ip = server.checkRemoteAddress(req)
|
||||
assert.Empty(t, ip)
|
||||
|
||||
req.Header.Set("X-Forwarded-For", fmt.Sprintf("%v, %v", remoteAddr2, remoteAddr1))
|
||||
ip = util.GetRealIP(req)
|
||||
req.Header.Set(xff, fmt.Sprintf("%v , %v", remoteAddr2, remoteAddr1))
|
||||
ip = util.GetRealIP(req, xff, 1)
|
||||
assert.Equal(t, remoteAddr2, ip)
|
||||
|
||||
req.RemoteAddr = remoteAddr2
|
||||
req.Header.Set("X-Forwarded-For", fmt.Sprintf("%v,%v", "12.34.56.78", "172.16.2.4"))
|
||||
req.Header.Set(xff, fmt.Sprintf("%v,%v", "12.34.56.78", "172.16.2.4"))
|
||||
server.binding.ClientIPHeaderDepth = 1
|
||||
server.binding.ClientIPProxyHeader = xff
|
||||
ip = server.checkRemoteAddress(req)
|
||||
assert.Equal(t, "12.34.56.78", ip)
|
||||
assert.Equal(t, ip, req.RemoteAddr)
|
||||
|
||||
req.RemoteAddr = remoteAddr2
|
||||
req.Header.Set(xff, fmt.Sprintf("%v,%v", "12.34.56.79", "172.16.2.5"))
|
||||
server.binding.ClientIPHeaderDepth = 0
|
||||
ip = server.checkRemoteAddress(req)
|
||||
assert.Equal(t, "172.16.2.5", ip)
|
||||
assert.Equal(t, ip, req.RemoteAddr)
|
||||
|
||||
req.RemoteAddr = "10.8.0.2"
|
||||
req.Header.Set("X-Forwarded-For", remoteAddr1)
|
||||
req.Header.Set(xff, remoteAddr1)
|
||||
ip = server.checkRemoteAddress(req)
|
||||
assert.Equal(t, remoteAddr1, ip)
|
||||
assert.Equal(t, ip, req.RemoteAddr)
|
||||
|
||||
req.RemoteAddr = "10.8.0.3"
|
||||
req.Header.Set("X-Forwarded-For", "not an ip")
|
||||
req.Header.Set(xff, "not an ip")
|
||||
ip = server.checkRemoteAddress(req)
|
||||
assert.Equal(t, "10.8.0.3", ip)
|
||||
assert.Equal(t, ip, req.RemoteAddr)
|
||||
|
||||
req.Header.Del("X-Forwarded-For")
|
||||
req.Header.Del(xff)
|
||||
req.RemoteAddr = ""
|
||||
req.Header.Set("X-Real-IP", remoteAddr1)
|
||||
ip = util.GetRealIP(req)
|
||||
req.Header.Set(xRealIP, remoteAddr1)
|
||||
ip = util.GetRealIP(req, "x-real-ip", 0)
|
||||
assert.Equal(t, remoteAddr1, ip)
|
||||
req.RemoteAddr = ""
|
||||
}
|
||||
|
|
|
@ -335,7 +335,7 @@ func (s *webDavServer) checkRemoteAddress(r *http.Request) string {
|
|||
if ip != nil {
|
||||
for _, allow := range s.binding.allowHeadersFrom {
|
||||
if allow(ip) {
|
||||
parsedIP := util.GetRealIP(r)
|
||||
parsedIP := util.GetRealIP(r, s.binding.ClientIPProxyHeader, s.binding.ClientIPHeaderDepth)
|
||||
if parsedIP != "" {
|
||||
ipAddr = parsedIP
|
||||
r.RemoteAddr = ipAddr
|
||||
|
|
|
@ -96,9 +96,16 @@ type Binding struct {
|
|||
// Prefix for WebDAV resources, if empty WebDAV resources will be available at the
|
||||
// root ("/") URI. If defined it must be an absolute URI.
|
||||
Prefix string `json:"prefix" mapstructure:"prefix"`
|
||||
// List of IP addresses and IP ranges allowed to set X-Forwarded-For/X-Real-IP headers.
|
||||
ProxyAllowed []string `json:"proxy_allowed" mapstructure:"proxy_allowed"`
|
||||
allowHeadersFrom []func(net.IP) bool
|
||||
// List of IP addresses and IP ranges allowed to set client IP proxy headers
|
||||
ProxyAllowed []string `json:"proxy_allowed" mapstructure:"proxy_allowed"`
|
||||
// Allowed client IP proxy header such as "X-Forwarded-For", "X-Real-IP"
|
||||
ClientIPProxyHeader string `json:"client_ip_proxy_header" mapstructure:"client_ip_proxy_header"`
|
||||
// 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
|
||||
ClientIPHeaderDepth int `json:"client_ip_header_depth" mapstructure:"client_ip_header_depth"`
|
||||
allowHeadersFrom []func(net.IP) bool
|
||||
}
|
||||
|
||||
func (b *Binding) parseAllowedProxy() error {
|
||||
|
|
Loading…
Reference in a new issue