From bb649aef483495f36be27cc4136ab7d606263491 Mon Sep 17 00:00:00 2001 From: Gregory Wells Date: Fri, 3 Apr 2026 18:24:14 -0400 Subject: [PATCH] start to write a new LDAP interface --- src/ldap/ldap.go | 181 --------------------------------------- src/ldap/ldap_helpers.go | 7 ++ src/ldap/ldap_search.go | 9 ++ src/ldap/ldap_server.go | 130 ++++++++++++++++++++++++++++ 4 files changed, 146 insertions(+), 181 deletions(-) delete mode 100644 src/ldap/ldap.go create mode 100644 src/ldap/ldap_helpers.go create mode 100644 src/ldap/ldap_search.go create mode 100644 src/ldap/ldap_server.go diff --git a/src/ldap/ldap.go b/src/ldap/ldap.go deleted file mode 100644 index 08bea11..0000000 --- a/src/ldap/ldap.go +++ /dev/null @@ -1,181 +0,0 @@ -package ldap - -import ( - "crypto/tls" - "fmt" - "strings" - - "astraltech.xyz/accountmanager/src/logging" - "github.com/go-ldap/ldap/v3" -) - -type LDAPServer struct { - URL string - StartTLS bool - IgnoreInsecureCert bool - Connection *ldap.Conn -} - -type LDAPSearch struct { - Succeeded bool - LDAPSearch *ldap.SearchResult -} - -func ConnectToLDAPServer(URL string, starttls bool, ignore_cert bool) *LDAPServer { - logging.Debugf("Connecting to LDAP server %s", URL) - l, err := ldap.DialURL(URL) - if err != nil { - logging.Fatal("Failed to connect to LDAP server") - logging.Fatal(err.Error()) - } - logging.Infof("Connected to LDAP server") - - if starttls { - logging.Debugf("Enabling StartTLS") - if err := l.StartTLS(&tls.Config{InsecureSkipVerify: ignore_cert}); err != nil { - logging.Errorf("StartTLS failed %s", err.Error()) - } - logging.Infof("StartTLS enabled") - } - - return &LDAPServer{ - Connection: l, - URL: URL, - StartTLS: starttls, - IgnoreInsecureCert: ignore_cert, - } -} - -func ReconnectToLDAPServer(server *LDAPServer) error { - logging.Debugf("Reconnecting to %s LDAP server", server.URL) - if server == nil { - logging.Errorf("Cannot reconnect: server is nil") - return fmt.Errorf("Server is nil") - } - - l, err := ldap.DialURL(server.URL) - if err != nil { - logging.Errorf("Failed to connect to LDAP server (has server gone down)") - return err - } - - if server.StartTLS { - logging.Debugf("StartTLS enabling") - if err := l.StartTLS(&tls.Config{InsecureSkipVerify: server.IgnoreInsecureCert}); err != nil { - logging.Error("StartTLS failed") - return err - } - logging.Debugf("Successfully Started TLS") - } - - server.Connection = l - return nil -} - -func ConnectAsLDAPUser(server *LDAPServer, bindDN, password string) error { - logging.Debugf("Connecting to %s LDAP server with %s BindDN", server.URL, bindDN) - if server == nil { - logging.Errorf("Failed to connect as user, LDAP server is NULL") - return fmt.Errorf("LDAP server is null") - } - - if server.Connection == nil || server.Connection.IsClosing() { - err := ReconnectToLDAPServer(server) - return err - } - err := server.Connection.Bind(bindDN, password) - if err != nil { - logging.Errorf("Failed to bind to LDAP as user %s", err.Error()) - return err - } - return nil -} - -func SearchLDAPServer(server *LDAPServer, baseDN string, searchFilter string, attributes []string) LDAPSearch { - logging.Debugf("Searching %s LDAP server\n\tBase DN: %s\n\tSearch Filter %s\n\tAttributes: %s", server.URL, baseDN, searchFilter, strings.Join(attributes, ",")) - if server == nil { - logging.Errorf("Server is nil, failed to search LDAP server") - return LDAPSearch{false, nil} - } - - if server.Connection == nil { - ReconnectToLDAPServer(server) - if server.Connection == nil { - return LDAPSearch{false, nil} - } - } - - searchRequest := ldap.NewSearchRequest( - baseDN, - ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, - searchFilter, attributes, - nil, - ) - - sr, err := server.Connection.Search(searchRequest) - if err != nil { - logging.Errorf("Failed to search LDAP server %s\n", err.Error()) - return LDAPSearch{false, nil} - } - - return LDAPSearch{true, sr} -} - -func ModifyLDAPAttribute(server *LDAPServer, userDN string, attribute string, data []string) error { - logging.Infof("Modifing LDAP attribute %s", attribute) - modify := ldap.NewModifyRequest(userDN, nil) - modify.Replace(attribute, data) - err := server.Connection.Modify(modify) - if err != nil { - logging.Errorf("Failed to modify %s", err.Error()) - return err - } - return nil -} - -func ChangeLDAPPassword(server *LDAPServer, userDN, oldPassword, newPassword string) error { - logging.Infof("Changing LDAP password for %s", userDN) - - if server == nil || server.Connection == nil { - return fmt.Errorf("LDAP connection not initialized") - } - - // Ensure connection is alive - if server.Connection.IsClosing() { - if err := ReconnectToLDAPServer(server); err != nil { - return err - } - } - - // Bind as the user (required for FreeIPA self-password change) - err := server.Connection.Bind(userDN, oldPassword) - if err != nil { - logging.Errorf("Failed to bind as user: %s", err.Error()) - return err - } - - // Perform password modify extended operation - _, err = server.Connection.PasswordModify(&ldap.PasswordModifyRequest{ - UserIdentity: userDN, - OldPassword: oldPassword, - NewPassword: newPassword, - }) - if err != nil { - logging.Errorf("Password modify failed: %s", err.Error()) - return err - } - logging.Infof("Password successfully changed for %s", userDN) - return nil -} - -func CloseLDAPServer(server *LDAPServer) { - if server != nil && server.Connection != nil { - logging.Debug("Closing connection to LDAP server") - err := server.Connection.Close() - if err != nil { - logging.Errorf("Failed to close LDAP server %s", err.Error()) - } - } -} - -func LDAPEscapeFilter(input string) string { return ldap.EscapeFilter(input) } diff --git a/src/ldap/ldap_helpers.go b/src/ldap/ldap_helpers.go new file mode 100644 index 0000000..3400c1e --- /dev/null +++ b/src/ldap/ldap_helpers.go @@ -0,0 +1,7 @@ +package ldap + +import ( + "github.com/go-ldap/ldap/v3" +) + +func LDAPEscapeFilter(input string) string { return ldap.EscapeFilter(input) } diff --git a/src/ldap/ldap_search.go b/src/ldap/ldap_search.go new file mode 100644 index 0000000..26195a5 --- /dev/null +++ b/src/ldap/ldap_search.go @@ -0,0 +1,9 @@ +package ldap + +import ( + "github.com/go-ldap/ldap/v3" +) + +type LDAPSearch struct { + search *ldap.SearchResult +} diff --git a/src/ldap/ldap_server.go b/src/ldap/ldap_server.go new file mode 100644 index 0000000..89e24c9 --- /dev/null +++ b/src/ldap/ldap_server.go @@ -0,0 +1,130 @@ +package ldap + +import ( + "crypto/tls" + "strings" + + "astraltech.xyz/accountmanager/src/logging" + "github.com/go-ldap/ldap/v3" +) + +type LDAPServer struct { + URL string + StartTLS bool + IgnoreInsecureCert bool +} + +func (s *LDAPServer) TestConnection() (bool, error) { + l, err := ldap.DialURL(s.URL) + l.Close() + if err != nil { + return false, err + } + return true, nil +} + +// internal connect, should not be used regularly +func (s *LDAPServer) connect() (*ldap.Conn, error) { + l, err := ldap.DialURL(s.URL) + if err != nil { + return nil, err + } + + if s.StartTLS { + err = l.StartTLS(&tls.Config{ + InsecureSkipVerify: s.IgnoreInsecureCert, + }) + if err != nil { + l.Close() + return nil, err + } + } + + return l, nil +} +func (s *LDAPServer) connectAsUser(userDN, password string) (*ldap.Conn, error) { + conn, err := s.connect() + if err != nil { + return nil, err + } + err = conn.Bind(userDN, password) + if err != nil { + return nil, err + } + return conn, nil +} + +func (s *LDAPServer) AuthenticateUser(userDN, password string) (bool, error) { + conn, err := s.connectAsUser(userDN, password) + if err != nil || conn == nil { + return false, err + } + conn.Close() + return true, nil +} + +func (s *LDAPServer) SerchServer( + userDN string, password string, + baseDN string, + searchFilter string, attributes []string, +) (*LDAPSearch, error) { + logging.Debugf("Searching %s LDAP server\n\tBase DN: %s\n\tSearch Filter %s\n\tAttributes: %s", s.URL, baseDN, searchFilter, strings.Join(attributes, ",")) + conn, err := s.connectAsUser(userDN, password) + if err != nil { + return nil, err + } + defer conn.Close() + + searchRequest := ldap.NewSearchRequest( + baseDN, + ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, + searchFilter, attributes, + nil, + ) + sr, err := conn.Search(searchRequest) + if err != nil { + logging.Errorf("Failed to search LDAP server %s\n", err.Error()) + return nil, err + } + return &LDAPSearch{sr}, nil +} + +func (s *LDAPServer) ChangePassword(userDN string, oldPassword string, newPassword string) error { + conn, err := s.connectAsUser(userDN, oldPassword) + if err != nil { + return err + } + defer conn.Close() + + // Perform password modify extended operation + _, err = conn.PasswordModify(&ldap.PasswordModifyRequest{ + UserIdentity: userDN, + OldPassword: oldPassword, + NewPassword: newPassword, + }) + if err != nil { + logging.Errorf("Password modify failed: %s", err.Error()) + return err + } + logging.Infof("Password successfully changed for %s", userDN) + return nil +} + +func (s *LDAPServer) ModifyAttribute(bindUserDN string, password string, userDN string, attribute string, data []string) error { + logging.Infof("Modifing LDAP attribute %s", attribute) + + conn, err := s.connectAsUser(bindUserDN, password) + if err != nil { + return err + } + defer conn.Close() + + modify := ldap.NewModifyRequest(userDN, nil) + modify.Replace(attribute, data) + err = conn.Modify(modify) + if err != nil { + logging.Errorf("Failed to modify %s", err.Error()) + return err + } + return nil +}