From 437ed4d393ff2a60a25fe1d1cd552759c73d3ea0 Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Mercier Date: Mon, 6 Jun 2022 10:48:06 +0200 Subject: [PATCH] dashboard: Can restrict access through htpasswd --- dashboard/app.go | 7 +++- dashboard/htpasswd.go | 78 +++++++++++++++++++++++++++++++++++++++++++ dashboard/main.go | 3 +- go.mod | 1 + go.sum | 2 ++ 5 files changed, 89 insertions(+), 2 deletions(-) create mode 100644 dashboard/htpasswd.go diff --git a/dashboard/app.go b/dashboard/app.go index b3cf5171..7dda3e8b 100644 --- a/dashboard/app.go +++ b/dashboard/app.go @@ -15,7 +15,7 @@ type App struct { bind string } -func NewApp(baseURL string, bind string) App { +func NewApp(htpasswd_file *string, baseURL string, bind string) App { gin.ForceConsoleColor() router := gin.Default() @@ -29,6 +29,11 @@ func NewApp(baseURL string, bind string) App { } else { baserouter = router.Group("") } + + if htpasswd_file != nil && len(*htpasswd_file) > 0 { + baserouter.Use(Htpassword(*htpasswd_file)) + } + declareStaticRoutes(baserouter, baseURL) app := App{ diff --git a/dashboard/htpasswd.go b/dashboard/htpasswd.go new file mode 100644 index 00000000..1f62bf9a --- /dev/null +++ b/dashboard/htpasswd.go @@ -0,0 +1,78 @@ +package main + +import ( + "bufio" + "fmt" + "log" + "net/http" + "os" + "strings" + + "github.com/gin-gonic/gin" + "gitlab.com/nyarla/go-crypt" +) + +func Htpassword(file string) func(c *gin.Context) { + htpasswd := &Htpasswd{} + + log.Println("Reading htpasswd file...") + var err error + if htpasswd, err = NewHtpasswd(file); htpasswd == nil { + log.Fatal("Unable to parse htpasswd:", err) + } + + return func(c *gin.Context) { + username, password, ok := c.Request.BasicAuth() + if !ok { + c.Writer.Header().Add("WWW-Authenticate", "Basic realm=\"FIC challenge Dashboard\"") + c.AbortWithError(http.StatusUnauthorized, fmt.Errorf("Please login")) + return + } + + if !htpasswd.Authenticate(username, password) { + c.Writer.Header().Add("WWW-Authenticate", "Basic realm=\"FIC challenge Dashboard\"") + c.AbortWithError(http.StatusUnauthorized, fmt.Errorf("Not authorized")) + return + } + + c.Next() + } +} + +type Htpasswd struct { + entries map[string]string +} + +func NewHtpasswd(path string) (*Htpasswd, error) { + if fd, err := os.Open(path); err != nil { + return nil, err + } else { + defer fd.Close() + + htpasswd := Htpasswd{ + map[string]string{}, + } + + scanner := bufio.NewScanner(fd) + for scanner.Scan() { + line := strings.SplitN(strings.TrimSpace(scanner.Text()), ":", 2) + if len(line) == 2 && len(line[1]) > 2 { + htpasswd.entries[line[0]] = line[1] + } + } + if err := scanner.Err(); err != nil { + return nil, err + } + return &htpasswd, nil + } +} + +func (h Htpasswd) Authenticate(username, password string) bool { + if hash, ok := h.entries[username]; !ok { + return false + } else if crypt.Crypt(password, hash[:2]) != hash { + return false + } else { + return true + } +} diff --git a/dashboard/main.go b/dashboard/main.go index a9e2b17e..9a245ca5 100644 --- a/dashboard/main.go +++ b/dashboard/main.go @@ -69,6 +69,7 @@ func main() { // Read parameters from command line var bind = flag.String("bind", "127.0.0.1:8082", "Bind port/socket") + htpasswd_file := flag.String("htpasswd", "", "Restrict access with password, Apache htpasswd format") flag.StringVar(&baseURL, "baseurl", baseURL, "URL prepended to each URL") staticDir := flag.String("static", "./htdocs-dashboard/", "Directory containing static files") flag.StringVar(&fic.FilesDir, "files", fic.FilesDir, "Base directory where found challenges files, local part") @@ -118,7 +119,7 @@ func main() { interrupt := make(chan os.Signal, 1) signal.Notify(interrupt, os.Interrupt, syscall.SIGTERM) - app := NewApp(baseURL, *bind) + app := NewApp(htpasswd_file, baseURL, *bind) go app.Start() // Wait shutdown signal diff --git a/go.mod b/go.mod index 02e5d18f..871ad780 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/julienschmidt/httprouter v1.3.0 github.com/studio-b12/gowebdav v0.0.0-20220128162035-c7b1ff8a5e62 github.com/yuin/goldmark v1.4.12 + gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b // indirect golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e golang.org/x/image v0.0.0-20220413100746-70e8d0d3baa9 golang.org/x/oauth2 v0.0.0-20220524215830-622c5d57e401 diff --git a/go.sum b/go.sum index 6e95ccb2..a7c3cd35 100644 --- a/go.sum +++ b/go.sum @@ -209,6 +209,8 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.12 h1:6hffw6vALvEDqJ19dOJvJKOoAOKe4NDaTqvd2sktGN0= github.com/yuin/goldmark v1.4.12/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b h1:7gd+rd8P3bqcn/96gOZa3F5dpJr/vEiDQYlNb/y2uNs= +gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b/go.mod h1:T3BPAOm2cqquPa0MKWeNkmOM5RQsRhkrwMWonFMN7fE= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=