add support for multiple bindings

Fixes #253
This commit is contained in:
Nicola Murino
2020-12-23 16:12:30 +01:00
parent 743b350fdd
commit c69d63c1f8
24 changed files with 1173 additions and 269 deletions

View File

@@ -159,7 +159,11 @@ func TestUserInvalidParams(t *testing.T) {
HomeDir: "invalid",
}
c := &Configuration{
BindPort: 9000,
Bindings: []Binding{
{
Port: 9000,
},
},
}
server, err := newServer(c, configDir)
assert.NoError(t, err)
@@ -670,7 +674,11 @@ func TestBasicUsersCache(t *testing.T) {
assert.NoError(t, err)
c := &Configuration{
BindPort: 9000,
Bindings: []Binding{
{
Port: 9000,
},
},
Cache: Cache{
Users: UsersCacheConfig{
MaxSize: 50,
@@ -782,7 +790,11 @@ func TestUsersCacheSizeAndExpiration(t *testing.T) {
assert.NoError(t, err)
c := &Configuration{
BindPort: 9000,
Bindings: []Binding{
{
Port: 9000,
},
},
Cache: Cache{
Users: UsersCacheConfig{
MaxSize: 3,
@@ -926,7 +938,11 @@ func TestUsersCacheSizeAndExpiration(t *testing.T) {
func TestRecoverer(t *testing.T) {
c := &Configuration{
BindPort: 9000,
Bindings: []Binding{
{
Port: 9000,
},
},
}
server, err := newServer(c, configDir)
assert.NoError(t, err)

View File

@@ -53,13 +53,9 @@ func newServer(config *Configuration, configDir string) (*webDavServer, error) {
return server, nil
}
func (s *webDavServer) listenAndServe() error {
addr := fmt.Sprintf("%s:%d", s.config.BindAddress, s.config.BindPort)
s.status.IsActive = true
s.status.Address = addr
s.status.Protocol = "HTTP"
func (s *webDavServer) listenAndServe(binding Binding) error {
httpServer := &http.Server{
Addr: addr,
Addr: binding.GetAddress(),
Handler: server,
ReadHeaderTimeout: 30 * time.Second,
IdleTimeout: 120 * time.Second,
@@ -76,17 +72,17 @@ func (s *webDavServer) listenAndServe() error {
OptionsPassthrough: true,
})
httpServer.Handler = c.Handler(server)
} else {
httpServer.Handler = server
}
if s.certMgr != nil {
s.status.Protocol = "HTTPS"
if s.certMgr != nil && binding.EnableHTTPS {
server.status.Bindings = append(server.status.Bindings, binding)
httpServer.TLSConfig = &tls.Config{
GetCertificate: s.certMgr.GetCertificateFunc(),
MinVersion: tls.VersionTLS12,
}
return httpServer.ListenAndServeTLS("", "")
}
binding.EnableHTTPS = false
server.status.Bindings = append(server.status.Bindings, binding)
return httpServer.ListenAndServe()
}

View File

@@ -2,8 +2,10 @@
package webdavd
import (
"fmt"
"path/filepath"
"github.com/drakkan/sftpgo/common"
"github.com/drakkan/sftpgo/logger"
"github.com/drakkan/sftpgo/utils"
)
@@ -25,9 +27,8 @@ var (
// ServiceStatus defines the service status
type ServiceStatus struct {
IsActive bool `json:"is_active"`
Address string `json:"address"`
Protocol string `json:"protocol"`
IsActive bool `json:"is_active"`
Bindings []Binding `json:"bindings"`
}
// Cors configuration
@@ -59,11 +60,33 @@ type Cache struct {
MimeTypes MimeCacheConfig `json:"mime_types" mapstructure:"mime_types"`
}
// Binding defines the configuration for a network listener
type Binding struct {
// The address to listen on. A blank value means listen on all available network interfaces.
Address string `json:"address" mapstructure:"address"`
// The port used for serving requests
Port int `json:"port" mapstructure:"port"`
// you also need to provide a certificate for enabling HTTPS
EnableHTTPS bool `json:"enable_https" mapstructure:"enable_https"`
}
// 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 port is > 0
func (b *Binding) IsValid() bool {
return b.Port > 0
}
// Configuration defines the configuration for the WevDAV server
type Configuration struct {
// The port used for serving FTP requests
// 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.
// Deprecated: please use Bindings
BindAddress string `json:"bind_address" mapstructure:"bind_address"`
// If files containing a certificate and matching private key for the server are provided the server will expect
// HTTPS connections.
@@ -85,6 +108,17 @@ func GetStatus() ServiceStatus {
return server.status
}
// ShouldBind returns true if there is at least a valid binding
func (c *Configuration) ShouldBind() bool {
for _, binding := range c.Bindings {
if binding.IsValid() {
return true
}
}
return false
}
// Initialize configures and starts the WebDAV server
func (c *Configuration) Initialize(configDir string) error {
var err error
@@ -96,11 +130,31 @@ func (c *Configuration) Initialize(configDir string) error {
if !c.Cache.MimeTypes.Enabled {
mimeTypeCache.maxSize = 0
}
if !c.ShouldBind() {
return common.ErrNoBinding
}
server, err = newServer(c, configDir)
if err != nil {
return err
}
return server.listenAndServe()
server.status.Bindings = nil
exitChannel := make(chan error)
for _, binding := range c.Bindings {
if !binding.IsValid() {
continue
}
go func(binding Binding) {
exitChannel <- server.listenAndServe(binding)
}(binding)
}
server.status.IsActive = true
return <-exitChannel
}
// ReloadTLSCertificate reloads the TLS certificate and key from the configured paths

View File

@@ -32,6 +32,7 @@ import (
"github.com/drakkan/sftpgo/httpd"
"github.com/drakkan/sftpgo/kms"
"github.com/drakkan/sftpgo/logger"
"github.com/drakkan/sftpgo/sftpd"
"github.com/drakkan/sftpgo/vfs"
"github.com/drakkan/sftpgo/webdavd"
)
@@ -143,11 +144,19 @@ func TestMain(m *testing.M) {
// required to test sftpfs
sftpdConf := config.GetSFTPDConfig()
sftpdConf.BindPort = 9022
sftpdConf.Bindings = []sftpd.Binding{
{
Port: 9022,
},
}
sftpdConf.HostKeys = []string{filepath.Join(os.TempDir(), "id_ecdsa")}
webDavConf := config.GetWebDAVDConfig()
webDavConf.BindPort = webDavServerPort
webDavConf.Bindings = []webdavd.Binding{
{
Port: webDavServerPort,
},
}
webDavConf.Cors = webdavd.Cors{
Enabled: true,
AllowedOrigins: []string{"*"},
@@ -196,9 +205,9 @@ func TestMain(m *testing.M) {
}
}()
waitTCPListening(fmt.Sprintf("%s:%d", webDavConf.BindAddress, webDavConf.BindPort))
waitTCPListening(webDavConf.Bindings[0].GetAddress())
waitTCPListening(fmt.Sprintf("%s:%d", httpdConf.BindAddress, httpdConf.BindPort))
waitTCPListening(fmt.Sprintf("%s:%d", sftpdConf.BindAddress, sftpdConf.BindPort))
waitTCPListening(sftpdConf.Bindings[0].GetAddress())
webdavd.ReloadTLSCertificate() //nolint:errcheck
exitCode := m.Run()
@@ -213,7 +222,15 @@ func TestMain(m *testing.M) {
func TestInitialization(t *testing.T) {
cfg := webdavd.Configuration{
BindPort: 1234,
Bindings: []webdavd.Binding{
{
Port: 1234,
EnableHTTPS: true,
},
{
Port: 0,
},
},
CertificateFile: "missing path",
CertificateKeyFile: "bad path",
}
@@ -221,13 +238,21 @@ func TestInitialization(t *testing.T) {
assert.Error(t, err)
cfg.Cache = config.GetWebDAVDConfig().Cache
cfg.BindPort = webDavServerPort
cfg.Bindings[0].Port = webDavServerPort
cfg.CertificateFile = certPath
cfg.CertificateKeyFile = keyPath
err = cfg.Initialize(configDir)
assert.Error(t, err)
err = webdavd.ReloadTLSCertificate()
assert.NoError(t, err)
cfg.Bindings = []webdavd.Binding{
{
Port: 0,
},
}
err = cfg.Initialize(configDir)
assert.EqualError(t, err, common.ErrNoBinding.Error())
}
func TestBasicHandling(t *testing.T) {