mirror of
https://github.com/drakkan/sftpgo.git
synced 2024-11-25 09:00:27 +00:00
httpd: make the built-in web interface optional
The built-in web admin will be disabled if both "templates_path" and "static_files_path" are empty Fixes #131
This commit is contained in:
parent
e86089a9f3
commit
b30614e9d8
5 changed files with 61 additions and 29 deletions
|
@ -102,7 +102,7 @@ The configuration file contains the following sections:
|
|||
- `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. 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
|
||||
- `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
|
||||
- `auth_user_file`, string. Path to a file used to store usernames and passwords for basic authentication. This can be an absolute path or a path relative to the config dir. We support HTTP basic authentication, and the file format must conform to the one generated using the Apache `htpasswd` tool. The supported password formats are bcrypt (`$2y$` prefix) and md5 crypt (`$apr1$` prefix). If empty, HTTP authentication is disabled.
|
||||
- `certificate_file`, string. Certificate for HTTPS. This can be an absolute path or a path relative to the config dir.
|
||||
|
|
|
@ -61,7 +61,8 @@ type Conf struct {
|
|||
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"`
|
||||
// Path to the static files for the web interface. This can be an absolute path or a path relative to the config dir
|
||||
// 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 TemplatesPath and StaticFilesPath are empty the built-in web interface will be disabled
|
||||
StaticFilesPath string `json:"static_files_path" mapstructure:"static_files_path"`
|
||||
// Path to the backup directory. This can be an absolute path or a path relative to the config dir
|
||||
BackupsPath string `json:"backups_path" mapstructure:"backups_path"`
|
||||
|
@ -91,15 +92,19 @@ func SetDataProvider(provider dataprovider.Provider) {
|
|||
}
|
||||
|
||||
// Initialize configures and starts the HTTP server
|
||||
func (c Conf) Initialize(configDir string, profiler bool) error {
|
||||
func (c Conf) Initialize(configDir string, enableProfiler bool) error {
|
||||
var err error
|
||||
logger.Debug(logSender, "", "initializing HTTP server with config %+v", c)
|
||||
backupsPath = getConfigPath(c.BackupsPath, configDir)
|
||||
staticFilesPath := getConfigPath(c.StaticFilesPath, configDir)
|
||||
templatesPath := getConfigPath(c.TemplatesPath, configDir)
|
||||
if len(backupsPath) == 0 || len(staticFilesPath) == 0 || len(templatesPath) == 0 {
|
||||
return fmt.Errorf("Required directory is invalid, backup path %#v, static file path: %#v template path: %#v",
|
||||
backupsPath, staticFilesPath, templatesPath)
|
||||
enableWebAdmin := len(staticFilesPath) > 0 || len(templatesPath) > 0
|
||||
if len(backupsPath) == 0 {
|
||||
return fmt.Errorf("Required directory is invalid, backup path %#v", backupsPath)
|
||||
}
|
||||
if enableWebAdmin && (len(staticFilesPath) == 0 || len(templatesPath) == 0) {
|
||||
return fmt.Errorf("Required directory is invalid, static file path: %#v template path: %#v",
|
||||
staticFilesPath, templatesPath)
|
||||
}
|
||||
authUserFile := getConfigPath(c.AuthUserFile, configDir)
|
||||
httpAuth, err = newBasicAuthProvider(authUserFile)
|
||||
|
@ -108,8 +113,12 @@ func (c Conf) Initialize(configDir string, profiler bool) error {
|
|||
}
|
||||
certificateFile := getConfigPath(c.CertificateFile, configDir)
|
||||
certificateKeyFile := getConfigPath(c.CertificateKeyFile, configDir)
|
||||
loadTemplates(templatesPath)
|
||||
initializeRouter(staticFilesPath, profiler)
|
||||
if enableWebAdmin {
|
||||
loadTemplates(templatesPath)
|
||||
} else {
|
||||
logger.Info(logSender, "", "built-in web interface disabled, please set templates_path and static_files_path to enable it")
|
||||
}
|
||||
initializeRouter(staticFilesPath, enableProfiler, enableWebAdmin)
|
||||
httpServer := &http.Server{
|
||||
Addr: fmt.Sprintf("%s:%d", c.BindAddress, c.BindPort),
|
||||
Handler: router,
|
||||
|
|
|
@ -178,15 +178,16 @@ func TestMain(m *testing.M) {
|
|||
func TestInitialization(t *testing.T) {
|
||||
err := config.LoadConfig(configDir, "")
|
||||
assert.NoError(t, err)
|
||||
invalidFile := "invalid file"
|
||||
httpdConf := config.GetHTTPDConfig()
|
||||
httpdConf.BackupsPath = "test_backups"
|
||||
httpdConf.AuthUserFile = "invalid_file"
|
||||
httpdConf.AuthUserFile = invalidFile
|
||||
err = httpdConf.Initialize(configDir, true)
|
||||
assert.Error(t, err)
|
||||
httpdConf.BackupsPath = backupsPath
|
||||
httpdConf.AuthUserFile = ""
|
||||
httpdConf.CertificateFile = "invalid file"
|
||||
httpdConf.CertificateKeyFile = "invalid file"
|
||||
httpdConf.CertificateFile = invalidFile
|
||||
httpdConf.CertificateKeyFile = invalidFile
|
||||
err = httpdConf.Initialize(configDir, true)
|
||||
assert.Error(t, err)
|
||||
httpdConf.CertificateFile = ""
|
||||
|
@ -196,6 +197,17 @@ func TestInitialization(t *testing.T) {
|
|||
assert.Error(t, err)
|
||||
err = httpd.ReloadTLSCertificate()
|
||||
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, true)
|
||||
assert.Error(t, err)
|
||||
httpdConf.BackupsPath = backupsPath
|
||||
httpdConf.CertificateFile = invalidFile
|
||||
httpdConf.CertificateKeyFile = invalidFile
|
||||
httpdConf.StaticFilesPath = ""
|
||||
httpdConf.TemplatesPath = ""
|
||||
err = httpdConf.Initialize(configDir, true)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestBasicUserHandling(t *testing.T) {
|
||||
|
|
|
@ -7,10 +7,10 @@ import (
|
|||
"github.com/go-chi/chi"
|
||||
"github.com/go-chi/chi/middleware"
|
||||
"github.com/go-chi/render"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
|
||||
"github.com/drakkan/sftpgo/dataprovider"
|
||||
"github.com/drakkan/sftpgo/logger"
|
||||
"github.com/drakkan/sftpgo/metrics"
|
||||
"github.com/drakkan/sftpgo/sftpd"
|
||||
"github.com/drakkan/sftpgo/utils"
|
||||
)
|
||||
|
@ -20,14 +20,14 @@ func GetHTTPRouter() http.Handler {
|
|||
return router
|
||||
}
|
||||
|
||||
func initializeRouter(staticFilesPath string, profiler bool) {
|
||||
func initializeRouter(staticFilesPath string, enableProfiler, enableWebAdmin bool) {
|
||||
router = chi.NewRouter()
|
||||
router.Use(middleware.RequestID)
|
||||
router.Use(middleware.RealIP)
|
||||
router.Use(logger.NewStructuredLogger(logger.GetLogger()))
|
||||
router.Use(middleware.Recoverer)
|
||||
|
||||
if profiler {
|
||||
if enableProfiler {
|
||||
logger.InfoToConsole("enabling the built-in profiler")
|
||||
logger.Info(logSender, "", "enabling the built-in profiler")
|
||||
router.Mount(pprofBasePath, middleware.Profiler())
|
||||
|
@ -52,7 +52,7 @@ func initializeRouter(staticFilesPath string, profiler bool) {
|
|||
http.Redirect(w, r, webUsersPath, http.StatusMovedPermanently)
|
||||
})
|
||||
|
||||
router.Handle(metricsPath, promhttp.Handler())
|
||||
metrics.AddMetricsEndpoint(metricsPath, router)
|
||||
|
||||
router.Get(versionPath, func(w http.ResponseWriter, r *http.Request) {
|
||||
render.JSON(w, r, utils.GetAppVersion())
|
||||
|
@ -86,22 +86,26 @@ func initializeRouter(staticFilesPath string, profiler bool) {
|
|||
router.Delete(folderPath, deleteFolderByPath)
|
||||
router.Get(dumpDataPath, dumpData)
|
||||
router.Get(loadDataPath, loadData)
|
||||
router.Get(webUsersPath, handleGetWebUsers)
|
||||
router.Get(webUserPath, handleWebAddUserGet)
|
||||
router.Get(webUserPath+"/{userID}", handleWebUpdateUserGet)
|
||||
router.Post(webUserPath, handleWebAddUserPost)
|
||||
router.Post(webUserPath+"/{userID}", handleWebUpdateUserPost)
|
||||
router.Get(webConnectionsPath, handleWebGetConnections)
|
||||
router.Get(webFoldersPath, handleWebGetFolders)
|
||||
router.Get(webFolderPath, handleWebAddFolderGet)
|
||||
router.Post(webFolderPath, handleWebAddFolderPost)
|
||||
if enableWebAdmin {
|
||||
router.Get(webUsersPath, handleGetWebUsers)
|
||||
router.Get(webUserPath, handleWebAddUserGet)
|
||||
router.Get(webUserPath+"/{userID}", handleWebUpdateUserGet)
|
||||
router.Post(webUserPath, handleWebAddUserPost)
|
||||
router.Post(webUserPath+"/{userID}", handleWebUpdateUserPost)
|
||||
router.Get(webConnectionsPath, handleWebGetConnections)
|
||||
router.Get(webFoldersPath, handleWebGetFolders)
|
||||
router.Get(webFolderPath, handleWebAddFolderGet)
|
||||
router.Post(webFolderPath, handleWebAddFolderPost)
|
||||
}
|
||||
})
|
||||
|
||||
router.Group(func(router chi.Router) {
|
||||
compressor := middleware.NewCompressor(5)
|
||||
router.Use(compressor.Handler)
|
||||
fileServer(router, webStaticFilesPath, http.Dir(staticFilesPath))
|
||||
})
|
||||
if enableWebAdmin {
|
||||
router.Group(func(router chi.Router) {
|
||||
compressor := middleware.NewCompressor(5)
|
||||
router.Use(compressor.Handler)
|
||||
fileServer(router, webStaticFilesPath, http.Dir(staticFilesPath))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func handleCloseConnection(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
|
@ -2,8 +2,10 @@
|
|||
package metrics
|
||||
|
||||
import (
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -13,6 +15,11 @@ const (
|
|||
loginMethodKeyAndKeyboardInt = "publickey+keyboard-interactive"
|
||||
)
|
||||
|
||||
// AddMetricsEndpoint exposes metrics to the specified endpoint
|
||||
func AddMetricsEndpoint(metricsPath string, handler chi.Router) {
|
||||
handler.Handle(metricsPath, promhttp.Handler())
|
||||
}
|
||||
|
||||
var (
|
||||
// dataproviderAvailability is the metric that reports the availability for the configured data provider
|
||||
dataproviderAvailability = promauto.NewGauge(prometheus.GaugeOpts{
|
||||
|
|
Loading…
Reference in a new issue