mirror of https://github.com/h44z/wg-portal.git
				
				
				
			cleanup recursive ldap group sync
This commit is contained in:
		
							parent
							
								
									f2afd4a21c
								
							
						
					
					
						commit
						53a6602a64
					
				|  | @ -175,7 +175,8 @@ The following configuration options are available: | ||||||
| | LDAP_USER                  | user                    | ldap        | company\\\\ldap_wireguard                                                                                       | The bind user.                                                                                                                                    | | | LDAP_USER                  | user                    | ldap        | company\\\\ldap_wireguard                                                                                       | The bind user.                                                                                                                                    | | ||||||
| | LDAP_PASSWORD              | pass                    | ldap        | SuperSecret                                                                                                     | The bind password.                                                                                                                                | | | LDAP_PASSWORD              | pass                    | ldap        | SuperSecret                                                                                                     | The bind password.                                                                                                                                | | ||||||
| | LDAP_LOGIN_FILTER          | loginFilter             | ldap        | (&(objectClass=organizationalPerson)(mail={{login_identifier}})(!userAccountControl:1.2.840.113556.1.4.803:=2)) | {{login_identifier}} will be replaced with the login email address.                                                                               | | | LDAP_LOGIN_FILTER          | loginFilter             | ldap        | (&(objectClass=organizationalPerson)(mail={{login_identifier}})(!userAccountControl:1.2.840.113556.1.4.803:=2)) | {{login_identifier}} will be replaced with the login email address.                                                                               | | ||||||
| | LDAP_SYNC_FILTER           | syncFilter              | ldap        | (&(objectClass=organizationalPerson)(!userAccountControl:1.2.840.113556.1.4.803:=2)(mail=*))                    | The filter string for the LDAP synchronization service.                                                                                           | | | LDAP_SYNC_FILTER           | syncFilter              | ldap        | (&(objectClass=organizationalPerson)(!userAccountControl:1.2.840.113556.1.4.803:=2)(mail=*))                    | The filter string for the LDAP synchronization service. Users matching this filter will be synchronized with the WireGuard Portal database.       | | ||||||
|  | | LDAP_SYNC_GROUP_FILTER     | syncGroupFilter         | ldap        | (&(objectClass=group))                                                                                          | The filter string for the LDAP groups. The groups are used to recursively check for admin group member ship of users.                             | | ||||||
| | LDAP_ADMIN_GROUP           | adminGroup              | ldap        | CN=WireGuardAdmins,OU=_O_IT,DC=COMPANY,DC=LOCAL                                                                 | Users in this group are marked as administrators.                                                                                                 | | | LDAP_ADMIN_GROUP           | adminGroup              | ldap        | CN=WireGuardAdmins,OU=_O_IT,DC=COMPANY,DC=LOCAL                                                                 | Users in this group are marked as administrators.                                                                                                 | | ||||||
| | LDAP_ATTR_EMAIL            | attrEmail               | ldap        | mail                                                                                                            | User email attribute.                                                                                                                             | | | LDAP_ATTR_EMAIL            | attrEmail               | ldap        | mail                                                                                                            | User email attribute.                                                                                                                             | | ||||||
| | LDAP_ATTR_FIRSTNAME        | attrFirstname           | ldap        | givenName                                                                                                       | User firstname attribute.                                                                                                                         | | | LDAP_ATTR_FIRSTNAME        | attrFirstname           | ldap        | givenName                                                                                                       | User firstname attribute.                                                                                                                         | | ||||||
|  |  | ||||||
|  | @ -8,11 +8,11 @@ import ( | ||||||
| 	"github.com/pkg/errors" | 	"github.com/pkg/errors" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type ObjectType int64 | type ObjectType int | ||||||
| 
 | 
 | ||||||
| const ( | const ( | ||||||
| 	Users  ObjectType = 1 | 	Users ObjectType = iota | ||||||
| 	Groups ObjectType = 2 | 	Groups | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type RawLdapData struct { | type RawLdapData struct { | ||||||
|  | @ -86,7 +86,8 @@ func FindAllObjects(cfg *Config, objType ObjectType) ([]RawLdapData, error) { | ||||||
| 	var searchRequest *ldap.SearchRequest | 	var searchRequest *ldap.SearchRequest | ||||||
| 	var attrs []string | 	var attrs []string | ||||||
| 
 | 
 | ||||||
| 	if objType == Users { | 	switch objType { | ||||||
|  | 	case Users: | ||||||
| 		// Search all users
 | 		// Search all users
 | ||||||
| 		attrs = []string{"dn", cfg.EmailAttribute, cfg.EmailAttribute, cfg.FirstNameAttribute, cfg.LastNameAttribute, | 		attrs = []string{"dn", cfg.EmailAttribute, cfg.EmailAttribute, cfg.FirstNameAttribute, cfg.LastNameAttribute, | ||||||
| 			cfg.PhoneAttribute, cfg.GroupMemberAttribute} | 			cfg.PhoneAttribute, cfg.GroupMemberAttribute} | ||||||
|  | @ -95,7 +96,7 @@ func FindAllObjects(cfg *Config, objType ObjectType) ([]RawLdapData, error) { | ||||||
| 			ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, | 			ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, | ||||||
| 			cfg.SyncFilter, attrs, nil, | 			cfg.SyncFilter, attrs, nil, | ||||||
| 		) | 		) | ||||||
| 	} else if objType == Groups { | 	case Groups: | ||||||
| 		// Search all groups
 | 		// Search all groups
 | ||||||
| 		attrs = []string{"dn", cfg.GroupMemberAttribute} | 		attrs = []string{"dn", cfg.GroupMemberAttribute} | ||||||
| 		searchRequest = ldap.NewSearchRequest( | 		searchRequest = ldap.NewSearchRequest( | ||||||
|  | @ -103,6 +104,8 @@ func FindAllObjects(cfg *Config, objType ObjectType) ([]RawLdapData, error) { | ||||||
| 			ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, | 			ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, | ||||||
| 			cfg.SyncGroupFilter, attrs, nil, | 			cfg.SyncGroupFilter, attrs, nil, | ||||||
| 		) | 		) | ||||||
|  | 	default: | ||||||
|  | 		panic("invalid object type") | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	sr, err := client.Search(searchRequest) | 	sr, err := client.Search(searchRequest) | ||||||
|  |  | ||||||
|  | @ -5,10 +5,9 @@ import ( | ||||||
| 	"time" | 	"time" | ||||||
| 
 | 
 | ||||||
| 	gldap "github.com/go-ldap/ldap/v3" | 	gldap "github.com/go-ldap/ldap/v3" | ||||||
| 	"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/h44z/wg-portal/internal/wireguard" | ||||||
| 	"github.com/sirupsen/logrus" | 	"github.com/sirupsen/logrus" | ||||||
| 	"gorm.io/gorm" | 	"gorm.io/gorm" | ||||||
| ) | ) | ||||||
|  | @ -21,12 +20,17 @@ func (s *Server) SyncLdapWithUserDatabase() { | ||||||
| 		// Main work here
 | 		// Main work here
 | ||||||
| 		logrus.Trace("syncing ldap users to database...") | 		logrus.Trace("syncing ldap users to database...") | ||||||
| 		ldapUsers, err := ldap.FindAllObjects(&s.config.LDAP, ldap.Users) | 		ldapUsers, err := ldap.FindAllObjects(&s.config.LDAP, ldap.Users) | ||||||
| 		ldapGroups, errGroups := ldap.FindAllObjects(&s.config.LDAP, ldap.Groups) | 		if err != nil { | ||||||
| 		if err != nil && errGroups != nil { |  | ||||||
| 			logrus.Errorf("failed to fetch users from ldap: %v", err) | 			logrus.Errorf("failed to fetch users from ldap: %v", err) | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| 		logrus.Tracef("found %d users in ldap", len(ldapUsers)) | 		ldapGroups, err := ldap.FindAllObjects(&s.config.LDAP, ldap.Groups) | ||||||
|  | 		if err != nil { | ||||||
|  | 			logrus.Errorf("failed to fetch groups from ldap: %v", err) | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		logrus.Tracef("found %d users and %d groups in ldap", len(ldapUsers), len(ldapGroups)) | ||||||
| 
 | 
 | ||||||
| 		// Update existing LDAP users
 | 		// Update existing LDAP users
 | ||||||
| 		s.updateLdapUsers(ldapUsers, ldapGroups) | 		s.updateLdapUsers(ldapUsers, ldapGroups) | ||||||
|  | @ -34,6 +38,8 @@ func (s *Server) SyncLdapWithUserDatabase() { | ||||||
| 		// Disable missing LDAP users
 | 		// Disable missing LDAP users
 | ||||||
| 		s.disableMissingLdapUsers(ldapUsers) | 		s.disableMissingLdapUsers(ldapUsers) | ||||||
| 
 | 
 | ||||||
|  | 		logrus.Trace("synchronized ldap users to database") | ||||||
|  | 
 | ||||||
| 		// 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): | ||||||
|  | @ -47,35 +53,62 @@ func (s *Server) SyncLdapWithUserDatabase() { | ||||||
| 	logrus.Info("ldap user synchronization stopped") | 	logrus.Info("ldap user synchronization stopped") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (s Server) userIsInAdminGroup(ldapData *ldap.RawLdapData, ldapGroupData []ldap.RawLdapData, layer int) bool { | func (s Server) userIsInAdminGroup(userData *ldap.RawLdapData, groupTreeData []ldap.RawLdapData) 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 _, userGroup := range userData.RawAttributes[s.config.LDAP.GroupMemberAttribute] { | ||||||
| 	for i := 0; i < layer; i++ { | 		var userGroupDn, _ = gldap.ParseDN(string(userGroup)) | ||||||
| 		prefix += "+" | 		if s.dnIsAdminGroup(userGroupDn, groupTreeData) { | ||||||
|  | 			return true | ||||||
| 		} | 		} | ||||||
| 	logrus.Tracef("%s Group layer: %d\n", prefix, layer) | 	} | ||||||
| 	for _, group := range ldapData.RawAttributes[s.config.LDAP.GroupMemberAttribute] { | 	return false | ||||||
| 		logrus.Tracef("%s%s\n", prefix, string(group)) | } | ||||||
| 		var dn, _ = gldap.ParseDN(string(group)) | 
 | ||||||
|  | // dnIsAdminGroup checks if the given DN is equal to the admin group, or if it is included in a groupTree that has the
 | ||||||
|  | // admin group as parent/root.
 | ||||||
|  | //
 | ||||||
|  | // WGPortal-Admin          (L0)
 | ||||||
|  | //
 | ||||||
|  | //	\_ IT-Admin            (L1)
 | ||||||
|  | //	  |_ Alice             (L2)
 | ||||||
|  | //	  |_ Bob               (L2)
 | ||||||
|  | //	  \_ Eve               (L2)
 | ||||||
|  | //	\_ External-Company    (L1)
 | ||||||
|  | //	  |_ External-Admin    (L2)
 | ||||||
|  | //	    |_ Sam             (L3)
 | ||||||
|  | //	    \_ Steve           (L3)
 | ||||||
|  | //
 | ||||||
|  | // All DNs in the example above are member of the admin group.
 | ||||||
|  | func (s Server) dnIsAdminGroup(dn *gldap.DN, groupTreeData []ldap.RawLdapData) bool { | ||||||
|  | 	if s.config.LDAP.AdminLdapGroup_ == nil { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	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) { | 	// Recursively check the whole group tree
 | ||||||
| 				logrus.Tracef("%sChecking nested: %s\n", prefix, group2.DN) | 	for _, group := range groupTreeData { | ||||||
| 				isAdmin := s.userIsInAdminGroup(&group2, ldapGroupData, layer+1) | 		var groupDn, _ = gldap.ParseDN(group.DN) | ||||||
| 				if isAdmin { | 		if !dn.Equal(groupDn) { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		for _, parentGroupDn := range group.RawAttributes[s.config.LDAP.GroupMemberAttribute] { | ||||||
|  | 			var parentDn, _ = gldap.ParseDN(string(parentGroupDn)) | ||||||
|  | 			if s.dnIsAdminGroup(parentDn, groupTreeData) { | ||||||
| 				return true | 				return true | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 		} | 
 | ||||||
|  | 		break | ||||||
| 	} | 	} | ||||||
| 	return false | 	return false | ||||||
| } | } | ||||||
|  | @ -101,7 +134,7 @@ func (s Server) userChangedInLdap(user *users.User, ldapData *ldap.RawLdapData, | ||||||
| 		return true | 		return true | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if user.IsAdmin != s.userIsInAdminGroup(ldapData, ldapGroupData, 0) { | 	if user.IsAdmin != s.userIsInAdminGroup(ldapData, ldapGroupData) { | ||||||
| 		return true | 		return true | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -176,7 +209,7 @@ func (s *Server) updateLdapUsers(ldapUsers []ldap.RawLdapData, ldapGroups []ldap | ||||||
| 			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], ldapGroups, 0) | 			user.IsAdmin = s.userIsInAdminGroup(&ldapUsers[i], ldapGroups) | ||||||
| 			user.Source = users.UserSourceLdap | 			user.Source = users.UserSourceLdap | ||||||
| 			user.DeletedAt = gorm.DeletedAt{} // Not deleted
 | 			user.DeletedAt = gorm.DeletedAt{} // Not deleted
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue