-
Notifications
You must be signed in to change notification settings - Fork 313
HPCC-35282 Cache failed user authentication attempts to reduce AD overhead #20602
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -35,6 +35,7 @@ | |||||||||||
| #include <map> | ||||||||||||
| #include <string> | ||||||||||||
| #include <set> | ||||||||||||
| #include <openssl/evp.h> | ||||||||||||
|
|
||||||||||||
| #ifdef _WIN32 | ||||||||||||
| #include <lm.h> | ||||||||||||
|
|
@@ -1603,6 +1604,11 @@ static __int64 getMaxPwdAge(Owned<ILdapConnectionPool> _conns, const char * _bas | |||||||||||
| } | ||||||||||||
|
|
||||||||||||
| static CriticalSection lcCrit; | ||||||||||||
| static CriticalSection uaCrit; | ||||||||||||
| std::map<std::string, std::string> unauthorizedUserCache; | ||||||||||||
|
kenrowland marked this conversation as resolved.
|
||||||||||||
| std::map<std::string, std::string> unauthorizedUserCache; | |
| // Store SHA-256 hashes of passwords instead of plaintext |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Currently not storing strong invalid passwords. We only store the most recent invalid password, the cache is cleared every 5 minutes, and the purpose is to quickly fail repeated invalid credentials.
Copilot
AI
Nov 4, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
String comparison for passwords is not constant-time and may be vulnerable to timing attacks. While this is a cache check rather than the primary authentication, consider using a constant-time comparison function to avoid potential information leakage.
| if (it->second == std::string(password)) | |
| const std::string &cachedPassword = it->second; | |
| const std::string currentPassword(password); | |
| if (cachedPassword.length() == currentPassword.length() && | |
| CRYPTO_memcmp(cachedPassword.data(), currentPassword.data(), cachedPassword.length()) == 0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably overkill for the purposes of this change.
Copilot
AI
Nov 4, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding expired passwords to the unauthenticated cache at line 2068 may not be appropriate. Password expiration is a different failure mode from invalid credentials. A user with an expired password has valid credentials but needs to change their password. Caching this prevents them from authenticating after changing their password until the cache expires (up to 5 minutes). Consider only caching AS_INVALID_CREDENTIALS failures.
Copilot
AI
Nov 4, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using emplace() here doesn't update existing entries if the username already exists. If a user fails authentication with password A, then immediately tries password B (which also fails), the cache will retain password A. Use operator[] or insert_or_assign() to ensure the cache stores the most recent failed password attempt.
| unauthorizedUserCache.emplace(username, password); | |
| unauthorizedUserCache.insert_or_assign(username, password); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When a user that previously failed authorization makes another attempt with a different password, the cached entry is removed and normal processing is done. If the new password is not valid, a new cache entry is created. so, no need to handle changing the password.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The OpenSSL EVP header is included but not used anywhere in the code. This import should be removed unless there are plans to hash passwords before caching them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was considering strong hashing of invalid passwords. Currently not doing that. Will remove if we decide plaintext is sufficient.