mirror of
https://github.com/drakkan/sftpgo.git
synced 2024-11-25 09:00:27 +00:00
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,
|
Debug: false,
|
||||||
}
|
}
|
||||||
defaultWebDAVDBinding = webdavd.Binding{
|
defaultWebDAVDBinding = webdavd.Binding{
|
||||||
Address: "",
|
Address: "",
|
||||||
Port: 0,
|
Port: 0,
|
||||||
EnableHTTPS: false,
|
EnableHTTPS: false,
|
||||||
CertificateFile: "",
|
CertificateFile: "",
|
||||||
CertificateKeyFile: "",
|
CertificateKeyFile: "",
|
||||||
MinTLSVersion: 12,
|
MinTLSVersion: 12,
|
||||||
ClientAuthType: 0,
|
ClientAuthType: 0,
|
||||||
TLSCipherSuites: nil,
|
TLSCipherSuites: nil,
|
||||||
Prefix: "",
|
Prefix: "",
|
||||||
ProxyAllowed: nil,
|
ProxyAllowed: nil,
|
||||||
|
ClientIPProxyHeader: "",
|
||||||
|
ClientIPHeaderDepth: 0,
|
||||||
}
|
}
|
||||||
defaultHTTPDBinding = httpd.Binding{
|
defaultHTTPDBinding = httpd.Binding{
|
||||||
Address: "",
|
Address: "",
|
||||||
|
@ -90,6 +92,8 @@ var (
|
||||||
ClientAuthType: 0,
|
ClientAuthType: 0,
|
||||||
TLSCipherSuites: nil,
|
TLSCipherSuites: nil,
|
||||||
ProxyAllowed: nil,
|
ProxyAllowed: nil,
|
||||||
|
ClientIPProxyHeader: "",
|
||||||
|
ClientIPHeaderDepth: 0,
|
||||||
HideLoginURL: 0,
|
HideLoginURL: 0,
|
||||||
RenderOpenAPI: true,
|
RenderOpenAPI: true,
|
||||||
WebClientIntegrations: nil,
|
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) {
|
func getWebDAVDBindingFromEnv(idx int) {
|
||||||
binding := webdavd.Binding{
|
binding := webdavd.Binding{
|
||||||
MinTLSVersion: 12,
|
MinTLSVersion: 12,
|
||||||
|
@ -1184,9 +1212,7 @@ func getWebDAVDBindingFromEnv(idx int) {
|
||||||
isSet = true
|
isSet = true
|
||||||
}
|
}
|
||||||
|
|
||||||
proxyAllowed, ok := lookupStringListFromEnv(fmt.Sprintf("SFTPGO_WEBDAVD__BINDINGS__%v__PROXY_ALLOWED", idx))
|
if getWebDAVDBindingProxyConfigsFromEnv(idx, &binding) {
|
||||||
if ok {
|
|
||||||
binding.ProxyAllowed = proxyAllowed
|
|
||||||
isSet = true
|
isSet = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1519,6 +1545,30 @@ func getHTTPDNestedObjectsFromEnv(idx int, binding *httpd.Binding) bool {
|
||||||
return isSet
|
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) {
|
func getHTTPDBindingFromEnv(idx int) {
|
||||||
binding := getDefaultHTTPBinding(idx)
|
binding := getDefaultHTTPBinding(idx)
|
||||||
isSet := false
|
isSet := false
|
||||||
|
@ -1589,9 +1639,7 @@ func getHTTPDBindingFromEnv(idx int) {
|
||||||
isSet = true
|
isSet = true
|
||||||
}
|
}
|
||||||
|
|
||||||
proxyAllowed, ok := lookupStringListFromEnv(fmt.Sprintf("SFTPGO_HTTPD__BINDINGS__%v__PROXY_ALLOWED", idx))
|
if getHTTPDBindingProxyConfigsFromEnv(idx, &binding) {
|
||||||
if ok {
|
|
||||||
binding.ProxyAllowed = proxyAllowed
|
|
||||||
isSet = true
|
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__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__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__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__ADDRESS", "127.0.1.1")
|
||||||
os.Setenv("SFTPGO_WEBDAVD__BINDINGS__2__PORT", "9000")
|
os.Setenv("SFTPGO_WEBDAVD__BINDINGS__2__PORT", "9000")
|
||||||
os.Setenv("SFTPGO_WEBDAVD__BINDINGS__2__ENABLE_HTTPS", "1")
|
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__ENABLE_HTTPS")
|
||||||
os.Unsetenv("SFTPGO_WEBDAVD__BINDINGS__1__TLS_CIPHER_SUITES")
|
os.Unsetenv("SFTPGO_WEBDAVD__BINDINGS__1__TLS_CIPHER_SUITES")
|
||||||
os.Unsetenv("SFTPGO_WEBDAVD__BINDINGS__1__PROXY_ALLOWED")
|
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__ADDRESS")
|
||||||
os.Unsetenv("SFTPGO_WEBDAVD__BINDINGS__2__PORT")
|
os.Unsetenv("SFTPGO_WEBDAVD__BINDINGS__2__PORT")
|
||||||
os.Unsetenv("SFTPGO_WEBDAVD__BINDINGS__2__ENABLE_HTTPS")
|
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.Equal(t, 12, bindings[0].MinTLSVersion)
|
||||||
require.Len(t, bindings[0].TLSCipherSuites, 0)
|
require.Len(t, bindings[0].TLSCipherSuites, 0)
|
||||||
require.Empty(t, bindings[0].Prefix)
|
require.Empty(t, bindings[0].Prefix)
|
||||||
|
require.Equal(t, 0, bindings[0].ClientIPHeaderDepth)
|
||||||
require.Equal(t, 8000, bindings[1].Port)
|
require.Equal(t, 8000, bindings[1].Port)
|
||||||
require.Equal(t, "127.0.0.1", bindings[1].Address)
|
require.Equal(t, "127.0.0.1", bindings[1].Address)
|
||||||
require.False(t, bindings[1].EnableHTTPS)
|
require.False(t, bindings[1].EnableHTTPS)
|
||||||
|
@ -881,6 +886,8 @@ func TestWebDAVBindingsFromEnv(t *testing.T) {
|
||||||
require.Len(t, bindings[1].TLSCipherSuites, 1)
|
require.Len(t, bindings[1].TLSCipherSuites, 1)
|
||||||
require.Equal(t, "TLS_RSA_WITH_AES_128_CBC_SHA", bindings[1].TLSCipherSuites[0])
|
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, "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.Empty(t, bindings[1].Prefix)
|
||||||
require.Equal(t, 9000, bindings[2].Port)
|
require.Equal(t, 9000, bindings[2].Port)
|
||||||
require.Equal(t, "127.0.1.1", bindings[2].Address)
|
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, "/dav2", bindings[2].Prefix)
|
||||||
require.Equal(t, "webdav.crt", bindings[2].CertificateFile)
|
require.Equal(t, "webdav.crt", bindings[2].CertificateFile)
|
||||||
require.Equal(t, "webdav.key", bindings[2].CertificateKeyFile)
|
require.Equal(t, "webdav.key", bindings[2].CertificateKeyFile)
|
||||||
|
require.Equal(t, 0, bindings[2].ClientIPHeaderDepth)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHTTPDBindingsFromEnv(t *testing.T) {
|
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__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__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__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__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__URL", "http://127.0.0.1/")
|
||||||
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__WEB_CLIENT_INTEGRATIONS__1__FILE_EXTENSIONS", ".pdf, .txt")
|
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__CLIENT_AUTH_TYPE")
|
||||||
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__TLS_CIPHER_SUITES")
|
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__TLS_CIPHER_SUITES")
|
||||||
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__PROXY_ALLOWED")
|
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__HIDE_LOGIN_URL")
|
||||||
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__WEB_CLIENT_INTEGRATIONS__1__URL")
|
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__WEB_CLIENT_INTEGRATIONS__1__URL")
|
||||||
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__WEB_CLIENT_INTEGRATIONS__1__FILE_EXTENSIONS")
|
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, "TLS_AES_128_GCM_SHA256", bindings[0].TLSCipherSuites[0])
|
||||||
require.Equal(t, 0, bindings[0].HideLoginURL)
|
require.Equal(t, 0, bindings[0].HideLoginURL)
|
||||||
require.False(t, bindings[0].Security.Enabled)
|
require.False(t, bindings[0].Security.Enabled)
|
||||||
|
require.Equal(t, 0, bindings[0].ClientIPHeaderDepth)
|
||||||
require.Equal(t, 8000, bindings[1].Port)
|
require.Equal(t, 8000, bindings[1].Port)
|
||||||
require.Equal(t, "127.0.0.1", bindings[1].Address)
|
require.Equal(t, "127.0.0.1", bindings[1].Address)
|
||||||
require.False(t, bindings[1].EnableHTTPS)
|
require.False(t, bindings[1].EnableHTTPS)
|
||||||
|
@ -1051,6 +1064,7 @@ func TestHTTPDBindingsFromEnv(t *testing.T) {
|
||||||
require.False(t, bindings[1].Security.Enabled)
|
require.False(t, bindings[1].Security.Enabled)
|
||||||
require.Equal(t, "Web Admin", bindings[1].Branding.WebAdmin.Name)
|
require.Equal(t, "Web Admin", bindings[1].Branding.WebAdmin.Name)
|
||||||
require.Equal(t, "WebClient", bindings[1].Branding.WebClient.ShortName)
|
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, 9000, bindings[2].Port)
|
||||||
require.Equal(t, "127.0.1.1", bindings[2].Address)
|
require.Equal(t, "127.0.1.1", bindings[2].Address)
|
||||||
require.True(t, bindings[2].EnableHTTPS)
|
require.True(t, bindings[2].EnableHTTPS)
|
||||||
|
@ -1065,6 +1079,8 @@ func TestHTTPDBindingsFromEnv(t *testing.T) {
|
||||||
require.Len(t, bindings[2].ProxyAllowed, 2)
|
require.Len(t, bindings[2].ProxyAllowed, 2)
|
||||||
require.Equal(t, "192.168.9.1", bindings[2].ProxyAllowed[0])
|
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, "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.Equal(t, 3, bindings[2].HideLoginURL)
|
||||||
require.Len(t, bindings[2].WebClientIntegrations, 1)
|
require.Len(t, bindings[2].WebClientIntegrations, 1)
|
||||||
require.Equal(t, "http://127.0.0.1/", bindings[2].WebClientIntegrations[0].URL)
|
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.
|
- `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.
|
- `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: "".
|
- `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_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.
|
- `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.
|
- `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.
|
- `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`.
|
- `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.
|
- `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.
|
- `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 `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.
|
- `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.
|
- `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`.
|
- `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:
|
- `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.
|
// any invalid name will be silently ignored.
|
||||||
// The order matters, the ciphers listed first will be the preferred ones.
|
// The order matters, the ciphers listed first will be the preferred ones.
|
||||||
TLSCipherSuites []string `json:"tls_cipher_suites" mapstructure:"tls_cipher_suites"`
|
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,
|
// List of IP addresses and IP ranges allowed to set client IP proxy headers and
|
||||||
// X-Forwarded-Proto headers.
|
// X-Forwarded-Proto header.
|
||||||
ProxyAllowed []string `json:"proxy_allowed" mapstructure:"proxy_allowed"`
|
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
|
// 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:
|
// 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
|
// - 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"
|
testIP := "10.29.1.9"
|
||||||
validForwardedFor := "172.19.2.6"
|
validForwardedFor := "172.19.2.6"
|
||||||
b := Binding{
|
b := Binding{
|
||||||
Address: "",
|
Address: "",
|
||||||
Port: 8080,
|
Port: 8080,
|
||||||
EnableWebAdmin: true,
|
EnableWebAdmin: true,
|
||||||
EnableWebClient: false,
|
EnableWebClient: false,
|
||||||
ProxyAllowed: []string{testIP, "10.8.0.0/30"},
|
ProxyAllowed: []string{testIP, "10.8.0.0/30"},
|
||||||
|
ClientIPProxyHeader: "x-forwarded-for",
|
||||||
}
|
}
|
||||||
err = b.parseAllowedProxy()
|
err = b.parseAllowedProxy()
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
|
@ -977,7 +977,7 @@ func (s *httpdServer) checkConnection(next http.Handler) http.Handler {
|
||||||
if ip != nil {
|
if ip != nil {
|
||||||
for _, allow := range s.binding.allowHeadersFrom {
|
for _, allow := range s.binding.allowHeadersFrom {
|
||||||
if allow(ip) {
|
if allow(ip) {
|
||||||
parsedIP := util.GetRealIP(r)
|
parsedIP := util.GetRealIP(r, s.binding.ClientIPProxyHeader, s.binding.ClientIPHeaderDepth)
|
||||||
if parsedIP != "" {
|
if parsedIP != "" {
|
||||||
ipAddr = parsedIP
|
ipAddr = parsedIP
|
||||||
r.RemoteAddr = ipAddr
|
r.RemoteAddr = ipAddr
|
||||||
|
|
|
@ -149,7 +149,9 @@
|
||||||
"client_auth_type": 0,
|
"client_auth_type": 0,
|
||||||
"tls_cipher_suites": [],
|
"tls_cipher_suites": [],
|
||||||
"prefix": "",
|
"prefix": "",
|
||||||
"proxy_allowed": []
|
"proxy_allowed": [],
|
||||||
|
"client_ip_proxy_header": "",
|
||||||
|
"client_ip_header_depth": 0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"certificate_file": "",
|
"certificate_file": "",
|
||||||
|
@ -251,6 +253,8 @@
|
||||||
"client_auth_type": 0,
|
"client_auth_type": 0,
|
||||||
"tls_cipher_suites": [],
|
"tls_cipher_suites": [],
|
||||||
"proxy_allowed": [],
|
"proxy_allowed": [],
|
||||||
|
"client_ip_proxy_header": "",
|
||||||
|
"client_ip_header_depth": 0,
|
||||||
"hide_login_url": 0,
|
"hide_login_url": 0,
|
||||||
"render_openapi": true,
|
"render_openapi": true,
|
||||||
"web_client_integrations": [],
|
"web_client_integrations": [],
|
||||||
|
|
44
util/util.go
44
util/util.go
|
@ -41,11 +41,7 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
xForwardedFor = http.CanonicalHeaderKey("X-Forwarded-For")
|
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}])))\\.?$")
|
||||||
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}])))\\.?$")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Contains reports whether v is present in elems.
|
// 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
|
// GetRealIP returns the ip address as result of parsing either the
|
||||||
// X-Real-IP header or the X-Forwarded-For header
|
// X-Real-IP header or the X-Forwarded-For header
|
||||||
func GetRealIP(r *http.Request) string {
|
func GetRealIP(r *http.Request, header string, depth int) string {
|
||||||
var ip string
|
if header == "" {
|
||||||
|
|
||||||
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 {
|
|
||||||
return ""
|
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
|
// GetHTTPLocalAddress returns the local address for an http.Request
|
||||||
|
|
|
@ -435,19 +435,26 @@ func TestRemoteAddress(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Empty(t, req.RemoteAddr)
|
assert.Empty(t, req.RemoteAddr)
|
||||||
|
|
||||||
req.Header.Set("True-Client-IP", remoteAddr1)
|
trueClientIP := "True-Client-IP"
|
||||||
ip := util.GetRealIP(req)
|
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)
|
assert.Equal(t, remoteAddr1, ip)
|
||||||
req.Header.Del("True-Client-IP")
|
ip = util.GetRealIP(req, trueClientIP, 2)
|
||||||
req.Header.Set("CF-Connecting-IP", remoteAddr1)
|
assert.Empty(t, ip)
|
||||||
ip = util.GetRealIP(req)
|
req.Header.Del(trueClientIP)
|
||||||
|
req.Header.Set(cfConnectingIP, remoteAddr1)
|
||||||
|
ip = util.GetRealIP(req, cfConnectingIP, 0)
|
||||||
assert.Equal(t, remoteAddr1, ip)
|
assert.Equal(t, remoteAddr1, ip)
|
||||||
req.Header.Del("CF-Connecting-IP")
|
req.Header.Del(cfConnectingIP)
|
||||||
req.Header.Set("X-Forwarded-For", remoteAddr1)
|
req.Header.Set(xff, remoteAddr1)
|
||||||
ip = util.GetRealIP(req)
|
ip = util.GetRealIP(req, xff, 0)
|
||||||
assert.Equal(t, remoteAddr1, ip)
|
assert.Equal(t, remoteAddr1, ip)
|
||||||
// this will be ignored, remoteAddr1 is not allowed to se this header
|
// 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
|
req.RemoteAddr = remoteAddr1
|
||||||
ip = server.checkRemoteAddress(req)
|
ip = server.checkRemoteAddress(req)
|
||||||
assert.Equal(t, remoteAddr1, ip)
|
assert.Equal(t, remoteAddr1, ip)
|
||||||
|
@ -455,32 +462,41 @@ func TestRemoteAddress(t *testing.T) {
|
||||||
ip = server.checkRemoteAddress(req)
|
ip = server.checkRemoteAddress(req)
|
||||||
assert.Empty(t, ip)
|
assert.Empty(t, ip)
|
||||||
|
|
||||||
req.Header.Set("X-Forwarded-For", fmt.Sprintf("%v, %v", remoteAddr2, remoteAddr1))
|
req.Header.Set(xff, fmt.Sprintf("%v , %v", remoteAddr2, remoteAddr1))
|
||||||
ip = util.GetRealIP(req)
|
ip = util.GetRealIP(req, xff, 1)
|
||||||
assert.Equal(t, remoteAddr2, ip)
|
assert.Equal(t, remoteAddr2, ip)
|
||||||
|
|
||||||
req.RemoteAddr = remoteAddr2
|
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)
|
ip = server.checkRemoteAddress(req)
|
||||||
assert.Equal(t, "12.34.56.78", ip)
|
assert.Equal(t, "12.34.56.78", ip)
|
||||||
assert.Equal(t, ip, req.RemoteAddr)
|
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.RemoteAddr = "10.8.0.2"
|
||||||
req.Header.Set("X-Forwarded-For", remoteAddr1)
|
req.Header.Set(xff, remoteAddr1)
|
||||||
ip = server.checkRemoteAddress(req)
|
ip = server.checkRemoteAddress(req)
|
||||||
assert.Equal(t, remoteAddr1, ip)
|
assert.Equal(t, remoteAddr1, ip)
|
||||||
assert.Equal(t, ip, req.RemoteAddr)
|
assert.Equal(t, ip, req.RemoteAddr)
|
||||||
|
|
||||||
req.RemoteAddr = "10.8.0.3"
|
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)
|
ip = server.checkRemoteAddress(req)
|
||||||
assert.Equal(t, "10.8.0.3", ip)
|
assert.Equal(t, "10.8.0.3", ip)
|
||||||
assert.Equal(t, ip, req.RemoteAddr)
|
assert.Equal(t, ip, req.RemoteAddr)
|
||||||
|
|
||||||
req.Header.Del("X-Forwarded-For")
|
req.Header.Del(xff)
|
||||||
req.RemoteAddr = ""
|
req.RemoteAddr = ""
|
||||||
req.Header.Set("X-Real-IP", remoteAddr1)
|
req.Header.Set(xRealIP, remoteAddr1)
|
||||||
ip = util.GetRealIP(req)
|
ip = util.GetRealIP(req, "x-real-ip", 0)
|
||||||
assert.Equal(t, remoteAddr1, ip)
|
assert.Equal(t, remoteAddr1, ip)
|
||||||
req.RemoteAddr = ""
|
req.RemoteAddr = ""
|
||||||
}
|
}
|
||||||
|
|
|
@ -335,7 +335,7 @@ func (s *webDavServer) checkRemoteAddress(r *http.Request) string {
|
||||||
if ip != nil {
|
if ip != nil {
|
||||||
for _, allow := range s.binding.allowHeadersFrom {
|
for _, allow := range s.binding.allowHeadersFrom {
|
||||||
if allow(ip) {
|
if allow(ip) {
|
||||||
parsedIP := util.GetRealIP(r)
|
parsedIP := util.GetRealIP(r, s.binding.ClientIPProxyHeader, s.binding.ClientIPHeaderDepth)
|
||||||
if parsedIP != "" {
|
if parsedIP != "" {
|
||||||
ipAddr = parsedIP
|
ipAddr = parsedIP
|
||||||
r.RemoteAddr = ipAddr
|
r.RemoteAddr = ipAddr
|
||||||
|
|
|
@ -96,9 +96,16 @@ type Binding struct {
|
||||||
// Prefix for WebDAV resources, if empty WebDAV resources will be available at the
|
// Prefix for WebDAV resources, if empty WebDAV resources will be available at the
|
||||||
// root ("/") URI. If defined it must be an absolute URI.
|
// root ("/") URI. If defined it must be an absolute URI.
|
||||||
Prefix string `json:"prefix" mapstructure:"prefix"`
|
Prefix string `json:"prefix" mapstructure:"prefix"`
|
||||||
// List of IP addresses and IP ranges allowed to set X-Forwarded-For/X-Real-IP headers.
|
// List of IP addresses and IP ranges allowed to set client IP proxy headers
|
||||||
ProxyAllowed []string `json:"proxy_allowed" mapstructure:"proxy_allowed"`
|
ProxyAllowed []string `json:"proxy_allowed" mapstructure:"proxy_allowed"`
|
||||||
allowHeadersFrom []func(net.IP) bool
|
// 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 {
|
func (b *Binding) parseAllowedProxy() error {
|
||||||
|
|
Loading…
Reference in a new issue