diff --git a/README.md b/README.md index 23dd06b..ecae632 100644 --- a/README.md +++ b/README.md @@ -5,9 +5,23 @@ Upon successful login, the browser gets a cookie, and further attempts to access will get the success page. I made this to use with the Traefik forward-auth middleware. +I now use Caddy: it works with that too. All I need is a simple password, that's easy to fill with a password manager. This checks those boxes. + +## Format of the `passwd` file + +It's just like `/etc/shadow`. + + username:crypted-password + +We use sha256, +until there's a Go library that supports everything. + +There's a program included called `crypt` that will output lines for this file. + + ## Installation with Traefik You need to have traefik forward the Path `/` to this application. diff --git a/cmd/crypt/main.go b/cmd/crypt/main.go new file mode 100644 index 0000000..e6b6e7a --- /dev/null +++ b/cmd/crypt/main.go @@ -0,0 +1,24 @@ +package main + +import ( + "fmt" + "log" + "os" + + "github.com/GehirnInc/crypt" + _ "github.com/GehirnInc/crypt/sha256_crypt" +) + +func main() { + if len(os.Args) != 3 { + log.Fatal("Usage: crypt USERNAME PASSWORD") + } + username := os.Args[1] + password := os.Args[2] + c := crypt.SHA256.New() + if crypted, err := c.Generate([]byte(password), nil); err != nil { + log.Fatal(err) + } else { + fmt.Printf("%s:%s\n", username, crypted) + } +} diff --git a/cmd/simpleauth/main.go b/cmd/simpleauth/main.go index c3588c6..0616306 100644 --- a/cmd/simpleauth/main.go +++ b/cmd/simpleauth/main.go @@ -1,6 +1,7 @@ package main import ( + "bufio" "flag" "fmt" "io/ioutil" @@ -11,6 +12,8 @@ import ( "strings" "time" + "github.com/GehirnInc/crypt" + _ "github.com/GehirnInc/crypt/sha256_crypt" "github.com/nealey/simpleauth/pkg/token" ) @@ -18,51 +21,68 @@ const CookieName = "auth" var secret []byte = make([]byte, 256) var lifespan time.Duration -var password string +var cryptedPasswords map[string]string var loginHtml []byte var successHtml []byte +func authenticationValid(username, password string) bool { + c := crypt.SHA256.New() + fmt.Println("checking", username, password) + if crypted, ok := cryptedPasswords[username]; ok { + fmt.Println(username, password, crypted) + if err := c.Verify(crypted, []byte(password)); err == nil { + return true + } else { + log.Println(err) + } + } + return false +} + func rootHandler(w http.ResponseWriter, req *http.Request) { - mechanism := "unauthenticated" - authenticated := false - if _, passwd, _ := req.BasicAuth(); passwd == password { - authenticated = true - mechanism = "HTTP-Basic" - } - if req.FormValue("passwd") == password { - authenticated = true - mechanism = "Form" - } if cookie, err := req.Cookie(CookieName); err == nil { t, _ := token.ParseString(cookie.Value) if t.Valid(secret) { - // Bypass logging and cookie setting: - // otherwise there is a torrent of logs - w.Write(successHtml) - return + // Bypass logging and cookie setting: + // otherwise there is a torrent of logs + w.Write(successHtml) + return } } - // Log the request - clientIP := req.Header.Get("X-Real-IP") - if clientIP == "" { - clientIP = req.RemoteAddr - } - log.Printf("%s %s %s [%s]", clientIP, req.Method, req.URL, mechanism) + authenticated := "" - if authenticated { + if username, password, ok := req.BasicAuth(); ok { + if authenticationValid(username, password) { + authenticated = "HTTP-Basic" + } + } + + if authenticationValid(req.FormValue("username"), req.FormValue("password")) { + authenticated = "Form" + } + + // Log the request + clientIP := req.Header.Get("X-Real-IP") + if clientIP == "" { + clientIP = req.RemoteAddr + } + log.Printf("%s %s %s [%s]", clientIP, req.Method, req.URL, authenticated) + + if authenticated == "" { + w.WriteHeader(http.StatusUnauthorized) + w.Write(loginHtml) + } else { t := token.New(secret, time.Now().Add(lifespan)) http.SetCookie(w, &http.Cookie{ Name: CookieName, Value: t.String(), - Path: "/", + Path: "/", Secure: true, SameSite: http.SameSiteStrictMode, }) + w.WriteHeader(http.StatusOK) w.Write(successHtml) - } else { - w.WriteHeader(http.StatusUnauthorized) - w.Write(loginHtml) } } @@ -80,8 +100,8 @@ func main() { ) passwordPath := flag.String( "passwd", - "/run/secrets/password", - "Path to a file containing the password", + "/run/secrets/passwd", + "Path to a file containing passwords", ) secretPath := flag.String( "secret", @@ -95,11 +115,25 @@ func main() { ) flag.Parse() - passwordBytes, err := ioutil.ReadFile(*passwordPath) - if err != nil { + cryptedPasswords = make(map[string]string, 10) + if f, err := os.Open(*passwordPath); err != nil { log.Fatal(err) + } else { + defer f.Close() + scanner := bufio.NewScanner(f) + for scanner.Scan() { + line := scanner.Text() + parts := strings.Split(line, ":") + if len(parts) >= 2 { + username := parts[0] + password := parts[1] + fmt.Println(username, password) + cryptedPasswords[username] = password + } + } } - password = strings.TrimSpace(string(passwordBytes)) + + var err error loginHtml, err = ioutil.ReadFile(path.Join(*htmlPath, "login.html")) if err != nil { @@ -117,8 +151,8 @@ func main() { } defer f.Close() l, err := f.Read(secret) - if l == 0 { - log.Fatal("Secret file provided 0 bytes. That's not enough bytes!") + if l < 8 { + log.Fatalf("Secret file provided %d bytes. That's not enough bytes!", l) } else if err != nil { log.Fatal(err) } diff --git a/go.mod b/go.mod index cc845a0..aa5da47 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,5 @@ module github.com/nealey/simpleauth go 1.13 + +require github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..0ffde46 --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962 h1:KeNholpO2xKjgaaSyd+DyQRrsQjhbSeS7qe4nEw8aQw= +github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962/go.mod h1:kC29dT1vFpj7py2OvG1khBdQpo3kInWP+6QipLbdngo= diff --git a/static/login.html b/static/login.html index 5abf98f..191b164 100644 --- a/static/login.html +++ b/static/login.html @@ -46,7 +46,8 @@

Log In

-
Password:
+
Username:
+
Password:
diff --git a/static/success.html b/static/success.html index c1d2ff9..212abcc 100644 --- a/static/success.html +++ b/static/success.html @@ -8,7 +8,7 @@ html { font-family: sans-serif; color: white; - background: seagreen linear-gradient(315, rgba(255,255,255,0.2), transparent); + background: seagreen linear-gradient(315deg, rgba(255,255,255,0.2), transparent); height: 100%; } div {