mirror of
https://github.com/drakkan/sftpgo.git
synced 2024-11-22 07:30:25 +00:00
httpd: add mTLS and multiple bindings support
This commit is contained in:
parent
899f1a1844
commit
57976b4085
13 changed files with 656 additions and 56 deletions
|
@ -58,6 +58,13 @@ var (
|
||||||
EnableHTTPS: false,
|
EnableHTTPS: false,
|
||||||
ClientAuthType: 0,
|
ClientAuthType: 0,
|
||||||
}
|
}
|
||||||
|
defaultHTTPDBinding = httpd.Binding{
|
||||||
|
Address: "127.0.0.1",
|
||||||
|
Port: 8080,
|
||||||
|
EnableWebAdmin: true,
|
||||||
|
EnableHTTPS: false,
|
||||||
|
ClientAuthType: 0,
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
type globalConfig struct {
|
type globalConfig struct {
|
||||||
|
@ -202,8 +209,7 @@ func Init() {
|
||||||
PreferDatabaseCredentials: false,
|
PreferDatabaseCredentials: false,
|
||||||
},
|
},
|
||||||
HTTPDConfig: httpd.Conf{
|
HTTPDConfig: httpd.Conf{
|
||||||
BindPort: 8080,
|
Bindings: []httpd.Binding{defaultHTTPDBinding},
|
||||||
BindAddress: "127.0.0.1",
|
|
||||||
TemplatesPath: "templates",
|
TemplatesPath: "templates",
|
||||||
StaticFilesPath: "static",
|
StaticFilesPath: "static",
|
||||||
BackupsPath: "backups",
|
BackupsPath: "backups",
|
||||||
|
@ -536,16 +542,38 @@ func checkWebDAVDBindingCompatibility() {
|
||||||
globalConf.WebDAVD.Bindings = append(globalConf.WebDAVD.Bindings, binding)
|
globalConf.WebDAVD.Bindings = append(globalConf.WebDAVD.Bindings, binding)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func checkHTTPDBindingCompatibility() {
|
||||||
|
if len(globalConf.HTTPDConfig.Bindings) > 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
binding := httpd.Binding{
|
||||||
|
EnableWebAdmin: globalConf.HTTPDConfig.StaticFilesPath != "" && globalConf.HTTPDConfig.TemplatesPath != "",
|
||||||
|
EnableHTTPS: globalConf.HTTPDConfig.CertificateFile != "" && globalConf.HTTPDConfig.CertificateKeyFile != "",
|
||||||
|
}
|
||||||
|
|
||||||
|
if globalConf.HTTPDConfig.BindPort > 0 { //nolint:staticcheck
|
||||||
|
binding.Port = globalConf.HTTPDConfig.BindPort //nolint:staticcheck
|
||||||
|
}
|
||||||
|
if globalConf.HTTPDConfig.BindAddress != "" { //nolint:staticcheck
|
||||||
|
binding.Address = globalConf.HTTPDConfig.BindAddress //nolint:staticcheck
|
||||||
|
}
|
||||||
|
|
||||||
|
globalConf.HTTPDConfig.Bindings = append(globalConf.HTTPDConfig.Bindings, binding)
|
||||||
|
}
|
||||||
|
|
||||||
func loadBindingsFromEnv() {
|
func loadBindingsFromEnv() {
|
||||||
checkSFTPDBindingsCompatibility()
|
checkSFTPDBindingsCompatibility()
|
||||||
checkFTPDBindingCompatibility()
|
checkFTPDBindingCompatibility()
|
||||||
checkWebDAVDBindingCompatibility()
|
checkWebDAVDBindingCompatibility()
|
||||||
|
checkHTTPDBindingCompatibility()
|
||||||
|
|
||||||
maxBindings := make([]int, 10)
|
maxBindings := make([]int, 10)
|
||||||
for idx := range maxBindings {
|
for idx := range maxBindings {
|
||||||
getSFTPDBindindFromEnv(idx)
|
getSFTPDBindindFromEnv(idx)
|
||||||
getFTPDBindingFromEnv(idx)
|
getFTPDBindingFromEnv(idx)
|
||||||
getWebDAVDBindingFromEnv(idx)
|
getWebDAVDBindingFromEnv(idx)
|
||||||
|
getHTTPDBindingFromEnv(idx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -678,6 +706,53 @@ func getWebDAVDBindingFromEnv(idx int) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getHTTPDBindingFromEnv(idx int) {
|
||||||
|
binding := httpd.Binding{}
|
||||||
|
if len(globalConf.HTTPDConfig.Bindings) > idx {
|
||||||
|
binding = globalConf.HTTPDConfig.Bindings[idx]
|
||||||
|
}
|
||||||
|
|
||||||
|
isSet := false
|
||||||
|
|
||||||
|
port, ok := lookupIntFromEnv(fmt.Sprintf("SFTPGO_HTTPD__BINDINGS__%v__PORT", idx))
|
||||||
|
if ok {
|
||||||
|
binding.Port = port
|
||||||
|
isSet = true
|
||||||
|
}
|
||||||
|
|
||||||
|
address, ok := os.LookupEnv(fmt.Sprintf("SFTPGO_HTTPD__BINDINGS__%v__ADDRESS", idx))
|
||||||
|
if ok {
|
||||||
|
binding.Address = address
|
||||||
|
isSet = true
|
||||||
|
}
|
||||||
|
|
||||||
|
enableWebAdmin, ok := lookupBoolFromEnv(fmt.Sprintf("SFTPGO_HTTPD__BINDINGS__%v__ENABLE_WEB_ADMIN", idx))
|
||||||
|
if ok {
|
||||||
|
binding.EnableWebAdmin = enableWebAdmin
|
||||||
|
isSet = true
|
||||||
|
}
|
||||||
|
|
||||||
|
enableHTTPS, ok := lookupBoolFromEnv(fmt.Sprintf("SFTPGO_HTTPD__BINDINGS__%v__ENABLE_HTTPS", idx))
|
||||||
|
if ok {
|
||||||
|
binding.EnableHTTPS = enableHTTPS
|
||||||
|
isSet = true
|
||||||
|
}
|
||||||
|
|
||||||
|
clientAuthType, ok := lookupIntFromEnv(fmt.Sprintf("SFTPGO_HTTPD__BINDINGS__%v__CLIENT_AUTH_TYPE", idx))
|
||||||
|
if ok {
|
||||||
|
binding.ClientAuthType = clientAuthType
|
||||||
|
isSet = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if isSet {
|
||||||
|
if len(globalConf.HTTPDConfig.Bindings) > idx {
|
||||||
|
globalConf.HTTPDConfig.Bindings[idx] = binding
|
||||||
|
} else {
|
||||||
|
globalConf.HTTPDConfig.Bindings = append(globalConf.HTTPDConfig.Bindings, binding)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func setViperDefaults() {
|
func setViperDefaults() {
|
||||||
viper.SetDefault("common.idle_timeout", globalConf.Common.IdleTimeout)
|
viper.SetDefault("common.idle_timeout", globalConf.Common.IdleTimeout)
|
||||||
viper.SetDefault("common.upload_mode", globalConf.Common.UploadMode)
|
viper.SetDefault("common.upload_mode", globalConf.Common.UploadMode)
|
||||||
|
@ -765,13 +840,13 @@ func setViperDefaults() {
|
||||||
viper.SetDefault("data_provider.password_hashing.argon2_options.iterations", globalConf.ProviderConf.PasswordHashing.Argon2Options.Iterations)
|
viper.SetDefault("data_provider.password_hashing.argon2_options.iterations", globalConf.ProviderConf.PasswordHashing.Argon2Options.Iterations)
|
||||||
viper.SetDefault("data_provider.password_hashing.argon2_options.parallelism", globalConf.ProviderConf.PasswordHashing.Argon2Options.Parallelism)
|
viper.SetDefault("data_provider.password_hashing.argon2_options.parallelism", globalConf.ProviderConf.PasswordHashing.Argon2Options.Parallelism)
|
||||||
viper.SetDefault("data_provider.update_mode", globalConf.ProviderConf.UpdateMode)
|
viper.SetDefault("data_provider.update_mode", globalConf.ProviderConf.UpdateMode)
|
||||||
viper.SetDefault("httpd.bind_port", globalConf.HTTPDConfig.BindPort)
|
|
||||||
viper.SetDefault("httpd.bind_address", globalConf.HTTPDConfig.BindAddress)
|
|
||||||
viper.SetDefault("httpd.templates_path", globalConf.HTTPDConfig.TemplatesPath)
|
viper.SetDefault("httpd.templates_path", globalConf.HTTPDConfig.TemplatesPath)
|
||||||
viper.SetDefault("httpd.static_files_path", globalConf.HTTPDConfig.StaticFilesPath)
|
viper.SetDefault("httpd.static_files_path", globalConf.HTTPDConfig.StaticFilesPath)
|
||||||
viper.SetDefault("httpd.backups_path", globalConf.HTTPDConfig.BackupsPath)
|
viper.SetDefault("httpd.backups_path", globalConf.HTTPDConfig.BackupsPath)
|
||||||
viper.SetDefault("httpd.certificate_file", globalConf.HTTPDConfig.CertificateFile)
|
viper.SetDefault("httpd.certificate_file", globalConf.HTTPDConfig.CertificateFile)
|
||||||
viper.SetDefault("httpd.certificate_key_file", globalConf.HTTPDConfig.CertificateKeyFile)
|
viper.SetDefault("httpd.certificate_key_file", globalConf.HTTPDConfig.CertificateKeyFile)
|
||||||
|
viper.SetDefault("httpd.ca_certificates", globalConf.HTTPDConfig.CACertificates)
|
||||||
|
viper.SetDefault("httpd.ca_revocation_lists", globalConf.HTTPDConfig.CARevocationLists)
|
||||||
viper.SetDefault("http.timeout", globalConf.HTTPConfig.Timeout)
|
viper.SetDefault("http.timeout", globalConf.HTTPConfig.Timeout)
|
||||||
viper.SetDefault("http.ca_certificates", globalConf.HTTPConfig.CACertificates)
|
viper.SetDefault("http.ca_certificates", globalConf.HTTPConfig.CACertificates)
|
||||||
viper.SetDefault("http.skip_tls_verify", globalConf.HTTPConfig.SkipTLSVerify)
|
viper.SetDefault("http.skip_tls_verify", globalConf.HTTPConfig.SkipTLSVerify)
|
||||||
|
|
|
@ -300,9 +300,9 @@ func TestSetGetConfig(t *testing.T) {
|
||||||
config.SetProviderConf(dataProviderConf)
|
config.SetProviderConf(dataProviderConf)
|
||||||
assert.Equal(t, dataProviderConf.Host, config.GetProviderConf().Host)
|
assert.Equal(t, dataProviderConf.Host, config.GetProviderConf().Host)
|
||||||
httpdConf := config.GetHTTPDConfig()
|
httpdConf := config.GetHTTPDConfig()
|
||||||
httpdConf.BindAddress = "0.0.0.0"
|
httpdConf.Bindings = append(httpdConf.Bindings, httpd.Binding{Address: "0.0.0.0"})
|
||||||
config.SetHTTPDConfig(httpdConf)
|
config.SetHTTPDConfig(httpdConf)
|
||||||
assert.Equal(t, httpdConf.BindAddress, config.GetHTTPDConfig().BindAddress)
|
assert.Equal(t, httpdConf.Bindings[0].Address, config.GetHTTPDConfig().Bindings[0].Address)
|
||||||
commonConf := config.GetCommonConfig()
|
commonConf := config.GetCommonConfig()
|
||||||
commonConf.IdleTimeout = 10
|
commonConf.IdleTimeout = 10
|
||||||
config.SetCommonConfig(commonConf)
|
config.SetCommonConfig(commonConf)
|
||||||
|
@ -513,6 +513,57 @@ func TestWebDAVDBindingsCompatibility(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//nolint:dupl
|
||||||
|
func TestHTTPDBindingsCompatibility(t *testing.T) {
|
||||||
|
reset()
|
||||||
|
|
||||||
|
configDir := ".."
|
||||||
|
confName := tempConfigName + ".json"
|
||||||
|
configFilePath := filepath.Join(configDir, confName)
|
||||||
|
err := config.LoadConfig(configDir, "")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
httpdConf := config.GetHTTPDConfig()
|
||||||
|
require.Len(t, httpdConf.Bindings, 1)
|
||||||
|
httpdConf.Bindings = nil
|
||||||
|
httpdConf.BindPort = 9080 //nolint:staticcheck
|
||||||
|
httpdConf.BindAddress = "127.1.1.1" //nolint:staticcheck
|
||||||
|
c := make(map[string]httpd.Conf)
|
||||||
|
c["httpd"] = httpdConf
|
||||||
|
jsonConf, err := json.Marshal(c)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
err = ioutil.WriteFile(configFilePath, jsonConf, os.ModePerm)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
err = config.LoadConfig(configDir, confName)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
httpdConf = config.GetHTTPDConfig()
|
||||||
|
// even if there is no binding configuration in httpd conf we load the default
|
||||||
|
require.Len(t, httpdConf.Bindings, 1)
|
||||||
|
require.Equal(t, 8080, httpdConf.Bindings[0].Port)
|
||||||
|
require.Equal(t, "127.0.0.1", httpdConf.Bindings[0].Address)
|
||||||
|
require.False(t, httpdConf.Bindings[0].EnableHTTPS)
|
||||||
|
require.True(t, httpdConf.Bindings[0].EnableWebAdmin)
|
||||||
|
// now set the global value to nil and reload the configuration
|
||||||
|
// this time we should get the values setted using the deprecated configuration
|
||||||
|
httpdConf.Bindings = nil
|
||||||
|
httpdConf.BindPort = 10080 //nolint:staticcheck
|
||||||
|
httpdConf.BindAddress = "" //nolint:staticcheck
|
||||||
|
config.SetHTTPDConfig(httpdConf)
|
||||||
|
require.Nil(t, config.GetHTTPDConfig().Bindings)
|
||||||
|
require.Equal(t, 10080, config.GetHTTPDConfig().BindPort) //nolint:staticcheck
|
||||||
|
require.Empty(t, config.GetHTTPDConfig().BindAddress) //nolint:staticcheck
|
||||||
|
|
||||||
|
err = config.LoadConfig(configDir, confName)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
httpdConf = config.GetHTTPDConfig()
|
||||||
|
require.Len(t, httpdConf.Bindings, 1)
|
||||||
|
require.Equal(t, 9080, httpdConf.Bindings[0].Port)
|
||||||
|
require.Equal(t, "127.1.1.1", httpdConf.Bindings[0].Address)
|
||||||
|
require.False(t, httpdConf.Bindings[0].EnableHTTPS)
|
||||||
|
require.True(t, httpdConf.Bindings[0].EnableWebAdmin)
|
||||||
|
err = os.Remove(configFilePath)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
func TestSFTPDBindingsFromEnv(t *testing.T) {
|
func TestSFTPDBindingsFromEnv(t *testing.T) {
|
||||||
reset()
|
reset()
|
||||||
|
|
||||||
|
@ -630,6 +681,57 @@ func TestWebDAVBindingsFromEnv(t *testing.T) {
|
||||||
require.Equal(t, 1, bindings[2].ClientAuthType)
|
require.Equal(t, 1, bindings[2].ClientAuthType)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestHTTPDBindingsFromEnv(t *testing.T) {
|
||||||
|
reset()
|
||||||
|
|
||||||
|
sockPath := filepath.Clean(os.TempDir())
|
||||||
|
|
||||||
|
os.Setenv("SFTPGO_HTTPD__BINDINGS__0__ADDRESS", sockPath)
|
||||||
|
os.Setenv("SFTPGO_HTTPD__BINDINGS__0__PORT", "0")
|
||||||
|
os.Setenv("SFTPGO_HTTPD__BINDINGS__1__ADDRESS", "127.0.0.1")
|
||||||
|
os.Setenv("SFTPGO_HTTPD__BINDINGS__1__PORT", "8000")
|
||||||
|
os.Setenv("SFTPGO_HTTPD__BINDINGS__1__ENABLE_HTTPS", "0")
|
||||||
|
os.Setenv("SFTPGO_HTTPD__BINDINGS__1__ENABLE_WEB_ADMIN", "1")
|
||||||
|
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__ADDRESS", "127.0.1.1")
|
||||||
|
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__PORT", "9000")
|
||||||
|
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__ENABLE_WEB_ADMIN", "0")
|
||||||
|
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__ENABLE_HTTPS", "1")
|
||||||
|
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__CLIENT_AUTH_TYPE", "1")
|
||||||
|
t.Cleanup(func() {
|
||||||
|
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__0__ADDRESS")
|
||||||
|
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__0__PORT")
|
||||||
|
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__1__ADDRESS")
|
||||||
|
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__1__PORT")
|
||||||
|
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__1__ENABLE_HTTPS")
|
||||||
|
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__1__ENABLE_WEB_ADMIN")
|
||||||
|
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__ADDRESS")
|
||||||
|
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__PORT")
|
||||||
|
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__ENABLE_HTTPS")
|
||||||
|
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__ENABLE_WEB_ADMIN")
|
||||||
|
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__CLIENT_AUTH_TYPE")
|
||||||
|
})
|
||||||
|
|
||||||
|
configDir := ".."
|
||||||
|
err := config.LoadConfig(configDir, "")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
bindings := config.GetHTTPDConfig().Bindings
|
||||||
|
require.Len(t, bindings, 3)
|
||||||
|
require.Equal(t, 0, bindings[0].Port)
|
||||||
|
require.Equal(t, sockPath, bindings[0].Address)
|
||||||
|
require.False(t, bindings[0].EnableHTTPS)
|
||||||
|
require.True(t, bindings[0].EnableWebAdmin)
|
||||||
|
require.Equal(t, 8000, bindings[1].Port)
|
||||||
|
require.Equal(t, "127.0.0.1", bindings[1].Address)
|
||||||
|
require.False(t, bindings[1].EnableHTTPS)
|
||||||
|
require.True(t, bindings[1].EnableWebAdmin)
|
||||||
|
|
||||||
|
require.Equal(t, 9000, bindings[2].Port)
|
||||||
|
require.Equal(t, "127.0.1.1", bindings[2].Address)
|
||||||
|
require.True(t, bindings[2].EnableHTTPS)
|
||||||
|
require.False(t, bindings[2].EnableWebAdmin)
|
||||||
|
require.Equal(t, 1, bindings[2].ClientAuthType)
|
||||||
|
}
|
||||||
|
|
||||||
func TestConfigFromEnv(t *testing.T) {
|
func TestConfigFromEnv(t *testing.T) {
|
||||||
reset()
|
reset()
|
||||||
|
|
||||||
|
|
|
@ -104,9 +104,9 @@ The configuration file contains the following sections:
|
||||||
- `proxy_allowed`, list of strings. Deprecated, please use the same key in `common` section.
|
- `proxy_allowed`, list of strings. Deprecated, please use the same key in `common` section.
|
||||||
- **"ftpd"**, the configuration for the FTP server
|
- **"ftpd"**, the configuration for the FTP server
|
||||||
- `bindings`, list of structs. Each struct has the following fields:
|
- `bindings`, list of structs. Each struct has the following fields:
|
||||||
- `port`, integer. The port used for serving FTP requests. 0 means disabled. Default: 0
|
- `port`, integer. The port used for serving FTP requests. 0 means disabled. Default: 0.
|
||||||
- `address`, string. Leave blank to listen on all available network interfaces. Default: ""
|
- `address`, string. Leave blank to listen on all available network interfaces. Default: "".
|
||||||
- `apply_proxy_config`, boolean. If enabled the common proxy configuration, if any, will be applied. Default `true`
|
- `apply_proxy_config`, boolean. If enabled the common proxy configuration, if any, will be applied. Default `true`.
|
||||||
- `tls_mode`, integer. 0 means accept both cleartext and encrypted sessions. 1 means TLS is required for both control and data connection. 2 means implicit TLS. Do not enable this blindly, please check that a proper TLS config is in place if you set `tls_mode` is different from 0.
|
- `tls_mode`, integer. 0 means accept both cleartext and encrypted sessions. 1 means TLS is required for both control and data connection. 2 means implicit TLS. Do not enable this blindly, please check that a proper TLS config is in place if you set `tls_mode` is different from 0.
|
||||||
- `force_passive_ip`, ip address. External IP address to expose for passive connections. Leavy empty to autodetect. Defaut: "".
|
- `force_passive_ip`, ip address. External IP address to expose for passive connections. Leavy empty to autodetect. Defaut: "".
|
||||||
- `client_auth_type`, integer. Set to `1` to require client certificate authentication in addition to FTP 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 FTP authentication. You need to define at least a certificate authority for this to work. Default: 0.
|
||||||
|
@ -126,14 +126,14 @@ The configuration file contains the following sections:
|
||||||
- `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.
|
||||||
- `ca_revocation_lists`, list of strings. Set a revocation lists, one for each root CA, to be used to check if a client certificate has been revoked. The revocation lists can be reloaded on demand sending a `SIGHUP` signal on Unix based systems and a `paramchange` request to the running service on Windows.
|
- `ca_revocation_lists`, list of strings. Set a revocation lists, one for each root CA, to be used to check if a client certificate has been revoked. The revocation lists can be reloaded on demand sending a `SIGHUP` signal on Unix based systems and a `paramchange` request to the running service on Windows.
|
||||||
- `tls_mode`, integer. Deprecated, please use `bindings`
|
- `tls_mode`, integer. Deprecated, please use `bindings`
|
||||||
- **webdavd**, the configuration for the WebDAV server, more info [here](./webdav.md)
|
- **"webdavd"**, the configuration for the WebDAV server, more info [here](./webdav.md)
|
||||||
- `bindings`, list of structs. Each struct has the following fields:
|
- `bindings`, list of structs. Each struct has the following fields:
|
||||||
- `port`, integer. The port used for serving WebDAV requests. 0 means disabled. Default: 0.
|
- `port`, integer. The port used for serving WebDAV requests. 0 means disabled. Default: 0.
|
||||||
- `address`, string. Leave blank to listen on all available network interfaces. Default: "".
|
- `address`, string. Leave blank to listen on all available network interfaces. Default: "".
|
||||||
- `enable_https`, boolean. Set to `true` and provide both a certificate and a key file to enable HTTPS connection for this binding. Default `false`
|
- `enable_https`, boolean. Set to `true` and provide both a certificate and a key file to enable HTTPS connection for this binding. Default `false`.
|
||||||
- `client_auth_type`, integer. Set to `1` to require client certificate authentication in addition to basic 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 basic authentication. You need to define at least a certificate authority for this to work. Default: 0.
|
||||||
- `bind_port`, integer. Deprecated, please use `bindings`
|
- `bind_port`, integer. Deprecated, please use `bindings`.
|
||||||
- `bind_address`, string. Deprecated, please use `bindings`
|
- `bind_address`, string. Deprecated, please use `bindings`.
|
||||||
- `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.
|
||||||
|
@ -187,13 +187,21 @@ The configuration file contains the following sections:
|
||||||
- `parallelism`. unsigned 8 bit integer. The number of threads (or lanes) used by the algorithm. Default: 2.
|
- `parallelism`. unsigned 8 bit integer. The number of threads (or lanes) used by the algorithm. Default: 2.
|
||||||
- `update_mode`, integer. Defines how the database will be initialized/updated. 0 means automatically. 1 means manually using the initprovider sub-command.
|
- `update_mode`, integer. Defines how the database will be initialized/updated. 0 means automatically. 1 means manually using the initprovider sub-command.
|
||||||
- **"httpd"**, the configuration for the HTTP server used to serve REST API and to expose the built-in web interface
|
- **"httpd"**, the configuration for the HTTP server used to serve REST API and to expose the built-in web interface
|
||||||
- `bind_port`, integer. The port used for serving HTTP requests. Set to 0 to disable HTTP server. Default: 8080
|
- `bindings`, list of structs. Each struct has the following fields:
|
||||||
- `bind_address`, string. Leave blank to listen on all available network interfaces. On \*NIX you can specify an absolute path to listen on a Unix-domain socket. Default: "127.0.0.1"
|
- `port`, integer. The port used for serving HTTP requests. Default: 8080.
|
||||||
|
- `address`, string. Leave blank to listen on all available network interfaces. On *NIX you can specify an absolute path to listen on a Unix-domain socket Default: "127.0.0.1".
|
||||||
|
- `enable_web_admin`, boolean. Set to `false` to disable the built-in web admin for this binding. You also need to define `templates_path` and `static_files_path` to enable the built-in web admin interface. Default `true`.
|
||||||
|
- `enable_https`, boolean. Set to `true` and provide both a certificate and a key file to enable HTTPS connection for this binding. Default `false`.
|
||||||
|
- `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.
|
||||||
|
- `bind_port`, integer. Deprecated, please use `bindings`.
|
||||||
|
- `bind_address`, string. Deprecated, please use `bindings`. Leave blank to listen on all available network interfaces. On \*NIX you can specify an absolute path to listen on a Unix-domain socket. Default: "127.0.0.1"
|
||||||
- `templates_path`, string. Path to the HTML web templates. This can be an absolute path or a path relative to the config dir
|
- `templates_path`, string. Path to the HTML web templates. This can be an absolute path or a path relative to the config dir
|
||||||
- `static_files_path`, string. Path to the static files for the web interface. This can be an absolute path or a path relative to the config dir. If both `templates_path` and `static_files_path` are empty the built-in web interface will be disabled
|
- `static_files_path`, string. Path to the static files for the web interface. This can be an absolute path or a path relative to the config dir. If both `templates_path` and `static_files_path` are empty the built-in web interface will be disabled
|
||||||
- `backups_path`, string. Path to the backup directory. This can be an absolute path or a path relative to the config dir. We don't allow backups in arbitrary paths for security reasons
|
- `backups_path`, string. Path to the backup directory. This can be an absolute path or a path relative to the config dir. We don't allow backups in arbitrary paths for security reasons
|
||||||
- `certificate_file`, string. Certificate for HTTPS. This can be an absolute path or a path relative to the config dir.
|
- `certificate_file`, string. Certificate for 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. If both the certificate and the private key are provided, the server will expect 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. If both the certificate and the private key are provided, the server will expect 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_revocation_lists`, list of strings. Set a revocation lists, one for each root CA, to be used to check if a client certificate has been revoked. The revocation lists can be reloaded on demand sending a `SIGHUP` signal on Unix based systems and a `paramchange` request to the running service on Windows.
|
||||||
- **"telemetry"**, the configuration for the telemetry server, more details [below](#telemetry-server)
|
- **"telemetry"**, the configuration for the telemetry server, more details [below](#telemetry-server)
|
||||||
- `bind_port`, integer. The port used for serving HTTP requests. Set to 0 to disable HTTP server. Default: 10000
|
- `bind_port`, integer. The port used for serving HTTP requests. Set to 0 to disable HTTP server. Default: 10000
|
||||||
- `bind_address`, string. Leave blank to listen on all available network interfaces. On \*NIX you can specify an absolute path to listen on a Unix-domain socket. Default: "127.0.0.1"
|
- `bind_address`, string. Leave blank to listen on all available network interfaces. On \*NIX you can specify an absolute path to listen on a Unix-domain socket. Default: "127.0.0.1"
|
||||||
|
@ -253,4 +261,4 @@ The telemetry server exposes the following endpoints:
|
||||||
|
|
||||||
- `/healthz`, health information (for health checks)
|
- `/healthz`, health information (for health checks)
|
||||||
- `/metrics`, Prometheus metrics
|
- `/metrics`, Prometheus metrics
|
||||||
- `/debug/pprof`, for pprof, more details [here](./profiling.md)
|
- `/debug/pprof`, if enabled via the `enable_profiler` configuration key, for profiling, more details [here](./profiling.md)
|
||||||
|
|
|
@ -21,6 +21,8 @@ once the access token has expired, you need to get a new one.
|
||||||
|
|
||||||
JWT tokens are not stored and we use a randomly generated secret to sign them so if you restart SFTPGo all the previous tokens will be invalidated and you will get a 401 HTTP response code.
|
JWT tokens are not stored and we use a randomly generated secret to sign them so if you restart SFTPGo all the previous tokens will be invalidated and you will get a 401 HTTP response code.
|
||||||
|
|
||||||
|
If you define multiple bindings, each binding will sign JWT tokens with a different secret so the token generated for a binding is not valid for the other ones.
|
||||||
|
|
||||||
You can create other administrator and assign them the following permissions:
|
You can create other administrator and assign them the following permissions:
|
||||||
|
|
||||||
- add users
|
- add users
|
||||||
|
|
|
@ -149,7 +149,7 @@ func TestMain(m *testing.M) {
|
||||||
}
|
}
|
||||||
|
|
||||||
httpdConf := config.GetHTTPDConfig()
|
httpdConf := config.GetHTTPDConfig()
|
||||||
httpdConf.BindPort = 8079
|
httpdConf.Bindings[0].Port = 8079
|
||||||
httpdtest.SetBaseURL("http://127.0.0.1:8079")
|
httpdtest.SetBaseURL("http://127.0.0.1:8079")
|
||||||
|
|
||||||
ftpdConf := config.GetFTPDConfig()
|
ftpdConf := config.GetFTPDConfig()
|
||||||
|
@ -209,7 +209,7 @@ func TestMain(m *testing.M) {
|
||||||
}()
|
}()
|
||||||
|
|
||||||
waitTCPListening(ftpdConf.Bindings[0].GetAddress())
|
waitTCPListening(ftpdConf.Bindings[0].GetAddress())
|
||||||
waitTCPListening(fmt.Sprintf("%s:%d", httpdConf.BindAddress, httpdConf.BindPort))
|
waitTCPListening(httpdConf.Bindings[0].GetAddress())
|
||||||
waitTCPListening(sftpdConf.Bindings[0].GetAddress())
|
waitTCPListening(sftpdConf.Bindings[0].GetAddress())
|
||||||
ftpd.ReloadCertificateMgr() //nolint:errcheck
|
ftpd.ReloadCertificateMgr() //nolint:errcheck
|
||||||
|
|
||||||
|
|
|
@ -75,6 +75,30 @@ type Binding struct {
|
||||||
Address string `json:"address" mapstructure:"address"`
|
Address string `json:"address" mapstructure:"address"`
|
||||||
// The port used for serving requests
|
// The port used for serving requests
|
||||||
Port int `json:"port" mapstructure:"port"`
|
Port int `json:"port" mapstructure:"port"`
|
||||||
|
// Enable the built-in admin interface.
|
||||||
|
// You have to define TemplatesPath and StaticFilesPath for this to work
|
||||||
|
EnableWebAdmin bool `json:"enable_web_admin" mapstructure:"enable_web_admin"`
|
||||||
|
// you also need to provide a certificate for enabling HTTPS
|
||||||
|
EnableHTTPS bool `json:"enable_https" mapstructure:"enable_https"`
|
||||||
|
// set to 1 to require client certificate authentication in addition to basic auth.
|
||||||
|
// You need to define at least a certificate authority for this to work
|
||||||
|
ClientAuthType int `json:"client_auth_type" mapstructure:"client_auth_type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAddress returns the binding address
|
||||||
|
func (b *Binding) GetAddress() string {
|
||||||
|
return fmt.Sprintf("%s:%d", b.Address, b.Port)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsValid returns true if the binding is valid
|
||||||
|
func (b *Binding) IsValid() bool {
|
||||||
|
if b.Port > 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if filepath.IsAbs(b.Address) && runtime.GOOS != osWindows {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
type defenderStatus struct {
|
type defenderStatus struct {
|
||||||
|
@ -92,9 +116,11 @@ type ServicesStatus struct {
|
||||||
|
|
||||||
// Conf httpd daemon configuration
|
// Conf httpd daemon configuration
|
||||||
type Conf struct {
|
type Conf struct {
|
||||||
// The port used for serving HTTP requests. 0 disable the HTTP server. Default: 8080
|
// Addresses and ports to bind to
|
||||||
|
Bindings []Binding `json:"bindings" mapstructure:"bindings"`
|
||||||
|
// Deprecated: please use Bindings
|
||||||
BindPort int `json:"bind_port" mapstructure:"bind_port"`
|
BindPort int `json:"bind_port" mapstructure:"bind_port"`
|
||||||
// The address to listen on. A blank value means listen on all available network interfaces. Default: "127.0.0.1"
|
// Deprecated: please use Bindings
|
||||||
BindAddress string `json:"bind_address" mapstructure:"bind_address"`
|
BindAddress string `json:"bind_address" mapstructure:"bind_address"`
|
||||||
// Path to the HTML web templates. This can be an absolute path or a path relative to the config dir
|
// Path to the HTML web templates. This can be an absolute path or a path relative to the config dir
|
||||||
TemplatesPath string `json:"templates_path" mapstructure:"templates_path"`
|
TemplatesPath string `json:"templates_path" mapstructure:"templates_path"`
|
||||||
|
@ -109,6 +135,11 @@ type Conf struct {
|
||||||
// "paramchange" request to the running service on Windows.
|
// "paramchange" request to the running service on Windows.
|
||||||
CertificateFile string `json:"certificate_file" mapstructure:"certificate_file"`
|
CertificateFile string `json:"certificate_file" mapstructure:"certificate_file"`
|
||||||
CertificateKeyFile string `json:"certificate_key_file" mapstructure:"certificate_key_file"`
|
CertificateKeyFile string `json:"certificate_key_file" mapstructure:"certificate_key_file"`
|
||||||
|
// CACertificates defines the set of root certificate authorities to be used to verify client certificates.
|
||||||
|
CACertificates []string `json:"ca_certificates" mapstructure:"ca_certificates"`
|
||||||
|
// CARevocationLists defines a set a revocation lists, one for each root CA, to be used to check
|
||||||
|
// if a client certificate has been revoked
|
||||||
|
CARevocationLists []string `json:"ca_revocation_lists" mapstructure:"ca_revocation_lists"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type apiResponse struct {
|
type apiResponse struct {
|
||||||
|
@ -116,20 +147,19 @@ type apiResponse struct {
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ShouldBind returns true if there service must be started
|
// ShouldBind returns true if there is at least a valid binding
|
||||||
func (c Conf) ShouldBind() bool {
|
func (c *Conf) ShouldBind() bool {
|
||||||
if c.BindPort > 0 {
|
for _, binding := range c.Bindings {
|
||||||
return true
|
if binding.IsValid() {
|
||||||
}
|
return true
|
||||||
if filepath.IsAbs(c.BindAddress) && runtime.GOOS != osWindows {
|
}
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize configures and starts the HTTP server
|
// Initialize configures and starts the HTTP server
|
||||||
func (c Conf) Initialize(configDir string) error {
|
func (c *Conf) Initialize(configDir string) error {
|
||||||
var err error
|
|
||||||
logger.Debug(logSender, "", "initializing HTTP server with config %+v", c)
|
logger.Debug(logSender, "", "initializing HTTP server with config %+v", c)
|
||||||
backupsPath = getConfigPath(c.BackupsPath, configDir)
|
backupsPath = getConfigPath(c.BackupsPath, configDir)
|
||||||
staticFilesPath := getConfigPath(c.StaticFilesPath, configDir)
|
staticFilesPath := getConfigPath(c.StaticFilesPath, configDir)
|
||||||
|
@ -150,13 +180,36 @@ func (c Conf) Initialize(configDir string) error {
|
||||||
logger.Info(logSender, "", "built-in web interface disabled, please set templates_path and static_files_path to enable it")
|
logger.Info(logSender, "", "built-in web interface disabled, please set templates_path and static_files_path to enable it")
|
||||||
}
|
}
|
||||||
if certificateFile != "" && certificateKeyFile != "" {
|
if certificateFile != "" && certificateKeyFile != "" {
|
||||||
certMgr, err = common.NewCertManager(certificateFile, certificateKeyFile, configDir, logSender)
|
mgr, err := common.NewCertManager(certificateFile, certificateKeyFile, configDir, logSender)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
mgr.SetCACertificates(c.CACertificates)
|
||||||
|
if err := mgr.LoadRootCAs(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
mgr.SetCARevocationLists(c.CARevocationLists)
|
||||||
|
if err := mgr.LoadCRLs(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
certMgr = mgr
|
||||||
}
|
}
|
||||||
server := newHttpdServer(c.BindAddress, c.BindPort, staticFilesPath, enableWebAdmin)
|
|
||||||
return server.listenAndServe()
|
exitChannel := make(chan error, 1)
|
||||||
|
|
||||||
|
for _, binding := range c.Bindings {
|
||||||
|
if !binding.IsValid() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
go func(b Binding) {
|
||||||
|
server := newHttpdServer(b, staticFilesPath, enableWebAdmin)
|
||||||
|
|
||||||
|
exitChannel <- server.listenAndServe()
|
||||||
|
}(binding)
|
||||||
|
}
|
||||||
|
|
||||||
|
return <-exitChannel
|
||||||
}
|
}
|
||||||
|
|
||||||
func isWebAdminRequest(r *http.Request) bool {
|
func isWebAdminRequest(r *http.Request) bool {
|
||||||
|
@ -220,7 +273,12 @@ func fileServer(r chi.Router, path string, root http.FileSystem) {
|
||||||
|
|
||||||
// GetHTTPRouter returns an HTTP handler suitable to use for test cases
|
// GetHTTPRouter returns an HTTP handler suitable to use for test cases
|
||||||
func GetHTTPRouter() http.Handler {
|
func GetHTTPRouter() http.Handler {
|
||||||
server := newHttpdServer("", 8080, "../static", true)
|
b := Binding{
|
||||||
|
Address: "",
|
||||||
|
Port: 8080,
|
||||||
|
EnableWebAdmin: true,
|
||||||
|
}
|
||||||
|
server := newHttpdServer(b, "../static", true)
|
||||||
server.initializeRouter()
|
server.initializeRouter()
|
||||||
return server.router
|
return server.router
|
||||||
}
|
}
|
||||||
|
|
|
@ -179,7 +179,7 @@ func TestMain(m *testing.M) {
|
||||||
|
|
||||||
httpdConf := config.GetHTTPDConfig()
|
httpdConf := config.GetHTTPDConfig()
|
||||||
|
|
||||||
httpdConf.BindPort = 8081
|
httpdConf.Bindings[0].Port = 8081
|
||||||
httpdtest.SetBaseURL(httpBaseURL)
|
httpdtest.SetBaseURL(httpBaseURL)
|
||||||
backupsPath = filepath.Join(os.TempDir(), "test_backups")
|
backupsPath = filepath.Join(os.TempDir(), "test_backups")
|
||||||
httpdConf.BackupsPath = backupsPath
|
httpdConf.BackupsPath = backupsPath
|
||||||
|
@ -196,7 +196,8 @@ func TestMain(m *testing.M) {
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
waitTCPListening(fmt.Sprintf("%s:%d", httpdConf.BindAddress, httpdConf.BindPort))
|
waitTCPListening(httpdConf.Bindings[0].GetAddress())
|
||||||
|
httpd.ReloadCertificateMgr() //nolint:errcheck
|
||||||
// now start an https server
|
// now start an https server
|
||||||
certPath := filepath.Join(os.TempDir(), "test.crt")
|
certPath := filepath.Join(os.TempDir(), "test.crt")
|
||||||
keyPath := filepath.Join(os.TempDir(), "test.key")
|
keyPath := filepath.Join(os.TempDir(), "test.key")
|
||||||
|
@ -210,9 +211,11 @@ func TestMain(m *testing.M) {
|
||||||
logger.ErrorToConsole("error writing HTTPS private key: %v", err)
|
logger.ErrorToConsole("error writing HTTPS private key: %v", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
httpdConf.BindPort = 8443
|
httpdConf.Bindings[0].Port = 8443
|
||||||
|
httpdConf.Bindings[0].EnableHTTPS = true
|
||||||
httpdConf.CertificateFile = certPath
|
httpdConf.CertificateFile = certPath
|
||||||
httpdConf.CertificateKeyFile = keyPath
|
httpdConf.CertificateKeyFile = keyPath
|
||||||
|
httpdConf.Bindings = append(httpdConf.Bindings, httpd.Binding{})
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
if err := httpdConf.Initialize(configDir); err != nil {
|
if err := httpdConf.Initialize(configDir); err != nil {
|
||||||
|
@ -220,7 +223,7 @@ func TestMain(m *testing.M) {
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
waitTCPListening(fmt.Sprintf("%s:%d", httpdConf.BindAddress, httpdConf.BindPort))
|
waitTCPListening(httpdConf.Bindings[0].GetAddress())
|
||||||
httpd.ReloadCertificateMgr() //nolint:errcheck
|
httpd.ReloadCertificateMgr() //nolint:errcheck
|
||||||
|
|
||||||
testServer = httptest.NewServer(httpd.GetHTTPRouter())
|
testServer = httptest.NewServer(httpd.GetHTTPRouter())
|
||||||
|
@ -250,8 +253,6 @@ func TestInitialization(t *testing.T) {
|
||||||
httpdConf.TemplatesPath = "."
|
httpdConf.TemplatesPath = "."
|
||||||
err = httpdConf.Initialize(configDir)
|
err = httpdConf.Initialize(configDir)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
err = httpd.ReloadCertificateMgr()
|
|
||||||
assert.NoError(t, err, "reloading TLS Certificate must return nil error if no certificate is configured")
|
|
||||||
httpdConf = config.GetHTTPDConfig()
|
httpdConf = config.GetHTTPDConfig()
|
||||||
httpdConf.BackupsPath = ".."
|
httpdConf.BackupsPath = ".."
|
||||||
err = httpdConf.Initialize(configDir)
|
err = httpdConf.Initialize(configDir)
|
||||||
|
@ -263,6 +264,21 @@ func TestInitialization(t *testing.T) {
|
||||||
httpdConf.TemplatesPath = ""
|
httpdConf.TemplatesPath = ""
|
||||||
err = httpdConf.Initialize(configDir)
|
err = httpdConf.Initialize(configDir)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
|
httpdConf.CertificateFile = filepath.Join(os.TempDir(), "test.crt")
|
||||||
|
httpdConf.CertificateKeyFile = filepath.Join(os.TempDir(), "test.key")
|
||||||
|
httpdConf.CACertificates = append(httpdConf.CACertificates, invalidFile)
|
||||||
|
err = httpdConf.Initialize(configDir)
|
||||||
|
assert.Error(t, err)
|
||||||
|
httpdConf.CACertificates = nil
|
||||||
|
httpdConf.CARevocationLists = append(httpdConf.CARevocationLists, invalidFile)
|
||||||
|
err = httpdConf.Initialize(configDir)
|
||||||
|
assert.Error(t, err)
|
||||||
|
httpdConf.CARevocationLists = nil
|
||||||
|
httpdConf.Bindings[0].Port = 8081
|
||||||
|
httpdConf.Bindings[0].EnableHTTPS = true
|
||||||
|
httpdConf.Bindings[0].ClientAuthType = 1
|
||||||
|
err = httpdConf.Initialize(configDir)
|
||||||
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBasicUserHandling(t *testing.T) {
|
func TestBasicUserHandling(t *testing.T) {
|
||||||
|
|
|
@ -3,15 +3,19 @@ package httpd
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -28,17 +32,249 @@ import (
|
||||||
"github.com/drakkan/sftpgo/utils"
|
"github.com/drakkan/sftpgo/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
httpdCert = `-----BEGIN CERTIFICATE-----
|
||||||
|
MIICHTCCAaKgAwIBAgIUHnqw7QnB1Bj9oUsNpdb+ZkFPOxMwCgYIKoZIzj0EAwIw
|
||||||
|
RTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGElu
|
||||||
|
dGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMDAyMDQwOTUzMDRaFw0zMDAyMDEw
|
||||||
|
OTUzMDRaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYD
|
||||||
|
VQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwdjAQBgcqhkjOPQIBBgUrgQQA
|
||||||
|
IgNiAARCjRMqJ85rzMC998X5z761nJ+xL3bkmGVqWvrJ51t5OxV0v25NsOgR82CA
|
||||||
|
NXUgvhVYs7vNFN+jxtb2aj6Xg+/2G/BNxkaFspIVCzgWkxiz7XE4lgUwX44FCXZM
|
||||||
|
3+JeUbKjUzBRMB0GA1UdDgQWBBRhLw+/o3+Z02MI/d4tmaMui9W16jAfBgNVHSME
|
||||||
|
GDAWgBRhLw+/o3+Z02MI/d4tmaMui9W16jAPBgNVHRMBAf8EBTADAQH/MAoGCCqG
|
||||||
|
SM49BAMCA2kAMGYCMQDqLt2lm8mE+tGgtjDmtFgdOcI72HSbRQ74D5rYTzgST1rY
|
||||||
|
/8wTi5xl8TiFUyLMUsICMQC5ViVxdXbhuG7gX6yEqSkMKZICHpO8hqFwOD/uaFVI
|
||||||
|
dV4vKmHUzwK/eIx+8Ay3neE=
|
||||||
|
-----END CERTIFICATE-----`
|
||||||
|
httpdKey = `-----BEGIN EC PARAMETERS-----
|
||||||
|
BgUrgQQAIg==
|
||||||
|
-----END EC PARAMETERS-----
|
||||||
|
-----BEGIN EC PRIVATE KEY-----
|
||||||
|
MIGkAgEBBDCfMNsN6miEE3rVyUPwElfiJSWaR5huPCzUenZOfJT04GAcQdWvEju3
|
||||||
|
UM2lmBLIXpGgBwYFK4EEACKhZANiAARCjRMqJ85rzMC998X5z761nJ+xL3bkmGVq
|
||||||
|
WvrJ51t5OxV0v25NsOgR82CANXUgvhVYs7vNFN+jxtb2aj6Xg+/2G/BNxkaFspIV
|
||||||
|
CzgWkxiz7XE4lgUwX44FCXZM3+JeUbI=
|
||||||
|
-----END EC PRIVATE KEY-----`
|
||||||
|
caCRT = `-----BEGIN CERTIFICATE-----
|
||||||
|
MIIE5jCCAs6gAwIBAgIBATANBgkqhkiG9w0BAQsFADATMREwDwYDVQQDEwhDZXJ0
|
||||||
|
QXV0aDAeFw0yMTAxMDIyMTIwNTVaFw0yMjA3MDIyMTMwNTJaMBMxETAPBgNVBAMT
|
||||||
|
CENlcnRBdXRoMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA4Tiho5xW
|
||||||
|
AC15JRkMwfp3/TJwI2As7MY5dele5cmdr5bHAE+sRKqC+Ti88OJWCV5saoyax/1S
|
||||||
|
CjxJlQMZMl169P1QYJskKjdG2sdv6RLWLMgwSNRRjxp/Bw9dHdiEb9MjLgu28Jro
|
||||||
|
9peQkHcRHeMf5hM9WvlIJGrdzbC4hUehmqggcqgARainBkYjf0SwuWxHeu4nMqkp
|
||||||
|
Ak5tcSTLCjHfEFHZ9Te0TIPG5YkWocQKyeLgu4lvuU+DD2W2lym+YVUtRMGs1Env
|
||||||
|
k7p+N0DcGU26qfzZ2sF5ZXkqm7dBsGQB9pIxwc2Q8T1dCIyP9OQCKVILdc5aVFf1
|
||||||
|
cryQFHYzYNNZXFlIBims5VV5Mgfp8ESHQSue+v6n6ykecLEyKt1F1Y/MWY/nWUSI
|
||||||
|
8zdq83jdBAZVjo9MSthxVn57/06s/hQca65IpcTZV2gX0a+eRlAVqaRbAhL3LaZe
|
||||||
|
bYsW3WHKoUOftwemuep3nL51TzlXZVL7Oz/ClGaEOsnGG9KFO6jh+W768qC0zLQI
|
||||||
|
CdE7v2Zex98sZteHCg9fGJHIaYoF0aJG5P3WI5oZf2fy7UIYN9ADLFZiorCXAZEh
|
||||||
|
CSU6mDoRViZ4RGR9GZxbDZ9KYn7O8M/KCR72bkQg73TlMsk1zSXEw0MKLUjtsw6c
|
||||||
|
rZ0Jt8t3sRatHO3JrYHALMt9vZfyNCZp0IsCAwEAAaNFMEMwDgYDVR0PAQH/BAQD
|
||||||
|
AgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFO1yCNAGr/zQTJIi8lw3
|
||||||
|
w5OiuBvMMA0GCSqGSIb3DQEBCwUAA4ICAQA6gCNuM7r8mnx674dm31GxBjQy5ZwB
|
||||||
|
7CxDzYEvL/oiZ3Tv3HlPfN2LAAsJUfGnghh9DOytenL2CTZWjl/emP5eijzmlP+9
|
||||||
|
zva5I6CIMCf/eDDVsRdO244t0o4uG7+At0IgSDM3bpVaVb4RHZNjEziYChsEYY8d
|
||||||
|
HK6iwuRSvFniV6yhR/Vj1Ymi9yZ5xclqseLXiQnUB0PkfIk23+7s42cXB16653fH
|
||||||
|
O/FsPyKBLiKJArizLYQc12aP3QOrYoYD9+fAzIIzew7A5C0aanZCGzkuFpO6TRlD
|
||||||
|
Tb7ry9Gf0DfPpCgxraH8tOcmnqp/ka3hjqo/SRnnTk0IFrmmLdarJvjD46rKwBo4
|
||||||
|
MjyAIR1mQ5j8GTlSFBmSgETOQ/EYvO3FPLmra1Fh7L+DvaVzTpqI9fG3TuyyY+Ri
|
||||||
|
Fby4ycTOGSZOe5Fh8lqkX5Y47mCUJ3zHzOA1vUJy2eTlMRGpu47Eb1++Vm6EzPUP
|
||||||
|
2EF5aD+zwcssh+atZvQbwxpgVqVcyLt91RSkKkmZQslh0rnlTb68yxvUnD3zw7So
|
||||||
|
o6TAf9UvwVMEvdLT9NnFd6hwi2jcNte/h538GJwXeBb8EkfpqLKpTKyicnOdkamZ
|
||||||
|
7E9zY8SHNRYMwB9coQ/W8NvufbCgkvOoLyMXk5edbXofXl3PhNGOlraWbghBnzf5
|
||||||
|
r3rwjFsQOoZotA==
|
||||||
|
-----END CERTIFICATE-----`
|
||||||
|
caKey = `-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
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
|
||||||
|
-----END RSA PRIVATE KEY-----`
|
||||||
|
caCRL = `-----BEGIN X509 CRL-----
|
||||||
|
MIICpzCBkAIBATANBgkqhkiG9w0BAQsFADATMREwDwYDVQQDEwhDZXJ0QXV0aBcN
|
||||||
|
MjEwMTAyMjEzNDA1WhcNMjMwMTAyMjEzNDA1WjAkMCICEQC+l04DbHWMyC3fG09k
|
||||||
|
VXf+Fw0yMTAxMDIyMTM0MDVaoCMwITAfBgNVHSMEGDAWgBTtcgjQBq/80EySIvJc
|
||||||
|
N8OTorgbzDANBgkqhkiG9w0BAQsFAAOCAgEAEJ7z+uNc8sqtxlOhSdTGDzX/xput
|
||||||
|
E857kFQkSlMnU2whQ8c+XpYrBLA5vIZJNSSwohTpM4+zVBX/bJpmu3wqqaArRO9/
|
||||||
|
YcW5mQk9Anvb4WjQW1cHmtNapMTzoC9AiYt/OWPfy+P6JCgCr4Hy6LgQyIRL6bM9
|
||||||
|
VYTalolOm1qa4Y5cIeT7iHq/91mfaqo8/6MYRjLl8DOTROpmw8OS9bCXkzGKdCat
|
||||||
|
AbAzwkQUSauyoCQ10rpX+Y64w9ng3g4Dr20aCqPf5osaqplEJ2HTK8ljDTidlslv
|
||||||
|
9anQj8ax3Su89vI8+hK+YbfVQwrThabgdSjQsn+veyx8GlP8WwHLAQ379KjZjWg+
|
||||||
|
OlOSwBeU1vTdP0QcB8X5C2gVujAyuQekbaV86xzIBOj7vZdfHZ6ee30TZ2FKiMyg
|
||||||
|
7/N2OqW0w77ChsjB4MSHJCfuTgIeg62GzuZXLM+Q2Z9LBdtm4Byg+sm/P52adOEg
|
||||||
|
gVb2Zf4KSvsAmA0PIBlu449/QXUFcMxzLFy7mwTeZj2B4Ln0Hm0szV9f9R8MwMtB
|
||||||
|
SyLYxVH+mgqaR6Jkk22Q/yYyLPaELfafX5gp/AIXG8n0zxfVaTvK3auSgb1Q6ZLS
|
||||||
|
5QH9dSIsmZHlPq7GoSXmKpMdjUL8eaky/IMteioyXgsBiATzl5L2dsw6MTX3MDF0
|
||||||
|
QbDK+MzhmbKfDxs=
|
||||||
|
-----END X509 CRL-----`
|
||||||
|
client1Crt = `-----BEGIN CERTIFICATE-----
|
||||||
|
MIIEITCCAgmgAwIBAgIRAIppZHoj1hM80D7WzTEKLuAwDQYJKoZIhvcNAQELBQAw
|
||||||
|
EzERMA8GA1UEAxMIQ2VydEF1dGgwHhcNMjEwMTAyMjEyMzEwWhcNMjIwNzAyMjEz
|
||||||
|
MDUxWjASMRAwDgYDVQQDEwdjbGllbnQxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
|
||||||
|
MIIBCgKCAQEAoKbYY9MdF2kF/nhBESIiZTdVYtA8XL9xrIZyDj9EnCiTxHiVbJtH
|
||||||
|
XVwszqSl5TRrotPmnmAQcX3r8OCk+z+RQZ0QQj257P3kG6q4rNnOcWCS5xEd20jP
|
||||||
|
yhQ3m+hMGfZsotNTQze1ochuQgLUN6IPyPxZkH22ia3jX4iu1eo/QxeLYHj1UHw4
|
||||||
|
3Cii9yE+j5kPUC21xmnrGKdUrB55NYLXHx6yTIqYR5znSOVB8oJi18/hwdZmH859
|
||||||
|
DHhm0Hx1HrS+jbjI3+CMorZJ3WUyNf+CkiVLD3xYutPbxzEpwiqkG/XYzLH0habT
|
||||||
|
cDcILo18n+o3jvem2KWBrDhyairjIDscwQIDAQABo3EwbzAOBgNVHQ8BAf8EBAMC
|
||||||
|
A7gwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBSJ5GIv
|
||||||
|
zIrE4ZSQt2+CGblKTDswizAfBgNVHSMEGDAWgBTtcgjQBq/80EySIvJcN8OTorgb
|
||||||
|
zDANBgkqhkiG9w0BAQsFAAOCAgEALh4f5GhvNYNou0Ab04iQBbLEdOu2RlbK1B5n
|
||||||
|
K9P/umYenBHMY/z6HT3+6tpcHsDuqE8UVdq3f3Gh4S2Gu9m8PRitT+cJ3gdo9Plm
|
||||||
|
3rD4ufn/s6rGg3ppydXcedm17492tbccUDWOBZw3IO/ASVq13WPgT0/Kev7cPq0k
|
||||||
|
sSdSNhVeXqx8Myc2/d+8GYyzbul2Kpfa7h9i24sK49E9ftnSmsIvngONo08eT1T0
|
||||||
|
3wAOyK2981LIsHaAWcneShKFLDB6LeXIT9oitOYhiykhFlBZ4M1GNlSNfhQ8IIQP
|
||||||
|
xbqMNXCLkW4/BtLhGEEcg0QVso6Kudl9rzgTfQknrdF7pHp6rS46wYUjoSyIY6dl
|
||||||
|
oLmnoAVJX36J3QPWelePI9e07X2wrTfiZWewwgw3KNRWjd6/zfPLe7GoqXnK1S2z
|
||||||
|
PT8qMfCaTwKTtUkzXuTFvQ8bAo2My/mS8FOcpkt2oQWeOsADHAUX7fz5BCoa2DL3
|
||||||
|
k/7Mh4gVT+JYZEoTwCFuYHgMWFWe98naqHi9lB4yR981p1QgXgxO7qBeipagKY1F
|
||||||
|
LlH1iwXUqZ3MZnkNA+4e1Fglsw3sa/rC+L98HnznJ/YbTfQbCP6aQ1qcOymrjMud
|
||||||
|
7MrFwqZjtd/SK4Qx1VpK6jGEAtPgWBTUS3p9ayg6lqjMBjsmySWfvRsDQbq6P5Ct
|
||||||
|
O/e3EH8=
|
||||||
|
-----END CERTIFICATE-----`
|
||||||
|
client1Key = `-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEpAIBAAKCAQEAoKbYY9MdF2kF/nhBESIiZTdVYtA8XL9xrIZyDj9EnCiTxHiV
|
||||||
|
bJtHXVwszqSl5TRrotPmnmAQcX3r8OCk+z+RQZ0QQj257P3kG6q4rNnOcWCS5xEd
|
||||||
|
20jPyhQ3m+hMGfZsotNTQze1ochuQgLUN6IPyPxZkH22ia3jX4iu1eo/QxeLYHj1
|
||||||
|
UHw43Cii9yE+j5kPUC21xmnrGKdUrB55NYLXHx6yTIqYR5znSOVB8oJi18/hwdZm
|
||||||
|
H859DHhm0Hx1HrS+jbjI3+CMorZJ3WUyNf+CkiVLD3xYutPbxzEpwiqkG/XYzLH0
|
||||||
|
habTcDcILo18n+o3jvem2KWBrDhyairjIDscwQIDAQABAoIBAEBSjVFqtbsp0byR
|
||||||
|
aXvyrtLX1Ng7h++at2jca85Ihq//jyqbHTje8zPuNAKI6eNbmb0YGr5OuEa4pD9N
|
||||||
|
ssDmMsKSoG/lRwwcm7h4InkSvBWpFShvMgUaohfHAHzsBYxfnh+TfULsi0y7c2n6
|
||||||
|
t/2OZcOTRkkUDIITnXYiw93ibHHv2Mv2bBDu35kGrcK+c2dN5IL5ZjTjMRpbJTe2
|
||||||
|
44RBJbdTxHBVSgoGBnugF+s2aEma6Ehsj70oyfoVpM6Aed5kGge0A5zA1JO7WCn9
|
||||||
|
Ay/DzlULRXHjJIoRWd2NKvx5n3FNppUc9vJh2plRHalRooZ2+MjSf8HmXlvG2Hpb
|
||||||
|
ScvmWgECgYEA1G+A/2KnxWsr/7uWIJ7ClcGCiNLdk17Pv3DZ3G4qUsU2ITftfIbb
|
||||||
|
tU0Q/b19na1IY8Pjy9ptP7t74/hF5kky97cf1FA8F+nMj/k4+wO8QDI8OJfzVzh9
|
||||||
|
PwielA5vbE+xmvis5Hdp8/od1Yrc/rPSy2TKtPFhvsqXjqoUmOAjDP8CgYEAwZjH
|
||||||
|
9dt1sc2lx/rMxihlWEzQ3JPswKW9/LJAmbRBoSWF9FGNjbX7uhWtXRKJkzb8ZAwa
|
||||||
|
88azluNo2oftbDD/+jw8b2cDgaJHlLAkSD4O1D1RthW7/LKD15qZ/oFsRb13NV85
|
||||||
|
ZNKtwslXGbfVNyGKUVFm7fVA8vBAOUey+LKDFj8CgYEAg8WWstOzVdYguMTXXuyb
|
||||||
|
ruEV42FJaDyLiSirOvxq7GTAKuLSQUg1yMRBIeQEo2X1XU0JZE3dLodRVhuO4EXP
|
||||||
|
g7Dn4X7Th9HSvgvNuIacowWGLWSz4Qp9RjhGhXhezUSx2nseY6le46PmFavJYYSR
|
||||||
|
4PBofMyt4PcyA6Cknh+KHmkCgYEAnTriG7ETE0a7v4DXUpB4TpCEiMCy5Xs2o8Z5
|
||||||
|
ZNva+W+qLVUWq+MDAIyechqeFSvxK6gRM69LJ96lx+XhU58wJiFJzAhT9rK/g+jS
|
||||||
|
bsHH9WOfu0xHkuHA5hgvvV2Le9B2wqgFyva4HJy82qxMxCu/VG/SMqyfBS9OWbb7
|
||||||
|
ibQhdq0CgYAl53LUWZsFSZIth1vux2LVOsI8C3X1oiXDGpnrdlQ+K7z57hq5EsRq
|
||||||
|
GC+INxwXbvKNqp5h0z2MvmKYPDlGVTgw8f8JjM7TkN17ERLcydhdRrMONUryZpo8
|
||||||
|
1xTob+8blyJgfxZUIAKbMbMbIiU0WAF0rfD/eJJwS4htOW/Hfv4TGA==
|
||||||
|
-----END RSA PRIVATE KEY-----`
|
||||||
|
// client 2 crt is revoked
|
||||||
|
client2Crt = `-----BEGIN CERTIFICATE-----
|
||||||
|
MIIEITCCAgmgAwIBAgIRAL6XTgNsdYzILd8bT2RVd/4wDQYJKoZIhvcNAQELBQAw
|
||||||
|
EzERMA8GA1UEAxMIQ2VydEF1dGgwHhcNMjEwMTAyMjEyMzIwWhcNMjIwNzAyMjEz
|
||||||
|
MDUxWjASMRAwDgYDVQQDEwdjbGllbnQyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
|
||||||
|
MIIBCgKCAQEA6xjW5KQR3/OFQtV5M75WINqQ4AzXSu6DhSz/yumaaQZP/UxY+6hi
|
||||||
|
jcrFzGo9MMie/Sza8DhkXOFAl2BelUubrOeB2cl+/Gr8OCyRi2Gv6j3zCsuN/4jQ
|
||||||
|
tNaoez/IbkDvI3l/ZpzBtnuNY2RiemGgHuORXHRVf3qVlsw+npBIRW5rM2HkO/xG
|
||||||
|
oZjeBErWVu390Lyn+Gvk2TqQDnkutWnxUC60/zPlHhXZ4BwaFAekbSnjsSDB1YFM
|
||||||
|
s8HwW4oBryoxdj3/+/qLrBHt75IdLw3T7/V1UDJQM3EvSQOr12w4egpldhtsC871
|
||||||
|
nnBQZeY6qA5feffIwwg/6lJm70o6S6OX6wIDAQABo3EwbzAOBgNVHQ8BAf8EBAMC
|
||||||
|
A7gwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBTB84v5
|
||||||
|
t9HqhLhMODbn6oYkEQt3KzAfBgNVHSMEGDAWgBTtcgjQBq/80EySIvJcN8OTorgb
|
||||||
|
zDANBgkqhkiG9w0BAQsFAAOCAgEALGtBCve5k8tToL3oLuXp/oSik6ovIB/zq4I/
|
||||||
|
4zNMYPU31+ZWz6aahysgx1JL1yqTa3Qm8o2tu52MbnV10dM7CIw7c/cYa+c+OPcG
|
||||||
|
5LF97kp13X+r2axy+CmwM86b4ILaDGs2Qyai6VB6k7oFUve+av5o7aUrNFpqGCJz
|
||||||
|
HWdtHZSVA3JMATzy0TfWanwkzreqfdw7qH0yZ9bDURlBKAVWrqnCstva9jRuv+AI
|
||||||
|
eqxr/4Ro986TFjJdoAP3Vr16CPg7/B6GA/KmsBWJrpeJdPWq4i2gpLKvYZoy89qD
|
||||||
|
mUZf34RbzcCtV4NvV1DadGnt4us0nvLrvS5rL2+2uWD09kZYq9RbLkvgzF/cY0fz
|
||||||
|
i7I1bi5XQ+alWe0uAk5ZZL/D+GTRYUX1AWwCqwJxmHrMxcskMyO9pXvLyuSWRDLo
|
||||||
|
YNBrbX9nLcfJzVCp+X+9sntTHjs4l6Cw+fLepJIgtgqdCHtbhTiv68vSM6cgb4br
|
||||||
|
6n2xrXRKuioiWFOrTSRr+oalZh8dGJ/xvwY8IbWknZAvml9mf1VvfE7Ma5P777QM
|
||||||
|
fsbYVTq0Y3R/5hIWsC3HA5z6MIM8L1oRe/YyhP3CTmrCHkVKyDOosGXpGz+JVcyo
|
||||||
|
cfYkY5A3yFKB2HaCwZSfwFmRhxkrYWGEbHv3Cd9YkZs1J3hNhGFZyVMC9Uh0S85a
|
||||||
|
6zdDidU=
|
||||||
|
-----END CERTIFICATE-----`
|
||||||
|
client2Key = `-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEpAIBAAKCAQEA6xjW5KQR3/OFQtV5M75WINqQ4AzXSu6DhSz/yumaaQZP/UxY
|
||||||
|
+6hijcrFzGo9MMie/Sza8DhkXOFAl2BelUubrOeB2cl+/Gr8OCyRi2Gv6j3zCsuN
|
||||||
|
/4jQtNaoez/IbkDvI3l/ZpzBtnuNY2RiemGgHuORXHRVf3qVlsw+npBIRW5rM2Hk
|
||||||
|
O/xGoZjeBErWVu390Lyn+Gvk2TqQDnkutWnxUC60/zPlHhXZ4BwaFAekbSnjsSDB
|
||||||
|
1YFMs8HwW4oBryoxdj3/+/qLrBHt75IdLw3T7/V1UDJQM3EvSQOr12w4egpldhts
|
||||||
|
C871nnBQZeY6qA5feffIwwg/6lJm70o6S6OX6wIDAQABAoIBAFatstVb1KdQXsq0
|
||||||
|
cFpui8zTKOUiduJOrDkWzTygAmlEhYtrccdfXu7OWz0x0lvBLDVGK3a0I/TGrAzj
|
||||||
|
4BuFY+FM/egxTVt9in6fmA3et4BS1OAfCryzUdfK6RV//8L+t+zJZ/qKQzWnugpy
|
||||||
|
QYjDo8ifuMFwtvEoXizaIyBNLAhEp9hnrv+Tyi2O2gahPvCHsD48zkyZRCHYRstD
|
||||||
|
NH5cIrwz9/RJgPO1KI+QsJE7Nh7stR0sbr+5TPU4fnsL2mNhMUF2TJrwIPrc1yp+
|
||||||
|
YIUjdnh3SO88j4TQT3CIrWi8i4pOy6N0dcVn3gpCRGaqAKyS2ZYUj+yVtLO4KwxZ
|
||||||
|
SZ1lNvECgYEA78BrF7f4ETfWSLcBQ3qxfLs7ibB6IYo2x25685FhZjD+zLXM1AKb
|
||||||
|
FJHEXUm3mUYrFJK6AFEyOQnyGKBOLs3S6oTAswMPbTkkZeD1Y9O6uv0AHASLZnK6
|
||||||
|
pC6ub0eSRF5LUyTQ55Jj8D7QsjXJueO8v+G5ihWhNSN9tB2UA+8NBmkCgYEA+weq
|
||||||
|
cvoeMIEMBQHnNNLy35bwfqrceGyPIRBcUIvzQfY1vk7KW6DYOUzC7u+WUzy/hA52
|
||||||
|
DjXVVhua2eMQ9qqtOav7djcMc2W9RbLowxvno7K5qiCss013MeWk64TCWy+WMp5A
|
||||||
|
AVAtOliC3hMkIKqvR2poqn+IBTh1449agUJQqTMCgYEAu06IHGq1GraV6g9XpGF5
|
||||||
|
wqoAlMzUTdnOfDabRilBf/YtSr+J++ThRcuwLvXFw7CnPZZ4TIEjDJ7xjj3HdxeE
|
||||||
|
fYYjineMmNd40UNUU556F1ZLvJfsVKizmkuCKhwvcMx+asGrmA+tlmds4p3VMS50
|
||||||
|
KzDtpKzLWlmU/p/RINWlRmkCgYBy0pHTn7aZZx2xWKqCDg+L2EXPGqZX6wgZDpu7
|
||||||
|
OBifzlfM4ctL2CmvI/5yPmLbVgkgBWFYpKUdiujsyyEiQvWTUKhn7UwjqKDHtcsk
|
||||||
|
G6p7xS+JswJrzX4885bZJ9Oi1AR2yM3sC9l0O7I4lDbNPmWIXBLeEhGMmcPKv/Kc
|
||||||
|
91Ff4wKBgQCF3ur+Vt0PSU0ucrPVHjCe7tqazm0LJaWbPXL1Aw0pzdM2EcNcW/MA
|
||||||
|
w0kqpr7MgJ94qhXCBcVcfPuFN9fBOadM3UBj1B45Cz3pptoK+ScI8XKno6jvVK/p
|
||||||
|
xr5cb9VBRBtB9aOKVfuRhpatAfS2Pzm2Htae9lFn7slGPUmu2hkjDw==
|
||||||
|
-----END RSA PRIVATE KEY-----`
|
||||||
|
)
|
||||||
|
|
||||||
func TestShouldBind(t *testing.T) {
|
func TestShouldBind(t *testing.T) {
|
||||||
c := Conf{
|
c := Conf{
|
||||||
BindPort: 10000,
|
Bindings: []Binding{
|
||||||
|
{
|
||||||
|
Port: 10000,
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
require.True(t, c.ShouldBind())
|
require.True(t, c.ShouldBind())
|
||||||
|
|
||||||
c.BindPort = 0
|
c.Bindings[0].Port = 0
|
||||||
require.False(t, c.ShouldBind())
|
require.False(t, c.ShouldBind())
|
||||||
|
|
||||||
if runtime.GOOS != osWindows {
|
if runtime.GOOS != osWindows {
|
||||||
c.BindAddress = "/absolute/path"
|
c.Bindings[0].Address = "/absolute/path"
|
||||||
require.True(t, c.ShouldBind())
|
require.True(t, c.ShouldBind())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -406,3 +642,66 @@ func TestQuotaScanInvalidFs(t *testing.T) {
|
||||||
err := doQuotaScan(user)
|
err := doQuotaScan(user)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestVerifyTLSConnection(t *testing.T) {
|
||||||
|
oldCertMgr := certMgr
|
||||||
|
|
||||||
|
caCrlPath := filepath.Join(os.TempDir(), "testcrl.crt")
|
||||||
|
certPath := filepath.Join(os.TempDir(), "testh.crt")
|
||||||
|
keyPath := filepath.Join(os.TempDir(), "testh.key")
|
||||||
|
err := ioutil.WriteFile(caCrlPath, []byte(caCRL), os.ModePerm)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
err = ioutil.WriteFile(certPath, []byte(httpdCert), os.ModePerm)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
err = ioutil.WriteFile(keyPath, []byte(httpdKey), os.ModePerm)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
certMgr, err = common.NewCertManager(certPath, keyPath, "", "webdav_test")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
certMgr.SetCARevocationLists([]string{caCrlPath})
|
||||||
|
err = certMgr.LoadCRLs()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
crt, err := tls.X509KeyPair([]byte(client1Crt), []byte(client1Key))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
x509crt, err := x509.ParseCertificate(crt.Certificate[0])
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
server := httpdServer{}
|
||||||
|
state := tls.ConnectionState{
|
||||||
|
PeerCertificates: []*x509.Certificate{x509crt},
|
||||||
|
}
|
||||||
|
|
||||||
|
err = server.verifyTLSConnection(state)
|
||||||
|
assert.Error(t, err) // no verified certification chain
|
||||||
|
|
||||||
|
crt, err = tls.X509KeyPair([]byte(caCRT), []byte(caKey))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
x509CAcrt, err := x509.ParseCertificate(crt.Certificate[0])
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
state.VerifiedChains = append(state.VerifiedChains, []*x509.Certificate{x509crt, x509CAcrt})
|
||||||
|
err = server.verifyTLSConnection(state)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
crt, err = tls.X509KeyPair([]byte(client2Crt), []byte(client2Key))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
x509crtRevoked, err := x509.ParseCertificate(crt.Certificate[0])
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
state.VerifiedChains = append(state.VerifiedChains, []*x509.Certificate{x509crtRevoked, x509CAcrt})
|
||||||
|
state.PeerCertificates = []*x509.Certificate{x509crtRevoked}
|
||||||
|
err = server.verifyTLSConnection(state)
|
||||||
|
assert.EqualError(t, err, common.ErrCrtRevoked.Error())
|
||||||
|
|
||||||
|
err = os.Remove(caCrlPath)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
err = os.Remove(certPath)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
err = os.Remove(keyPath)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
certMgr = oldCertMgr
|
||||||
|
}
|
||||||
|
|
|
@ -2,6 +2,8 @@ package httpd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -27,14 +29,11 @@ type httpdServer struct {
|
||||||
tokenAuth *jwtauth.JWTAuth
|
tokenAuth *jwtauth.JWTAuth
|
||||||
}
|
}
|
||||||
|
|
||||||
func newHttpdServer(bindAddress string, bindPort int, staticFilesPath string, enableWebAdmin bool) *httpdServer {
|
func newHttpdServer(b Binding, staticFilesPath string, enableWebAdmin bool) *httpdServer {
|
||||||
return &httpdServer{
|
return &httpdServer{
|
||||||
binding: Binding{
|
binding: b,
|
||||||
Address: bindAddress,
|
|
||||||
Port: bindPort,
|
|
||||||
},
|
|
||||||
staticFilesPath: staticFilesPath,
|
staticFilesPath: staticFilesPath,
|
||||||
enableWebAdmin: enableWebAdmin,
|
enableWebAdmin: enableWebAdmin && b.EnableWebAdmin,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,17 +47,49 @@ func (s *httpdServer) listenAndServe() error {
|
||||||
MaxHeaderBytes: 1 << 16, // 64KB
|
MaxHeaderBytes: 1 << 16, // 64KB
|
||||||
ErrorLog: log.New(&logger.StdLoggerWrapper{Sender: logSender}, "", 0),
|
ErrorLog: log.New(&logger.StdLoggerWrapper{Sender: logSender}, "", 0),
|
||||||
}
|
}
|
||||||
if certMgr != nil {
|
if certMgr != nil && s.binding.EnableHTTPS {
|
||||||
config := &tls.Config{
|
config := &tls.Config{
|
||||||
GetCertificate: certMgr.GetCertificateFunc(),
|
GetCertificate: certMgr.GetCertificateFunc(),
|
||||||
MinVersion: tls.VersionTLS12,
|
MinVersion: tls.VersionTLS12,
|
||||||
}
|
}
|
||||||
httpServer.TLSConfig = config
|
httpServer.TLSConfig = config
|
||||||
|
if s.binding.ClientAuthType == 1 {
|
||||||
|
httpServer.TLSConfig.ClientCAs = certMgr.GetRootCAs()
|
||||||
|
httpServer.TLSConfig.ClientAuth = tls.RequireAndVerifyClientCert
|
||||||
|
httpServer.TLSConfig.VerifyConnection = s.verifyTLSConnection
|
||||||
|
}
|
||||||
return utils.HTTPListenAndServe(httpServer, s.binding.Address, s.binding.Port, true, logSender)
|
return utils.HTTPListenAndServe(httpServer, s.binding.Address, s.binding.Port, true, logSender)
|
||||||
}
|
}
|
||||||
return utils.HTTPListenAndServe(httpServer, s.binding.Address, s.binding.Port, false, logSender)
|
return utils.HTTPListenAndServe(httpServer, s.binding.Address, s.binding.Port, false, logSender)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *httpdServer) verifyTLSConnection(state tls.ConnectionState) error {
|
||||||
|
if certMgr != nil {
|
||||||
|
var clientCrt *x509.Certificate
|
||||||
|
var clientCrtName string
|
||||||
|
if len(state.PeerCertificates) > 0 {
|
||||||
|
clientCrt = state.PeerCertificates[0]
|
||||||
|
clientCrtName = clientCrt.Subject.String()
|
||||||
|
}
|
||||||
|
if len(state.VerifiedChains) == 0 {
|
||||||
|
logger.Warn(logSender, "", "TLS connection cannot be verified: unable to get verification chain")
|
||||||
|
return errors.New("TLS connection cannot be verified: unable to get verification chain")
|
||||||
|
}
|
||||||
|
for _, verifiedChain := range state.VerifiedChains {
|
||||||
|
var caCrt *x509.Certificate
|
||||||
|
if len(verifiedChain) > 0 {
|
||||||
|
caCrt = verifiedChain[len(verifiedChain)-1]
|
||||||
|
}
|
||||||
|
if certMgr.IsRevoked(clientCrt, caCrt) {
|
||||||
|
logger.Debug(logSender, "", "tls handshake error, client certificate %#v has been revoked", clientCrtName)
|
||||||
|
return common.ErrCrtRevoked
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *httpdServer) refreshCookie(next http.Handler) http.Handler {
|
func (s *httpdServer) refreshCookie(next http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
s.checkCookieExpiration(w, r)
|
s.checkCookieExpiration(w, r)
|
||||||
|
|
|
@ -47,7 +47,7 @@ func (s *Service) StartPortableMode(sftpdPort, ftpPort, webdavPort int, enabledS
|
||||||
dataProviderConf.PreferDatabaseCredentials = true
|
dataProviderConf.PreferDatabaseCredentials = true
|
||||||
config.SetProviderConf(dataProviderConf)
|
config.SetProviderConf(dataProviderConf)
|
||||||
httpdConf := config.GetHTTPDConfig()
|
httpdConf := config.GetHTTPDConfig()
|
||||||
httpdConf.BindPort = 0
|
httpdConf.Bindings = nil
|
||||||
config.SetHTTPDConfig(httpdConf)
|
config.SetHTTPDConfig(httpdConf)
|
||||||
sftpdConf := config.GetSFTPDConfig()
|
sftpdConf := config.GetSFTPDConfig()
|
||||||
sftpdConf.MaxAuthTries = 12
|
sftpdConf.MaxAuthTries = 12
|
||||||
|
|
|
@ -236,7 +236,7 @@ func TestMain(m *testing.M) {
|
||||||
}()
|
}()
|
||||||
|
|
||||||
waitTCPListening(sftpdConf.Bindings[0].GetAddress())
|
waitTCPListening(sftpdConf.Bindings[0].GetAddress())
|
||||||
waitTCPListening(fmt.Sprintf("%s:%d", httpdConf.BindAddress, httpdConf.BindPort))
|
waitTCPListening(httpdConf.Bindings[0].GetAddress())
|
||||||
|
|
||||||
sftpdConf.Bindings = []sftpd.Binding{
|
sftpdConf.Bindings = []sftpd.Binding{
|
||||||
{
|
{
|
||||||
|
|
15
sftpgo.json
15
sftpgo.json
|
@ -147,13 +147,22 @@
|
||||||
"update_mode": 0
|
"update_mode": 0
|
||||||
},
|
},
|
||||||
"httpd": {
|
"httpd": {
|
||||||
"bind_port": 8080,
|
"bindings": [
|
||||||
"bind_address": "127.0.0.1",
|
{
|
||||||
|
"port": 8080,
|
||||||
|
"address": "127.0.0.1",
|
||||||
|
"enable_web_admin": true,
|
||||||
|
"enable_https": false,
|
||||||
|
"client_auth_type": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
"templates_path": "templates",
|
"templates_path": "templates",
|
||||||
"static_files_path": "static",
|
"static_files_path": "static",
|
||||||
"backups_path": "backups",
|
"backups_path": "backups",
|
||||||
"certificate_file": "",
|
"certificate_file": "",
|
||||||
"certificate_key_file": ""
|
"certificate_key_file": "",
|
||||||
|
"ca_certificates": [],
|
||||||
|
"ca_revocation_lists": []
|
||||||
},
|
},
|
||||||
"telemetry": {
|
"telemetry": {
|
||||||
"bind_port": 10000,
|
"bind_port": 10000,
|
||||||
|
|
|
@ -143,7 +143,7 @@ func TestMain(m *testing.M) {
|
||||||
}
|
}
|
||||||
|
|
||||||
httpdConf := config.GetHTTPDConfig()
|
httpdConf := config.GetHTTPDConfig()
|
||||||
httpdConf.BindPort = 8078
|
httpdConf.Bindings[0].Port = 8078
|
||||||
httpdtest.SetBaseURL("http://127.0.0.1:8078")
|
httpdtest.SetBaseURL("http://127.0.0.1:8078")
|
||||||
|
|
||||||
// required to test sftpfs
|
// required to test sftpfs
|
||||||
|
@ -211,7 +211,7 @@ func TestMain(m *testing.M) {
|
||||||
}()
|
}()
|
||||||
|
|
||||||
waitTCPListening(webDavConf.Bindings[0].GetAddress())
|
waitTCPListening(webDavConf.Bindings[0].GetAddress())
|
||||||
waitTCPListening(fmt.Sprintf("%s:%d", httpdConf.BindAddress, httpdConf.BindPort))
|
waitTCPListening(httpdConf.Bindings[0].GetAddress())
|
||||||
waitTCPListening(sftpdConf.Bindings[0].GetAddress())
|
waitTCPListening(sftpdConf.Bindings[0].GetAddress())
|
||||||
webdavd.ReloadCertificateMgr() //nolint:errcheck
|
webdavd.ReloadCertificateMgr() //nolint:errcheck
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue