Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[v16] Improve Teleport's ability to reconnect to LDAP #48008

Merged
merged 5 commits into from
Nov 18, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions lib/srv/desktop/discovery.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,15 @@ func (s *WindowsService) ldapSearchFilter() string {

// getDesktopsFromLDAP discovers Windows hosts via LDAP
func (s *WindowsService) getDesktopsFromLDAP() map[string]types.WindowsDesktop {
if !s.ldapReady() {
s.cfg.Logger.WarnContext(context.Background(), "skipping desktop discovery: LDAP not yet initialized")
// Check whether we've ever successfully initialized our LDAP client.
s.mu.Lock()
if !s.ldapInitialized {
s.cfg.Logger.DebugContext(context.Background(), "LDAP not ready, skipping discovery and attempting to reconnect")
s.mu.Unlock()
s.initializeLDAP()
return nil
}
s.mu.Unlock()

filter := s.ldapSearchFilter()
s.cfg.Logger.DebugContext(context.Background(), "searching for desktops", "filter", filter)
Expand Down Expand Up @@ -248,7 +253,11 @@ func (s *WindowsService) lookupDesktop(ctx context.Context, hostname string) ([]

// ldapEntryToWindowsDesktop generates the Windows Desktop resource
// from an LDAP search result
func (s *WindowsService) ldapEntryToWindowsDesktop(ctx context.Context, entry *ldap.Entry, getHostLabels func(string) map[string]string) (types.WindowsDesktop, error) {
func (s *WindowsService) ldapEntryToWindowsDesktop(
ctx context.Context,
entry *ldap.Entry,
getHostLabels func(string) map[string]string,
) (types.WindowsDesktop, error) {
hostname := entry.GetAttributeValue(windows.AttrDNSHostName)
if hostname == "" {
attrs := make([]string, len(entry.Attributes))
Expand Down
56 changes: 50 additions & 6 deletions lib/srv/desktop/windows_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -419,10 +419,60 @@ func NewWindowsService(cfg WindowsServiceConfig) (*WindowsService, error) {
s.cfg.Logger.InfoContext(ctx, "desktop discovery via LDAP is disabled, set 'base_dn' to enable")
}

// if LDAP-based discovery is not enabled, but we have configured LDAP
// then it's important that we periodically try to use the LDAP connection
// to detect connection closure
if s.ldapConfigured && len(s.cfg.DiscoveryBaseDN) == 0 {
s.startLDAPConnectionCheck(ctx)
}

ok = true
return s, nil
}

// startLDAPConnectionCheck starts a background process that
// periodically reads from the LDAP connection in order to detect
// connection closure, and reconnects if necessary.
// This is useful when LDAP-based discovery is disabled, because without
// discovery the connection goes idle and may be closed by the server.
func (s *WindowsService) startLDAPConnectionCheck(ctx context.Context) {
s.cfg.Logger.DebugContext(ctx, "starting LDAP connection checker")
go func() {
t := s.cfg.Clock.NewTicker(5 * time.Minute)
defer t.Stop()

for {
select {
case <-t.Chan():
// First check if we have successfully initialized the LDAP client.
// If not, then do that now and return.
// (This mimics the check that is performed when LDAP discovery is enabled.)
s.mu.Lock()
if !s.ldapInitialized {
s.cfg.Logger.DebugContext(context.Background(), "LDAP not ready, attempting to reconnect")
s.mu.Unlock()
s.initializeLDAP()
return
}
s.mu.Unlock()

// If we have initizlied the LDAP client, then try to use it to make sure we're still connected
webvictim marked this conversation as resolved.
Show resolved Hide resolved
// by attempting to read CAs in the NTAuth store (we know we have permissions to do so).
ntAuthDN := "CN=NTAuthCertificates,CN=Public Key Services,CN=Services,CN=Configuration," + s.cfg.LDAPConfig.DomainDN()
_, err := s.lc.Read(ntAuthDN, "certificationAuthority", []string{"cACertificate"})
if trace.IsConnectionProblem(err) {
s.cfg.Logger.DebugContext(ctx, "detected broken LDAP connection, will reconnect")
if err := s.initializeLDAP(); err != nil {
s.cfg.Logger.WarnContext(ctx, "failed to reconnect to LDAP", "error", err)
}
}
case <-ctx.Done():
return
}
}
}()
}

func (s *WindowsService) newSessionRecorder(recConfig types.SessionRecordingConfig, sessionID string) (libevents.SessionPreparerRecorder, error) {
return recorder.New(recorder.Config{
SessionID: session.ID(sessionID),
Expand Down Expand Up @@ -686,12 +736,6 @@ func (s *WindowsService) readyForConnections() bool {
return s.ldapInitialized
}

func (s *WindowsService) ldapReady() bool {
s.mu.Lock()
defer s.mu.Unlock()
return s.ldapInitialized
}

// handleConnection handles TLS connections from a Teleport proxy.
// It authenticates and authorizes the connection, and then begins
// translating the TDP messages from the proxy into native RDP.
Expand Down
Loading