241 lines
6.0 KiB
Go
241 lines
6.0 KiB
Go
package main
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"errors"
|
|
"fmt"
|
|
"log"
|
|
"net"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/pyke369/golang-support/rcache"
|
|
"gopkg.in/ldap.v2"
|
|
)
|
|
|
|
type ldapConfig struct {
|
|
servers []string
|
|
baseDN string
|
|
bindCn string
|
|
bindPw string
|
|
searchFilter string
|
|
attributes []string
|
|
validGroups []string
|
|
mfaType string
|
|
certAuth string
|
|
ipMin net.IP
|
|
ipMax net.IP
|
|
upgradeFrom string
|
|
routes []string
|
|
}
|
|
|
|
func (l *ldapConfig) addIPRange(s string) error {
|
|
ips := strings.Split(s, "-")
|
|
if len(ips) != 2 {
|
|
return errors.New("invalid IPs")
|
|
}
|
|
for k, v := range []*net.IP{&(l.ipMin), &(l.ipMax)} {
|
|
if ip := net.ParseIP(strings.Trim(ips[k], " ")); ip == nil {
|
|
return errors.New(fmt.Sprintf("invalid IP '%s'", ips[k]))
|
|
} else {
|
|
*v = ip
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// auth loop. Try all auth profiles from startProfile
|
|
// return the last possible profile and the mail if we found a mail like login
|
|
func (s *OpenVpnMgt) AuthLoop(startProfile, user, pass string, overridePwdCheck bool) (string, string, string) {
|
|
login := []string{user}
|
|
profile := startProfile
|
|
mail := ""
|
|
otpSalt := ""
|
|
|
|
re := rcache.Get("^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+.[a-zA-Z0-9-.]+$")
|
|
|
|
for {
|
|
if mail == "" && re.MatchString(login[0]) {
|
|
mail = login[0]
|
|
}
|
|
n := profile
|
|
for k, ldap := range s.ldap {
|
|
if ldap.upgradeFrom != profile {
|
|
continue
|
|
}
|
|
err, userOk, passOk, attributes := ldap.Auth(login, pass)
|
|
|
|
if otpSalt == "" && len(attributes) > 1 && len(attributes[1]) > 0 {
|
|
otpSalt = attributes[1][0]
|
|
}
|
|
|
|
// if there is an error, try the other configurations
|
|
if err != nil {
|
|
log.Printf("user %s not validated as %s\n", user, k)
|
|
continue
|
|
}
|
|
|
|
// we did find a valid User
|
|
if userOk {
|
|
// the login for the new auth level is given by the current one
|
|
login = attributes[0]
|
|
|
|
if passOk && profile != "" {
|
|
// it's at least the second auth level, and we have a valid
|
|
// password on 2 different auth system. It's a dupplicate
|
|
// password, let's log it
|
|
log.Printf("User %s has a dupplicate password\n", user)
|
|
}
|
|
|
|
// we have either a positive auth ok a previous valid one
|
|
if passOk || profile != "" || overridePwdCheck {
|
|
profile = k
|
|
}
|
|
}
|
|
}
|
|
|
|
// no profile update this turn, no need to continue
|
|
if n == profile {
|
|
break
|
|
}
|
|
}
|
|
|
|
return profile, mail, otpSalt
|
|
}
|
|
|
|
// override the real DialTLS function
|
|
func myDialTLS(network, addr string, config *tls.Config) (*ldap.Conn, error) {
|
|
dc, err := net.DialTimeout(network, addr, 3*time.Second)
|
|
if err != nil {
|
|
return nil, ldap.NewError(ldap.ErrorNetwork, err)
|
|
}
|
|
c := tls.Client(dc, config)
|
|
if err = c.Handshake(); err != nil {
|
|
// Handshake error, close the established connection before we return an error
|
|
dc.Close()
|
|
return nil, ldap.NewError(ldap.ErrorNetwork, err)
|
|
}
|
|
conn := ldap.NewConn(c, true)
|
|
conn.Start()
|
|
return conn, nil
|
|
}
|
|
|
|
func (conf *ldapConfig) Auth(logins []string, pass string) (e error, userOk, passOk bool, attributes [][]string) {
|
|
// special case. This configuration is a filter on the previous one
|
|
if len(conf.servers) == 0 && len(conf.validGroups) > 0 {
|
|
if inArray(logins, conf.validGroups) {
|
|
return nil, true, false, [][]string{logins}
|
|
}
|
|
}
|
|
|
|
// no server ldap or multiple login should not happen here
|
|
if len(logins) != 1 || len(conf.servers) == 0 {
|
|
return nil, false, false, nil
|
|
}
|
|
|
|
attributes = [][]string{logins}
|
|
for _, s := range conf.servers {
|
|
// we force ldaps because we can
|
|
l, err := myDialTLS("tcp", s+":636", &tls.Config{ServerName: s})
|
|
if err != nil {
|
|
log.Println(err)
|
|
continue
|
|
}
|
|
defer l.Close()
|
|
|
|
// First bind with a read only user
|
|
if err = l.Bind(conf.bindCn, conf.bindPw); err != nil {
|
|
log.Println(err)
|
|
return err, false, false, nil
|
|
}
|
|
|
|
search := append(conf.attributes, "dn")
|
|
|
|
// search the user
|
|
searchRequest := ldap.NewSearchRequest(
|
|
conf.baseDN,
|
|
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
|
|
fmt.Sprintf(conf.searchFilter, logins[0]),
|
|
search,
|
|
nil,
|
|
)
|
|
|
|
sr, err := l.Search(searchRequest)
|
|
if err != nil {
|
|
log.Println(err)
|
|
return err, false, false, nil
|
|
}
|
|
if len(sr.Entries) != 1 {
|
|
return errors.New("User does not exist or too many entries returned"), false, false, nil
|
|
}
|
|
|
|
// check the attributes requested in the search
|
|
// a valid account must be part of the correct group (per instance)
|
|
|
|
ret := [][]string{}
|
|
|
|
for _, needed := range conf.attributes {
|
|
ok := false
|
|
for _, attribute := range sr.Entries[0].Attributes {
|
|
if (*attribute).Name == needed {
|
|
ret = append(ret, attribute.Values)
|
|
ok = true
|
|
}
|
|
}
|
|
if !ok {
|
|
ret = append(ret, []string{})
|
|
}
|
|
}
|
|
|
|
// user must have both the first and second attributes
|
|
if len(ret[0]) == 0 {
|
|
log.Printf("User %s has no %s attribute", logins[0], conf.attributes[0])
|
|
return nil, false, false, nil
|
|
}
|
|
|
|
if len(ret[1]) == 0 {
|
|
log.Printf("User %s has no %s attribute", logins[0], conf.attributes[1])
|
|
return nil, false, false, nil
|
|
}
|
|
|
|
// check if the first attribute valus are in the validGroups list
|
|
if len(conf.validGroups) > 0 && !inArray(conf.validGroups, ret[0]) {
|
|
return nil, false, false, nil
|
|
}
|
|
|
|
// if there is no validGroups check, pass the first attribute values to the
|
|
// next level
|
|
if len(conf.validGroups) == 0 {
|
|
attributes = [][]string{ret[0]}
|
|
} else {
|
|
attributes = [][]string{ret[1]}
|
|
}
|
|
|
|
if len(ret) > 2 {
|
|
attributes = append(attributes, ret[2:]...)
|
|
}
|
|
log.Println(attributes)
|
|
|
|
log.Printf("User %s has a valid account on %s", logins[0], s)
|
|
|
|
userdn := sr.Entries[0].DN
|
|
|
|
// if the password is empty, stop here
|
|
if pass == "" {
|
|
return nil, true, false, attributes
|
|
}
|
|
|
|
// if there is an error, it's because the password is invalid
|
|
if err = l.Bind(userdn, pass); err != nil {
|
|
return nil, true, false, attributes
|
|
}
|
|
|
|
// everything is fine,
|
|
log.Printf("User %s has a valid password on %s", logins[0], s)
|
|
return nil, true, true, attributes
|
|
}
|
|
// if we are here, no server is responding, rejectif auth
|
|
log.Println("can't join any ldap server")
|
|
return
|
|
}
|