optimisations

* use pyke's re cache
* get an unlimited number of ldap attributes
* get a perturbator for the OTP secret, in case of stolen phone
* lowercase the username, to avoid strange behaviour with the OTP
This commit is contained in:
Xavier Henner
2019-07-12 22:33:22 +02:00
parent 3d1801ee50
commit 24544a6260
7 changed files with 96 additions and 84 deletions

102
ldap.go
View File

@@ -6,28 +6,27 @@ import (
"fmt"
"log"
"net"
"regexp"
"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
primaryAttribute string
secondaryAttribute string
validGroups []string
mfaType string
certAuth string
ipMin net.IP
ipMax net.IP
upgradeFrom string
routes []string
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 {
@@ -46,24 +45,28 @@ func (l *ldapConfig) addIPRange(s string) error {
// 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) {
func (s *OpenVpnMgt) AuthLoop(startProfile, user, pass string, overridePwdCheck bool) (string, string, string) {
login := []string{user}
profile := startProfile
mail := ""
otpSalt := ""
re := regexp.MustCompile("^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+.[a-zA-Z0-9-.]+$")
re := rcache.Get("^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+.[a-zA-Z0-9-.]+$")
for {
if re.MatchString(login[0]) && mail == "" {
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, secondary := ldap.Auth(login, pass)
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 {
@@ -74,7 +77,7 @@ func (s *OpenVpnMgt) AuthLoop(startProfile, user, pass string, overridePwdCheck
// we did find a valid User
if userOk {
// the login for the new auth level is given by the current one
login = secondary
login = attributes[0]
if passOk && profile != "" {
// it's at least the second auth level, and we have a valid
@@ -96,7 +99,7 @@ func (s *OpenVpnMgt) AuthLoop(startProfile, user, pass string, overridePwdCheck
}
}
return profile, mail
return profile, mail, otpSalt
}
// override the real DialTLS function
@@ -116,13 +119,11 @@ func myDialTLS(network, addr string, config *tls.Config) (*ldap.Conn, error) {
return conn, nil
}
func (conf *ldapConfig) Auth(logins []string, pass string) (e error, userOk, passOk bool, attributes []string) {
var primary, secondary []string
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, logins
return nil, true, false, [][]string{logins}
}
}
@@ -131,7 +132,7 @@ func (conf *ldapConfig) Auth(logins []string, pass string) (e error, userOk, pas
return nil, false, false, nil
}
attributes = logins
attributes = [][]string{logins}
for _, s := range conf.servers {
// we force ldaps because we can
l, err := myDialTLS("tcp", s+":636", &tls.Config{ServerName: s})
@@ -147,10 +148,7 @@ func (conf *ldapConfig) Auth(logins []string, pass string) (e error, userOk, pas
return err, false, false, nil
}
search := []string{"dn", conf.primaryAttribute}
if conf.secondaryAttribute != "" {
search = append(search, conf.secondaryAttribute)
}
search := append(conf.attributes, "dn")
// search the user
searchRequest := ldap.NewSearchRequest(
@@ -172,39 +170,51 @@ func (conf *ldapConfig) Auth(logins []string, pass string) (e error, userOk, pas
// check the attributes requested in the search
// a valid account must be part of the correct group (per instance)
for _, attribute := range sr.Entries[0].Attributes {
if (*attribute).Name == conf.primaryAttribute {
primary = attribute.Values
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 (*attribute).Name == conf.secondaryAttribute {
secondary = attribute.Values
if !ok {
ret = append(ret, []string{})
}
}
// user must have both primary and secondary attributes
if len(primary) == 0 {
log.Printf("User %s has no %s attribute", logins[0], conf.primaryAttribute)
// 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(secondary) == 0 {
log.Printf("User %s has no %s attribute", logins[0], conf.secondaryAttribute)
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 primary attributes are in the validGroups list
if len(conf.validGroups) > 0 && !inArray(conf.validGroups, primary) {
// 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 primary attributes to the
// if there is no validGroups check, pass the first attribute values to the
// next level
if len(conf.validGroups) == 0 {
attributes = primary
attributes = [][]string{ret[0]}
} else {
attributes = secondary
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