Merge branch 'asterix11-master'

This commit is contained in:
Christoph Haas 2022-12-27 12:36:23 +01:00
commit f2afd4a21c
4 changed files with 72 additions and 32 deletions

View File

@ -27,6 +27,7 @@ type Config struct {
LoginFilter string `yaml:"loginFilter" envconfig:"LDAP_LOGIN_FILTER"` // {{login_identifier}} gets replaced with the login email address LoginFilter string `yaml:"loginFilter" envconfig:"LDAP_LOGIN_FILTER"` // {{login_identifier}} gets replaced with the login email address
SyncFilter string `yaml:"syncFilter" envconfig:"LDAP_SYNC_FILTER"` SyncFilter string `yaml:"syncFilter" envconfig:"LDAP_SYNC_FILTER"`
SyncGroupFilter string `yaml:"syncGroupFilter" envconfig:"LDAP_SYNC_GROUP_FILTER"`
AdminLdapGroup string `yaml:"adminGroup" envconfig:"LDAP_ADMIN_GROUP"` // Members of this group receive admin rights in WG-Portal AdminLdapGroup string `yaml:"adminGroup" envconfig:"LDAP_ADMIN_GROUP"` // Members of this group receive admin rights in WG-Portal
AdminLdapGroup_ *gldap.DN `yaml:"-"` AdminLdapGroup_ *gldap.DN `yaml:"-"`
EveryoneAdmin bool `yaml:"everyoneAdmin" envconfig:"LDAP_EVERYONE_ADMIN"` EveryoneAdmin bool `yaml:"everyoneAdmin" envconfig:"LDAP_EVERYONE_ADMIN"`

View File

@ -8,6 +8,13 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
) )
type ObjectType int64
const (
Users ObjectType = 1
Groups ObjectType = 2
)
type RawLdapData struct { type RawLdapData struct {
DN string DN string
Attributes map[string]string Attributes map[string]string
@ -69,21 +76,34 @@ func Close(conn *ldap.Conn) {
} }
} }
func FindAllUsers(cfg *Config) ([]RawLdapData, error) { func FindAllObjects(cfg *Config, objType ObjectType) ([]RawLdapData, error) {
client, err := Open(cfg) client, err := Open(cfg)
if err != nil { if err != nil {
return nil, errors.WithMessage(err, "failed to open ldap connection") return nil, errors.WithMessage(err, "failed to open ldap connection")
} }
defer Close(client) defer Close(client)
// Search all users var searchRequest *ldap.SearchRequest
attrs := []string{"dn", cfg.EmailAttribute, cfg.EmailAttribute, cfg.FirstNameAttribute, cfg.LastNameAttribute, var attrs []string
cfg.PhoneAttribute, cfg.GroupMemberAttribute}
searchRequest := ldap.NewSearchRequest( if objType == Users {
cfg.BaseDN, // Search all users
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, attrs = []string{"dn", cfg.EmailAttribute, cfg.EmailAttribute, cfg.FirstNameAttribute, cfg.LastNameAttribute,
cfg.SyncFilter, attrs, nil, cfg.PhoneAttribute, cfg.GroupMemberAttribute}
) searchRequest = ldap.NewSearchRequest(
cfg.BaseDN,
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
cfg.SyncFilter, attrs, nil,
)
} else if objType == Groups {
// Search all groups
attrs = []string{"dn", cfg.GroupMemberAttribute}
searchRequest = ldap.NewSearchRequest(
cfg.BaseDN,
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
cfg.SyncGroupFilter, attrs, nil,
)
}
sr, err := client.Search(searchRequest) sr, err := client.Search(searchRequest)
if err != nil { if err != nil {

View File

@ -114,6 +114,7 @@ func NewConfig() *Config {
cfg.LDAP.AdminLdapGroup = "CN=WireGuardAdmins,OU=_O_IT,DC=COMPANY,DC=LOCAL" cfg.LDAP.AdminLdapGroup = "CN=WireGuardAdmins,OU=_O_IT,DC=COMPANY,DC=LOCAL"
cfg.LDAP.LoginFilter = "(&(objectClass=organizationalPerson)(mail={{login_identifier}})(!userAccountControl:1.2.840.113556.1.4.803:=2))" cfg.LDAP.LoginFilter = "(&(objectClass=organizationalPerson)(mail={{login_identifier}})(!userAccountControl:1.2.840.113556.1.4.803:=2))"
cfg.LDAP.SyncFilter = "(&(objectClass=organizationalPerson)(!userAccountControl:1.2.840.113556.1.4.803:=2)(mail=*))" cfg.LDAP.SyncFilter = "(&(objectClass=organizationalPerson)(!userAccountControl:1.2.840.113556.1.4.803:=2)(mail=*))"
cfg.LDAP.SyncGroupFilter = "(&(objectClass=group))"
cfg.WG.DeviceNames = []string{"wg0"} cfg.WG.DeviceNames = []string{"wg0"}
cfg.WG.DefaultDeviceName = "wg0" cfg.WG.DefaultDeviceName = "wg0"

View File

@ -4,20 +4,36 @@ import (
"strings" "strings"
"time" "time"
gldap "github.com/go-ldap/ldap/v3"
"github.com/h44z/wg-portal/internal/wireguard" "github.com/h44z/wg-portal/internal/wireguard"
"github.com/h44z/wg-portal/internal/ldap" "github.com/h44z/wg-portal/internal/ldap"
"github.com/h44z/wg-portal/internal/users" "github.com/h44z/wg-portal/internal/users"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"gorm.io/gorm" "gorm.io/gorm"
gldap "github.com/go-ldap/ldap/v3"
) )
func (s *Server) SyncLdapWithUserDatabase() { func (s *Server) SyncLdapWithUserDatabase() {
logrus.Info("starting ldap user synchronization...") logrus.Info("starting ldap user synchronization...")
running := true running := true
for running { for running {
// Main work here
logrus.Trace("syncing ldap users to database...")
ldapUsers, err := ldap.FindAllObjects(&s.config.LDAP, ldap.Users)
ldapGroups, errGroups := ldap.FindAllObjects(&s.config.LDAP, ldap.Groups)
if err != nil && errGroups != nil {
logrus.Errorf("failed to fetch users from ldap: %v", err)
continue
}
logrus.Tracef("found %d users in ldap", len(ldapUsers))
// Update existing LDAP users
s.updateLdapUsers(ldapUsers, ldapGroups)
// Disable missing LDAP users
s.disableMissingLdapUsers(ldapUsers)
// Select blocks until one of the cases happens // Select blocks until one of the cases happens
select { select {
case <-time.After(1 * time.Minute): case <-time.After(1 * time.Minute):
@ -27,42 +43,44 @@ func (s *Server) SyncLdapWithUserDatabase() {
running = false running = false
continue continue
} }
// Main work here
logrus.Trace("syncing ldap users to database...")
ldapUsers, err := ldap.FindAllUsers(&s.config.LDAP)
if err != nil {
logrus.Errorf("failed to fetch users from ldap: %v", err)
continue
}
logrus.Tracef("found %d users in ldap", len(ldapUsers))
// Update existing LDAP users
s.updateLdapUsers(ldapUsers)
// Disable missing LDAP users
s.disableMissingLdapUsers(ldapUsers)
} }
logrus.Info("ldap user synchronization stopped") logrus.Info("ldap user synchronization stopped")
} }
func (s Server) userIsInAdminGroup(ldapData *ldap.RawLdapData) bool { func (s Server) userIsInAdminGroup(ldapData *ldap.RawLdapData, ldapGroupData []ldap.RawLdapData, layer int) bool {
if s.config.LDAP.EveryoneAdmin { if s.config.LDAP.EveryoneAdmin {
return true return true
} }
if s.config.LDAP.AdminLdapGroup_ == nil { if s.config.LDAP.AdminLdapGroup_ == nil {
return false return false
} }
//fmt.Printf("%+v\n", ldapData.Attributes)
var prefix string
for i := 0; i < layer; i++ {
prefix += "+"
}
logrus.Tracef("%s Group layer: %d\n", prefix, layer)
for _, group := range ldapData.RawAttributes[s.config.LDAP.GroupMemberAttribute] { for _, group := range ldapData.RawAttributes[s.config.LDAP.GroupMemberAttribute] {
logrus.Tracef("%s%s\n", prefix, string(group))
var dn, _ = gldap.ParseDN(string(group)) var dn, _ = gldap.ParseDN(string(group))
if s.config.LDAP.AdminLdapGroup_.Equal(dn) { if s.config.LDAP.AdminLdapGroup_.Equal(dn) {
logrus.Tracef("%sFOUND: %s\n", prefix, string(group))
return true return true
} }
for _, group2 := range ldapGroupData {
if group2.DN == string(group) {
logrus.Tracef("%sChecking nested: %s\n", prefix, group2.DN)
isAdmin := s.userIsInAdminGroup(&group2, ldapGroupData, layer+1)
if isAdmin {
return true
}
}
}
} }
return false return false
} }
func (s Server) userChangedInLdap(user *users.User, ldapData *ldap.RawLdapData) bool { func (s Server) userChangedInLdap(user *users.User, ldapData *ldap.RawLdapData, ldapGroupData []ldap.RawLdapData) bool {
if user.Firstname != ldapData.Attributes[s.config.LDAP.FirstNameAttribute] { if user.Firstname != ldapData.Attributes[s.config.LDAP.FirstNameAttribute] {
return true return true
} }
@ -83,7 +101,7 @@ func (s Server) userChangedInLdap(user *users.User, ldapData *ldap.RawLdapData)
return true return true
} }
if user.IsAdmin != s.userIsInAdminGroup(ldapData) { if user.IsAdmin != s.userIsInAdminGroup(ldapData, ldapGroupData, 0) {
return true return true
} }
@ -126,7 +144,7 @@ func (s *Server) disableMissingLdapUsers(ldapUsers []ldap.RawLdapData) {
} }
} }
func (s *Server) updateLdapUsers(ldapUsers []ldap.RawLdapData) { func (s *Server) updateLdapUsers(ldapUsers []ldap.RawLdapData, ldapGroups []ldap.RawLdapData) {
for i := range ldapUsers { for i := range ldapUsers {
if ldapUsers[i].Attributes[s.config.LDAP.EmailAttribute] == "" { if ldapUsers[i].Attributes[s.config.LDAP.EmailAttribute] == "" {
logrus.Tracef("skipping sync of %s, empty email attribute", ldapUsers[i].DN) logrus.Tracef("skipping sync of %s, empty email attribute", ldapUsers[i].DN)
@ -152,13 +170,13 @@ func (s *Server) updateLdapUsers(ldapUsers []ldap.RawLdapData) {
} }
// Sync attributes from ldap // Sync attributes from ldap
if s.userChangedInLdap(user, &ldapUsers[i]) { if s.userChangedInLdap(user, &ldapUsers[i], ldapGroups) {
logrus.Debugf("updating ldap user %s", user.Email) logrus.Debugf("updating ldap user %s", user.Email)
user.Firstname = ldapUsers[i].Attributes[s.config.LDAP.FirstNameAttribute] user.Firstname = ldapUsers[i].Attributes[s.config.LDAP.FirstNameAttribute]
user.Lastname = ldapUsers[i].Attributes[s.config.LDAP.LastNameAttribute] user.Lastname = ldapUsers[i].Attributes[s.config.LDAP.LastNameAttribute]
user.Email = ldapUsers[i].Attributes[s.config.LDAP.EmailAttribute] user.Email = ldapUsers[i].Attributes[s.config.LDAP.EmailAttribute]
user.Phone = ldapUsers[i].Attributes[s.config.LDAP.PhoneAttribute] user.Phone = ldapUsers[i].Attributes[s.config.LDAP.PhoneAttribute]
user.IsAdmin = s.userIsInAdminGroup(&ldapUsers[i]) user.IsAdmin = s.userIsInAdminGroup(&ldapUsers[i], ldapGroups, 0)
user.Source = users.UserSourceLdap user.Source = users.UserSourceLdap
user.DeletedAt = gorm.DeletedAt{} // Not deleted user.DeletedAt = gorm.DeletedAt{} // Not deleted