Преглед изворни кода

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
Nicola Murino пре 5 година
родитељ
комит
b30614e9d8
5 измењених фајлова са 61 додато и 29 уклоњено
  1. 1 1
      docs/full-configuration.md
  2. 16 7
      httpd/httpd.go
  3. 15 3
      httpd/httpd_test.go
  4. 22 18
      httpd/router.go
  5. 7 0
      metrics/metrics.go

+ 1 - 1
docs/full-configuration.md

@@ -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.

+ 16 - 7
httpd/httpd.go

@@ -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,

+ 15 - 3
httpd/httpd_test.go

@@ -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) {

+ 22 - 18
httpd/router.go

@@ -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) {

+ 7 - 0
metrics/metrics.go

@@ -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{