Skip to content

fix(outlook): retry LDAP user searches with fresh connections - #4441

Merged
Jan-Kazlouski-elastic merged 6 commits into
mainfrom
fix/outlook-ldap-search-retry
Sep 9, 2026
Merged

Jan-Kazlouski-elastic merged 6 commits into
mainfrom
fix/outlook-ldap-search-retry

Conversation

@Jan-Kazlouski-elastic

@Jan-Kazlouski-elastic Jan-Kazlouski-elastic commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Part of https://github.com/elastic/sdh-search/issues/1898

Problem

On large on-prem Exchange deployments the Outlook Server connector enumerates users from Active Directory via LDAP before opening each mailbox over EWS. ExchangeUsers kept a single cached ldap3 connection for both the normal-user and admin-user searches.

get_user_accounts() consumes users lazily: it yields each normal user, the sync processes that mailbox (often for hours), then continues. The admin-user LDAP search can therefore run days after the first bind. AD or intermediate network gear often drops the idle TCP session, and the next search fails with errors such as:

UsersFetchFailed: Something went wrong while fetching users. Error: socket sending error[Errno 104] Connection reset by peer

Unlike the Office365 Graph path, LDAP had no retry layer. A single transient failure aborted the entire sync. A customer on SDH #1898 hit this after indexing 4.2M+ documents over ~45 hours.

Fix

  • Replace the cached LDAP connection with a fresh Connection per search.
  • Add _ldap_search() with the existing @retryable settings (RETRIES, RETRY_INTERVAL, exponential backoff) and skipped_exceptions=[UsersFetchFailed] so empty LDAP results are not retried.
  • Unbind after each search attempt.
  • Refactor _fetch_normal_users / _fetch_admin_users to call the shared helper.

Checklists

Pre-Review Checklist

  • this PR does NOT contain credentials of any kind, such as API keys or username/passwords (double check config.yml.example)
  • this PR has a meaningful title
  • this PR links to all relevant github issues that it fixes or partially addresses
  • if there is no GH issue, please create it. Each PR should have a link to an issue
  • this PR has a thorough description
  • Covered the changes with automated tests
  • Tested the changes locally
  • Added a label for each target release version (example: v7.13.2, v7.14.0, v8.0.0)
  • For bugfixes: backport safely to all minor branches still receiving patch releases
  • Considered corresponding documentation changes
  • Contributed any configuration settings changes to the configuration reference
  • if you added or changed Rich Configurable Fields for a Native Connector, you made a corresponding PR in Kibana

Changes Requiring Extra Attention

  • Security-related changes (encryption, TLS, SSRF, etc)
  • New external service dependencies added.

Related Pull Requests

Release Note

Outlook Server connector: LDAP user enumeration now opens a fresh Active Directory connection per search and retries transient network failures instead of aborting long syncs when the admin-user query runs on a stale connection.

Made with Cursor

On long on-prem Exchange syncs the admin-user LDAP query can run days
after the normal-user search. Reusing a cached ldap3 connection caused
Connection reset by peer and aborted the sync.

Use a new connection per search, unbind after each attempt, and retry
transient failures with the same backoff settings as Office365Users.

Co-authored-by: Cursor <cursoragent@cursor.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The notice lists an incorrect google-auth version, and cleanup behavior lacks a regression assertion.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Adds resilient LDAP user enumeration for long-running Outlook Server syncs.

Changes:

  • Creates and unbinds a fresh LDAP connection per search attempt.
  • Retries transient LDAP failures with exponential backoff.
  • Adds retry and connection-lifecycle tests.
File summaries
File Description
connectors/sources/outlook/client.py Implements fresh, retryable LDAP searches.
tests/sources/test_outlook.py Tests retries and fresh connections.
NOTICE.txt Updates dependency notices.
Review details
  • Files reviewed: 3/3 changed files
  • Comments generated: 2
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread app/connectors_service/tests/sources/test_outlook.py
Comment thread app/connectors_service/NOTICE.txt
Comment thread app/connectors_service/connectors/sources/outlook/client.py
Jan-Kazlouski-elastic added a commit that referenced this pull request Sep 4, 2026
## Summary

`test_close_with_client_session` was making real AWS `ListBuckets` HTTP
calls with dummy credentials. That caused slow, flaky failures under
`--fail-slow=1` when network latency exceeded 1s.

The test now mocks the S3 client (same pattern as `test_ping`) and
asserts that `close()` tears down the `AsyncExitStack` used for the
client session.

## Checklists

