From 26f0b0d0cee07f664d13705d010eb3bac35b825f Mon Sep 17 00:00:00 2001 From: Hector Date: Thu, 13 Jan 2022 20:34:42 +0000 Subject: [PATCH] store basic auth credentials as hash instead of raw value Update how the basic auth credentials are stored in the application to use a hashed value instead of the raw value. This prevents the raw value from being accidentally leaked. Add new `auth` package for functions related to authentication. --- src/auth/hash.go | 14 ++++++++++++++ src/auth/middleware.go | 30 ++++++++++++++++++++++++++++++ src/cfg/cfg.go | 14 ++++++++++++-- src/exporter.go | 20 +++----------------- 4 files changed, 59 insertions(+), 19 deletions(-) create mode 100644 src/auth/hash.go create mode 100644 src/auth/middleware.go diff --git a/src/auth/hash.go b/src/auth/hash.go new file mode 100644 index 0000000..e1bf8e2 --- /dev/null +++ b/src/auth/hash.go @@ -0,0 +1,14 @@ +package auth + +import ( + "crypto/sha256" +) + +func Hash(data []byte) []byte { + b := sha256.Sum256(data) + return b[:] +} + +func HashString(data string) string { + return string(Hash([]byte(data))) +} diff --git a/src/auth/middleware.go b/src/auth/middleware.go new file mode 100644 index 0000000..f83c291 --- /dev/null +++ b/src/auth/middleware.go @@ -0,0 +1,30 @@ +package auth + +import ( + "fail2ban-prometheus-exporter/cfg" + "net/http" +) + +func BasicAuthMiddleware(handlerFunc http.HandlerFunc, appSettings *cfg.AppSettings) http.HandlerFunc { + authEnabled := len(appSettings.BasicAuthUsername) > 0 && len(appSettings.BasicAuthPassword) > 0 + if authEnabled { + return func(w http.ResponseWriter, r *http.Request) { + if doesBasicAuthMatch(r, appSettings) { + w.WriteHeader(http.StatusUnauthorized) + } else { + handlerFunc.ServeHTTP(w, r) + } + } + } + return handlerFunc +} + +func doesBasicAuthMatch(r *http.Request, appSettings *cfg.AppSettings) bool { + rawUsername, rawPassword, ok := r.BasicAuth() + if ok { + username := HashString(rawUsername) + password := HashString(rawPassword) + return username == appSettings.BasicAuthUsername && password == appSettings.BasicAuthPassword + } + return false +} diff --git a/src/cfg/cfg.go b/src/cfg/cfg.go index 85d291c..4b8da26 100644 --- a/src/cfg/cfg.go +++ b/src/cfg/cfg.go @@ -1,6 +1,7 @@ package cfg import ( + "fail2ban-prometheus-exporter/auth" "flag" "fmt" "os" @@ -23,6 +24,9 @@ type AppSettings struct { } func Parse() *AppSettings { + var rawBasicAuthUsername string + var rawBasicAuthPassword string + appSettings := &AppSettings{} flag.BoolVar(&appSettings.VersionMode, "version", false, "show version info and exit") flag.StringVar(&appSettings.MetricsAddress, "web.listen-address", "0.0.0.0", "address to use for the metrics server") @@ -30,14 +34,20 @@ func Parse() *AppSettings { flag.StringVar(&appSettings.Fail2BanSocketPath, "socket", "", "path to the fail2ban server socket") flag.BoolVar(&appSettings.FileCollectorEnabled, "collector.textfile", false, "enable the textfile collector") flag.StringVar(&appSettings.FileCollectorPath, "collector.textfile.directory", "", "directory to read text files with metrics from") - flag.StringVar(&appSettings.BasicAuthUsername, "web.basic-auth.username", "", "username to use to protect endpoints with basic auth") - flag.StringVar(&appSettings.BasicAuthPassword, "web.basic-auth.password", "", "password to use to protect endpoints with basic auth") + flag.StringVar(&rawBasicAuthUsername, "web.basic-auth.username", "", "username to use to protect endpoints with basic auth") + flag.StringVar(&rawBasicAuthPassword, "web.basic-auth.password", "", "password to use to protect endpoints with basic auth") flag.Parse() + appSettings.setBasicAuthValues(rawBasicAuthUsername, rawBasicAuthPassword) appSettings.validateFlags() return appSettings } +func (settings *AppSettings) setBasicAuthValues(username, password string) { + settings.BasicAuthUsername = auth.HashString(username) + settings.BasicAuthPassword = auth.HashString(password) +} + func (settings *AppSettings) validateFlags() { var flagsValid = true if !settings.VersionMode { diff --git a/src/exporter.go b/src/exporter.go index 843b861..743b577 100644 --- a/src/exporter.go +++ b/src/exporter.go @@ -1,6 +1,7 @@ package main import ( + "fail2ban-prometheus-exporter/auth" "fail2ban-prometheus-exporter/cfg" "fail2ban-prometheus-exporter/collector/f2b" "fail2ban-prometheus-exporter/collector/textfile" @@ -48,21 +49,6 @@ func metricHandler(w http.ResponseWriter, r *http.Request, collector *textfile.C collector.WriteTextFileMetrics(w, r) } -func authMiddleware(handlerFunc http.HandlerFunc, appSettings *cfg.AppSettings) http.HandlerFunc { - authEnabled := len(appSettings.BasicAuthUsername) > 0 && len(appSettings.BasicAuthPassword) > 0 - if authEnabled { - return func(w http.ResponseWriter, r *http.Request) { - username, password, ok := r.BasicAuth() - if !ok || username != appSettings.BasicAuthUsername || password != appSettings.BasicAuthPassword { - w.WriteHeader(http.StatusUnauthorized) - return - } - handlerFunc.ServeHTTP(w, r) - } - } - return handlerFunc -} - func main() { appSettings := cfg.Parse() if appSettings.VersionMode { @@ -78,8 +64,8 @@ func main() { textFileCollector := textfile.NewCollector(appSettings) prometheus.MustRegister(textFileCollector) - http.HandleFunc("/", authMiddleware(rootHtmlHandler, appSettings)) - http.HandleFunc(metricsPath, authMiddleware( + http.HandleFunc("/", auth.BasicAuthMiddleware(rootHtmlHandler, appSettings)) + http.HandleFunc(metricsPath, auth.BasicAuthMiddleware( func(w http.ResponseWriter, r *http.Request) { metricHandler(w, r, textFileCollector) },