From 173d059c8244d8a6c5d21532f8d75f8ad13180d5 Mon Sep 17 00:00:00 2001 From: Nick Penkov Date: Fri, 26 Jan 2018 12:38:19 +0100 Subject: [PATCH] Initial version. --- Dockerfile | 12 ++++ Makefile | 21 +++++++ README.md | 31 ++++++++++ app/ldap.go | 130 ++++++++++++++++++++++++++++++++++++++++++ app/util.go | 42 ++++++++++++++ app/web.go | 113 ++++++++++++++++++++++++++++++++++++ main.go | 18 ++++++ screenshots/index.png | Bin 0 -> 37635 bytes static/style.css | 112 ++++++++++++++++++++++++++++++++++++ templates/index.html | 43 ++++++++++++++ 10 files changed, 522 insertions(+) create mode 100644 Dockerfile create mode 100644 Makefile create mode 100644 README.md create mode 100644 app/ldap.go create mode 100644 app/util.go create mode 100644 app/web.go create mode 100644 main.go create mode 100644 screenshots/index.png create mode 100644 static/style.css create mode 100644 templates/index.html diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..dea99b1 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,12 @@ +FROM alpine:3.7 + +WORKDIR /app + +ADD ldap-pass-webui /app/ldap-pass-webui +ADD static /app/static +ADD templates /app/templates +RUN chmod +x /app/ldap-pass-webui + +EXPOSE 8080 + +ENTRYPOINT [ "/app/ldap-pass-webui" ] \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..98e92d0 --- /dev/null +++ b/Makefile @@ -0,0 +1,21 @@ +REPO=npenkov/docker-ldap-passwd-webui +VER=1.0 + +.PHONY: all build push + +all: build docker push clean + +build: + GOOS=linux go build -o ldap-pass-webui main.go + +docker: + @echo "Building docker image" + docker build -t ${REPO}:${VER} -t ${REPO}:latest . + +push: + @echo "Pushing to dockerhub" + docker push ${REPO}:${VER} + docker push ${REPO}:latest + +clean: + rm -rf ldap-pass-webui \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..b19a63f --- /dev/null +++ b/README.md @@ -0,0 +1,31 @@ +# Web UI for LDAP changing password + +WebUI Client capable of connecting to backend LDAP server and changing the users password. + +![Screenshot](screenshots/index.png) + +## Running in docker container + +```sh +docker run -d -p 8080:8080 --name ldap-passwd-webui \ + -e LPW_TITLE="Change your global password for example.org" \ + -e LPW_HOST="your_ldap_host" \ + -e LPW_PORT="636" \ + -e LPW_ENCRYPTED="true" \ + -e LPW_START_TLS="false" \ + -e LPW_SSL_SKIP_VERIFY="true" \ + -e LPW_USER_DN="uid=%s,ou=people,dc=example,dc=org" \ + -e LPW_USER_BASE="ou=people,dc=example,dc=org" \ + npenkov/docker-ldap-passwd-webui:latest +``` + +## Building and tagging + +```sh +make +``` + +## Credits + + * [Web UI for changing LDAP password - python](https://github.com/jirutka/ldap-passwd-webui) + * [Gitea](https://github.com/go-gitea/gitea) \ No newline at end of file diff --git a/app/ldap.go b/app/ldap.go new file mode 100644 index 0000000..e45abd8 --- /dev/null +++ b/app/ldap.go @@ -0,0 +1,130 @@ +package app + +import ( + "crypto/tls" + "fmt" + "log" + "strings" + + "gopkg.in/ldap.v2" +) + +// SecurityProtocol protocol type +type SecurityProtocol int + +// Note: new type must be added at the end of list to maintain compatibility. +const ( + SecurityProtocolUnencrypted SecurityProtocol = iota + SecurityProtocolLDAPS + SecurityProtocolStartTLS +) + +// LDAPClient Basic LDAP authentication service +type LDAPClient struct { + Name string // canonical name (ie. corporate.ad) + Host string // LDAP host + Port int // port number + SecurityProtocol SecurityProtocol + SkipVerify bool + UserBase string // Base search path for users + UserDN string // Template for the DN of the user for simple auth + Enabled bool // if this LDAPClient is disabled +} + +func bindUser(l *ldap.Conn, userDN, passwd string) error { + log.Printf("\nBinding with userDN: %s", userDN) + err := l.Bind(userDN, passwd) + if err != nil { + log.Printf("\nLDAP auth. failed for %s, reason: %v", userDN, err) + return err + } + log.Printf("\nBound successfully with userDN: %s", userDN) + return err +} + +func (ls *LDAPClient) sanitizedUserDN(username string) (string, bool) { + // See http://tools.ietf.org/search/rfc4514: "special characters" + badCharacters := "\x00()*\\,='\"#+;<>" + if strings.ContainsAny(username, badCharacters) { + log.Printf("\n'%s' contains invalid DN characters. Aborting.", username) + return "", false + } + + return fmt.Sprintf(ls.UserDN, username), true +} + +func dial(ls *LDAPClient) (*ldap.Conn, error) { + log.Printf("\nDialing LDAP with security protocol (%v) without verifying: %v", ls.SecurityProtocol, ls.SkipVerify) + + tlsCfg := &tls.Config{ + ServerName: ls.Host, + InsecureSkipVerify: ls.SkipVerify, + } + if ls.SecurityProtocol == SecurityProtocolLDAPS { + return ldap.DialTLS("tcp", fmt.Sprintf("%s:%d", ls.Host, ls.Port), tlsCfg) + } + + conn, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", ls.Host, ls.Port)) + if err != nil { + return nil, fmt.Errorf("Dial: %v", err) + } + + if ls.SecurityProtocol == SecurityProtocolStartTLS { + if err = conn.StartTLS(tlsCfg); err != nil { + conn.Close() + return nil, fmt.Errorf("StartTLS: %v", err) + } + } + + return conn, nil +} + +// ModifyPassword : modify user's password +func (ls *LDAPClient) ModifyPassword(name, passwd, newPassword string) error { + if len(passwd) == 0 { + return fmt.Errorf("Auth. failed for %s, password cannot be empty", name) + } + l, err := dial(ls) + if err != nil { + ls.Enabled = false + return fmt.Errorf("LDAP Connect error, %s:%v", ls.Host, err) + } + defer l.Close() + + var userDN string + log.Printf("\nLDAP will bind directly via UserDN template: %s", ls.UserDN) + + var ok bool + userDN, ok = ls.sanitizedUserDN(name) + if !ok { + return fmt.Errorf("Error sanitizing name %s", name) + } + bindUser(l, userDN, passwd) + + log.Printf("\nLDAP will execute password change on: %s", userDN) + req := ldap.NewPasswordModifyRequest(userDN, passwd, newPassword) + _, err = l.PasswordModify(req) + + return err +} + +// NewLDAPClient : Creates new LDAPClient capable of binding and changing passwords +func NewLDAPClient() *LDAPClient { + + securityProtocol := SecurityProtocolUnencrypted + if envBool("LPW_ENCRYPTED", true) { + securityProtocol = SecurityProtocolLDAPS + if envBool("LPW_START_TLS", false) { + securityProtocol = SecurityProtocolStartTLS + } + } + + return &LDAPClient{ + Host: envStr("LPW_HOST", ""), + Port: envInt("LPW_PORT", 636), // 389 + SecurityProtocol: securityProtocol, + SkipVerify: envBool("LPW_SSL_SKIP_VERIFY", false), + UserDN: envStr("LPW_USER_DN", "uid=%s,ou=people,dc=example,dc=org"), + UserBase: envStr("LPW_USER_BASE", "ou=people,dc=example,dc=org"), + } +} diff --git a/app/util.go b/app/util.go new file mode 100644 index 0000000..187ef06 --- /dev/null +++ b/app/util.go @@ -0,0 +1,42 @@ +package app + +import ( + "os" + "strconv" +) + +func getTitle() string { + return envStr("LPW_TITLE", "Change your password on example.org") +} + +func envStr(key, defaultValue string) string { + val := os.Getenv(key) + if val != "" { + return val + } + return defaultValue +} + +func envInt(key string, defaultValue int) int { + val := os.Getenv(key) + if val != "" { + i, err := strconv.Atoi(val) + if err != nil { + return defaultValue + } + return i + } + return defaultValue +} + +func envBool(key string, defaultValue bool) bool { + val := os.Getenv(key) + if val != "" { + b, err := strconv.ParseBool(val) + if err != nil { + return defaultValue + } + return b + } + return defaultValue +} diff --git a/app/web.go b/app/web.go new file mode 100644 index 0000000..c8a2ae0 --- /dev/null +++ b/app/web.go @@ -0,0 +1,113 @@ +package app + +import ( + "fmt" + "log" + "path" + "strings" + + "html/template" + + "regexp" + + "net/http" +) + +type route struct { + pattern *regexp.Regexp + verb string + handler http.Handler +} + +type RegexpHandler struct { + routes []*route +} + +func (h *RegexpHandler) Handler(pattern *regexp.Regexp, verb string, handler http.Handler) { + h.routes = append(h.routes, &route{pattern, verb, handler}) +} + +func (h *RegexpHandler) HandleFunc(r string, v string, handler func(http.ResponseWriter, *http.Request)) { + re := regexp.MustCompile(r) + h.routes = append(h.routes, &route{re, v, http.HandlerFunc(handler)}) +} + +func (h *RegexpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + for _, route := range h.routes { + if route.pattern.MatchString(r.URL.Path) && route.verb == r.Method { + route.handler.ServeHTTP(w, r) + return + } + } + http.NotFound(w, r) +} + +type pageData struct { + Title string + Username string + Alerts map[string]string +} + +// ServeAssets : Serves the static assets +func ServeAssets(w http.ResponseWriter, req *http.Request) { + http.ServeFile(w, req, path.Join("static", req.URL.Path[1:])) +} + +// ServeIndex : Serves index page on GET request +func ServeIndex(w http.ResponseWriter, req *http.Request) { + p := &pageData{Title: getTitle()} + t, e := template.ParseFiles(path.Join("templates", "index.html")) + if e != nil { + log.Printf("Error parsing file %v\n", e) + } else { + t.Execute(w, p) + } +} + +// ChangePassword : Serves index page on POST request - executes the change +func ChangePassword(w http.ResponseWriter, req *http.Request) { + req.ParseForm() + un := "" + username := req.Form["username"] + oldPassword := req.Form["old-password"] + newPassword := req.Form["new-password"] + confirmPassword := req.Form["confirm-password"] + + alerts := map[string]string{} + + if len(username) < 1 || username[0] == "" { + alerts["error"] = "Username not specified.
" + } else { + un = username[0] + } + if len(oldPassword) < 1 || oldPassword[0] == "" { + alerts["error"] = alerts["error"] + "Old password not specified.
" + } + if len(newPassword) < 1 || newPassword[0] == "" { + alerts["error"] = alerts["error"] + "New password not specified.
" + } + if len(confirmPassword) < 1 || confirmPassword[0] == "" { + alerts["error"] = alerts["error"] + "Confirmation password not specified.
" + } + + if len(confirmPassword) >= 1 && len(newPassword) >= 1 && strings.Compare(newPassword[0], confirmPassword[0]) != 0 { + alerts["error"] = alerts["error"] + "New and confirmation passwords does not match.
" + } + if len(alerts) == 0 { + client := NewLDAPClient() + if err := client.ModifyPassword(un, oldPassword[0], newPassword[0]); err != nil { + alerts["error"] = fmt.Sprintf("%v", err) + } else { + alerts["success"] = "Password successfuly changed" + } + } + + p := &pageData{Title: getTitle(), Alerts: alerts, Username: un} + + t, e := template.ParseFiles(path.Join("templates", "index.html")) + if e != nil { + log.Printf("Error parsing file %v\n", e) + } else { + t.Execute(w, p) + } +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..3babe99 --- /dev/null +++ b/main.go @@ -0,0 +1,18 @@ +package main + +import ( + "fmt" + "github.com/npenkov/ldap-passwd-webui/app" + "net/http" +) + +func main() { + reHandler := new(app.RegexpHandler) + + reHandler.HandleFunc(".*.[js|css|png|eof|svg|ttf|woff]", "GET", app.ServeAssets) + reHandler.HandleFunc("/", "GET", app.ServeIndex) + reHandler.HandleFunc("/", "POST", app.ChangePassword) + + fmt.Println("Starting server on port 8080") + http.ListenAndServe(":8080", reHandler) +} diff --git a/screenshots/index.png b/screenshots/index.png new file mode 100644 index 0000000000000000000000000000000000000000..a4fd472dead5c00fd4854e85505e71e9596f9452 GIT binary patch literal 37635 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 + + + + + + + + {{.Title}} + + + + + +
+

{{.Title}}

+ +
+ + + + + + + + + + + + + +
+ +
+ {{ range $key, $value := .Alerts }} +
{{ $value }}
+ {{ end }} +
+
+ +