#### Pre-Review Checklist
- [x] this PR does NOT contain credentials of any kind, such as API keys
or username/passwords (double check `config.yml.example`)
- [x] this PR has a meaningful title
- [x] this PR links to all relevant github issues that it fixes or
partially addresses
- [x] if there is no GH issue, please create it. Each PR should have a
link to an issue
- [x] this PR has a thorough description
- [x] Covered the changes with automated tests
- [x] Tested the changes locally
- [ ] Added a label for each target release version (example: `v7.13.2`,
`v7.14.0`, `v8.0.0`)
- [ ] For bugfixes: backport safely to all minor branches still
receiving patch releases
- [x] Considered corresponding documentation changes
- [x] Contributed any configuration settings changes to the
configuration reference
- [x] if you added or changed Rich Configurable Fields for a Native
Connector, you made a corresponding PR in
[Kibana](https://github.com/elastic/kibana/blob/main/packages/kbn-search-connectors/types/native_connectors.ts)

#### Changes Requiring Extra Attention

- [ ] Security-related changes (encryption, TLS, SSRF, etc)
- [ ] New external service dependencies added.

## Related Pull Requests

* #4441 — Outlook LDAP retry
fix (where the flake was first observed)

## Release Note

n/a — test-only change.

Made with [Cursor](https://cursor.com)

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
Wrap LDAP unbind in a defensive try/except so cleanup failures do not
mask search errors or block retries, and assert unbind runs on each
retry attempt in the transient-error test.
@Jan-Kazlouski-elastic

Copy link
Copy Markdown
Contributor Author

Thank you @erikcurrin-elastic
Your comment is addressed. Could you please give this PR another look?

@Jan-Kazlouski-elastic
Jan-Kazlouski-elastic enabled auto-merge (squash) September 4, 2026 22:09
@Jan-Kazlouski-elastic Jan-Kazlouski-elastic added the bug Something isn't working label Sep 8, 2026
@Jan-Kazlouski-elastic

Copy link
Copy Markdown
Contributor Author

Greetings @artem-shelkovnikov

Would you please take a look at this PR as it is waiting for a review for a long time?
Thank you in advance.


@cached_property
def _create_connection(self):
def _create_ldap_connection(self):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd personally inline this into the method that does LDAP search just in case so that nobody would think of returning back @cached_property and causing trouble

@artem-shelkovnikov artem-shelkovnikov left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, left non-blocking comment

@Jan-Kazlouski-elastic
Jan-Kazlouski-elastic merged commit 48047a0 into main Sep 9, 2026
2 checks passed
@Jan-Kazlouski-elastic
Jan-Kazlouski-elastic deleted the fix/outlook-ldap-search-retry branch September 9, 2026 10:58
@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown

💔 Failed to create backport PR(s)

Status Branch Result
9.4 #4463
8.19 Commit could not be cherrypicked due to conflicts
9.5 #4464

Successful backport PRs will be merged automatically after passing CI.

To backport manually run:
backport --pr 4441 --autoMerge --autoMergeMethod squash

Jan-Kazlouski-elastic added a commit that referenced this pull request Sep 9, 2026
…4441) (#4463)

Backports the following commits to 9.4:
 - fix(outlook): retry LDAP user searches with fresh connections (#4441)

Co-authored-by: Jan-Kazlouski-elastic <jan.kazlouski@elastic.co>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
Jan-Kazlouski-elastic added a commit that referenced this pull request Sep 9, 2026
…4441) (#4464)

Backports the following commits to 9.5:
 - fix(outlook): retry LDAP user searches with fresh connections (#4441)

Co-authored-by: Jan-Kazlouski-elastic <jan.kazlouski@elastic.co>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
Jan-Kazlouski-elastic added a commit that referenced this pull request Sep 9, 2026
…4441) (#4465)

Backports #4441 to `8.19`.

Related to elastic/sdh-search#1898

## Summary
- Replace the cached LDAP connection with a fresh `Connection` per
search.
- Add `_ldap_search()` with `@retryable` backoff and
`skipped_exceptions=[UsersFetchFailed]`.
- Unbind after each search attempt (with defensive cleanup).
- Refactor `_fetch_normal_users` / `_fetch_admin_users` to call the
shared helper.

## Backport notes
- The 8.19 branch still uses the monolithic
`connectors/sources/outlook.py` layout (rather than the `outlook/`
package split into `client.py`/`datasource.py` on `main`), so the
changes were manually adapted to that single module. Behaviour is
identical to the original PR.
- The incidental `NOTICE.txt` version bump from `make install` is
intentionally excluded.

## Test plan
- [x] `make clean install autoformat lint test`
- [x] `pytest tests/sources/test_outlook.py`
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants