add support to redirect HTTP to HTTPS
Fixes #777 Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
This commit is contained in:
parent
aaf940edab
commit
5cccb872bb
6 changed files with 33 additions and 1 deletions
|
@ -98,6 +98,8 @@ var (
|
||||||
AllowedHosts: nil,
|
AllowedHosts: nil,
|
||||||
AllowedHostsAreRegex: false,
|
AllowedHostsAreRegex: false,
|
||||||
HostsProxyHeaders: nil,
|
HostsProxyHeaders: nil,
|
||||||
|
HTTPSRedirect: false,
|
||||||
|
HTTPSHost: "",
|
||||||
HTTPSProxyHeaders: nil,
|
HTTPSProxyHeaders: nil,
|
||||||
STSSeconds: 0,
|
STSSeconds: 0,
|
||||||
STSIncludeSubdomains: false,
|
STSIncludeSubdomains: false,
|
||||||
|
@ -1142,7 +1144,7 @@ func getHTTPDSecurityProxyHeadersFromEnv(idx int) []httpd.HTTPSProxyHeader {
|
||||||
return httpsProxyHeaders
|
return httpsProxyHeaders
|
||||||
}
|
}
|
||||||
|
|
||||||
func getHTTPDSecurityConfFromEnv(idx int) (httpd.SecurityConf, bool) {
|
func getHTTPDSecurityConfFromEnv(idx int) (httpd.SecurityConf, bool) { //nolint:gocyclo
|
||||||
var result httpd.SecurityConf
|
var result httpd.SecurityConf
|
||||||
isSet := false
|
isSet := false
|
||||||
|
|
||||||
|
@ -1170,6 +1172,18 @@ func getHTTPDSecurityConfFromEnv(idx int) (httpd.SecurityConf, bool) {
|
||||||
isSet = true
|
isSet = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
httpsRedirect, ok := lookupBoolFromEnv(fmt.Sprintf("SFTPGO_HTTPD__BINDINGS__%v__SECURITY__HTTPS_REDIRECT", idx))
|
||||||
|
if ok {
|
||||||
|
result.HTTPSRedirect = httpsRedirect
|
||||||
|
isSet = true
|
||||||
|
}
|
||||||
|
|
||||||
|
httpsHost, ok := os.LookupEnv(fmt.Sprintf("SFTPGO_HTTPD__BINDINGS__%v__SECURITY__HTTPS_HOST", idx))
|
||||||
|
if ok {
|
||||||
|
result.HTTPSHost = httpsHost
|
||||||
|
isSet = true
|
||||||
|
}
|
||||||
|
|
||||||
httpsProxyHeaders := getHTTPDSecurityProxyHeadersFromEnv(idx)
|
httpsProxyHeaders := getHTTPDSecurityProxyHeadersFromEnv(idx)
|
||||||
if len(httpsProxyHeaders) > 0 {
|
if len(httpsProxyHeaders) > 0 {
|
||||||
result.HTTPSProxyHeaders = httpsProxyHeaders
|
result.HTTPSProxyHeaders = httpsProxyHeaders
|
||||||
|
|
|
@ -854,6 +854,8 @@ func TestHTTPDBindingsFromEnv(t *testing.T) {
|
||||||
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__SECURITY__ALLOWED_HOSTS", "*.example.com,*.example.net")
|
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__SECURITY__ALLOWED_HOSTS", "*.example.com,*.example.net")
|
||||||
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__SECURITY__ALLOWED_HOSTS_ARE_REGEX", "1")
|
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__SECURITY__ALLOWED_HOSTS_ARE_REGEX", "1")
|
||||||
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__SECURITY__HOSTS_PROXY_HEADERS", "X-Forwarded-Host")
|
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__SECURITY__HOSTS_PROXY_HEADERS", "X-Forwarded-Host")
|
||||||
|
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__SECURITY__HTTPS_REDIRECT", "1")
|
||||||
|
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__SECURITY__HTTPS_HOST", "www.example.com")
|
||||||
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__SECURITY__HTTPS_PROXY_HEADERS__1__KEY", "X-Forwarded-Proto")
|
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__SECURITY__HTTPS_PROXY_HEADERS__1__KEY", "X-Forwarded-Proto")
|
||||||
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__SECURITY__HTTPS_PROXY_HEADERS__1__VALUE", "https")
|
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__SECURITY__HTTPS_PROXY_HEADERS__1__VALUE", "https")
|
||||||
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__SECURITY__STS_SECONDS", "31536000")
|
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__SECURITY__STS_SECONDS", "31536000")
|
||||||
|
@ -900,6 +902,8 @@ func TestHTTPDBindingsFromEnv(t *testing.T) {
|
||||||
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__SECURITY__ALLOWED_HOSTS")
|
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__SECURITY__ALLOWED_HOSTS")
|
||||||
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__SECURITY__ALLOWED_HOSTS_ARE_REGEX")
|
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__SECURITY__ALLOWED_HOSTS_ARE_REGEX")
|
||||||
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__SECURITY__HOSTS_PROXY_HEADERS")
|
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__SECURITY__HOSTS_PROXY_HEADERS")
|
||||||
|
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__SECURITY__HTTPS_REDIRECT")
|
||||||
|
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__SECURITY__HTTPS_HOST")
|
||||||
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__SECURITY__HTTPS_PROXY_HEADERS__1__KEY")
|
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__SECURITY__HTTPS_PROXY_HEADERS__1__KEY")
|
||||||
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__SECURITY__HTTPS_PROXY_HEADERS__1__VALUE")
|
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__SECURITY__HTTPS_PROXY_HEADERS__1__VALUE")
|
||||||
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__SECURITY__STS_SECONDS")
|
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__SECURITY__STS_SECONDS")
|
||||||
|
@ -975,6 +979,8 @@ func TestHTTPDBindingsFromEnv(t *testing.T) {
|
||||||
require.True(t, bindings[2].Security.AllowedHostsAreRegex)
|
require.True(t, bindings[2].Security.AllowedHostsAreRegex)
|
||||||
require.Len(t, bindings[2].Security.HostsProxyHeaders, 1)
|
require.Len(t, bindings[2].Security.HostsProxyHeaders, 1)
|
||||||
require.Equal(t, "X-Forwarded-Host", bindings[2].Security.HostsProxyHeaders[0])
|
require.Equal(t, "X-Forwarded-Host", bindings[2].Security.HostsProxyHeaders[0])
|
||||||
|
require.True(t, bindings[2].Security.HTTPSRedirect)
|
||||||
|
require.Equal(t, "www.example.com", bindings[2].Security.HTTPSHost)
|
||||||
require.Len(t, bindings[2].Security.HTTPSProxyHeaders, 1)
|
require.Len(t, bindings[2].Security.HTTPSProxyHeaders, 1)
|
||||||
require.Equal(t, "X-Forwarded-Proto", bindings[2].Security.HTTPSProxyHeaders[0].Key)
|
require.Equal(t, "X-Forwarded-Proto", bindings[2].Security.HTTPSProxyHeaders[0].Key)
|
||||||
require.Equal(t, "https", bindings[2].Security.HTTPSProxyHeaders[0].Value)
|
require.Equal(t, "https", bindings[2].Security.HTTPSProxyHeaders[0].Value)
|
||||||
|
|
|
@ -256,6 +256,8 @@ The configuration file contains the following sections:
|
||||||
- `allowed_hosts`, list of strings. Fully qualified domain names that are allowed. An empty list allows any and all host names. Default: empty.
|
- `allowed_hosts`, list of strings. Fully qualified domain names that are allowed. An empty list allows any and all host names. Default: empty.
|
||||||
- `allowed_hosts_are_regex`, boolean. Determines if the provided allowed hosts contains valid regular expressions. Default: `false`.
|
- `allowed_hosts_are_regex`, boolean. Determines if the provided allowed hosts contains valid regular expressions. Default: `false`.
|
||||||
- `hosts_proxy_headers`, list of string. Defines a set of header keys that may hold a proxied hostname value for the request, for example `X-Forwarded-Host`. Default: empty.
|
- `hosts_proxy_headers`, list of string. Defines a set of header keys that may hold a proxied hostname value for the request, for example `X-Forwarded-Host`. Default: empty.
|
||||||
|
- `https_redirect`, boolean. Set to `true` to redirect HTTP requests to HTTPS. Default: `false`.
|
||||||
|
- `https_host`, string. Defines the host name that is used to redirect HTTP requests to HTTPS. Default is blank, which indicates to use the same host. For example, if `https_redirect` is enabled and `https_host` is blank, a request for `http://127.0.0.1/web/client/login` will be redirected to `https://127.0.0.1/web/client/login`, if `https_host` is set to `www.example.com` the same request will be redirected to `https://www.example.com/web/client/login`.
|
||||||
- `https_proxy_headers`, list of struct, each struct contains the fields `key` and `value`. Defines a a list of header keys with associated values that would indicate a valid https request. For example `key` could be `X-Forwarded-Proto` and `value` `https`. Default: empty.
|
- `https_proxy_headers`, list of struct, each struct contains the fields `key` and `value`. Defines a a list of header keys with associated values that would indicate a valid https request. For example `key` could be `X-Forwarded-Proto` and `value` `https`. Default: empty.
|
||||||
- `sts_seconds`, integer. Defines the max-age of the `Strict-Transport-Security` header. This header will be included for `https` responses or for HTTP request if the request includes a defined HTTPS proxy header. Default: `0`, which would NOT include the header.
|
- `sts_seconds`, integer. Defines the max-age of the `Strict-Transport-Security` header. This header will be included for `https` responses or for HTTP request if the request includes a defined HTTPS proxy header. Default: `0`, which would NOT include the header.
|
||||||
- `sts_include_subdomains`, boolean. Set to `true`, the `includeSubdomains` will be appended to the `Strict-Transport-Security` header. Default: `false`.
|
- `sts_include_subdomains`, boolean. Set to `true`, the `includeSubdomains` will be appended to the `Strict-Transport-Security` header. Default: `false`.
|
||||||
|
|
|
@ -255,6 +255,11 @@ type SecurityConf struct {
|
||||||
AllowedHostsAreRegex bool `json:"allowed_hosts_are_regex" mapstructure:"allowed_hosts_are_regex"`
|
AllowedHostsAreRegex bool `json:"allowed_hosts_are_regex" mapstructure:"allowed_hosts_are_regex"`
|
||||||
// HostsProxyHeaders is a set of header keys that may hold a proxied hostname value for the request.
|
// HostsProxyHeaders is a set of header keys that may hold a proxied hostname value for the request.
|
||||||
HostsProxyHeaders []string `json:"hosts_proxy_headers" mapstructure:"hosts_proxy_headers"`
|
HostsProxyHeaders []string `json:"hosts_proxy_headers" mapstructure:"hosts_proxy_headers"`
|
||||||
|
// Set to true to redirect HTTP requests to HTTPS
|
||||||
|
HTTPSRedirect bool `json:"https_redirect" mapstructure:"https_redirect"`
|
||||||
|
// HTTPSHost defines the host name that is used to redirect HTTP requests to HTTPS.
|
||||||
|
// Default is "", which indicates to use the same host.
|
||||||
|
HTTPSHost string `json:"https_host" mapstructure:"https_host"`
|
||||||
// HTTPSProxyHeaders is a list of header keys with associated values that would indicate a valid https request.
|
// HTTPSProxyHeaders is a list of header keys with associated values that would indicate a valid https request.
|
||||||
HTTPSProxyHeaders []HTTPSProxyHeader `json:"https_proxy_headers" mapstructure:"https_proxy_headers"`
|
HTTPSProxyHeaders []HTTPSProxyHeader `json:"https_proxy_headers" mapstructure:"https_proxy_headers"`
|
||||||
// STSSeconds is the max-age of the Strict-Transport-Security header.
|
// STSSeconds is the max-age of the Strict-Transport-Security header.
|
||||||
|
|
|
@ -1092,6 +1092,9 @@ func (s *httpdServer) initializeRouter() {
|
||||||
AllowedHosts: s.binding.Security.AllowedHosts,
|
AllowedHosts: s.binding.Security.AllowedHosts,
|
||||||
AllowedHostsAreRegex: s.binding.Security.AllowedHostsAreRegex,
|
AllowedHostsAreRegex: s.binding.Security.AllowedHostsAreRegex,
|
||||||
HostsProxyHeaders: s.binding.Security.HostsProxyHeaders,
|
HostsProxyHeaders: s.binding.Security.HostsProxyHeaders,
|
||||||
|
SSLRedirect: s.binding.Security.HTTPSRedirect,
|
||||||
|
SSLHost: s.binding.Security.HTTPSHost,
|
||||||
|
SSLTemporaryRedirect: true,
|
||||||
SSLProxyHeaders: s.binding.Security.getHTTPSProxyHeaders(),
|
SSLProxyHeaders: s.binding.Security.getHTTPSProxyHeaders(),
|
||||||
STSSeconds: s.binding.Security.STSSeconds,
|
STSSeconds: s.binding.Security.STSSeconds,
|
||||||
STSIncludeSubdomains: s.binding.Security.STSIncludeSubdomains,
|
STSIncludeSubdomains: s.binding.Security.STSIncludeSubdomains,
|
||||||
|
|
|
@ -241,6 +241,8 @@
|
||||||
"allowed_hosts": [],
|
"allowed_hosts": [],
|
||||||
"allowed_hosts_are_regex": false,
|
"allowed_hosts_are_regex": false,
|
||||||
"hosts_proxy_headers": [],
|
"hosts_proxy_headers": [],
|
||||||
|
"https_redirect": false,
|
||||||
|
"https_host": "",
|
||||||
"https_proxy_headers": [],
|
"https_proxy_headers": [],
|
||||||
"sts_seconds": 0,
|
"sts_seconds": 0,
|
||||||
"sts_include_subdomains": false,
|
"sts_include_subdomains": false,
|
||||||
|
|
Loading…
Reference in a new issue