From c961388d73262e6668d581bbf52a9271999c8cc4 Mon Sep 17 00:00:00 2001 From: Zach Brown Date: Tue, 1 Mar 2022 04:19:03 +0000 Subject: [PATCH] Add support for user/pass authentication --- README.md | 9 +++++++++ main.go | 31 ++++++++++++++++++++++++------- server.go | 23 ++++++++++++++++++++++- 3 files changed, 55 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 9d446d1..61d03f2 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ Current stable release: [v1.3.0](https://github.com/EmbarkStudios/wg-ui/releases * Self-serve and web based * QR-Code for convenient mobile client configuration * Optional multi-user support behind an authenticating proxy + * Simple authentication support * Zero external dependencies - just a single binary using the wireguard kernel module * Binary and container deployment @@ -46,6 +47,14 @@ and are the same. +### Authentication +You can configure basic authentication using the flags/environment variables `--auth-basic-user=` and `--auth-basic-pass=` The password is +a bcrypt hash that you can generate yourself using the docker container: +``` +$ docker run -it embarkstudios/wireguard-ui:latest passwd mySecretPass +INFO[0001] Password Hash: $2a$14$D2jsPnpJixC0U0lyaGUd0OatV7QGzQ08yKV.gsmITVZgNevfZXj36 +``` + ## Docker images There are two ways to run wg-ui today, you can run it with kernel module installed on your host which is the best way to do it if you want performance. diff --git a/main.go b/main.go index a3ba2a3..a424e60 100644 --- a/main.go +++ b/main.go @@ -4,6 +4,7 @@ import ( "strings" log "github.com/sirupsen/logrus" + "golang.org/x/crypto/bcrypt" "gopkg.in/alecthomas/kingpin.v2" ) @@ -14,7 +15,11 @@ var ( func main() { kingpin.HelpFlag.Short('h') kingpin.CommandLine.DefaultEnvars() - kingpin.Parse() + kingpin.Command("server", "Start server.").Default() + passwdCmd := kingpin.Command("passwd", "Generate password hash.") + passwdCmdPassword := passwdCmd.Arg("password", "The password to hash").Required().String() + cmd := kingpin.Parse() + switch strings.ToLower(*logLevel) { case "debug": log.SetLevel(log.DebugLevel) @@ -28,11 +33,23 @@ func main() { log.SetLevel(log.InfoLevel) } - log.Info("Starting") - - server := NewServer() - err := server.Start() - if err != nil { - log.Fatal(err) + switch cmd { + case "passwd": + bytes, err := bcrypt.GenerateFromPassword([]byte(*passwdCmdPassword), 14) + if err != nil { + log.Fatalf("generate password error: %v", err) + } + log.Infof("Password Hash: %s", string(bytes)) + return + case "server": + log.Info("Starting") + server := NewServer() + err := server.Start() + if err != nil { + log.Fatal(err) + } + default: + log.Fatal("Unknown command") } + } diff --git a/server.go b/server.go index 78693b2..9fca86d 100644 --- a/server.go +++ b/server.go @@ -27,6 +27,7 @@ import ( "github.com/skip2/go-qrcode" "github.com/vishvananda/netlink" "github.com/vishvananda/netns" + "golang.org/x/crypto/bcrypt" "golang.zx2c4.com/wireguard/wgctrl" "golang.zx2c4.com/wireguard/wgctrl/wgtypes" "gopkg.in/alecthomas/kingpin.v2" @@ -40,6 +41,8 @@ var ( natLink = kingpin.Flag("nat-device", "Network interface to masquerade").Default("wlp2s0").String() clientIPRange = kingpin.Flag("client-ip-range", "Client IP CIDR").Default("172.31.255.0/24").String() authUserHeader = kingpin.Flag("auth-user-header", "Header containing username").Default("X-Forwarded-User").String() + authBasicUser = kingpin.Flag("auth-basic-user", "Basic auth static username").Default("").String() + authBasicPass = kingpin.Flag("auth-basic-pass", "Basic auth static password").Default("").String() maxNumberClientConfig = kingpin.Flag("max-number-client-config", "Max number of configs an client can use. 0 is unlimited").Default("0").Int() wgLinkName = kingpin.Flag("wg-device-name", "WireGuard network device name").Default("wg0").String() @@ -421,7 +424,25 @@ func (s *Server) Start() error { log.WithField("listenAddr", *listenAddr).Info("Starting server") - return http.ListenAndServe(*listenAddr, s.userFromHeader(router)) + return http.ListenAndServe(*listenAddr, s.basicAuth(s.userFromHeader(router))) +} + +func (s *Server) basicAuth(handler http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + + // If we specified a user, require auth + if *authBasicUser != "" { + u, p, ok := r.BasicAuth() + if !ok || u != *authBasicUser || bcrypt.CompareHashAndPassword([]byte(*authBasicPass), []byte(p)) != nil { + w.Header().Set("WWW-Authenticate", `Basic realm="restricted", charset="UTF-8"`) + http.Error(w, "Unauthorized", http.StatusUnauthorized) + return + } + } + + handler.ServeHTTP(w, r) + + }) } func (s *Server) userFromHeader(handler http.Handler) http.Handler {