mirror of
https://github.com/drakkan/sftpgo.git
synced 2024-11-21 23:20:24 +00:00
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.
This commit is contained in:
parent
e6f969cb04
commit
87751e562e
1 changed files with 54 additions and 14 deletions
|
@ -4,17 +4,22 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"log/syslog"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/go-ldap/ldap/v3"
|
"github.com/go-ldap/ldap/v3"
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
bindUsername = "cn=Directory Manager"
|
rootDN = "dc=example,dc=com"
|
||||||
bindPassword = "YOUR_ADMIN_PASSWORD_HERE"
|
bindUsername = "cn=sftpgo," + rootDN
|
||||||
bindURL = "ldap://192.168.1.103:389"
|
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 {
|
type userFilters struct {
|
||||||
|
@ -32,6 +37,7 @@ type minimalSFTPGoUser struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func exitError() {
|
func exitError() {
|
||||||
|
log.Printf("exitError\n")
|
||||||
u := minimalSFTPGoUser{
|
u := minimalSFTPGoUser{
|
||||||
Username: "",
|
Username: "",
|
||||||
}
|
}
|
||||||
|
@ -40,7 +46,7 @@ func exitError() {
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func printSuccessResponse(username, homeDir string, uid, gid int) {
|
func printSuccessResponse(username, homeDir string, uid, gid int, permissions []string) {
|
||||||
u := minimalSFTPGoUser{
|
u := minimalSFTPGoUser{
|
||||||
Username: username,
|
Username: username,
|
||||||
HomeDir: homeDir,
|
HomeDir: homeDir,
|
||||||
|
@ -49,46 +55,66 @@ func printSuccessResponse(username, homeDir string, uid, gid int) {
|
||||||
Status: 1,
|
Status: 1,
|
||||||
}
|
}
|
||||||
u.Permissions = make(map[string][]string)
|
u.Permissions = make(map[string][]string)
|
||||||
u.Permissions["/"] = []string{"*"}
|
u.Permissions["/"] = permissions
|
||||||
// uncomment the next line to require publickey+password authentication
|
// uncomment the next line to require publickey+password authentication
|
||||||
//u.Filters.DeniedLoginMethods = []string{"publickey", "password", "keyboard-interactive", "publickey+keyboard-interactive"}
|
//u.Filters.DeniedLoginMethods = []string{"publickey", "password", "keyboard-interactive", "publickey+keyboard-interactive"}
|
||||||
resp, _ := json.Marshal(u)
|
resp, _ := json.Marshal(u)
|
||||||
|
log.Printf("%v\n", string(resp))
|
||||||
fmt.Printf("%v\n", string(resp))
|
fmt.Printf("%v\n", string(resp))
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
logWriter, err := syslog.New(syslog.LOG_NOTICE, "sftpgo")
|
||||||
|
if err == nil {
|
||||||
|
log.SetOutput(logWriter)
|
||||||
|
}
|
||||||
// get credentials from env vars
|
// get credentials from env vars
|
||||||
username := os.Getenv("SFTPGO_AUTHD_USERNAME")
|
username := os.Getenv("SFTPGO_AUTHD_USERNAME")
|
||||||
password := os.Getenv("SFTPGO_AUTHD_PASSWORD")
|
password := os.Getenv("SFTPGO_AUTHD_PASSWORD")
|
||||||
publickey := os.Getenv("SFTPGO_AUTHD_PUBLIC_KEY")
|
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)
|
l, err := ldap.DialURL(bindURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Printf("DialURL: %s\n", err.Error())
|
||||||
exitError()
|
exitError()
|
||||||
}
|
}
|
||||||
defer l.Close()
|
defer l.Close()
|
||||||
// bind to the ldap server with an account that can read users
|
// 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 {
|
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()
|
exitError()
|
||||||
}
|
}
|
||||||
|
|
||||||
// search the user trying to login and fetch some attributes, this search string is tested against 389ds using the default configuration
|
// 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(
|
searchRequest := ldap.NewSearchRequest(
|
||||||
"dc=example,dc=com",
|
"ou=people," + rootDN,
|
||||||
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
|
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
|
||||||
fmt.Sprintf("(&(objectClass=nsPerson)(uid=%s))", username),
|
searchFilter,
|
||||||
[]string{"dn", "homeDirectory", "uidNumber", "gidNumber", "nsSshPublicKey"},
|
[]string{"dn", "uid", "homeDirectory", "uidNumber", "gidNumber", "nsSshPublicKey"},
|
||||||
nil,
|
nil,
|
||||||
)
|
)
|
||||||
|
|
||||||
sr, err := l.Search(searchRequest)
|
sr, err := l.Search(searchRequest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Printf("Search(%s): %s\n", searchFilter, err.Error())
|
||||||
exitError()
|
exitError()
|
||||||
}
|
}
|
||||||
|
|
||||||
// we expect exactly one user
|
// we expect exactly one user
|
||||||
if len(sr.Entries) != 1 {
|
if len(sr.Entries) != 1 {
|
||||||
|
log.Printf("Search(%s): %d entries\n", searchFilter, len(sr.Entries))
|
||||||
exitError()
|
exitError()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,6 +122,7 @@ func main() {
|
||||||
// check public key
|
// check public key
|
||||||
userKey, _, _, _, err := ssh.ParseAuthorizedKey([]byte(publickey))
|
userKey, _, _, _, err := ssh.ParseAuthorizedKey([]byte(publickey))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Printf("ParseAuthorizedKey(%s): %s\n", publickey, err.Error())
|
||||||
exitError()
|
exitError()
|
||||||
}
|
}
|
||||||
authOk := false
|
authOk := false
|
||||||
|
@ -111,25 +138,38 @@ func main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !authOk {
|
if !authOk {
|
||||||
|
log.Printf("publickey %s !authOk\n", publickey)
|
||||||
exitError()
|
exitError()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// bind to the LDAP server with the user dn and the given password to check the password
|
// bind to the LDAP server with the user dn and the given password to check the password
|
||||||
userdn := sr.Entries[0].DN
|
userdn := sr.Entries[0].DN
|
||||||
|
// log.Printf("password=%s\n", password)
|
||||||
err = l.Bind(userdn, password)
|
err = l.Bind(userdn, password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Printf("Bind(%s): %s\n", userdn, err.Error())
|
||||||
exitError()
|
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 {
|
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 {
|
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
|
// return the authenticated user
|
||||||
printSuccessResponse(username, sr.Entries[0].GetAttributeValue("homeDirectory"), uid, gid)
|
printSuccessResponse(sr.Entries[0].GetAttributeValue("uid"), homeDir, uid, gid, []string{"*"})
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue