From 87751e562e2d19ae03a4c853bbced8f4ceda6b3f Mon Sep 17 00:00:00 2001 From: root Date: Thu, 30 Sep 2021 17:13:41 -0700 Subject: [PATCH] Flesh out examples/ldapauth, specifically: Support 'virtual' users who have no homeDirectory, uidNumber or gidNumber. Permit read-only access by a user named "anonymous", with any password. Assume a conventional DIT with users under ou=people,dc=example,dc=com. Read the LDAP bindPassword from a file (not baked into the code). Log progress and problems to syslog. --- examples/ldapauth/main.go | 68 +++++++++++++++++++++++++++++++-------- 1 file changed, 54 insertions(+), 14 deletions(-) diff --git a/examples/ldapauth/main.go b/examples/ldapauth/main.go index 8bb9a259..fcaaa164 100644 --- a/examples/ldapauth/main.go +++ b/examples/ldapauth/main.go @@ -4,17 +4,22 @@ import ( "bytes" "encoding/json" "fmt" + "log" + "log/syslog" "os" "strconv" + "strings" "github.com/go-ldap/ldap/v3" "golang.org/x/crypto/ssh" ) const ( - bindUsername = "cn=Directory Manager" - bindPassword = "YOUR_ADMIN_PASSWORD_HERE" - bindURL = "ldap://192.168.1.103:389" + rootDN = "dc=example,dc=com" + bindUsername = "cn=sftpgo," + rootDN + bindURL = "ldap:///" // That is, the server on the default port of localhost. + passwordFile = "/etc/sftpgo/admin-password.txt" // make this file readable only by the server + publicDir = "/var/www/webdav/public" ) type userFilters struct { @@ -32,6 +37,7 @@ type minimalSFTPGoUser struct { } func exitError() { + log.Printf("exitError\n") u := minimalSFTPGoUser{ Username: "", } @@ -40,7 +46,7 @@ func exitError() { os.Exit(1) } -func printSuccessResponse(username, homeDir string, uid, gid int) { +func printSuccessResponse(username, homeDir string, uid, gid int, permissions []string) { u := minimalSFTPGoUser{ Username: username, HomeDir: homeDir, @@ -49,46 +55,66 @@ func printSuccessResponse(username, homeDir string, uid, gid int) { Status: 1, } u.Permissions = make(map[string][]string) - u.Permissions["/"] = []string{"*"} + u.Permissions["/"] = permissions // uncomment the next line to require publickey+password authentication //u.Filters.DeniedLoginMethods = []string{"publickey", "password", "keyboard-interactive", "publickey+keyboard-interactive"} resp, _ := json.Marshal(u) + log.Printf("%v\n", string(resp)) fmt.Printf("%v\n", string(resp)) os.Exit(0) } func main() { + logWriter, err := syslog.New(syslog.LOG_NOTICE, "sftpgo") + if err == nil { + log.SetOutput(logWriter) + } // get credentials from env vars username := os.Getenv("SFTPGO_AUTHD_USERNAME") password := os.Getenv("SFTPGO_AUTHD_PASSWORD") publickey := os.Getenv("SFTPGO_AUTHD_PUBLIC_KEY") + if strings.ToLower(username) == "anonymous" { + printSuccessResponse("anonymous", publicDir, 0, 0, []string{"list", "download"}) + return + } l, err := ldap.DialURL(bindURL) if err != nil { + log.Printf("DialURL: %s\n", err.Error()) exitError() } defer l.Close() // bind to the ldap server with an account that can read users - err = l.Bind(bindUsername, bindPassword) + bindPassword, err := os.ReadFile(passwordFile) if err != nil { + log.Printf("ReadFile(%s): %s\n", passwordFile, err.Error()) + exitError() + } + err = l.Bind(bindUsername, string(bindPassword)) + if err != nil { + log.Printf("Bind(%s): %s\n", bindUsername, err.Error()) exitError() } // search the user trying to login and fetch some attributes, this search string is tested against 389ds using the default configuration + log.Printf("username=%s\n", username) + searchFilter := fmt.Sprintf("(uid=%s)", username) searchRequest := ldap.NewSearchRequest( - "dc=example,dc=com", + "ou=people," + rootDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, - fmt.Sprintf("(&(objectClass=nsPerson)(uid=%s))", username), - []string{"dn", "homeDirectory", "uidNumber", "gidNumber", "nsSshPublicKey"}, + searchFilter, + []string{"dn", "uid", "homeDirectory", "uidNumber", "gidNumber", "nsSshPublicKey"}, nil, ) sr, err := l.Search(searchRequest) if err != nil { + log.Printf("Search(%s): %s\n", searchFilter, err.Error()) exitError() } // we expect exactly one user if len(sr.Entries) != 1 { + log.Printf("Search(%s): %d entries\n", searchFilter, len(sr.Entries)) exitError() } @@ -96,6 +122,7 @@ func main() { // check public key userKey, _, _, _, err := ssh.ParseAuthorizedKey([]byte(publickey)) if err != nil { + log.Printf("ParseAuthorizedKey(%s): %s\n", publickey, err.Error()) exitError() } authOk := false @@ -111,25 +138,38 @@ func main() { } } if !authOk { + log.Printf("publickey %s !authOk\n", publickey) exitError() } } else { // bind to the LDAP server with the user dn and the given password to check the password userdn := sr.Entries[0].DN + // log.Printf("password=%s\n", password) err = l.Bind(userdn, password) if err != nil { + log.Printf("Bind(%s): %s\n", userdn, err.Error()) exitError() } } - uid, err := strconv.Atoi(sr.Entries[0].GetAttributeValue("uidNumber")) + // People in the LDAP directory aren't necessarily Linux users; + // so they might not have a uidNumber or gidNumber. + uidNumber := sr.Entries[0].GetAttributeValue("uidNumber") + uid, err := strconv.Atoi(uidNumber) if err != nil { - exitError() + //log.Printf("uid Atoi(%s) = %s\n", uidNumber, err.Error()) + uid = 0 } - gid, err := strconv.Atoi(sr.Entries[0].GetAttributeValue("gidNumber")) + gidNumber := sr.Entries[0].GetAttributeValue("gidNumber") + gid, err := strconv.Atoi(gidNumber) if err != nil { - exitError() + //log.Printf("gid Atoi(%s) = %s\n", gidNumber, err.Error()) + gid = 0 + } + homeDir := sr.Entries[0].GetAttributeValue("homeDirectory") + if (len(homeDir) <= 0) { + homeDir = publicDir // homeDir is a required attribute. } // return the authenticated user - printSuccessResponse(username, sr.Entries[0].GetAttributeValue("homeDirectory"), uid, gid) + printSuccessResponse(sr.Entries[0].GetAttributeValue("uid"), homeDir, uid, gid, []string{"*"}) }