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,
|
||||
ClientAuthType: 0,
|
||||
}
|
||||
defaultHTTPDBinding = httpd.Binding{
|
||||
Address: "127.0.0.1",
|
||||
Port: 8080,
|
||||
EnableWebAdmin: true,
|
||||
EnableHTTPS: false,
|
||||
ClientAuthType: 0,
|
||||
}
|
||||
)
|
||||
|
||||
type globalConfig struct {
|
||||
|
@ -202,8 +209,7 @@ func Init() {
|
|||
PreferDatabaseCredentials: false,
|
||||
},
|
||||
HTTPDConfig: httpd.Conf{
|
||||
BindPort: 8080,
|
||||
BindAddress: "127.0.0.1",
|
||||
Bindings: []httpd.Binding{defaultHTTPDBinding},
|
||||
TemplatesPath: "templates",
|
||||
StaticFilesPath: "static",
|
||||
BackupsPath: "backups",
|
||||
|
@ -536,16 +542,38 @@ func checkWebDAVDBindingCompatibility() {
|
|||
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() {
|
||||
checkSFTPDBindingsCompatibility()
|
||||
checkFTPDBindingCompatibility()
|
||||
checkWebDAVDBindingCompatibility()
|
||||
checkHTTPDBindingCompatibility()
|
||||
|
||||
maxBindings := make([]int, 10)
|
||||
for idx := range maxBindings {
|
||||
getSFTPDBindindFromEnv(idx)
|
||||
getFTPDBindingFromEnv(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() {
|
||||
viper.SetDefault("common.idle_timeout", globalConf.Common.IdleTimeout)
|
||||
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.parallelism", globalConf.ProviderConf.PasswordHashing.Argon2Options.Parallelism)
|
||||
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.static_files_path", globalConf.HTTPDConfig.StaticFilesPath)
|
||||
viper.SetDefault("httpd.backups_path", globalConf.HTTPDConfig.BackupsPath)
|
||||
viper.SetDefault("httpd.certificate_file", globalConf.HTTPDConfig.CertificateFile)
|
||||
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.ca_certificates", globalConf.HTTPConfig.CACertificates)
|
||||
viper.SetDefault("http.skip_tls_verify", globalConf.HTTPConfig.SkipTLSVerify)
|
||||
|
|
|
@ -300,9 +300,9 @@ func TestSetGetConfig(t *testing.T) {
|
|||
config.SetProviderConf(dataProviderConf)
|
||||
assert.Equal(t, dataProviderConf.Host, config.GetProviderConf().Host)
|
||||
httpdConf := config.GetHTTPDConfig()
|
||||
httpdConf.BindAddress = "0.0.0.0"
|
||||
httpdConf.Bindings = append(httpdConf.Bindings, httpd.Binding{Address: "0.0.0.0"})
|
||||
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.IdleTimeout = 10
|
||||
config.SetCommonConfig(commonConf)
|
||||
|
@ -513,6 +513,57 @@ func TestWebDAVDBindingsCompatibility(t *testing.T) {
|
|||
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) {
|
||||
reset()
|
||||
|
||||
|
@ -630,6 +681,57 @@ func TestWebDAVBindingsFromEnv(t *testing.T) {
|
|||
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) {
|
||||
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.
|
||||
- **"ftpd"**, the configuration for the FTP server
|
||||
- `bindings`, list of structs. Each struct has the following fields:
|
||||
- `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: ""
|
||||
- `apply_proxy_config`, boolean. If enabled the common proxy configuration, if any, will be applied. Default `true`
|
||||
- `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: "".
|
||||
- `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.
|
||||
- `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.
|
||||
|
@ -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_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`
|
||||
- **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:
|
||||
- `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: "".
|
||||
- `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.
|
||||
- `bind_port`, integer. Deprecated, please use `bindings`
|
||||
- `bind_address`, string. Deprecated, please use `bindings`
|
||||
- `bind_port`, integer. 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_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.
|
||||
|
@ -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.
|
||||
- `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
|
||||
- `bind_port`, integer. The port used for serving HTTP requests. Set to 0 to disable HTTP server. Default: 8080
|
||||
- `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"
|
||||
- `bindings`, list of structs. Each struct has the following fields:
|
||||
- `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
|
||||
- `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
|
||||
- `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.
|
||||
- `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)
|
||||
- `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"
|
||||
|
@ -253,4 +261,4 @@ The telemetry server exposes the following endpoints:
|
|||
|
||||
- `/healthz`, health information (for health checks)
|
||||
- `/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.
|
||||
|
||||
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:
|
||||
|
||||
- add users
|
||||
|
|
|
@ -149,7 +149,7 @@ func TestMain(m *testing.M) {
|
|||
}
|
||||
|
||||
httpdConf := config.GetHTTPDConfig()
|
||||
httpdConf.BindPort = 8079
|
||||
httpdConf.Bindings[0].Port = 8079
|
||||
httpdtest.SetBaseURL("http://127.0.0.1:8079")
|
||||
|
||||
ftpdConf := config.GetFTPDConfig()
|
||||
|
@ -209,7 +209,7 @@ func TestMain(m *testing.M) {
|
|||
}()
|
||||
|
||||
waitTCPListening(ftpdConf.Bindings[0].GetAddress())
|
||||
waitTCPListening(fmt.Sprintf("%s:%d", httpdConf.BindAddress, httpdConf.BindPort))
|
||||
waitTCPListening(httpdConf.Bindings[0].GetAddress())
|
||||
waitTCPListening(sftpdConf.Bindings[0].GetAddress())
|
||||
ftpd.ReloadCertificateMgr() //nolint:errcheck
|
||||
|
||||
|
|
|
@ -75,6 +75,30 @@ type Binding struct {
|
|||
Address string `json:"address" mapstructure:"address"`
|
||||
// The port used for serving requests
|
||||
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 {
|
||||
|
@ -92,9 +116,11 @@ type ServicesStatus struct {
|
|||
|
||||
// Conf httpd daemon configuration
|
||||
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"`
|
||||
// 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"`
|
||||
// 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"`
|
||||
|
@ -109,6 +135,11 @@ type Conf struct {
|
|||
// "paramchange" request to the running service on Windows.
|
||||
CertificateFile string `json:"certificate_file" mapstructure:"certificate_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 {
|
||||
|
@ -116,20 +147,19 @@ type apiResponse struct {
|
|||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
// ShouldBind returns true if there service must be started
|
||||
func (c Conf) ShouldBind() bool {
|
||||
if c.BindPort > 0 {
|
||||
return true
|
||||
}
|
||||
if filepath.IsAbs(c.BindAddress) && runtime.GOOS != osWindows {
|
||||
return true
|
||||
// ShouldBind returns true if there is at least a valid binding
|
||||
func (c *Conf) ShouldBind() bool {
|
||||
for _, binding := range c.Bindings {
|
||||
if binding.IsValid() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Initialize configures and starts the HTTP server
|
||||
func (c Conf) Initialize(configDir string) error {
|
||||
var err error
|
||||
func (c *Conf) Initialize(configDir string) error {
|
||||
logger.Debug(logSender, "", "initializing HTTP server with config %+v", c)
|
||||
backupsPath = getConfigPath(c.BackupsPath, 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")
|
||||
}
|
||||
if certificateFile != "" && certificateKeyFile != "" {
|
||||
certMgr, err = common.NewCertManager(certificateFile, certificateKeyFile, configDir, logSender)
|
||||
mgr, err := common.NewCertManager(certificateFile, certificateKeyFile, configDir, logSender)
|
||||
if err != nil {
|
||||
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 {
|
||||
|
@ -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
|
||||
func GetHTTPRouter() http.Handler {
|
||||
server := newHttpdServer("", 8080, "../static", true)
|
||||
b := Binding{
|
||||
Address: "",
|
||||
Port: 8080,
|
||||
EnableWebAdmin: true,
|
||||
}
|
||||
server := newHttpdServer(b, "../static", true)
|
||||
server.initializeRouter()
|
||||
return server.router
|
||||
}
|
||||
|
|
|
@ -179,7 +179,7 @@ func TestMain(m *testing.M) {
|
|||
|
||||
httpdConf := config.GetHTTPDConfig()
|
||||
|
||||
httpdConf.BindPort = 8081
|
||||
httpdConf.Bindings[0].Port = 8081
|
||||
httpdtest.SetBaseURL(httpBaseURL)
|
||||
backupsPath = filepath.Join(os.TempDir(), "test_backups")
|
||||
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
|
||||
certPath := filepath.Join(os.TempDir(), "test.crt")
|
||||
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)
|
||||
os.Exit(1)
|
||||
}
|
||||
httpdConf.BindPort = 8443
|
||||
httpdConf.Bindings[0].Port = 8443
|
||||
httpdConf.Bindings[0].EnableHTTPS = true
|
||||
httpdConf.CertificateFile = certPath
|
||||
httpdConf.CertificateKeyFile = keyPath
|
||||
httpdConf.Bindings = append(httpdConf.Bindings, httpd.Binding{})
|
||||
|
||||
go func() {
|
||||
if err := httpdConf.Initialize(configDir); err != nil {
|
||||
|
@ -220,7 +223,7 @@ func TestMain(m *testing.M) {
|
|||
os.Exit(1)
|
||||
}
|
||||
}()
|
||||
waitTCPListening(fmt.Sprintf("%s:%d", httpdConf.BindAddress, httpdConf.BindPort))
|
||||
waitTCPListening(httpdConf.Bindings[0].GetAddress())
|
||||
httpd.ReloadCertificateMgr() //nolint:errcheck
|
||||
|
||||
testServer = httptest.NewServer(httpd.GetHTTPRouter())
|
||||
|
@ -250,8 +253,6 @@ func TestInitialization(t *testing.T) {
|
|||
httpdConf.TemplatesPath = "."
|
||||
err = httpdConf.Initialize(configDir)
|
||||
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.BackupsPath = ".."
|
||||
err = httpdConf.Initialize(configDir)
|
||||
|
@ -263,6 +264,21 @@ func TestInitialization(t *testing.T) {
|
|||
httpdConf.TemplatesPath = ""
|
||||
err = httpdConf.Initialize(configDir)
|
||||
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) {
|
||||
|
|
|
@ -3,15 +3,19 @@ package httpd
|
|||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
@ -28,17 +32,249 @@ import (
|
|||
"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-----
|
||||
MIIJKQIBAAKCAgEA4Tiho5xWAC15JRkMwfp3/TJwI2As7MY5dele5cmdr5bHAE+s
|
||||
RKqC+Ti88OJWCV5saoyax/1SCjxJlQMZMl169P1QYJskKjdG2sdv6RLWLMgwSNRR
|
||||
jxp/Bw9dHdiEb9MjLgu28Jro9peQkHcRHeMf5hM9WvlIJGrdzbC4hUehmqggcqgA
|
||||
RainBkYjf0SwuWxHeu4nMqkpAk5tcSTLCjHfEFHZ9Te0TIPG5YkWocQKyeLgu4lv
|
||||
uU+DD2W2lym+YVUtRMGs1Envk7p+N0DcGU26qfzZ2sF5ZXkqm7dBsGQB9pIxwc2Q
|
||||
8T1dCIyP9OQCKVILdc5aVFf1cryQFHYzYNNZXFlIBims5VV5Mgfp8ESHQSue+v6n
|
||||
6ykecLEyKt1F1Y/MWY/nWUSI8zdq83jdBAZVjo9MSthxVn57/06s/hQca65IpcTZ
|
||||
V2gX0a+eRlAVqaRbAhL3LaZebYsW3WHKoUOftwemuep3nL51TzlXZVL7Oz/ClGaE
|
||||
OsnGG9KFO6jh+W768qC0zLQICdE7v2Zex98sZteHCg9fGJHIaYoF0aJG5P3WI5oZ
|
||||
f2fy7UIYN9ADLFZiorCXAZEhCSU6mDoRViZ4RGR9GZxbDZ9KYn7O8M/KCR72bkQg
|
||||
73TlMsk1zSXEw0MKLUjtsw6crZ0Jt8t3sRatHO3JrYHALMt9vZfyNCZp0IsCAwEA
|
||||
AQKCAgAV+ElERYbaI5VyufvVnFJCH75ypPoc6sVGLEq2jbFVJJcq/5qlZCC8oP1F
|
||||
Xj7YUR6wUiDzK1Hqb7EZ2SCHGjlZVrCVi+y+NYAy7UuMZ+r+mVSkdhmypPoJPUVv
|
||||
GOTqZ6VB46Cn3eSl0WknvoWr7bD555yPmEuiSc5zNy74yWEJTidEKAFGyknowcTK
|
||||
sG+w1tAuPLcUKQ44DGB+rgEkcHL7C5EAa7upzx0C3RmZFB+dTAVyJdkBMbFuOhTS
|
||||
sB7DLeTplR7/4mp9da7EQw51ZXC1DlZOEZt++4/desXsqATNAbva1OuzrLG7mMKe
|
||||
N/PCBh/aERQcsCvgUmaXqGQgqN1Jhw8kbXnjZnVd9iE7TAh7ki3VqNy1OMgTwOex
|
||||
bBYWaCqHuDYIxCjeW0qLJcn0cKQ13FVYrxgInf4Jp82SQht5b/zLL3IRZEyKcLJF
|
||||
kL6g1wlmTUTUX0z8eZzlM0ZCrqtExjgElMO/rV971nyNV5WU8Og3NmE8/slqMrmJ
|
||||
DlrQr9q0WJsDKj1IMe46EUM6ix7bbxC5NIfJ96dgdxZDn6ghjca6iZYqqUACvmUj
|
||||
cq08s3R4Ouw9/87kn11wwGBx2yDueCwrjKEGc0RKjweGbwu0nBxOrkJ8JXz6bAv7
|
||||
1OKfYaX3afI9B8x4uaiuRs38oBQlg9uAYFfl4HNBPuQikGLmsQKCAQEA8VjFOsaz
|
||||
y6NMZzKXi7WZ48uu3ed5x3Kf6RyDr1WvQ1jkBMv9b6b8Gp1CRnPqviRBto9L8QAg
|
||||
bCXZTqnXzn//brskmW8IZgqjAlf89AWa53piucu9/hgidrHRZobs5gTqev28uJdc
|
||||
zcuw1g8c3nCpY9WeTjHODzX5NXYRLFpkazLfYa6c8Q9jZR4KKrpdM+66fxL0JlOd
|
||||
7dN0oQtEqEAugsd3cwkZgvWhY4oM7FGErrZoDLy273ZdJzi/vU+dThyVzfD8Ab8u
|
||||
VxxuobVMT/S608zbe+uaiUdov5s96OkCl87403UNKJBH+6LNb3rjBBLE9NPN5ET9
|
||||
JLQMrYd+zj8jQwKCAQEA7uU5I9MOufo9bIgJqjY4Ie1+Ex9DZEMUYFAvGNCJCVcS
|
||||
mwOdGF8AWzIavTLACmEDJO7t/OrBdoo4L7IEsCNjgA3WiIwIMiWUVqveAGUMEXr6
|
||||
TRI5EolV6FTqqIP6AS+BAeBq7G1ELgsTrWNHh11rW3+3kBMuOCn77PUQ8WHwcq/r
|
||||
teZcZn4Ewcr6P7cBODgVvnBPhe/J8xHS0HFVCeS1CvaiNYgees5yA80Apo9IPjDJ
|
||||
YWawLjmH5wUBI5yDFVp067wjqJnoKPSoKwWkZXqUk+zgFXx5KT0gh/c5yh1frASp
|
||||
q6oaYnHEVC5qj2SpT1GFLonTcrQUXiSkiUudvNu1GQKCAQEAmko+5GFtRe0ihgLQ
|
||||
4S76r6diJli6AKil1Fg3U1r6zZpBQ1PJtJxTJQyN9w5Z7q6tF/GqAesrzxevQdvQ
|
||||
rCImAPtA3ZofC2UXawMnIjWHHx6diNvYnV1+gtUQ4nO1dSOFZ5VZFcUmPiZO6boF
|
||||
oaryj3FcX+71JcJCjEvrlKhA9Es0hXUkvfMxfs5if4he1zlyHpTWYr4oA4egUugq
|
||||
P0mwskikc3VIyvEO+NyjgFxo72yLPkFSzemkidN8uKDyFqKtnlfGM7OuA2CY1WZa
|
||||
3+67lXWshx9KzyJIs92iCYkU8EoPxtdYzyrV6efdX7x27v60zTOut5TnJJS6WiF6
|
||||
Do5MkwKCAQAxoR9IyP0DN/BwzqYrXU42Bi+t603F04W1KJNQNWpyrUspNwv41yus
|
||||
xnD1o0hwH41Wq+h3JZIBfV+E0RfWO9Pc84MBJQ5C1LnHc7cQH+3s575+Km3+4tcd
|
||||
CB8j2R8kBeloKWYtLdn/Mr/ownpGreqyvIq2/LUaZ+Z1aMgXTYB1YwS16mCBzmZQ
|
||||
mEl62RsAwe4KfSyYJ6OtwqMoOJMxFfliiLBULK4gVykqjvk2oQeiG+KKQJoTUFJi
|
||||
dRCyhD5bPkqR+qjxyt+HOqSBI4/uoROi05AOBqjpH1DVzk+MJKQOiX1yM0l98CKY
|
||||
Vng+x+vAla/0Zh+ucajVkgk4mKPxazdpAoIBAQC17vWk4KYJpF2RC3pKPcQ0PdiX
|
||||
bN35YNlvyhkYlSfDNdyH3aDrGiycUyW2mMXUgEDFsLRxHMTL+zPC6efqO6sTAJDY
|
||||
cBptsW4drW/qo8NTx3dNOisLkW+mGGJOR/w157hREFr29ymCVMYu/Z7fVWIeSpCq
|
||||
p3u8YX8WTljrxwSczlGjvpM7uJx3SfYRM4TUoy+8wU8bK74LywLa5f60bQY6Dye0
|
||||
Gqd9O6OoPfgcQlwjC5MiAofeqwPJvU0hQOPoehZyNLAmOCWXTYWaTP7lxO1r6+NE
|
||||
M3hGYqW3W8Ixua71OskCypBZg/HVlIP/lzjRzdx+VOB2hbWVth2Iup/Z1egW
|
||||
-----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) {
|
||||
c := Conf{
|
||||
BindPort: 10000,
|
||||
Bindings: []Binding{
|
||||
{
|
||||
Port: 10000,
|
||||
},
|
||||
},
|
||||
}
|
||||
require.True(t, c.ShouldBind())
|
||||
|
||||
c.BindPort = 0
|
||||
c.Bindings[0].Port = 0
|
||||
require.False(t, c.ShouldBind())
|
||||
|
||||
if runtime.GOOS != osWindows {
|
||||
c.BindAddress = "/absolute/path"
|
||||
c.Bindings[0].Address = "/absolute/path"
|
||||
require.True(t, c.ShouldBind())
|
||||
}
|
||||
}
|
||||
|
@ -406,3 +642,66 @@ func TestQuotaScanInvalidFs(t *testing.T) {
|
|||
err := doQuotaScan(user)
|
||||
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 (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
|
@ -27,14 +29,11 @@ type httpdServer struct {
|
|||
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{
|
||||
binding: Binding{
|
||||
Address: bindAddress,
|
||||
Port: bindPort,
|
||||
},
|
||||
binding: b,
|
||||
staticFilesPath: staticFilesPath,
|
||||
enableWebAdmin: enableWebAdmin,
|
||||
enableWebAdmin: enableWebAdmin && b.EnableWebAdmin,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -48,17 +47,49 @@ func (s *httpdServer) listenAndServe() error {
|
|||
MaxHeaderBytes: 1 << 16, // 64KB
|
||||
ErrorLog: log.New(&logger.StdLoggerWrapper{Sender: logSender}, "", 0),
|
||||
}
|
||||
if certMgr != nil {
|
||||
if certMgr != nil && s.binding.EnableHTTPS {
|
||||
config := &tls.Config{
|
||||
GetCertificate: certMgr.GetCertificateFunc(),
|
||||
MinVersion: tls.VersionTLS12,
|
||||
}
|
||||
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, 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 {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
s.checkCookieExpiration(w, r)
|
||||
|
|
|
@ -47,7 +47,7 @@ func (s *Service) StartPortableMode(sftpdPort, ftpPort, webdavPort int, enabledS
|
|||
dataProviderConf.PreferDatabaseCredentials = true
|
||||
config.SetProviderConf(dataProviderConf)
|
||||
httpdConf := config.GetHTTPDConfig()
|
||||
httpdConf.BindPort = 0
|
||||
httpdConf.Bindings = nil
|
||||
config.SetHTTPDConfig(httpdConf)
|
||||
sftpdConf := config.GetSFTPDConfig()
|
||||
sftpdConf.MaxAuthTries = 12
|
||||
|
|
|
@ -236,7 +236,7 @@ func TestMain(m *testing.M) {
|
|||
}()
|
||||
|
||||
waitTCPListening(sftpdConf.Bindings[0].GetAddress())
|
||||
waitTCPListening(fmt.Sprintf("%s:%d", httpdConf.BindAddress, httpdConf.BindPort))
|
||||
waitTCPListening(httpdConf.Bindings[0].GetAddress())
|
||||
|
||||
sftpdConf.Bindings = []sftpd.Binding{
|
||||
{
|
||||
|
|
15
sftpgo.json
15
sftpgo.json
|
@ -147,13 +147,22 @@
|
|||
"update_mode": 0
|
||||
},
|
||||
"httpd": {
|
||||
"bind_port": 8080,
|
||||
"bind_address": "127.0.0.1",
|
||||
"bindings": [
|
||||
{
|
||||
"port": 8080,
|
||||
"address": "127.0.0.1",
|
||||
"enable_web_admin": true,
|
||||
"enable_https": false,
|
||||
"client_auth_type": 0
|
||||
}
|
||||
],
|
||||
"templates_path": "templates",
|
||||
"static_files_path": "static",
|
||||
"backups_path": "backups",
|
||||
"certificate_file": "",
|
||||
"certificate_key_file": ""
|
||||
"certificate_key_file": "",
|
||||
"ca_certificates": [],
|
||||
"ca_revocation_lists": []
|
||||
},
|
||||
"telemetry": {
|
||||
"bind_port": 10000,
|
||||
|
|
|
@ -143,7 +143,7 @@ func TestMain(m *testing.M) {
|
|||
}
|
||||
|
||||
httpdConf := config.GetHTTPDConfig()
|
||||
httpdConf.BindPort = 8078
|
||||
httpdConf.Bindings[0].Port = 8078
|
||||
httpdtest.SetBaseURL("http://127.0.0.1:8078")
|
||||
|
||||
// required to test sftpfs
|
||||
|
@ -211,7 +211,7 @@ func TestMain(m *testing.M) {
|
|||
}()
|
||||
|
||||
waitTCPListening(webDavConf.Bindings[0].GetAddress())
|
||||
waitTCPListening(fmt.Sprintf("%s:%d", httpdConf.BindAddress, httpdConf.BindPort))
|
||||
waitTCPListening(httpdConf.Bindings[0].GetAddress())
|
||||
waitTCPListening(sftpdConf.Bindings[0].GetAddress())
|
||||
webdavd.ReloadCertificateMgr() //nolint:errcheck
|
||||
|
||||
|
|
Loading…
Reference in a new issue