Merge pull request #15988 from traceon/ldap-cache-login

Allow caching of successful "bind" attempts to LDAP server for configurable period of time
This commit is contained in:
Vitaly Baranov 2020-12-30 17:24:45 +03:00 committed by GitHub
commit a84887aff8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 1870 additions and 195 deletions

View File

@ -324,6 +324,9 @@
auth_dn_prefix, auth_dn_suffix - prefix and suffix used to construct the DN to bind to.
Effectively, the resulting DN will be constructed as auth_dn_prefix + escape(user_name) + auth_dn_suffix string.
Note, that this implies that auth_dn_suffix should usually have comma ',' as its first non-space character.
verification_cooldown - a period of time, in seconds, after a successful bind attempt, during which a user will be assumed
to be successfully authenticated for all consecutive requests without contacting the LDAP server.
Specify 0 (the default) to disable caching and force contacting the LDAP server for each authentication request.
enable_tls - flag to trigger use of secure connection to the LDAP server.
Specify 'no' for plain text (ldap://) protocol (not recommended).
Specify 'yes' for LDAP over SSL/TLS (ldaps://) protocol (recommended, the default).
@ -343,6 +346,7 @@
<port>636</port>
<auth_dn_prefix>uid=</auth_dn_prefix>
<auth_dn_suffix>,ou=users,dc=example,dc=com</auth_dn_suffix>
<verification_cooldown>300</verification_cooldown>
<enable_tls>yes</enable_tls>
<tls_minimum_protocol_version>tls1.2</tls_minimum_protocol_version>
<tls_require_cert>demand</tls_require_cert>

View File

@ -1,6 +1,5 @@
#include <Access/Authentication.h>
#include <Access/ExternalAuthenticators.h>
#include <Access/LDAPClient.h>
#include <Common/Exception.h>
#include <Poco/SHA1Engine.h>
@ -49,7 +48,7 @@ Authentication::Digest Authentication::getPasswordDoubleSHA1() const
}
bool Authentication::isCorrectPassword(const String & password_, const String & user_, const ExternalAuthenticators & external_authenticators) const
bool Authentication::isCorrectPassword(const String & user_, const String & password_, const ExternalAuthenticators & external_authenticators) const
{
switch (type)
{
@ -81,14 +80,7 @@ bool Authentication::isCorrectPassword(const String & password_, const String &
}
case LDAP_SERVER:
{
auto ldap_server_params = external_authenticators.getLDAPServerParams(server_name);
ldap_server_params.user = user_;
ldap_server_params.password = password_;
LDAPSimpleAuthClient ldap_client(ldap_server_params);
return ldap_client.check();
}
return external_authenticators.checkLDAPCredentials(server_name, user_, password_);
case MAX_TYPE:
break;

View File

@ -88,8 +88,8 @@ public:
void setServerName(const String & server_name_);
/// Checks if the provided password is correct. Returns false if not.
/// User name and external authenticators' info are used only by some specific authentication type (e.g., LDAP_SERVER).
bool isCorrectPassword(const String & password_, const String & user_, const ExternalAuthenticators & external_authenticators) const;
/// User name and external authenticators are used by the specific authentication types only (e.g., LDAP_SERVER).
bool isCorrectPassword(const String & user_, const String & password_, const ExternalAuthenticators & external_authenticators) const;
friend bool operator ==(const Authentication & lhs, const Authentication & rhs) { return (lhs.type == rhs.type) && (lhs.password_hash == rhs.password_hash); }
friend bool operator !=(const Authentication & lhs, const Authentication & rhs) { return !(lhs == rhs); }

View File

@ -1,9 +1,13 @@
#include <Access/ExternalAuthenticators.h>
#include <Access/LDAPClient.h>
#include <Common/Exception.h>
#include <Common/quoteString.h>
#include <Poco/Util/AbstractConfiguration.h>
#include <boost/algorithm/string/case_conv.hpp>
#include <optional>
#include <utility>
namespace DB
{
@ -29,6 +33,7 @@ auto parseLDAPServer(const Poco::Util::AbstractConfiguration & config, const Str
const bool has_port = config.has(ldap_server_config + ".port");
const bool has_auth_dn_prefix = config.has(ldap_server_config + ".auth_dn_prefix");
const bool has_auth_dn_suffix = config.has(ldap_server_config + ".auth_dn_suffix");
const bool has_verification_cooldown = config.has(ldap_server_config + ".verification_cooldown");
const bool has_enable_tls = config.has(ldap_server_config + ".enable_tls");
const bool has_tls_minimum_protocol_version = config.has(ldap_server_config + ".tls_minimum_protocol_version");
const bool has_tls_require_cert = config.has(ldap_server_config + ".tls_require_cert");
@ -52,6 +57,9 @@ auto parseLDAPServer(const Poco::Util::AbstractConfiguration & config, const Str
if (has_auth_dn_suffix)
params.auth_dn_suffix = config.getString(ldap_server_config + ".auth_dn_suffix");
if (has_verification_cooldown)
params.verification_cooldown = std::chrono::seconds{config.getUInt64(ldap_server_config + ".verification_cooldown")};
if (has_enable_tls)
{
String enable_tls_lc_str = config.getString(ldap_server_config + ".enable_tls");
@ -130,16 +138,28 @@ auto parseLDAPServer(const Poco::Util::AbstractConfiguration & config, const Str
return params;
}
void parseAndAddLDAPServers(ExternalAuthenticators & external_authenticators, const Poco::Util::AbstractConfiguration & config, Poco::Logger * log)
}
void ExternalAuthenticators::reset()
{
std::scoped_lock lock(mutex);
ldap_server_params.clear();
ldap_server_caches.clear();
}
void ExternalAuthenticators::setConfiguration(const Poco::Util::AbstractConfiguration & config, Poco::Logger * log)
{
std::scoped_lock lock(mutex);
reset();
Poco::Util::AbstractConfiguration::Keys ldap_server_names;
config.keys("ldap_servers", ldap_server_names);
for (const auto & ldap_server_name : ldap_server_names)
{
try
{
external_authenticators.setLDAPServerParams(ldap_server_name, parseLDAPServer(config, ldap_server_name));
ldap_server_params.insert_or_assign(ldap_server_name, parseLDAPServer(config, ldap_server_name));
}
catch (...)
{
@ -148,35 +168,100 @@ void parseAndAddLDAPServers(ExternalAuthenticators & external_authenticators, co
}
}
}
void ExternalAuthenticators::reset()
bool ExternalAuthenticators::checkLDAPCredentials(const String & server, const String & user_name, const String & password) const
{
std::scoped_lock lock(mutex);
ldap_server_params.clear();
}
std::optional<LDAPServerParams> params;
std::size_t params_hash = 0;
void ExternalAuthenticators::setConfiguration(const Poco::Util::AbstractConfiguration & config, Poco::Logger * log)
{
std::scoped_lock lock(mutex);
reset();
parseAndAddLDAPServers(*this, config, log);
}
{
std::scoped_lock lock(mutex);
void ExternalAuthenticators::setLDAPServerParams(const String & server, const LDAPServerParams & params)
{
std::scoped_lock lock(mutex);
ldap_server_params.erase(server);
ldap_server_params[server] = params;
}
// Retrieve the server parameters.
const auto pit = ldap_server_params.find(server);
if (pit == ldap_server_params.end())
throw Exception("LDAP server '" + server + "' is not configured", ErrorCodes::BAD_ARGUMENTS);
LDAPServerParams ExternalAuthenticators::getLDAPServerParams(const String & server) const
{
std::scoped_lock lock(mutex);
auto it = ldap_server_params.find(server);
if (it == ldap_server_params.end())
throw Exception("LDAP server '" + server + "' is not configured", ErrorCodes::BAD_ARGUMENTS);
return it->second;
params = pit->second;
params->user = user_name;
params->password = password;
params_hash = params->getCoreHash();
// Check the cache, but only if the caching is enabled at all.
if (params->verification_cooldown > std::chrono::seconds{0})
{
const auto cit = ldap_server_caches.find(server);
if (cit != ldap_server_caches.end())
{
auto & cache = cit->second;
const auto eit = cache.find(user_name);
if (eit != cache.end())
{
const auto & entry = eit->second;
const auto last_check_period = std::chrono::steady_clock::now() - entry.last_successful_authentication_timestamp;
if (
// Forbid the initial values explicitly.
entry.last_successful_params_hash != 0 &&
entry.last_successful_authentication_timestamp != std::chrono::steady_clock::time_point{} &&
// Check if we can safely "reuse" the result of the previous successful password verification.
entry.last_successful_params_hash == params_hash &&
last_check_period >= std::chrono::seconds{0} &&
last_check_period <= params->verification_cooldown
)
{
return true;
}
// Erase the entry, if expired.
if (last_check_period > params->verification_cooldown)
cache.erase(eit);
}
// Erase the cache, if empty.
if (cache.empty())
ldap_server_caches.erase(cit);
}
}
}
LDAPSimpleAuthClient client(params.value());
const auto result = client.check();
const auto current_check_timestamp = std::chrono::steady_clock::now();
// Update the cache, but only if this is the latest check and the server is still configured in a compatible way.
if (result)
{
std::scoped_lock lock(mutex);
// If the server was removed from the config while we were checking the password, we discard the current result.
const auto pit = ldap_server_params.find(server);
if (pit == ldap_server_params.end())
return false;
auto new_params = pit->second;
new_params.user = user_name;
new_params.password = password;
// If the critical server params have changed while we were checking the password, we discard the current result.
if (params_hash != new_params.getCoreHash())
return false;
auto & entry = ldap_server_caches[server][user_name];
if (entry.last_successful_authentication_timestamp < current_check_timestamp)
{
entry.last_successful_params_hash = params_hash;
entry.last_successful_authentication_timestamp = current_check_timestamp;
}
else if (entry.last_successful_params_hash != params_hash)
{
// Somehow a newer check with different params/password succeeded, so the current result is obsolete and we discard it.
return false;
}
}
return result;
}
}

View File

@ -3,9 +3,10 @@
#include <Access/LDAPParams.h>
#include <common/types.h>
#include <chrono>
#include <map>
#include <memory>
#include <mutex>
#include <unordered_map>
namespace Poco
@ -27,13 +28,23 @@ class ExternalAuthenticators
public:
void reset();
void setConfiguration(const Poco::Util::AbstractConfiguration & config, Poco::Logger * log);
bool checkLDAPCredentials(const String & server, const String & user_name, const String & password) const;
void setLDAPServerParams(const String & server, const LDAPServerParams & params);
LDAPServerParams getLDAPServerParams(const String & server) const;
private:
struct LDAPCacheEntry
{
std::size_t last_successful_params_hash = 0;
std::chrono::steady_clock::time_point last_successful_authentication_timestamp;
};
using LDAPServerCache = std::unordered_map<String, LDAPCacheEntry>; // user name -> cache entry
using LDAPServerCaches = std::map<String, LDAPServerCache>; // server name -> cache
using LDAPServersParams = std::map<String, LDAPServerParams>; // server name -> params
private:
mutable std::recursive_mutex mutex;
std::map<String, LDAPServerParams> ldap_server_params;
LDAPServersParams ldap_server_params;
mutable LDAPServerCaches ldap_server_caches;
};
}

View File

@ -463,7 +463,7 @@ UUID IAccessStorage::loginImpl(
bool IAccessStorage::isPasswordCorrectImpl(const User & user, const String & password, const ExternalAuthenticators & external_authenticators) const
{
return user.authentication.isCorrectPassword(password, user.getName(), external_authenticators);
return user.authentication.isCorrectPassword(user.getName(), password, external_authenticators);
}

View File

@ -2,6 +2,8 @@
#include <common/types.h>
#include <boost/container_hash/hash.hpp>
#include <chrono>
@ -68,10 +70,26 @@ struct LDAPServerParams
String user;
String password;
std::chrono::seconds verification_cooldown{0};
std::chrono::seconds operation_timeout{40};
std::chrono::seconds network_timeout{30};
std::chrono::seconds search_timeout{20};
std::uint32_t search_limit = 100;
std::size_t getCoreHash() const
{
std::size_t seed = 0;
boost::hash_combine(seed, host);
boost::hash_combine(seed, port);
boost::hash_combine(seed, auth_dn_prefix);
boost::hash_combine(seed, auth_dn_suffix);
boost::hash_combine(seed, user);
boost::hash_combine(seed, password);
return seed;
}
};
}

View File

@ -37,6 +37,23 @@ class Node(object):
self.cluster.command(None, f'{self.cluster.docker_compose} restart {self.name}', timeout=timeout)
def start(self, timeout=300, safe=True):
"""Start node.
"""
self.cluster.command(None, f'{self.cluster.docker_compose} start {self.name}', timeout=timeout)
def stop(self, timeout=300, safe=True):
"""Stop node.
"""
with self.cluster.lock:
for key in list(self.cluster._bash.keys()):
if key.endswith(f"-{self.name}"):
shell = self.cluster._bash.pop(key)
shell.__exit__(None, None, None)
self.cluster.command(None, f'{self.cluster.docker_compose} stop {self.name}', timeout=timeout)
def command(self, *args, **kwargs):
return self.cluster.command(self.name, *args, **kwargs)

View File

@ -1,4 +1,5 @@
# SRS-007 ClickHouse Authentication of Users via LDAP
# Software Requirements Specification
## Table of Contents
@ -57,22 +58,28 @@
* 4.2.25 [RQ.SRS-007.LDAP.Configuration.Server.TLSCACertDir](#rqsrs-007ldapconfigurationservertlscacertdir)
* 4.2.26 [RQ.SRS-007.LDAP.Configuration.Server.TLSCACertFile](#rqsrs-007ldapconfigurationservertlscacertfile)
* 4.2.27 [RQ.SRS-007.LDAP.Configuration.Server.TLSCipherSuite](#rqsrs-007ldapconfigurationservertlsciphersuite)
* 4.2.28 [RQ.SRS-007.LDAP.Configuration.Server.Syntax](#rqsrs-007ldapconfigurationserversyntax)
* 4.2.29 [RQ.SRS-007.LDAP.Configuration.User.RBAC](#rqsrs-007ldapconfigurationuserrbac)
* 4.2.30 [RQ.SRS-007.LDAP.Configuration.User.Syntax](#rqsrs-007ldapconfigurationusersyntax)
* 4.2.31 [RQ.SRS-007.LDAP.Configuration.User.Name.Empty](#rqsrs-007ldapconfigurationusernameempty)
* 4.2.32 [RQ.SRS-007.LDAP.Configuration.User.BothPasswordAndLDAP](#rqsrs-007ldapconfigurationuserbothpasswordandldap)
* 4.2.33 [RQ.SRS-007.LDAP.Configuration.User.LDAP.InvalidServerName.NotDefined](#rqsrs-007ldapconfigurationuserldapinvalidservernamenotdefined)
* 4.2.34 [RQ.SRS-007.LDAP.Configuration.User.LDAP.InvalidServerName.Empty](#rqsrs-007ldapconfigurationuserldapinvalidservernameempty)
* 4.2.35 [RQ.SRS-007.LDAP.Configuration.User.OnlyOneServer](#rqsrs-007ldapconfigurationuseronlyoneserver)
* 4.2.36 [RQ.SRS-007.LDAP.Configuration.User.Name.Long](#rqsrs-007ldapconfigurationusernamelong)
* 4.2.37 [RQ.SRS-007.LDAP.Configuration.User.Name.UTF8](#rqsrs-007ldapconfigurationusernameutf8)
* 4.2.38 [RQ.SRS-007.LDAP.Authentication.Username.Empty](#rqsrs-007ldapauthenticationusernameempty)
* 4.2.39 [RQ.SRS-007.LDAP.Authentication.Username.Long](#rqsrs-007ldapauthenticationusernamelong)
* 4.2.40 [RQ.SRS-007.LDAP.Authentication.Username.UTF8](#rqsrs-007ldapauthenticationusernameutf8)
* 4.2.41 [RQ.SRS-007.LDAP.Authentication.Password.Empty](#rqsrs-007ldapauthenticationpasswordempty)
* 4.2.42 [RQ.SRS-007.LDAP.Authentication.Password.Long](#rqsrs-007ldapauthenticationpasswordlong)
* 4.2.43 [RQ.SRS-007.LDAP.Authentication.Password.UTF8](#rqsrs-007ldapauthenticationpasswordutf8)
* 4.2.28 [RQ.SRS-007.LDAP.Configuration.Server.VerificationCooldown](#rqsrs-007ldapconfigurationserververificationcooldown)
* 4.2.29 [RQ.SRS-007.LDAP.Configuration.Server.VerificationCooldown.Default](#rqsrs-007ldapconfigurationserververificationcooldowndefault)
* 4.2.30 [RQ.SRS-007.LDAP.Configuration.Server.VerificationCooldown.Invalid](#rqsrs-007ldapconfigurationserververificationcooldowninvalid)
* 4.2.31 [RQ.SRS-007.LDAP.Configuration.Server.Syntax](#rqsrs-007ldapconfigurationserversyntax)
* 4.2.32 [RQ.SRS-007.LDAP.Configuration.User.RBAC](#rqsrs-007ldapconfigurationuserrbac)
* 4.2.33 [RQ.SRS-007.LDAP.Configuration.User.Syntax](#rqsrs-007ldapconfigurationusersyntax)
* 4.2.34 [RQ.SRS-007.LDAP.Configuration.User.Name.Empty](#rqsrs-007ldapconfigurationusernameempty)
* 4.2.35 [RQ.SRS-007.LDAP.Configuration.User.BothPasswordAndLDAP](#rqsrs-007ldapconfigurationuserbothpasswordandldap)
* 4.2.36 [RQ.SRS-007.LDAP.Configuration.User.LDAP.InvalidServerName.NotDefined](#rqsrs-007ldapconfigurationuserldapinvalidservernamenotdefined)
* 4.2.37 [RQ.SRS-007.LDAP.Configuration.User.LDAP.InvalidServerName.Empty](#rqsrs-007ldapconfigurationuserldapinvalidservernameempty)
* 4.2.38 [RQ.SRS-007.LDAP.Configuration.User.OnlyOneServer](#rqsrs-007ldapconfigurationuseronlyoneserver)
* 4.2.39 [RQ.SRS-007.LDAP.Configuration.User.Name.Long](#rqsrs-007ldapconfigurationusernamelong)
* 4.2.40 [RQ.SRS-007.LDAP.Configuration.User.Name.UTF8](#rqsrs-007ldapconfigurationusernameutf8)
* 4.2.41 [RQ.SRS-007.LDAP.Authentication.Username.Empty](#rqsrs-007ldapauthenticationusernameempty)
* 4.2.42 [RQ.SRS-007.LDAP.Authentication.Username.Long](#rqsrs-007ldapauthenticationusernamelong)
* 4.2.43 [RQ.SRS-007.LDAP.Authentication.Username.UTF8](#rqsrs-007ldapauthenticationusernameutf8)
* 4.2.44 [RQ.SRS-007.LDAP.Authentication.Password.Empty](#rqsrs-007ldapauthenticationpasswordempty)
* 4.2.45 [RQ.SRS-007.LDAP.Authentication.Password.Long](#rqsrs-007ldapauthenticationpasswordlong)
* 4.2.46 [RQ.SRS-007.LDAP.Authentication.Password.UTF8](#rqsrs-007ldapauthenticationpasswordutf8)
* 4.2.47 [RQ.SRS-007.LDAP.Authentication.VerificationCooldown.Performance](#rqsrs-007ldapauthenticationverificationcooldownperformance)
* 4.2.48 [RQ.SRS-007.LDAP.Authentication.VerificationCooldown.Reset.ChangeInCoreServerParameters](#rqsrs-007ldapauthenticationverificationcooldownresetchangeincoreserverparameters)
* 4.2.49 [RQ.SRS-007.LDAP.Authentication.VerificationCooldown.Reset.InvalidPassword](#rqsrs-007ldapauthenticationverificationcooldownresetinvalidpassword)
* 5 [References](#references)
## Revision History
@ -393,9 +400,44 @@ For example,
The available suites SHALL depend on the [OpenSSL] library version and variant used to build
[ClickHouse] and therefore might change.
#### RQ.SRS-007.LDAP.Configuration.Server.Syntax
#### RQ.SRS-007.LDAP.Configuration.Server.VerificationCooldown
version: 1.0
[ClickHouse] SHALL support `verification_cooldown` parameter in the [LDAP] server configuration section
that SHALL define a period of time, in seconds, after a successful bind attempt, during which a user SHALL be assumed
to be successfully authenticated for all consecutive requests without contacting the [LDAP] server.
After period of time since the last successful attempt expires then on the authentication attempt
SHALL result in contacting the [LDAP] server to verify the username and password.
#### RQ.SRS-007.LDAP.Configuration.Server.VerificationCooldown.Default
version: 1.0
[ClickHouse] `verification_cooldown` parameter in the [LDAP] server configuration section
SHALL have a default value of `0` that disables caching and forces contacting
the [LDAP] server for each authentication request.
#### RQ.SRS-007.LDAP.Configuration.Server.VerificationCooldown.Invalid
version: 1.0
[Clickhouse] SHALL return an error if the value provided for the `verification_cooldown` parameter is not a valid positive integer.
For example:
* negative integer
* string
* empty value
* extremely large positive value (overflow)
* extremely large negative value (overflow)
The error SHALL appear in the log and SHALL be similar to the following:
```bash
<Error> Access(user directories): Could not parse LDAP server `openldap1`: Poco::Exception. Code: 1000, e.code() = 0, e.displayText() = Syntax error: Not a valid unsigned integer: *input value*
```
#### RQ.SRS-007.LDAP.Configuration.Server.Syntax
version: 2.0
[ClickHouse] SHALL support the following example syntax to create an entry for an [LDAP] server inside the `config.xml`
configuration file or of any configuration file inside the `config.d` directory.
@ -406,6 +448,7 @@ configuration file or of any configuration file inside the `config.d` directory.
<port>636</port>
<auth_dn_prefix>cn=</auth_dn_prefix>
<auth_dn_suffix>, ou=users, dc=example, dc=com</auth_dn_suffix>
<verification_cooldown>0</verification_cooldown>
<enable_tls>yes</enable_tls>
<tls_minimum_protocol_version>tls1.2</tls_minimum_protocol_version>
<tls_require_cert>demand</tls_require_cert>
@ -521,6 +564,33 @@ version: 1.0
[ClickHouse] SHALL support [UTF-8] characters in passwords
used to authenticate users using an [LDAP] server.
#### RQ.SRS-007.LDAP.Authentication.VerificationCooldown.Performance
version: 1.0
[ClickHouse] SHALL provide better login performance of [LDAP] authenticated users
when `verification_cooldown` parameter is set to a positive value when comparing
to the the case when `verification_cooldown` is turned off either for a single user or multiple users
making a large number of repeated requests.
#### RQ.SRS-007.LDAP.Authentication.VerificationCooldown.Reset.ChangeInCoreServerParameters
version: 1.0
[ClickHouse] SHALL reset any currently cached [LDAP] authentication bind requests enabled by the
`verification_cooldown` parameter in the [LDAP] server configuration section
if either `host`, `port`, `auth_dn_prefix`, or `auth_dn_suffix` parameter values
change in the configuration file. The reset SHALL cause any subsequent authentication attempts for any user
to result in contacting the [LDAP] server to verify user's username and password.
#### RQ.SRS-007.LDAP.Authentication.VerificationCooldown.Reset.InvalidPassword
version: 1.0
[ClickHouse] SHALL reset current cached [LDAP] authentication bind request enabled by the
`verification_cooldown` parameter in the [LDAP] server configuration section
for the user if the password provided in the current authentication attempt does not match
the valid password provided during the first successful authentication request that was cached
for this exact user. The reset SHALL cause the next authentication attempt for this user
to result in contacting the [LDAP] server to verify user's username and password.
## References
* **ClickHouse:** https://clickhouse.tech

View File

@ -790,9 +790,74 @@ RQ_SRS_007_LDAP_Configuration_Server_TLSCipherSuite = Requirement(
level=3,
num='4.2.27')
RQ_SRS_007_LDAP_Configuration_Server_VerificationCooldown = Requirement(
name='RQ.SRS-007.LDAP.Configuration.Server.VerificationCooldown',
version='1.0',
priority=None,
group=None,
type=None,
uid=None,
description=(
'[ClickHouse] SHALL support `verification_cooldown` parameter in the [LDAP] server configuration section\n'
'that SHALL define a period of time, in seconds, after a successful bind attempt, during which a user SHALL be assumed\n'
'to be successfully authenticated for all consecutive requests without contacting the [LDAP] server.\n'
'After period of time since the last successful attempt expires then on the authentication attempt\n'
'SHALL result in contacting the [LDAP] server to verify the username and password. \n'
'\n'
),
link=None,
level=3,
num='4.2.28')
RQ_SRS_007_LDAP_Configuration_Server_VerificationCooldown_Default = Requirement(
name='RQ.SRS-007.LDAP.Configuration.Server.VerificationCooldown.Default',
version='1.0',
priority=None,
group=None,
type=None,
uid=None,
description=(
'[ClickHouse] `verification_cooldown` parameter in the [LDAP] server configuration section\n'
'SHALL have a default value of `0` that disables caching and forces contacting\n'
'the [LDAP] server for each authentication request.\n'
'\n'
),
link=None,
level=3,
num='4.2.29')
RQ_SRS_007_LDAP_Configuration_Server_VerificationCooldown_Invalid = Requirement(
name='RQ.SRS-007.LDAP.Configuration.Server.VerificationCooldown.Invalid',
version='1.0',
priority=None,
group=None,
type=None,
uid=None,
description=(
'[Clickhouse] SHALL return an error if the value provided for the `verification_cooldown` parameter is not a valid positive integer.\n'
'\n'
'For example:\n'
'\n'
'* negative integer\n'
'* string\n'
'* empty value\n'
'* extremely large positive value (overflow)\n'
'* extremely large negative value (overflow)\n'
'\n'
'The error SHALL appear in the log and SHALL be similar to the following:\n'
'\n'
'```bash\n'
'<Error> Access(user directories): Could not parse LDAP server `openldap1`: Poco::Exception. Code: 1000, e.code() = 0, e.displayText() = Syntax error: Not a valid unsigned integer: *input value*\n'
'```\n'
'\n'
),
link=None,
level=3,
num='4.2.30')
RQ_SRS_007_LDAP_Configuration_Server_Syntax = Requirement(
name='RQ.SRS-007.LDAP.Configuration.Server.Syntax',
version='1.0',
version='2.0',
priority=None,
group=None,
type=None,
@ -808,6 +873,7 @@ RQ_SRS_007_LDAP_Configuration_Server_Syntax = Requirement(
' <port>636</port>\n'
' <auth_dn_prefix>cn=</auth_dn_prefix>\n'
' <auth_dn_suffix>, ou=users, dc=example, dc=com</auth_dn_suffix>\n'
' <verification_cooldown>0</verification_cooldown>\n'
' <enable_tls>yes</enable_tls>\n'
' <tls_minimum_protocol_version>tls1.2</tls_minimum_protocol_version>\n'
' <tls_require_cert>demand</tls_require_cert>\n'
@ -823,7 +889,7 @@ RQ_SRS_007_LDAP_Configuration_Server_Syntax = Requirement(
),
link=None,
level=3,
num='4.2.28')
num='4.2.31')
RQ_SRS_007_LDAP_Configuration_User_RBAC = Requirement(
name='RQ.SRS-007.LDAP.Configuration.User.RBAC',
@ -843,7 +909,7 @@ RQ_SRS_007_LDAP_Configuration_User_RBAC = Requirement(
),
link=None,
level=3,
num='4.2.29')
num='4.2.32')
RQ_SRS_007_LDAP_Configuration_User_Syntax = Requirement(
name='RQ.SRS-007.LDAP.Configuration.User.Syntax',
@ -871,7 +937,7 @@ RQ_SRS_007_LDAP_Configuration_User_Syntax = Requirement(
),
link=None,
level=3,
num='4.2.30')
num='4.2.33')
RQ_SRS_007_LDAP_Configuration_User_Name_Empty = Requirement(
name='RQ.SRS-007.LDAP.Configuration.User.Name.Empty',
@ -886,7 +952,7 @@ RQ_SRS_007_LDAP_Configuration_User_Name_Empty = Requirement(
),
link=None,
level=3,
num='4.2.31')
num='4.2.34')
RQ_SRS_007_LDAP_Configuration_User_BothPasswordAndLDAP = Requirement(
name='RQ.SRS-007.LDAP.Configuration.User.BothPasswordAndLDAP',
@ -902,7 +968,7 @@ RQ_SRS_007_LDAP_Configuration_User_BothPasswordAndLDAP = Requirement(
),
link=None,
level=3,
num='4.2.32')
num='4.2.35')
RQ_SRS_007_LDAP_Configuration_User_LDAP_InvalidServerName_NotDefined = Requirement(
name='RQ.SRS-007.LDAP.Configuration.User.LDAP.InvalidServerName.NotDefined',
@ -919,7 +985,7 @@ RQ_SRS_007_LDAP_Configuration_User_LDAP_InvalidServerName_NotDefined = Requireme
),
link=None,
level=3,
num='4.2.33')
num='4.2.36')
RQ_SRS_007_LDAP_Configuration_User_LDAP_InvalidServerName_Empty = Requirement(
name='RQ.SRS-007.LDAP.Configuration.User.LDAP.InvalidServerName.Empty',
@ -936,7 +1002,7 @@ RQ_SRS_007_LDAP_Configuration_User_LDAP_InvalidServerName_Empty = Requirement(
),
link=None,
level=3,
num='4.2.34')
num='4.2.37')
RQ_SRS_007_LDAP_Configuration_User_OnlyOneServer = Requirement(
name='RQ.SRS-007.LDAP.Configuration.User.OnlyOneServer',
@ -951,7 +1017,7 @@ RQ_SRS_007_LDAP_Configuration_User_OnlyOneServer = Requirement(
),
link=None,
level=3,
num='4.2.35')
num='4.2.38')
RQ_SRS_007_LDAP_Configuration_User_Name_Long = Requirement(
name='RQ.SRS-007.LDAP.Configuration.User.Name.Long',
@ -967,7 +1033,7 @@ RQ_SRS_007_LDAP_Configuration_User_Name_Long = Requirement(
),
link=None,
level=3,
num='4.2.36')
num='4.2.39')
RQ_SRS_007_LDAP_Configuration_User_Name_UTF8 = Requirement(
name='RQ.SRS-007.LDAP.Configuration.User.Name.UTF8',
@ -982,7 +1048,7 @@ RQ_SRS_007_LDAP_Configuration_User_Name_UTF8 = Requirement(
),
link=None,
level=3,
num='4.2.37')
num='4.2.40')
RQ_SRS_007_LDAP_Authentication_Username_Empty = Requirement(
name='RQ.SRS-007.LDAP.Authentication.Username.Empty',
@ -997,7 +1063,7 @@ RQ_SRS_007_LDAP_Authentication_Username_Empty = Requirement(
),
link=None,
level=3,
num='4.2.38')
num='4.2.41')
RQ_SRS_007_LDAP_Authentication_Username_Long = Requirement(
name='RQ.SRS-007.LDAP.Authentication.Username.Long',
@ -1012,7 +1078,7 @@ RQ_SRS_007_LDAP_Authentication_Username_Long = Requirement(
),
link=None,
level=3,
num='4.2.39')
num='4.2.42')
RQ_SRS_007_LDAP_Authentication_Username_UTF8 = Requirement(
name='RQ.SRS-007.LDAP.Authentication.Username.UTF8',
@ -1027,7 +1093,7 @@ RQ_SRS_007_LDAP_Authentication_Username_UTF8 = Requirement(
),
link=None,
level=3,
num='4.2.40')
num='4.2.43')
RQ_SRS_007_LDAP_Authentication_Password_Empty = Requirement(
name='RQ.SRS-007.LDAP.Authentication.Password.Empty',
@ -1044,7 +1110,7 @@ RQ_SRS_007_LDAP_Authentication_Password_Empty = Requirement(
),
link=None,
level=3,
num='4.2.41')
num='4.2.44')
RQ_SRS_007_LDAP_Authentication_Password_Long = Requirement(
name='RQ.SRS-007.LDAP.Authentication.Password.Long',
@ -1060,7 +1126,7 @@ RQ_SRS_007_LDAP_Authentication_Password_Long = Requirement(
),
link=None,
level=3,
num='4.2.42')
num='4.2.45')
RQ_SRS_007_LDAP_Authentication_Password_UTF8 = Requirement(
name='RQ.SRS-007.LDAP.Authentication.Password.UTF8',
@ -1076,7 +1142,64 @@ RQ_SRS_007_LDAP_Authentication_Password_UTF8 = Requirement(
),
link=None,
level=3,
num='4.2.43')
num='4.2.46')
RQ_SRS_007_LDAP_Authentication_VerificationCooldown_Performance = Requirement(
name='RQ.SRS-007.LDAP.Authentication.VerificationCooldown.Performance',
version='1.0',
priority=None,
group=None,
type=None,
uid=None,
description=(
'[ClickHouse] SHALL provide better login performance of [LDAP] authenticated users\n'
'when `verification_cooldown` parameter is set to a positive value when comparing\n'
'to the the case when `verification_cooldown` is turned off either for a single user or multiple users\n'
'making a large number of repeated requests.\n'
'\n'
),
link=None,
level=3,
num='4.2.47')
RQ_SRS_007_LDAP_Authentication_VerificationCooldown_Reset_ChangeInCoreServerParameters = Requirement(
name='RQ.SRS-007.LDAP.Authentication.VerificationCooldown.Reset.ChangeInCoreServerParameters',
version='1.0',
priority=None,
group=None,
type=None,
uid=None,
description=(
'[ClickHouse] SHALL reset any currently cached [LDAP] authentication bind requests enabled by the\n'
'`verification_cooldown` parameter in the [LDAP] server configuration section\n'
'if either `host`, `port`, `auth_dn_prefix`, or `auth_dn_suffix` parameter values\n'
'change in the configuration file. The reset SHALL cause any subsequent authentication attempts for any user\n'
"to result in contacting the [LDAP] server to verify user's username and password.\n"
'\n'
),
link=None,
level=3,
num='4.2.48')
RQ_SRS_007_LDAP_Authentication_VerificationCooldown_Reset_InvalidPassword = Requirement(
name='RQ.SRS-007.LDAP.Authentication.VerificationCooldown.Reset.InvalidPassword',
version='1.0',
priority=None,
group=None,
type=None,
uid=None,
description=(
'[ClickHouse] SHALL reset current cached [LDAP] authentication bind request enabled by the\n'
'`verification_cooldown` parameter in the [LDAP] server configuration section\n'
'for the user if the password provided in the current authentication attempt does not match\n'
'the valid password provided during the first successful authentication request that was cached\n'
'for this exact user. The reset SHALL cause the next authentication attempt for this user\n'
"to result in contacting the [LDAP] server to verify user's username and password.\n"
'\n'
),
link=None,
level=3,
num='4.2.49')
SRS_007_ClickHouse_Authentication_of_Users_via_LDAP = Specification(
name='SRS-007 ClickHouse Authentication of Users via LDAP',
@ -1150,22 +1273,28 @@ SRS_007_ClickHouse_Authentication_of_Users_via_LDAP = Specification(
Heading(name='RQ.SRS-007.LDAP.Configuration.Server.TLSCACertDir', level=3, num='4.2.25'),
Heading(name='RQ.SRS-007.LDAP.Configuration.Server.TLSCACertFile', level=3, num='4.2.26'),
Heading(name='RQ.SRS-007.LDAP.Configuration.Server.TLSCipherSuite', level=3, num='4.2.27'),
Heading(name='RQ.SRS-007.LDAP.Configuration.Server.Syntax', level=3, num='4.2.28'),
Heading(name='RQ.SRS-007.LDAP.Configuration.User.RBAC', level=3, num='4.2.29'),
Heading(name='RQ.SRS-007.LDAP.Configuration.User.Syntax', level=3, num='4.2.30'),
Heading(name='RQ.SRS-007.LDAP.Configuration.User.Name.Empty', level=3, num='4.2.31'),
Heading(name='RQ.SRS-007.LDAP.Configuration.User.BothPasswordAndLDAP', level=3, num='4.2.32'),
Heading(name='RQ.SRS-007.LDAP.Configuration.User.LDAP.InvalidServerName.NotDefined', level=3, num='4.2.33'),
Heading(name='RQ.SRS-007.LDAP.Configuration.User.LDAP.InvalidServerName.Empty', level=3, num='4.2.34'),
Heading(name='RQ.SRS-007.LDAP.Configuration.User.OnlyOneServer', level=3, num='4.2.35'),
Heading(name='RQ.SRS-007.LDAP.Configuration.User.Name.Long', level=3, num='4.2.36'),
Heading(name='RQ.SRS-007.LDAP.Configuration.User.Name.UTF8', level=3, num='4.2.37'),
Heading(name='RQ.SRS-007.LDAP.Authentication.Username.Empty', level=3, num='4.2.38'),
Heading(name='RQ.SRS-007.LDAP.Authentication.Username.Long', level=3, num='4.2.39'),
Heading(name='RQ.SRS-007.LDAP.Authentication.Username.UTF8', level=3, num='4.2.40'),
Heading(name='RQ.SRS-007.LDAP.Authentication.Password.Empty', level=3, num='4.2.41'),
Heading(name='RQ.SRS-007.LDAP.Authentication.Password.Long', level=3, num='4.2.42'),
Heading(name='RQ.SRS-007.LDAP.Authentication.Password.UTF8', level=3, num='4.2.43'),
Heading(name='RQ.SRS-007.LDAP.Configuration.Server.VerificationCooldown', level=3, num='4.2.28'),
Heading(name='RQ.SRS-007.LDAP.Configuration.Server.VerificationCooldown.Default', level=3, num='4.2.29'),
Heading(name='RQ.SRS-007.LDAP.Configuration.Server.VerificationCooldown.Invalid', level=3, num='4.2.30'),
Heading(name='RQ.SRS-007.LDAP.Configuration.Server.Syntax', level=3, num='4.2.31'),
Heading(name='RQ.SRS-007.LDAP.Configuration.User.RBAC', level=3, num='4.2.32'),
Heading(name='RQ.SRS-007.LDAP.Configuration.User.Syntax', level=3, num='4.2.33'),
Heading(name='RQ.SRS-007.LDAP.Configuration.User.Name.Empty', level=3, num='4.2.34'),
Heading(name='RQ.SRS-007.LDAP.Configuration.User.BothPasswordAndLDAP', level=3, num='4.2.35'),
Heading(name='RQ.SRS-007.LDAP.Configuration.User.LDAP.InvalidServerName.NotDefined', level=3, num='4.2.36'),
Heading(name='RQ.SRS-007.LDAP.Configuration.User.LDAP.InvalidServerName.Empty', level=3, num='4.2.37'),
Heading(name='RQ.SRS-007.LDAP.Configuration.User.OnlyOneServer', level=3, num='4.2.38'),
Heading(name='RQ.SRS-007.LDAP.Configuration.User.Name.Long', level=3, num='4.2.39'),
Heading(name='RQ.SRS-007.LDAP.Configuration.User.Name.UTF8', level=3, num='4.2.40'),
Heading(name='RQ.SRS-007.LDAP.Authentication.Username.Empty', level=3, num='4.2.41'),
Heading(name='RQ.SRS-007.LDAP.Authentication.Username.Long', level=3, num='4.2.42'),
Heading(name='RQ.SRS-007.LDAP.Authentication.Username.UTF8', level=3, num='4.2.43'),
Heading(name='RQ.SRS-007.LDAP.Authentication.Password.Empty', level=3, num='4.2.44'),
Heading(name='RQ.SRS-007.LDAP.Authentication.Password.Long', level=3, num='4.2.45'),
Heading(name='RQ.SRS-007.LDAP.Authentication.Password.UTF8', level=3, num='4.2.46'),
Heading(name='RQ.SRS-007.LDAP.Authentication.VerificationCooldown.Performance', level=3, num='4.2.47'),
Heading(name='RQ.SRS-007.LDAP.Authentication.VerificationCooldown.Reset.ChangeInCoreServerParameters', level=3, num='4.2.48'),
Heading(name='RQ.SRS-007.LDAP.Authentication.VerificationCooldown.Reset.InvalidPassword', level=3, num='4.2.49'),
Heading(name='References', level=1, num='5'),
),
requirements=(
@ -1218,6 +1347,9 @@ SRS_007_ClickHouse_Authentication_of_Users_via_LDAP = Specification(
RQ_SRS_007_LDAP_Configuration_Server_TLSCACertDir,
RQ_SRS_007_LDAP_Configuration_Server_TLSCACertFile,
RQ_SRS_007_LDAP_Configuration_Server_TLSCipherSuite,
RQ_SRS_007_LDAP_Configuration_Server_VerificationCooldown,
RQ_SRS_007_LDAP_Configuration_Server_VerificationCooldown_Default,
RQ_SRS_007_LDAP_Configuration_Server_VerificationCooldown_Invalid,
RQ_SRS_007_LDAP_Configuration_Server_Syntax,
RQ_SRS_007_LDAP_Configuration_User_RBAC,
RQ_SRS_007_LDAP_Configuration_User_Syntax,
@ -1234,9 +1366,13 @@ SRS_007_ClickHouse_Authentication_of_Users_via_LDAP = Specification(
RQ_SRS_007_LDAP_Authentication_Password_Empty,
RQ_SRS_007_LDAP_Authentication_Password_Long,
RQ_SRS_007_LDAP_Authentication_Password_UTF8,
RQ_SRS_007_LDAP_Authentication_VerificationCooldown_Performance,
RQ_SRS_007_LDAP_Authentication_VerificationCooldown_Reset_ChangeInCoreServerParameters,
RQ_SRS_007_LDAP_Authentication_VerificationCooldown_Reset_InvalidPassword,
),
content='''
# SRS-007 ClickHouse Authentication of Users via LDAP
# Software Requirements Specification
## Table of Contents
@ -1295,22 +1431,28 @@ SRS_007_ClickHouse_Authentication_of_Users_via_LDAP = Specification(
* 4.2.25 [RQ.SRS-007.LDAP.Configuration.Server.TLSCACertDir](#rqsrs-007ldapconfigurationservertlscacertdir)
* 4.2.26 [RQ.SRS-007.LDAP.Configuration.Server.TLSCACertFile](#rqsrs-007ldapconfigurationservertlscacertfile)
* 4.2.27 [RQ.SRS-007.LDAP.Configuration.Server.TLSCipherSuite](#rqsrs-007ldapconfigurationservertlsciphersuite)
* 4.2.28 [RQ.SRS-007.LDAP.Configuration.Server.Syntax](#rqsrs-007ldapconfigurationserversyntax)
* 4.2.29 [RQ.SRS-007.LDAP.Configuration.User.RBAC](#rqsrs-007ldapconfigurationuserrbac)
* 4.2.30 [RQ.SRS-007.LDAP.Configuration.User.Syntax](#rqsrs-007ldapconfigurationusersyntax)
* 4.2.31 [RQ.SRS-007.LDAP.Configuration.User.Name.Empty](#rqsrs-007ldapconfigurationusernameempty)
* 4.2.32 [RQ.SRS-007.LDAP.Configuration.User.BothPasswordAndLDAP](#rqsrs-007ldapconfigurationuserbothpasswordandldap)
* 4.2.33 [RQ.SRS-007.LDAP.Configuration.User.LDAP.InvalidServerName.NotDefined](#rqsrs-007ldapconfigurationuserldapinvalidservernamenotdefined)
* 4.2.34 [RQ.SRS-007.LDAP.Configuration.User.LDAP.InvalidServerName.Empty](#rqsrs-007ldapconfigurationuserldapinvalidservernameempty)
* 4.2.35 [RQ.SRS-007.LDAP.Configuration.User.OnlyOneServer](#rqsrs-007ldapconfigurationuseronlyoneserver)
* 4.2.36 [RQ.SRS-007.LDAP.Configuration.User.Name.Long](#rqsrs-007ldapconfigurationusernamelong)
* 4.2.37 [RQ.SRS-007.LDAP.Configuration.User.Name.UTF8](#rqsrs-007ldapconfigurationusernameutf8)
* 4.2.38 [RQ.SRS-007.LDAP.Authentication.Username.Empty](#rqsrs-007ldapauthenticationusernameempty)
* 4.2.39 [RQ.SRS-007.LDAP.Authentication.Username.Long](#rqsrs-007ldapauthenticationusernamelong)
* 4.2.40 [RQ.SRS-007.LDAP.Authentication.Username.UTF8](#rqsrs-007ldapauthenticationusernameutf8)
* 4.2.41 [RQ.SRS-007.LDAP.Authentication.Password.Empty](#rqsrs-007ldapauthenticationpasswordempty)
* 4.2.42 [RQ.SRS-007.LDAP.Authentication.Password.Long](#rqsrs-007ldapauthenticationpasswordlong)
* 4.2.43 [RQ.SRS-007.LDAP.Authentication.Password.UTF8](#rqsrs-007ldapauthenticationpasswordutf8)
* 4.2.28 [RQ.SRS-007.LDAP.Configuration.Server.VerificationCooldown](#rqsrs-007ldapconfigurationserververificationcooldown)
* 4.2.29 [RQ.SRS-007.LDAP.Configuration.Server.VerificationCooldown.Default](#rqsrs-007ldapconfigurationserververificationcooldowndefault)
* 4.2.30 [RQ.SRS-007.LDAP.Configuration.Server.VerificationCooldown.Invalid](#rqsrs-007ldapconfigurationserververificationcooldowninvalid)
* 4.2.31 [RQ.SRS-007.LDAP.Configuration.Server.Syntax](#rqsrs-007ldapconfigurationserversyntax)
* 4.2.32 [RQ.SRS-007.LDAP.Configuration.User.RBAC](#rqsrs-007ldapconfigurationuserrbac)
* 4.2.33 [RQ.SRS-007.LDAP.Configuration.User.Syntax](#rqsrs-007ldapconfigurationusersyntax)
* 4.2.34 [RQ.SRS-007.LDAP.Configuration.User.Name.Empty](#rqsrs-007ldapconfigurationusernameempty)
* 4.2.35 [RQ.SRS-007.LDAP.Configuration.User.BothPasswordAndLDAP](#rqsrs-007ldapconfigurationuserbothpasswordandldap)
* 4.2.36 [RQ.SRS-007.LDAP.Configuration.User.LDAP.InvalidServerName.NotDefined](#rqsrs-007ldapconfigurationuserldapinvalidservernamenotdefined)
* 4.2.37 [RQ.SRS-007.LDAP.Configuration.User.LDAP.InvalidServerName.Empty](#rqsrs-007ldapconfigurationuserldapinvalidservernameempty)
* 4.2.38 [RQ.SRS-007.LDAP.Configuration.User.OnlyOneServer](#rqsrs-007ldapconfigurationuseronlyoneserver)
* 4.2.39 [RQ.SRS-007.LDAP.Configuration.User.Name.Long](#rqsrs-007ldapconfigurationusernamelong)
* 4.2.40 [RQ.SRS-007.LDAP.Configuration.User.Name.UTF8](#rqsrs-007ldapconfigurationusernameutf8)
* 4.2.41 [RQ.SRS-007.LDAP.Authentication.Username.Empty](#rqsrs-007ldapauthenticationusernameempty)
* 4.2.42 [RQ.SRS-007.LDAP.Authentication.Username.Long](#rqsrs-007ldapauthenticationusernamelong)
* 4.2.43 [RQ.SRS-007.LDAP.Authentication.Username.UTF8](#rqsrs-007ldapauthenticationusernameutf8)
* 4.2.44 [RQ.SRS-007.LDAP.Authentication.Password.Empty](#rqsrs-007ldapauthenticationpasswordempty)
* 4.2.45 [RQ.SRS-007.LDAP.Authentication.Password.Long](#rqsrs-007ldapauthenticationpasswordlong)
* 4.2.46 [RQ.SRS-007.LDAP.Authentication.Password.UTF8](#rqsrs-007ldapauthenticationpasswordutf8)
* 4.2.47 [RQ.SRS-007.LDAP.Authentication.VerificationCooldown.Performance](#rqsrs-007ldapauthenticationverificationcooldownperformance)
* 4.2.48 [RQ.SRS-007.LDAP.Authentication.VerificationCooldown.Reset.ChangeInCoreServerParameters](#rqsrs-007ldapauthenticationverificationcooldownresetchangeincoreserverparameters)
* 4.2.49 [RQ.SRS-007.LDAP.Authentication.VerificationCooldown.Reset.InvalidPassword](#rqsrs-007ldapauthenticationverificationcooldownresetinvalidpassword)
* 5 [References](#references)
## Revision History
@ -1631,9 +1773,44 @@ For example,
The available suites SHALL depend on the [OpenSSL] library version and variant used to build
[ClickHouse] and therefore might change.
#### RQ.SRS-007.LDAP.Configuration.Server.Syntax
#### RQ.SRS-007.LDAP.Configuration.Server.VerificationCooldown
version: 1.0
[ClickHouse] SHALL support `verification_cooldown` parameter in the [LDAP] server configuration section
that SHALL define a period of time, in seconds, after a successful bind attempt, during which a user SHALL be assumed
to be successfully authenticated for all consecutive requests without contacting the [LDAP] server.
After period of time since the last successful attempt expires then on the authentication attempt
SHALL result in contacting the [LDAP] server to verify the username and password.
#### RQ.SRS-007.LDAP.Configuration.Server.VerificationCooldown.Default
version: 1.0
[ClickHouse] `verification_cooldown` parameter in the [LDAP] server configuration section
SHALL have a default value of `0` that disables caching and forces contacting
the [LDAP] server for each authentication request.
#### RQ.SRS-007.LDAP.Configuration.Server.VerificationCooldown.Invalid
version: 1.0
[Clickhouse] SHALL return an error if the value provided for the `verification_cooldown` parameter is not a valid positive integer.
For example:
* negative integer
* string
* empty value
* extremely large positive value (overflow)
* extremely large negative value (overflow)
The error SHALL appear in the log and SHALL be similar to the following:
```bash
<Error> Access(user directories): Could not parse LDAP server `openldap1`: Poco::Exception. Code: 1000, e.code() = 0, e.displayText() = Syntax error: Not a valid unsigned integer: *input value*
```
#### RQ.SRS-007.LDAP.Configuration.Server.Syntax
version: 2.0
[ClickHouse] SHALL support the following example syntax to create an entry for an [LDAP] server inside the `config.xml`
configuration file or of any configuration file inside the `config.d` directory.
@ -1644,6 +1821,7 @@ configuration file or of any configuration file inside the `config.d` directory.
<port>636</port>
<auth_dn_prefix>cn=</auth_dn_prefix>
<auth_dn_suffix>, ou=users, dc=example, dc=com</auth_dn_suffix>
<verification_cooldown>0</verification_cooldown>
<enable_tls>yes</enable_tls>
<tls_minimum_protocol_version>tls1.2</tls_minimum_protocol_version>
<tls_require_cert>demand</tls_require_cert>
@ -1759,6 +1937,33 @@ version: 1.0
[ClickHouse] SHALL support [UTF-8] characters in passwords
used to authenticate users using an [LDAP] server.
#### RQ.SRS-007.LDAP.Authentication.VerificationCooldown.Performance
version: 1.0
[ClickHouse] SHALL provide better login performance of [LDAP] authenticated users
when `verification_cooldown` parameter is set to a positive value when comparing
to the the case when `verification_cooldown` is turned off either for a single user or multiple users
making a large number of repeated requests.
#### RQ.SRS-007.LDAP.Authentication.VerificationCooldown.Reset.ChangeInCoreServerParameters
version: 1.0
[ClickHouse] SHALL reset any currently cached [LDAP] authentication bind requests enabled by the
`verification_cooldown` parameter in the [LDAP] server configuration section
if either `host`, `port`, `auth_dn_prefix`, or `auth_dn_suffix` parameter values
change in the configuration file. The reset SHALL cause any subsequent authentication attempts for any user
to result in contacting the [LDAP] server to verify user's username and password.
#### RQ.SRS-007.LDAP.Authentication.VerificationCooldown.Reset.InvalidPassword
version: 1.0
[ClickHouse] SHALL reset current cached [LDAP] authentication bind request enabled by the
`verification_cooldown` parameter in the [LDAP] server configuration section
for the user if the password provided in the current authentication attempt does not match
the valid password provided during the first successful authentication request that was cached
for this exact user. The reset SHALL cause the next authentication attempt for this user
to result in contacting the [LDAP] server to verify user's username and password.
## References
* **ClickHouse:** https://clickhouse.tech

View File

@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
import random
import time
from multiprocessing.dummy import Pool
from testflows.core import *
@ -27,6 +28,7 @@ servers = {
@TestStep(When)
@Name("I login as {username} and execute query")
@Args(format_name=True)
def login_and_execute_query(self, username, password, exitcode=None, message=None, steps=True):
"""Execute query as some user.
"""
@ -129,7 +131,7 @@ def login_after_user_is_deleted_from_ldap(self, server, rbac=False):
user = add_user_to_ldap(**user)
with ldap_authenticated_users({"username": user["cn"], "server": server}, config_file=f"ldap_users_{getuid()}.xml",
restart=True, rbac=rbac):
restart=True, rbac=rbac):
login_and_execute_query(username=user["cn"], password=user["userpassword"])
with When("I delete this user from LDAP"):
@ -200,7 +202,7 @@ def login_after_user_cn_changed_in_ldap(self, server, rbac=False):
user = add_user_to_ldap(**user)
with ldap_authenticated_users({"username": user["cn"], "server": server},
config_file=f"ldap_users_{getuid()}.xml", restart=True, rbac=rbac):
config_file=f"ldap_users_{getuid()}.xml", restart=True, rbac=rbac):
login_and_execute_query(username=user["cn"], password=user["userpassword"])
with When("I change user password in LDAP"):
@ -474,6 +476,470 @@ def empty_username_and_empty_password(self, server=None, rbac=False):
"""
login_and_execute_query(username="", password="")
@TestScenario
@Tags("verification_cooldown")
@Requirements(
RQ_SRS_007_LDAP_Configuration_Server_VerificationCooldown_Default("1.0")
)
def default_verification_cooldown_value(self, server, rbac=False, timeout=20):
"""Check that the default value (0) for the verification cooldown parameter
disables caching and forces contacting the LDAP server for each
authentication request.
"""
error_message = "DB::Exception: testVCD: Authentication failed: password is incorrect or there is no user with such name"
error_exitcode = 4
user = None
with Given("I have an LDAP configuration that uses the default verification_cooldown value (0)"):
servers = {"openldap1": {"host": "openldap1", "port": "389", "enable_tls": "no",
"auth_dn_prefix": "cn=", "auth_dn_suffix": ",ou=users,dc=company,dc=com"
}}
self.context.ldap_node = self.context.cluster.node(server)
try:
with Given("I add user to LDAP"):
user = {"cn": "testVCD", "userpassword": "testVCD"}
user = add_user_to_ldap(**user)
with ldap_servers(servers):
with ldap_authenticated_users({"username": user["cn"], "server": server}, config_file=f"ldap_users_{getuid()}.xml"):
with When("I login and execute a query"):
login_and_execute_query(username=user["cn"], password=user["userpassword"])
with And("I change user password in LDAP"):
change_user_password_in_ldap(user, "newpassword")
with Then("when I try to login immediately with the old user password it should fail"):
login_and_execute_query(username=user["cn"], password=user["userpassword"],
exitcode=error_exitcode, message=error_message)
finally:
with Finally("I make sure LDAP user is deleted"):
if user is not None:
delete_user_from_ldap(user, exitcode=None)
@TestScenario
@Tags("verification_cooldown")
@Requirements(
RQ_SRS_007_LDAP_Configuration_Server_VerificationCooldown("1.0")
)
def valid_verification_cooldown_value_cn_change(self, server, rbac=False, timeout=20):
"""Check that we can perform requests without contacting the LDAP server
after successful authentication when the verification_cooldown parameter
is set and the user cn is changed.
"""
error_message = "DB::Exception: testVCD: Authentication failed: password is incorrect or there is no user with such name"
error_exitcode = 4
user = None
new_user = None
with Given("I have an LDAP configuration that sets verification_cooldown parameter to 2 sec"):
servers = { "openldap1": {
"host": "openldap1",
"port": "389",
"enable_tls": "no",
"auth_dn_prefix": "cn=",
"auth_dn_suffix": ",ou=users,dc=company,dc=com",
"verification_cooldown": "2"
}}
self.context.ldap_node = self.context.cluster.node(server)
try:
with Given("I add user to LDAP"):
user = {"cn": "testVCD", "userpassword": "testVCD"}
user = add_user_to_ldap(**user)
with ldap_servers(servers):
with ldap_authenticated_users({"username": user["cn"], "server": server}, config_file=f"ldap_users_{getuid()}.xml"):
with When("I login and execute a query"):
login_and_execute_query(username=user["cn"], password=user["userpassword"])
with And("I change user cn in LDAP"):
new_user = change_user_cn_in_ldap(user, "testVCD2")
with Then("when I try to login again with the old user cn it should work"):
login_and_execute_query(username=user["cn"], password=user["userpassword"])
with And("when I sleep for 2 seconds and try to log in, it should fail"):
time.sleep(2)
login_and_execute_query(username=user["cn"], password=user["userpassword"],
exitcode=error_exitcode, message=error_message)
finally:
with Finally("I make sure LDAP user is deleted"):
if new_user is not None:
delete_user_from_ldap(new_user, exitcode=None)
@TestScenario
@Tags("verification_cooldown")
@Requirements(
RQ_SRS_007_LDAP_Configuration_Server_VerificationCooldown("1.0")
)
def valid_verification_cooldown_value_password_change(self, server, rbac=False, timeout=20):
"""Check that we can perform requests without contacting the LDAP server
after successful authentication when the verification_cooldown parameter
is set and the user password is changed.
"""
error_message = "DB::Exception: testVCD: Authentication failed: password is incorrect or there is no user with such name"
error_exitcode = 4
user = None
with Given("I have an LDAP configuration that sets verification_cooldown parameter to 2 sec"):
servers = { "openldap1": {
"host": "openldap1",
"port": "389",
"enable_tls": "no",
"auth_dn_prefix": "cn=",
"auth_dn_suffix": ",ou=users,dc=company,dc=com",
"verification_cooldown": "2"
}}
self.context.ldap_node = self.context.cluster.node(server)
try:
with Given("I add user to LDAP"):
user = {"cn": "testVCD", "userpassword": "testVCD"}
user = add_user_to_ldap(**user)
with ldap_servers(servers):
with ldap_authenticated_users({"username": user["cn"], "server": server}, config_file=f"ldap_users_{getuid()}.xml"):
with When("I login and execute a query"):
login_and_execute_query(username=user["cn"], password=user["userpassword"])
with And("I change user password in LDAP"):
change_user_password_in_ldap(user, "newpassword")
with Then("when I try to login again with the old password it should work"):
login_and_execute_query(username=user["cn"], password=user["userpassword"])
with And("when I sleep for 2 seconds and try to log in, it should fail"):
time.sleep(2)
login_and_execute_query(username=user["cn"], password=user["userpassword"],
exitcode=error_exitcode, message=error_message)
finally:
with Finally("I make sure LDAP user is deleted"):
if user is not None:
delete_user_from_ldap(user, exitcode=None)
@TestScenario
@Tags("verification_cooldown")
@Requirements(
RQ_SRS_007_LDAP_Configuration_Server_VerificationCooldown("1.0")
)
def valid_verification_cooldown_value_ldap_unavailable(self, server, rbac=False, timeout=20):
"""Check that we can perform requests without contacting the LDAP server
after successful authentication when the verification_cooldown parameter
is set, even when the LDAP server is offline.
"""
error_message = "DB::Exception: testVCD: Authentication failed: password is incorrect or there is no user with such name"
error_exitcode = 4
user = None
with Given("I have an LDAP configuration that sets verification_cooldown parameter to 2 sec"):
servers = { "openldap1": {
"host": "openldap1",
"port": "389",
"enable_tls": "no",
"auth_dn_prefix": "cn=",
"auth_dn_suffix": ",ou=users,dc=company,dc=com",
"verification_cooldown": "2"
}}
self.context.ldap_node = self.context.cluster.node(server)
try:
with Given("I add a new user to LDAP"):
user = {"cn": "testVCD", "userpassword": "testVCD"}
user = add_user_to_ldap(**user)
with ldap_servers(servers):
with ldap_authenticated_users({"username": user["cn"], "server": server},
config_file=f"ldap_users_{getuid()}.xml"):
with When("I login and execute a query"):
login_and_execute_query(username=user["cn"], password=user["userpassword"])
try:
with And("then I stop the ldap server"):
self.context.ldap_node.stop()
with Then("when I try to login again with the server offline it should work"):
login_and_execute_query(username=user["cn"], password=user["userpassword"])
with And("when I sleep for 2 seconds and try to log in, it should fail"):
time.sleep(2)
login_and_execute_query(username=user["cn"], password=user["userpassword"],
exitcode=error_exitcode, message=error_message)
finally:
with Finally("I start the ldap server back up"):
self.context.ldap_node.start()
finally:
with Finally("I make sure LDAP user is deleted"):
if user is not None:
delete_user_from_ldap(user, exitcode=None)
@TestOutline
def repeat_requests(self, server, iterations, vcd_value, rbac=False):
"""Run repeated requests from some user to the LDAP server.
"""
user = None
with Given(f"I have an LDAP configuration that sets verification_cooldown parameter to {vcd_value} sec"):
servers = { "openldap1": {
"host": "openldap1",
"port": "389",
"enable_tls": "no",
"auth_dn_prefix": "cn=",
"auth_dn_suffix": ",ou=users,dc=company,dc=com",
"verification_cooldown": vcd_value
}}
self.context.ldap_node = self.context.cluster.node(server)
try:
with And("I add a new user to LDAP"):
user = {"cn": "testVCD", "userpassword": "testVCD"}
user = add_user_to_ldap(**user)
with ldap_servers(servers):
with ldap_authenticated_users({"username": user["cn"], "server": server}, config_file=f"ldap_users_{getuid()}.xml"):
with When(f"I login and execute some query {iterations} times"):
start_time = time.time()
r = self.context.node.command(f"time for i in {{1..{iterations}}}; do clickhouse client -q \"SELECT 1\" --user {user['cn']} --password {user['userpassword']} > /dev/null; done")
end_time = time.time()
return end_time - start_time
finally:
with Finally("I make sure LDAP user is deleted"):
if user is not None:
delete_user_from_ldap(user, exitcode=None)
@TestScenario
@Tags("verification_cooldown")
@Requirements(
RQ_SRS_007_LDAP_Authentication_VerificationCooldown_Performance("1.0")
)
def verification_cooldown_performance(self, server, rbac=False, iterations=5000):
"""Check that login performance is better when the verification cooldown
parameter is set to a positive value when comparing to the case when
the verification cooldown parameter is turned off.
"""
vcd_time = 0
no_vcd_time = 0
with Example(f"Repeated requests with verification cooldown parameter set to 600 seconds, {iterations} iterations"):
vcd_time = repeat_requests(server=server, iterations=iterations, vcd_value="600", rbac=rbac)
metric("login_with_vcd_value_600", units="seconds", value=vcd_time)
with Example(f"Repeated requests with verification cooldown parameter set to 0 seconds, {iterations} iterations"):
no_vcd_time = repeat_requests(server=server, iterations=iterations, vcd_value="0", rbac=rbac)
metric("login_with_vcd_value_0", units="seconds", value=no_vcd_time)
with Then("The performance with verification cooldown parameter set is better than the performance with no verification cooldown parameter."):
assert no_vcd_time > vcd_time, error()
with And("Log the performance improvement as a percentage."):
metric("percentage_improvement", units="%", value=100*(no_vcd_time - vcd_time)/vcd_time)
@TestOutline
def check_verification_cooldown_reset_on_core_server_parameter_change(self, server,
parameter_name, parameter_value, rbac=False):
"""Check that the LDAP login cache is reset for all the LDAP authentication users
when verification_cooldown parameter is set after one of the core server
parameters is changed in the LDAP server configuration.
"""
config_d_dir="/etc/clickhouse-server/config.d"
config_file="ldap_servers.xml"
error_message = "DB::Exception: {user}: Authentication failed: password is incorrect or there is no user with such name"
error_exitcode = 4
user = None
config=None
updated_config=None
with Given("I have an LDAP configuration that sets verification_cooldown parameter to 600 sec"):
servers = { "openldap1": {
"host": "openldap1",
"port": "389",
"enable_tls": "no",
"auth_dn_prefix": "cn=",
"auth_dn_suffix": ",ou=users,dc=company,dc=com",
"verification_cooldown": "600"
}}
self.context.ldap_node = self.context.cluster.node(server)
with And("LDAP authenticated user"):
users = [
{"cn": f"testVCD_0", "userpassword": "testVCD_0"},
{"cn": f"testVCD_1", "userpassword": "testVCD_1"}
]
with And("I create LDAP servers configuration file"):
config = create_ldap_servers_config_content(servers, config_d_dir, config_file)
with ldap_users(*users) as users:
with ldap_servers(servers, restart=True):
with ldap_authenticated_users(*[{"username": user["cn"], "server": server} for user in users]):
with When("I login and execute a query"):
for user in users:
with By(f"as user {user['cn']}"):
login_and_execute_query(username=user["cn"], password=user["userpassword"])
with And("I change user password in LDAP"):
for user in users:
with By(f"for user {user['cn']}"):
change_user_password_in_ldap(user, "newpassword")
with And(f"I change the server {parameter_name} core parameter", description=f"{parameter_value}"):
servers["openldap1"][parameter_name] = parameter_value
with And("I create an updated the config file that has a different server host name"):
updated_config = create_ldap_servers_config_content(servers, config_d_dir, config_file)
with modify_config(updated_config, restart=False):
with Then("when I try to log in it should fail as cache should have been reset"):
for user in users:
with By(f"as user {user['cn']}"):
login_and_execute_query(username=user["cn"], password=user["userpassword"],
exitcode=error_exitcode, message=error_message.format(user=user["cn"]))
@TestScenario
@Tags("verification_cooldown")
@Requirements(
RQ_SRS_007_LDAP_Authentication_VerificationCooldown_Reset_ChangeInCoreServerParameters("1.0")
)
def verification_cooldown_reset_on_server_host_parameter_change(self, server, rbac=False):
"""Check that the LDAP login cache is reset for all the LDAP authentication users
when verification_cooldown parameter is set after server host name
is changed in the LDAP server configuration.
"""
check_verification_cooldown_reset_on_core_server_parameter_change(server=server,
parameter_name="host", parameter_value="openldap2", rbac=rbac)
@TestScenario
@Tags("verification_cooldown")
@Requirements(
RQ_SRS_007_LDAP_Authentication_VerificationCooldown_Reset_ChangeInCoreServerParameters("1.0")
)
def verification_cooldown_reset_on_server_port_parameter_change(self, server, rbac=False):
"""Check that the LDAP login cache is reset for all the LDAP authentication users
when verification_cooldown parameter is set after server port is changed in the
LDAP server configuration.
"""
check_verification_cooldown_reset_on_core_server_parameter_change(server=server,
parameter_name="port", parameter_value="9006", rbac=rbac)
@TestScenario
@Tags("verification_cooldown")
@Requirements(
RQ_SRS_007_LDAP_Authentication_VerificationCooldown_Reset_ChangeInCoreServerParameters("1.0")
)
def verification_cooldown_reset_on_server_auth_dn_prefix_parameter_change(self, server, rbac=False):
"""Check that the LDAP login cache is reset for all the LDAP authentication users
when verification_cooldown parameter is set after server auth_dn_prefix
is changed in the LDAP server configuration.
"""
check_verification_cooldown_reset_on_core_server_parameter_change(server=server,
parameter_name="auth_dn_prefix", parameter_value="cxx=", rbac=rbac)
@TestScenario
@Tags("verification_cooldown")
@Requirements(
RQ_SRS_007_LDAP_Authentication_VerificationCooldown_Reset_ChangeInCoreServerParameters("1.0")
)
def verification_cooldown_reset_on_server_auth_dn_suffix_parameter_change(self, server, rbac=False):
"""Check that the LDAP login cache is reset for all the LDAP authentication users
when verification_cooldown parameter is set after server auth_dn_suffix
is changed in the LDAP server configuration.
"""
check_verification_cooldown_reset_on_core_server_parameter_change(server=server,
parameter_name="auth_dn_suffix",
parameter_value=",ou=company,dc=users,dc=com", rbac=rbac)
@TestScenario
@Name("verification cooldown reset when invalid password is provided")
@Tags("verification_cooldown")
@Requirements(
RQ_SRS_007_LDAP_Authentication_VerificationCooldown_Reset_InvalidPassword("1.0")
)
def scenario(self, server, rbac=False):
"""Check that cached bind requests for the user are discarded when
the user provides invalid login credentials.
"""
user = None
error_exitcode = 4
error_message = "DB::Exception: testVCD: Authentication failed: password is incorrect or there is no user with such name"
with Given("I have an LDAP configuration that sets verification_cooldown parameter to 600 sec"):
servers = { "openldap1": {
"host": "openldap1",
"port": "389",
"enable_tls": "no",
"auth_dn_prefix": "cn=",
"auth_dn_suffix": ",ou=users,dc=company,dc=com",
"verification_cooldown": "600"
}}
self.context.ldap_node = self.context.cluster.node(server)
try:
with Given("I add a new user to LDAP"):
user = {"cn": "testVCD", "userpassword": "testVCD"}
user = add_user_to_ldap(**user)
with ldap_servers(servers):
with ldap_authenticated_users({"username": user["cn"], "server": server},
config_file=f"ldap_users_{getuid()}.xml"):
with When("I login and execute a query"):
login_and_execute_query(username=user["cn"], password=user["userpassword"])
with And("I change user password in LDAP"):
change_user_password_in_ldap(user, "newpassword")
with Then("When I try to log in with the cached password it should work"):
login_and_execute_query(username=user["cn"], password=user["userpassword"])
with And("When I try to log in with an incorrect password it should fail"):
login_and_execute_query(username=user["cn"], password="incorrect", exitcode=error_exitcode,
message=error_message)
with And("When I try to log in with the cached password it should fail"):
login_and_execute_query(username=user["cn"], password="incorrect", exitcode=error_exitcode,
message=error_message)
finally:
with Finally("I make sure LDAP user is deleted"):
if user is not None:
delete_user_from_ldap(user, exitcode=None)
@TestFeature
def verification_cooldown(self, rbac, servers=None, node="clickhouse1"):
"""Check verification cooldown parameter functionality.
"""
for scenario in loads(current_module(), Scenario, filter=has.tag("verification_cooldown")):
scenario(server="openldap1", rbac=rbac)
@TestOutline(Feature)
@Name("user authentications")
@Requirements(
@ -493,5 +959,11 @@ def feature(self, rbac, servers=None, node="clickhouse1"):
servers = globals()["servers"]
with ldap_servers(servers):
for scenario in loads(current_module(), Scenario):
for scenario in loads(current_module(), Scenario, filter=~has.tag("verification_cooldown")):
scenario(server="openldap1", rbac=rbac)
Feature(test=verification_cooldown)(rbac=rbac, servers=servers, node=node)

View File

@ -78,7 +78,7 @@ def restart(node=None, safe=False, timeout=60):
f"ConfigReloader: Loaded config '/etc/clickhouse-server/config.xml', performed update on configuration",
timeout=timeout)
def add_config(config, timeout=60, restart=False):
def add_config(config, timeout=60, restart=False, modify=False):
"""Add dynamic configuration file to ClickHouse.
:param node: node
@ -165,19 +165,20 @@ def add_config(config, timeout=60, restart=False):
wait_for_config_to_be_loaded()
yield
finally:
with Finally(f"I remove {config.name}"):
with node.cluster.shell(node.name) as bash:
bash.expect(bash.prompt)
bash.send("tail -n 0 -f /var/log/clickhouse-server/clickhouse-server.log")
if not modify:
with Finally(f"I remove {config.name}"):
with node.cluster.shell(node.name) as bash:
bash.expect(bash.prompt)
bash.send("tail -n 0 -f /var/log/clickhouse-server/clickhouse-server.log")
with By("removing the config file", description=config.path):
node.command(f"rm -rf {config.path}", exitcode=0)
with By("removing the config file", description=config.path):
node.command(f"rm -rf {config.path}", exitcode=0)
with Then(f"{config.preprocessed_name} should be updated", description=f"timeout {timeout}"):
check_preprocessed_config_is_updated(after_removal=True)
with Then(f"{config.preprocessed_name} should be updated", description=f"timeout {timeout}"):
check_preprocessed_config_is_updated(after_removal=True)
with And("I wait for config to be reloaded"):
wait_for_config_to_be_loaded()
with And("I wait for config to be reloaded"):
wait_for_config_to_be_loaded()
def create_ldap_servers_config_content(servers, config_d_dir="/etc/clickhouse-server/config.d", config_file="ldap_servers.xml"):
"""Create LDAP servers configuration content.
@ -201,12 +202,19 @@ def create_ldap_servers_config_content(servers, config_d_dir="/etc/clickhouse-se
return Config(content, path, name, uid, "config.xml")
@contextmanager
def modify_config(config, restart=False):
"""Apply updated configuration file.
"""
return add_config(config, restart=restart, modify=True)
@contextmanager
def ldap_servers(servers, config_d_dir="/etc/clickhouse-server/config.d", config_file="ldap_servers.xml",
timeout=60, restart=False):
timeout=60, restart=False, config=None):
"""Add LDAP servers configuration.
"""
config = create_ldap_servers_config_content(servers, config_d_dir, config_file)
if config is None:
config = create_ldap_servers_config_content(servers, config_d_dir, config_file)
return add_config(config, restart=restart)
def create_ldap_users_config_content(*users, config_d_dir="/etc/clickhouse-server/users.d", config_file="ldap_users.xml"):

View File

@ -217,9 +217,39 @@ def auth_dn_value(self):
login(servers, user)
@TestOutline(Scenario)
@Examples("invalid_value", [
("-1", Name("negative int")),
("foo", Name("string")),
("", Name("empty string")),
("36893488147419103232", Name("overflow with extremely large int value")),
("-36893488147419103232", Name("overflow with extremely large negative int value")),
("@#", Name("special characters"))
])
@Requirements(
RQ_SRS_007_LDAP_Configuration_Server_VerificationCooldown_Invalid("1.0")
)
def invalid_verification_cooldown_value(self, invalid_value, timeout=20):
"""Check that server returns an error when LDAP server
verification cooldown parameter is invalid.
"""
error_message = ("<Error> Access(user directories): Could not parse LDAP server"
" \\`openldap1\\`: Poco::Exception. Code: 1000, e.code() = 0,"
f" e.displayText() = Syntax error: Not a valid unsigned integer{': ' + invalid_value if invalid_value else invalid_value}")
with Given("LDAP server configuration that uses a negative integer for the verification_cooldown parameter"):
servers = {"openldap1": {"host": "openldap1", "port": "389", "enable_tls": "no",
"auth_dn_prefix": "cn=", "auth_dn_suffix": ",ou=users,dc=company,dc=com",
"verification_cooldown": f"{invalid_value}"
}}
with When("I try to use this configuration then it should not work"):
invalid_server_config(servers, message=error_message, tail=17, timeout=timeout)
@TestScenario
@Requirements(
RQ_SRS_007_LDAP_Configuration_Server_Syntax("1.0")
RQ_SRS_007_LDAP_Configuration_Server_Syntax("2.0")
)
def syntax(self):
"""Check that server configuration with valid syntax can be loaded.
@ -230,6 +260,7 @@ def syntax(self):
<port>636</port>
<auth_dn_prefix>cn=</auth_dn_prefix>
<auth_dn_suffix>, ou=users, dc=example, dc=com</auth_dn_suffix>
<verification_cooldown>0</verification_cooldown>
<enable_tls>yes</enable_tls>
<tls_minimum_protocol_version>tls1.2</tls_minimum_protocol_version>
<tls_require_cert>demand</tls_require_cert>
@ -248,6 +279,7 @@ def syntax(self):
"port": "389",
"auth_dn_prefix": "cn=",
"auth_dn_suffix": ",ou=users,dc=company,dc=com",
"verification_cooldown": "0",
"enable_tls": "yes",
"tls_minimum_protocol_version": "tls1.2" ,
"tls_require_cert": "demand",

View File

@ -80,20 +80,23 @@
* 4.2.3.26 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.TLSCACertDir](#rqsrs-009ldapexternaluserdirectoryconfigurationservertlscacertdir)
* 4.2.3.27 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.TLSCACertFile](#rqsrs-009ldapexternaluserdirectoryconfigurationservertlscacertfile)
* 4.2.3.28 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.TLSCipherSuite](#rqsrs-009ldapexternaluserdirectoryconfigurationservertlsciphersuite)
* 4.2.3.29 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.Syntax](#rqsrs-009ldapexternaluserdirectoryconfigurationserversyntax)
* 4.2.3.30 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.LDAPUserDirectory](#rqsrs-009ldapexternaluserdirectoryconfigurationusersldapuserdirectory)
* 4.2.3.31 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.LDAPUserDirectory.MoreThanOne](#rqsrs-009ldapexternaluserdirectoryconfigurationusersldapuserdirectorymorethanone)
* 4.2.3.32 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Syntax](#rqsrs-009ldapexternaluserdirectoryconfigurationuserssyntax)
* 4.2.3.33 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Server](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersserver)
* 4.2.3.34 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Server.Empty](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersserverempty)
* 4.2.3.35 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Server.Missing](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersservermissing)
* 4.2.3.36 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Server.MoreThanOne](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersservermorethanone)
* 4.2.3.37 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Server.Invalid](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersserverinvalid)
* 4.2.3.38 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Roles](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersroles)
* 4.2.3.39 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Roles.MoreThanOne](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersrolesmorethanone)
* 4.2.3.40 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Roles.Invalid](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersrolesinvalid)
* 4.2.3.41 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Roles.Empty](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersrolesempty)
* 4.2.3.42 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Roles.Missing](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersrolesmissing)
* 4.2.3.29 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.VerificationCooldown](#rqsrs-009ldapexternaluserdirectoryconfigurationserververificationcooldown)
* 4.2.3.30 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.VerificationCooldown.Default](#rqsrs-009ldapexternaluserdirectoryconfigurationserververificationcooldowndefault)
* 4.2.3.31 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.VerificationCooldown.Invalid](#rqsrs-009ldapexternaluserdirectoryconfigurationserververificationcooldowninvalid)
* 4.2.3.32 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.Syntax](#rqsrs-009ldapexternaluserdirectoryconfigurationserversyntax)
* 4.2.3.33 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.LDAPUserDirectory](#rqsrs-009ldapexternaluserdirectoryconfigurationusersldapuserdirectory)
* 4.2.3.34 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.LDAPUserDirectory.MoreThanOne](#rqsrs-009ldapexternaluserdirectoryconfigurationusersldapuserdirectorymorethanone)
* 4.2.3.35 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Syntax](#rqsrs-009ldapexternaluserdirectoryconfigurationuserssyntax)
* 4.2.3.36 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Server](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersserver)
* 4.2.3.37 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Server.Empty](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersserverempty)
* 4.2.3.38 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Server.Missing](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersservermissing)
* 4.2.3.39 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Server.MoreThanOne](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersservermorethanone)
* 4.2.3.40 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Server.Invalid](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersserverinvalid)
* 4.2.3.41 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Roles](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersroles)
* 4.2.3.42 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Roles.MoreThanOne](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersrolesmorethanone)
* 4.2.3.43 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Roles.Invalid](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersrolesinvalid)
* 4.2.3.44 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Roles.Empty](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersrolesempty)
* 4.2.3.45 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Roles.Missing](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersrolesmissing)
* 4.2.4 [Authentication](#authentication)
* 4.2.4.1 [RQ.SRS-009.LDAP.ExternalUserDirectory.Authentication.Username.Empty](#rqsrs-009ldapexternaluserdirectoryauthenticationusernameempty)
* 4.2.4.2 [RQ.SRS-009.LDAP.ExternalUserDirectory.Authentication.Username.Long](#rqsrs-009ldapexternaluserdirectoryauthenticationusernamelong)
@ -101,6 +104,9 @@
* 4.2.4.4 [RQ.SRS-009.LDAP.ExternalUserDirectory.Authentication.Password.Empty](#rqsrs-009ldapexternaluserdirectoryauthenticationpasswordempty)
* 4.2.4.5 [RQ.SRS-009.LDAP.ExternalUserDirectory.Authentication.Password.Long](#rqsrs-009ldapexternaluserdirectoryauthenticationpasswordlong)
* 4.2.4.6 [RQ.SRS-009.LDAP.ExternalUserDirectory.Authentication.Password.UTF8](#rqsrs-009ldapexternaluserdirectoryauthenticationpasswordutf8)
* 4.2.4.7 [RQ.SRS-009.LDAP.ExternalUserDirectory.Authentication.VerificationCooldown.Performance](#rqsrs-009ldapexternaluserdirectoryauthenticationverificationcooldownperformance)
* 4.2.4.8 [RQ.SRS-009.LDAP.ExternalUserDirectory.Authentication.VerificationCooldown.Reset.ChangeInCoreServerParameters](#rqsrs-009ldapexternaluserdirectoryauthenticationverificationcooldownresetchangeincoreserverparameters)
* 4.2.4.9 [RQ.SRS-009.LDAP.ExternalUserDirectory.Authentication.VerificationCooldown.Reset.InvalidPassword](#rqsrs-009ldapexternaluserdirectoryauthenticationverificationcooldownresetinvalidpassword)
* 5 [References](#references)
## Revision History
@ -556,9 +562,44 @@ For example,
The available suites SHALL depend on the [OpenSSL] library version and variant used to build
[ClickHouse] and therefore might change.
##### RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.Syntax
##### RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.VerificationCooldown
version: 1.0
[ClickHouse] SHALL support `verification_cooldown` parameter in the [LDAP] server configuration section
that SHALL define a period of time, in seconds, after a successful bind attempt, during which a user SHALL be assumed
to be successfully authenticated for all consecutive requests without contacting the [LDAP] server.
After period of time since the last successful attempt expires then on the authentication attempt
SHALL result in contacting the [LDAP] server to verify the username and password.
##### RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.VerificationCooldown.Default
version: 1.0
[ClickHouse] `verification_cooldown` parameter in the [LDAP] server configuration section
SHALL have a default value of `0` that disables caching and forces contacting
the [LDAP] server for each authentication request.
##### RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.VerificationCooldown.Invalid
version: 1.0
[Clickhouse] SHALL return an error if the value provided for the `verification_cooldown` parameter is not a valid positive integer.
For example:
* negative integer
* string
* empty value
* extremely large positive value (overflow)
* extremely large negative value (overflow)
The error SHALL appear in the log and SHALL be similar to the following:
```bash
<Error> Access(user directories): Could not parse LDAP server `openldap1`: Poco::Exception. Code: 1000, e.code() = 0, e.displayText() = Syntax error: Not a valid unsigned integer: *input value*
```
##### RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.Syntax
version: 2.0
[ClickHouse] SHALL support the following example syntax to create an entry for an [LDAP] server inside the `config.xml`
configuration file or of any configuration file inside the `config.d` directory.
@ -569,6 +610,7 @@ configuration file or of any configuration file inside the `config.d` directory.
<port>636</port>
<auth_dn_prefix>cn=</auth_dn_prefix>
<auth_dn_suffix>, ou=users, dc=example, dc=com</auth_dn_suffix>
<verification_cooldown>0</verification_cooldown>
<enable_tls>yes</enable_tls>
<tls_minimum_protocol_version>tls1.2</tls_minimum_protocol_version>
<tls_require_cert>demand</tls_require_cert>
@ -717,6 +759,33 @@ version: 1.0
[ClickHouse] SHALL support [UTF-8] characters in passwords
used to authenticate users when using [LDAP] external user directory.
##### RQ.SRS-009.LDAP.ExternalUserDirectory.Authentication.VerificationCooldown.Performance
version: 1.0
[ClickHouse] SHALL provide better login performance of users authenticated using [LDAP] external user directory
when `verification_cooldown` parameter is set to a positive value when comparing
to the the case when `verification_cooldown` is turned off either for a single user or multiple users
making a large number of repeated requests.
##### RQ.SRS-009.LDAP.ExternalUserDirectory.Authentication.VerificationCooldown.Reset.ChangeInCoreServerParameters
version: 1.0
[ClickHouse] SHALL reset any currently cached [LDAP] authentication bind requests enabled by the
`verification_cooldown` parameter in the [LDAP] server configuration section
if either `host`, `port`, `auth_dn_prefix`, or `auth_dn_suffix` parameter values
change in the configuration file. The reset SHALL cause any subsequent authentication attempts for any user
to result in contacting the [LDAP] server to verify user's username and password.
##### RQ.SRS-009.LDAP.ExternalUserDirectory.Authentication.VerificationCooldown.Reset.InvalidPassword
version: 1.0
[ClickHouse] SHALL reset current cached [LDAP] authentication bind request enabled by the
`verification_cooldown` parameter in the [LDAP] server configuration section
for the user if the password provided in the current authentication attempt does not match
the valid password provided during the first successful authentication request that was cached
for this exact user. The reset SHALL cause the next authentication attempt for this user
to result in contacting the [LDAP] server to verify user's username and password.
## References
* **Access Control and Account Management**: https://clickhouse.tech/docs/en/operations/access-rights/

View File

@ -1073,9 +1073,74 @@ RQ_SRS_009_LDAP_ExternalUserDirectory_Configuration_Server_TLSCipherSuite = Requ
level=4,
num='4.2.3.28')
RQ_SRS_009_LDAP_ExternalUserDirectory_Configuration_Server_VerificationCooldown = Requirement(
name='RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.VerificationCooldown',
version='1.0',
priority=None,
group=None,
type=None,
uid=None,
description=(
'[ClickHouse] SHALL support `verification_cooldown` parameter in the [LDAP] server configuration section\n'
'that SHALL define a period of time, in seconds, after a successful bind attempt, during which a user SHALL be assumed\n'
'to be successfully authenticated for all consecutive requests without contacting the [LDAP] server.\n'
'After period of time since the last successful attempt expires then on the authentication attempt\n'
'SHALL result in contacting the [LDAP] server to verify the username and password.\n'
'\n'
),
link=None,
level=4,
num='4.2.3.29')
RQ_SRS_009_LDAP_ExternalUserDirectory_Configuration_Server_VerificationCooldown_Default = Requirement(
name='RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.VerificationCooldown.Default',
version='1.0',
priority=None,
group=None,
type=None,
uid=None,
description=(
'[ClickHouse] `verification_cooldown` parameter in the [LDAP] server configuration section\n'
'SHALL have a default value of `0` that disables caching and forces contacting\n'
'the [LDAP] server for each authentication request.\n'
'\n'
),
link=None,
level=4,
num='4.2.3.30')
RQ_SRS_009_LDAP_ExternalUserDirectory_Configuration_Server_VerificationCooldown_Invalid = Requirement(
name='RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.VerificationCooldown.Invalid',
version='1.0',
priority=None,
group=None,
type=None,
uid=None,
description=(
'[Clickhouse] SHALL return an error if the value provided for the `verification_cooldown` parameter is not a valid positive integer.\n'
'\n'
'For example:\n'
'\n'
'* negative integer\n'
'* string\n'
'* empty value\n'
'* extremely large positive value (overflow)\n'
'* extremely large negative value (overflow)\n'
'\n'
'The error SHALL appear in the log and SHALL be similar to the following:\n'
'\n'
'```bash\n'
'<Error> Access(user directories): Could not parse LDAP server `openldap1`: Poco::Exception. Code: 1000, e.code() = 0, e.displayText() = Syntax error: Not a valid unsigned integer: *input value*\n'
'```\n'
'\n'
),
link=None,
level=4,
num='4.2.3.31')
RQ_SRS_009_LDAP_ExternalUserDirectory_Configuration_Server_Syntax = Requirement(
name='RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.Syntax',
version='1.0',
version='2.0',
priority=None,
group=None,
type=None,
@ -1091,6 +1156,7 @@ RQ_SRS_009_LDAP_ExternalUserDirectory_Configuration_Server_Syntax = Requirement(
' <port>636</port>\n'
' <auth_dn_prefix>cn=</auth_dn_prefix>\n'
' <auth_dn_suffix>, ou=users, dc=example, dc=com</auth_dn_suffix>\n'
' <verification_cooldown>0</verification_cooldown>\n'
' <enable_tls>yes</enable_tls>\n'
' <tls_minimum_protocol_version>tls1.2</tls_minimum_protocol_version>\n'
' <tls_require_cert>demand</tls_require_cert>\n'
@ -1106,7 +1172,7 @@ RQ_SRS_009_LDAP_ExternalUserDirectory_Configuration_Server_Syntax = Requirement(
),
link=None,
level=4,
num='4.2.3.29')
num='4.2.3.32')
RQ_SRS_009_LDAP_ExternalUserDirectory_Configuration_Users_LDAPUserDirectory = Requirement(
name='RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.LDAPUserDirectory',
@ -1122,7 +1188,7 @@ RQ_SRS_009_LDAP_ExternalUserDirectory_Configuration_Users_LDAPUserDirectory = Re
),
link=None,
level=4,
num='4.2.3.30')
num='4.2.3.33')
RQ_SRS_009_LDAP_ExternalUserDirectory_Configuration_Users_LDAPUserDirectory_MoreThanOne = Requirement(
name='RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.LDAPUserDirectory.MoreThanOne',
@ -1139,7 +1205,7 @@ RQ_SRS_009_LDAP_ExternalUserDirectory_Configuration_Users_LDAPUserDirectory_More
),
link=None,
level=4,
num='4.2.3.31')
num='4.2.3.34')
RQ_SRS_009_LDAP_ExternalUserDirectory_Configuration_Users_Syntax = Requirement(
name='RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Syntax',
@ -1168,7 +1234,7 @@ RQ_SRS_009_LDAP_ExternalUserDirectory_Configuration_Users_Syntax = Requirement(
),
link=None,
level=4,
num='4.2.3.32')
num='4.2.3.35')
RQ_SRS_009_LDAP_ExternalUserDirectory_Configuration_Users_Parameters_Server = Requirement(
name='RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Server',
@ -1185,7 +1251,7 @@ RQ_SRS_009_LDAP_ExternalUserDirectory_Configuration_Users_Parameters_Server = Re
),
link=None,
level=4,
num='4.2.3.33')
num='4.2.3.36')
RQ_SRS_009_LDAP_ExternalUserDirectory_Configuration_Users_Parameters_Server_Empty = Requirement(
name='RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Server.Empty',
@ -1201,7 +1267,7 @@ RQ_SRS_009_LDAP_ExternalUserDirectory_Configuration_Users_Parameters_Server_Empt
),
link=None,
level=4,
num='4.2.3.34')
num='4.2.3.37')
RQ_SRS_009_LDAP_ExternalUserDirectory_Configuration_Users_Parameters_Server_Missing = Requirement(
name='RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Server.Missing',
@ -1217,7 +1283,7 @@ RQ_SRS_009_LDAP_ExternalUserDirectory_Configuration_Users_Parameters_Server_Miss
),
link=None,
level=4,
num='4.2.3.35')
num='4.2.3.38')
RQ_SRS_009_LDAP_ExternalUserDirectory_Configuration_Users_Parameters_Server_MoreThanOne = Requirement(
name='RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Server.MoreThanOne',
@ -1233,7 +1299,7 @@ RQ_SRS_009_LDAP_ExternalUserDirectory_Configuration_Users_Parameters_Server_More
),
link=None,
level=4,
num='4.2.3.36')
num='4.2.3.39')
RQ_SRS_009_LDAP_ExternalUserDirectory_Configuration_Users_Parameters_Server_Invalid = Requirement(
name='RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Server.Invalid',
@ -1249,7 +1315,7 @@ RQ_SRS_009_LDAP_ExternalUserDirectory_Configuration_Users_Parameters_Server_Inva
),
link=None,
level=4,
num='4.2.3.37')
num='4.2.3.40')
RQ_SRS_009_LDAP_ExternalUserDirectory_Configuration_Users_Parameters_Roles = Requirement(
name='RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Roles',
@ -1266,7 +1332,7 @@ RQ_SRS_009_LDAP_ExternalUserDirectory_Configuration_Users_Parameters_Roles = Req
),
link=None,
level=4,
num='4.2.3.38')
num='4.2.3.41')
RQ_SRS_009_LDAP_ExternalUserDirectory_Configuration_Users_Parameters_Roles_MoreThanOne = Requirement(
name='RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Roles.MoreThanOne',
@ -1283,7 +1349,7 @@ RQ_SRS_009_LDAP_ExternalUserDirectory_Configuration_Users_Parameters_Roles_MoreT
),
link=None,
level=4,
num='4.2.3.39')
num='4.2.3.42')
RQ_SRS_009_LDAP_ExternalUserDirectory_Configuration_Users_Parameters_Roles_Invalid = Requirement(
name='RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Roles.Invalid',
@ -1299,7 +1365,7 @@ RQ_SRS_009_LDAP_ExternalUserDirectory_Configuration_Users_Parameters_Roles_Inval
),
link=None,
level=4,
num='4.2.3.40')
num='4.2.3.43')
RQ_SRS_009_LDAP_ExternalUserDirectory_Configuration_Users_Parameters_Roles_Empty = Requirement(
name='RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Roles.Empty',
@ -1316,7 +1382,7 @@ RQ_SRS_009_LDAP_ExternalUserDirectory_Configuration_Users_Parameters_Roles_Empty
),
link=None,
level=4,
num='4.2.3.41')
num='4.2.3.44')
RQ_SRS_009_LDAP_ExternalUserDirectory_Configuration_Users_Parameters_Roles_Missing = Requirement(
name='RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Roles.Missing',
@ -1333,7 +1399,7 @@ RQ_SRS_009_LDAP_ExternalUserDirectory_Configuration_Users_Parameters_Roles_Missi
),
link=None,
level=4,
num='4.2.3.42')
num='4.2.3.45')
RQ_SRS_009_LDAP_ExternalUserDirectory_Authentication_Username_Empty = Requirement(
name='RQ.SRS-009.LDAP.ExternalUserDirectory.Authentication.Username.Empty',
@ -1432,6 +1498,63 @@ RQ_SRS_009_LDAP_ExternalUserDirectory_Authentication_Password_UTF8 = Requirement
level=4,
num='4.2.4.6')
RQ_SRS_009_LDAP_ExternalUserDirectory_Authentication_VerificationCooldown_Performance = Requirement(
name='RQ.SRS-009.LDAP.ExternalUserDirectory.Authentication.VerificationCooldown.Performance',
version='1.0',
priority=None,
group=None,
type=None,
uid=None,
description=(
'[ClickHouse] SHALL provide better login performance of users authenticated using [LDAP] external user directory\n'
'when `verification_cooldown` parameter is set to a positive value when comparing\n'
'to the the case when `verification_cooldown` is turned off either for a single user or multiple users\n'
'making a large number of repeated requests.\n'
'\n'
),
link=None,
level=4,
num='4.2.4.7')
RQ_SRS_009_LDAP_ExternalUserDirectory_Authentication_VerificationCooldown_Reset_ChangeInCoreServerParameters = Requirement(
name='RQ.SRS-009.LDAP.ExternalUserDirectory.Authentication.VerificationCooldown.Reset.ChangeInCoreServerParameters',
version='1.0',
priority=None,
group=None,
type=None,
uid=None,
description=(
'[ClickHouse] SHALL reset any currently cached [LDAP] authentication bind requests enabled by the\n'
'`verification_cooldown` parameter in the [LDAP] server configuration section\n'
'if either `host`, `port`, `auth_dn_prefix`, or `auth_dn_suffix` parameter values\n'
'change in the configuration file. The reset SHALL cause any subsequent authentication attempts for any user\n'
"to result in contacting the [LDAP] server to verify user's username and password.\n"
'\n'
),
link=None,
level=4,
num='4.2.4.8')
RQ_SRS_009_LDAP_ExternalUserDirectory_Authentication_VerificationCooldown_Reset_InvalidPassword = Requirement(
name='RQ.SRS-009.LDAP.ExternalUserDirectory.Authentication.VerificationCooldown.Reset.InvalidPassword',
version='1.0',
priority=None,
group=None,
type=None,
uid=None,
description=(
'[ClickHouse] SHALL reset current cached [LDAP] authentication bind request enabled by the\n'
'`verification_cooldown` parameter in the [LDAP] server configuration section\n'
'for the user if the password provided in the current authentication attempt does not match\n'
'the valid password provided during the first successful authentication request that was cached\n'
'for this exact user. The reset SHALL cause the next authentication attempt for this user\n'
"to result in contacting the [LDAP] server to verify user's username and password.\n"
'\n'
),
link=None,
level=4,
num='4.2.4.9')
SRS_009_ClickHouse_LDAP_External_User_Directory = Specification(
name='SRS-009 ClickHouse LDAP External User Directory',
description=None,
@ -1526,20 +1649,23 @@ SRS_009_ClickHouse_LDAP_External_User_Directory = Specification(
Heading(name='RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.TLSCACertDir', level=4, num='4.2.3.26'),
Heading(name='RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.TLSCACertFile', level=4, num='4.2.3.27'),
Heading(name='RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.TLSCipherSuite', level=4, num='4.2.3.28'),
Heading(name='RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.Syntax', level=4, num='4.2.3.29'),
Heading(name='RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.LDAPUserDirectory', level=4, num='4.2.3.30'),
Heading(name='RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.LDAPUserDirectory.MoreThanOne', level=4, num='4.2.3.31'),
Heading(name='RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Syntax', level=4, num='4.2.3.32'),
Heading(name='RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Server', level=4, num='4.2.3.33'),
Heading(name='RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Server.Empty', level=4, num='4.2.3.34'),
Heading(name='RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Server.Missing', level=4, num='4.2.3.35'),
Heading(name='RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Server.MoreThanOne', level=4, num='4.2.3.36'),
Heading(name='RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Server.Invalid', level=4, num='4.2.3.37'),
Heading(name='RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Roles', level=4, num='4.2.3.38'),
Heading(name='RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Roles.MoreThanOne', level=4, num='4.2.3.39'),
Heading(name='RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Roles.Invalid', level=4, num='4.2.3.40'),
Heading(name='RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Roles.Empty', level=4, num='4.2.3.41'),
Heading(name='RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Roles.Missing', level=4, num='4.2.3.42'),
Heading(name='RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.VerificationCooldown', level=4, num='4.2.3.29'),
Heading(name='RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.VerificationCooldown.Default', level=4, num='4.2.3.30'),
Heading(name='RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.VerificationCooldown.Invalid', level=4, num='4.2.3.31'),
Heading(name='RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.Syntax', level=4, num='4.2.3.32'),
Heading(name='RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.LDAPUserDirectory', level=4, num='4.2.3.33'),
Heading(name='RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.LDAPUserDirectory.MoreThanOne', level=4, num='4.2.3.34'),
Heading(name='RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Syntax', level=4, num='4.2.3.35'),
Heading(name='RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Server', level=4, num='4.2.3.36'),
Heading(name='RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Server.Empty', level=4, num='4.2.3.37'),
Heading(name='RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Server.Missing', level=4, num='4.2.3.38'),
Heading(name='RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Server.MoreThanOne', level=4, num='4.2.3.39'),
Heading(name='RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Server.Invalid', level=4, num='4.2.3.40'),
Heading(name='RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Roles', level=4, num='4.2.3.41'),
Heading(name='RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Roles.MoreThanOne', level=4, num='4.2.3.42'),
Heading(name='RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Roles.Invalid', level=4, num='4.2.3.43'),
Heading(name='RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Roles.Empty', level=4, num='4.2.3.44'),
Heading(name='RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Roles.Missing', level=4, num='4.2.3.45'),
Heading(name='Authentication', level=3, num='4.2.4'),
Heading(name='RQ.SRS-009.LDAP.ExternalUserDirectory.Authentication.Username.Empty', level=4, num='4.2.4.1'),
Heading(name='RQ.SRS-009.LDAP.ExternalUserDirectory.Authentication.Username.Long', level=4, num='4.2.4.2'),
@ -1547,6 +1673,9 @@ SRS_009_ClickHouse_LDAP_External_User_Directory = Specification(
Heading(name='RQ.SRS-009.LDAP.ExternalUserDirectory.Authentication.Password.Empty', level=4, num='4.2.4.4'),
Heading(name='RQ.SRS-009.LDAP.ExternalUserDirectory.Authentication.Password.Long', level=4, num='4.2.4.5'),
Heading(name='RQ.SRS-009.LDAP.ExternalUserDirectory.Authentication.Password.UTF8', level=4, num='4.2.4.6'),
Heading(name='RQ.SRS-009.LDAP.ExternalUserDirectory.Authentication.VerificationCooldown.Performance', level=4, num='4.2.4.7'),
Heading(name='RQ.SRS-009.LDAP.ExternalUserDirectory.Authentication.VerificationCooldown.Reset.ChangeInCoreServerParameters', level=4, num='4.2.4.8'),
Heading(name='RQ.SRS-009.LDAP.ExternalUserDirectory.Authentication.VerificationCooldown.Reset.InvalidPassword', level=4, num='4.2.4.9'),
Heading(name='References', level=1, num='5'),
),
requirements=(
@ -1615,6 +1744,9 @@ SRS_009_ClickHouse_LDAP_External_User_Directory = Specification(
RQ_SRS_009_LDAP_ExternalUserDirectory_Configuration_Server_TLSCACertDir,
RQ_SRS_009_LDAP_ExternalUserDirectory_Configuration_Server_TLSCACertFile,
RQ_SRS_009_LDAP_ExternalUserDirectory_Configuration_Server_TLSCipherSuite,
RQ_SRS_009_LDAP_ExternalUserDirectory_Configuration_Server_VerificationCooldown,
RQ_SRS_009_LDAP_ExternalUserDirectory_Configuration_Server_VerificationCooldown_Default,
RQ_SRS_009_LDAP_ExternalUserDirectory_Configuration_Server_VerificationCooldown_Invalid,
RQ_SRS_009_LDAP_ExternalUserDirectory_Configuration_Server_Syntax,
RQ_SRS_009_LDAP_ExternalUserDirectory_Configuration_Users_LDAPUserDirectory,
RQ_SRS_009_LDAP_ExternalUserDirectory_Configuration_Users_LDAPUserDirectory_MoreThanOne,
@ -1635,6 +1767,9 @@ SRS_009_ClickHouse_LDAP_External_User_Directory = Specification(
RQ_SRS_009_LDAP_ExternalUserDirectory_Authentication_Password_Empty,
RQ_SRS_009_LDAP_ExternalUserDirectory_Authentication_Password_Long,
RQ_SRS_009_LDAP_ExternalUserDirectory_Authentication_Password_UTF8,
RQ_SRS_009_LDAP_ExternalUserDirectory_Authentication_VerificationCooldown_Performance,
RQ_SRS_009_LDAP_ExternalUserDirectory_Authentication_VerificationCooldown_Reset_ChangeInCoreServerParameters,
RQ_SRS_009_LDAP_ExternalUserDirectory_Authentication_VerificationCooldown_Reset_InvalidPassword,
),
content='''
# SRS-009 ClickHouse LDAP External User Directory
@ -1719,20 +1854,23 @@ SRS_009_ClickHouse_LDAP_External_User_Directory = Specification(
* 4.2.3.26 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.TLSCACertDir](#rqsrs-009ldapexternaluserdirectoryconfigurationservertlscacertdir)
* 4.2.3.27 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.TLSCACertFile](#rqsrs-009ldapexternaluserdirectoryconfigurationservertlscacertfile)
* 4.2.3.28 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.TLSCipherSuite](#rqsrs-009ldapexternaluserdirectoryconfigurationservertlsciphersuite)
* 4.2.3.29 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.Syntax](#rqsrs-009ldapexternaluserdirectoryconfigurationserversyntax)
* 4.2.3.30 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.LDAPUserDirectory](#rqsrs-009ldapexternaluserdirectoryconfigurationusersldapuserdirectory)
* 4.2.3.31 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.LDAPUserDirectory.MoreThanOne](#rqsrs-009ldapexternaluserdirectoryconfigurationusersldapuserdirectorymorethanone)
* 4.2.3.32 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Syntax](#rqsrs-009ldapexternaluserdirectoryconfigurationuserssyntax)
* 4.2.3.33 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Server](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersserver)
* 4.2.3.34 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Server.Empty](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersserverempty)
* 4.2.3.35 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Server.Missing](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersservermissing)
* 4.2.3.36 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Server.MoreThanOne](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersservermorethanone)
* 4.2.3.37 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Server.Invalid](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersserverinvalid)
* 4.2.3.38 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Roles](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersroles)
* 4.2.3.39 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Roles.MoreThanOne](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersrolesmorethanone)
* 4.2.3.40 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Roles.Invalid](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersrolesinvalid)
* 4.2.3.41 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Roles.Empty](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersrolesempty)
* 4.2.3.42 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Roles.Missing](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersrolesmissing)
* 4.2.3.29 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.VerificationCooldown](#rqsrs-009ldapexternaluserdirectoryconfigurationserververificationcooldown)
* 4.2.3.30 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.VerificationCooldown.Default](#rqsrs-009ldapexternaluserdirectoryconfigurationserververificationcooldowndefault)
* 4.2.3.31 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.VerificationCooldown.Invalid](#rqsrs-009ldapexternaluserdirectoryconfigurationserververificationcooldowninvalid)
* 4.2.3.32 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.Syntax](#rqsrs-009ldapexternaluserdirectoryconfigurationserversyntax)
* 4.2.3.33 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.LDAPUserDirectory](#rqsrs-009ldapexternaluserdirectoryconfigurationusersldapuserdirectory)
* 4.2.3.34 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.LDAPUserDirectory.MoreThanOne](#rqsrs-009ldapexternaluserdirectoryconfigurationusersldapuserdirectorymorethanone)
* 4.2.3.35 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Syntax](#rqsrs-009ldapexternaluserdirectoryconfigurationuserssyntax)
* 4.2.3.36 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Server](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersserver)
* 4.2.3.37 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Server.Empty](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersserverempty)
* 4.2.3.38 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Server.Missing](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersservermissing)
* 4.2.3.39 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Server.MoreThanOne](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersservermorethanone)
* 4.2.3.40 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Server.Invalid](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersserverinvalid)
* 4.2.3.41 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Roles](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersroles)
* 4.2.3.42 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Roles.MoreThanOne](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersrolesmorethanone)
* 4.2.3.43 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Roles.Invalid](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersrolesinvalid)
* 4.2.3.44 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Roles.Empty](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersrolesempty)
* 4.2.3.45 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Roles.Missing](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersrolesmissing)
* 4.2.4 [Authentication](#authentication)
* 4.2.4.1 [RQ.SRS-009.LDAP.ExternalUserDirectory.Authentication.Username.Empty](#rqsrs-009ldapexternaluserdirectoryauthenticationusernameempty)
* 4.2.4.2 [RQ.SRS-009.LDAP.ExternalUserDirectory.Authentication.Username.Long](#rqsrs-009ldapexternaluserdirectoryauthenticationusernamelong)
@ -1740,6 +1878,9 @@ SRS_009_ClickHouse_LDAP_External_User_Directory = Specification(
* 4.2.4.4 [RQ.SRS-009.LDAP.ExternalUserDirectory.Authentication.Password.Empty](#rqsrs-009ldapexternaluserdirectoryauthenticationpasswordempty)
* 4.2.4.5 [RQ.SRS-009.LDAP.ExternalUserDirectory.Authentication.Password.Long](#rqsrs-009ldapexternaluserdirectoryauthenticationpasswordlong)
* 4.2.4.6 [RQ.SRS-009.LDAP.ExternalUserDirectory.Authentication.Password.UTF8](#rqsrs-009ldapexternaluserdirectoryauthenticationpasswordutf8)
* 4.2.4.7 [RQ.SRS-009.LDAP.ExternalUserDirectory.Authentication.VerificationCooldown.Performance](#rqsrs-009ldapexternaluserdirectoryauthenticationverificationcooldownperformance)
* 4.2.4.8 [RQ.SRS-009.LDAP.ExternalUserDirectory.Authentication.VerificationCooldown.Reset.ChangeInCoreServerParameters](#rqsrs-009ldapexternaluserdirectoryauthenticationverificationcooldownresetchangeincoreserverparameters)
* 4.2.4.9 [RQ.SRS-009.LDAP.ExternalUserDirectory.Authentication.VerificationCooldown.Reset.InvalidPassword](#rqsrs-009ldapexternaluserdirectoryauthenticationverificationcooldownresetinvalidpassword)
* 5 [References](#references)
## Revision History
@ -2195,9 +2336,44 @@ For example,
The available suites SHALL depend on the [OpenSSL] library version and variant used to build
[ClickHouse] and therefore might change.
##### RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.Syntax
##### RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.VerificationCooldown
version: 1.0
[ClickHouse] SHALL support `verification_cooldown` parameter in the [LDAP] server configuration section
that SHALL define a period of time, in seconds, after a successful bind attempt, during which a user SHALL be assumed
to be successfully authenticated for all consecutive requests without contacting the [LDAP] server.
After period of time since the last successful attempt expires then on the authentication attempt
SHALL result in contacting the [LDAP] server to verify the username and password.
##### RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.VerificationCooldown.Default
version: 1.0
[ClickHouse] `verification_cooldown` parameter in the [LDAP] server configuration section
SHALL have a default value of `0` that disables caching and forces contacting
the [LDAP] server for each authentication request.
##### RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.VerificationCooldown.Invalid
version: 1.0
[Clickhouse] SHALL return an error if the value provided for the `verification_cooldown` parameter is not a valid positive integer.
For example:
* negative integer
* string
* empty value
* extremely large positive value (overflow)
* extremely large negative value (overflow)
The error SHALL appear in the log and SHALL be similar to the following:
```bash
<Error> Access(user directories): Could not parse LDAP server `openldap1`: Poco::Exception. Code: 1000, e.code() = 0, e.displayText() = Syntax error: Not a valid unsigned integer: *input value*
```
##### RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.Syntax
version: 2.0
[ClickHouse] SHALL support the following example syntax to create an entry for an [LDAP] server inside the `config.xml`
configuration file or of any configuration file inside the `config.d` directory.
@ -2208,6 +2384,7 @@ configuration file or of any configuration file inside the `config.d` directory.
<port>636</port>
<auth_dn_prefix>cn=</auth_dn_prefix>
<auth_dn_suffix>, ou=users, dc=example, dc=com</auth_dn_suffix>
<verification_cooldown>0</verification_cooldown>
<enable_tls>yes</enable_tls>
<tls_minimum_protocol_version>tls1.2</tls_minimum_protocol_version>
<tls_require_cert>demand</tls_require_cert>
@ -2356,6 +2533,33 @@ version: 1.0
[ClickHouse] SHALL support [UTF-8] characters in passwords
used to authenticate users when using [LDAP] external user directory.
##### RQ.SRS-009.LDAP.ExternalUserDirectory.Authentication.VerificationCooldown.Performance
version: 1.0
[ClickHouse] SHALL provide better login performance of users authenticated using [LDAP] external user directory
when `verification_cooldown` parameter is set to a positive value when comparing
to the the case when `verification_cooldown` is turned off either for a single user or multiple users
making a large number of repeated requests.
##### RQ.SRS-009.LDAP.ExternalUserDirectory.Authentication.VerificationCooldown.Reset.ChangeInCoreServerParameters
version: 1.0
[ClickHouse] SHALL reset any currently cached [LDAP] authentication bind requests enabled by the
`verification_cooldown` parameter in the [LDAP] server configuration section
if either `host`, `port`, `auth_dn_prefix`, or `auth_dn_suffix` parameter values
change in the configuration file. The reset SHALL cause any subsequent authentication attempts for any user
to result in contacting the [LDAP] server to verify user's username and password.
##### RQ.SRS-009.LDAP.ExternalUserDirectory.Authentication.VerificationCooldown.Reset.InvalidPassword
version: 1.0
[ClickHouse] SHALL reset current cached [LDAP] authentication bind request enabled by the
`verification_cooldown` parameter in the [LDAP] server configuration section
for the user if the password provided in the current authentication attempt does not match
the valid password provided during the first successful authentication request that was cached
for this exact user. The reset SHALL cause the next authentication attempt for this user
to result in contacting the [LDAP] server to verify user's username and password.
## References
* **Access Control and Account Management**: https://clickhouse.tech/docs/en/operations/access-rights/

View File

@ -698,6 +698,460 @@ def empty_username_and_empty_password(self, server=None):
"""
login_and_execute_query(username="", password="")
@TestScenario
@Tags("verification_cooldown")
@Requirements(
RQ_SRS_009_LDAP_ExternalUserDirectory_Configuration_Server_VerificationCooldown_Default("1.0")
)
def default_verification_cooldown_value(self, server, rbac=False, timeout=20):
"""Check that the default value (0) for the verification cooldown parameter
disables caching and forces contacting the LDAP server for each
authentication request.
"""
error_message = "DB::Exception: testVCD: Authentication failed: password is incorrect or there is no user with such name"
error_exitcode = 4
user = None
with Given("I have an LDAP configuration that uses the default verification_cooldown value (0)"):
servers = {"openldap1": {"host": "openldap1", "port": "389", "enable_tls": "no",
"auth_dn_prefix": "cn=", "auth_dn_suffix": ",ou=users,dc=company,dc=com"
}}
self.context.ldap_node = self.context.cluster.node(server)
try:
with Given("I add user to LDAP"):
user = {"cn": "testVCD", "userpassword": "testVCD"}
user = add_user_to_ldap(**user)
with ldap_servers(servers):
with rbac_roles("ldap_role") as roles:
with ldap_external_user_directory(server=server, roles=roles, restart=True):
with When("I login and execute a query"):
login_and_execute_query(username=user["cn"], password=user["userpassword"])
with And("I change user password in LDAP"):
change_user_password_in_ldap(user, "newpassword")
with Then("when I try to login immediately with the old user password it should fail"):
login_and_execute_query(username=user["cn"], password=user["userpassword"],
exitcode=error_exitcode, message=error_message)
finally:
with Finally("I make sure LDAP user is deleted"):
if user is not None:
delete_user_from_ldap(user, exitcode=None)
@TestScenario
@Tags("verification_cooldown")
@Requirements(
RQ_SRS_009_LDAP_ExternalUserDirectory_Configuration_Server_VerificationCooldown("1.0")
)
def valid_verification_cooldown_value_cn_change(self, server, rbac=False, timeout=20):
"""Check that we can perform requests without contacting the LDAP server
after successful authentication when the verification_cooldown parameter
is set and the user cn is changed.
"""
error_message = "DB::Exception: testVCD: Authentication failed: password is incorrect or there is no user with such name"
error_exitcode = 4
user = None
new_user = None
with Given("I have an LDAP configuration that sets verification_cooldown parameter to 2 sec"):
servers = { "openldap1": {
"host": "openldap1",
"port": "389",
"enable_tls": "no",
"auth_dn_prefix": "cn=",
"auth_dn_suffix": ",ou=users,dc=company,dc=com",
"verification_cooldown": "2"
}}
self.context.ldap_node = self.context.cluster.node(server)
try:
with Given("I add user to LDAP"):
user = {"cn": "testVCD", "userpassword": "testVCD"}
user = add_user_to_ldap(**user)
with ldap_servers(servers):
with rbac_roles("ldap_role") as roles:
with ldap_external_user_directory(server=server, roles=roles, restart=True):
with When("I login and execute a query"):
login_and_execute_query(username=user["cn"], password=user["userpassword"])
with And("I change user cn in LDAP"):
new_user = change_user_cn_in_ldap(user, "testVCD2")
with Then("when I try to login again with the old user cn it should work"):
login_and_execute_query(username=user["cn"], password=user["userpassword"])
with And("when I sleep for 2 seconds and try to log in, it should fail"):
time.sleep(2)
login_and_execute_query(username=user["cn"], password=user["userpassword"],
exitcode=error_exitcode, message=error_message)
finally:
with Finally("I make sure LDAP user is deleted"):
if new_user is not None:
delete_user_from_ldap(new_user, exitcode=None)
@TestScenario
@Tags("verification_cooldown")
@Requirements(
RQ_SRS_009_LDAP_ExternalUserDirectory_Configuration_Server_VerificationCooldown("1.0")
)
def valid_verification_cooldown_value_password_change(self, server, rbac=False, timeout=20):
"""Check that we can perform requests without contacting the LDAP server
after successful authentication when the verification_cooldown parameter
is set and the user password is changed.
"""
error_message = "DB::Exception: testVCD: Authentication failed: password is incorrect or there is no user with such name"
error_exitcode = 4
user = None
with Given("I have an LDAP configuration that sets verification_cooldown parameter to 2 sec"):
servers = { "openldap1": {
"host": "openldap1",
"port": "389",
"enable_tls": "no",
"auth_dn_prefix": "cn=",
"auth_dn_suffix": ",ou=users,dc=company,dc=com",
"verification_cooldown": "2"
}}
self.context.ldap_node = self.context.cluster.node(server)
try:
with Given("I add user to LDAP"):
user = {"cn": "testVCD", "userpassword": "testVCD"}
user = add_user_to_ldap(**user)
with ldap_servers(servers):
with rbac_roles("ldap_role") as roles:
with ldap_external_user_directory(server=server, roles=roles, restart=True):
with When("I login and execute a query"):
login_and_execute_query(username=user["cn"], password=user["userpassword"])
with And("I change user password in LDAP"):
change_user_password_in_ldap(user, "newpassword")
with Then("when I try to login again with the old password it should work"):
login_and_execute_query(username=user["cn"], password=user["userpassword"])
with And("when I sleep for 2 seconds and try to log in, it should fail"):
time.sleep(2)
login_and_execute_query(username=user["cn"], password=user["userpassword"],
exitcode=error_exitcode, message=error_message)
finally:
with Finally("I make sure LDAP user is deleted"):
if user is not None:
delete_user_from_ldap(user, exitcode=None)
@TestScenario
@Tags("verification_cooldown")
@Requirements(
RQ_SRS_009_LDAP_ExternalUserDirectory_Configuration_Server_VerificationCooldown("1.0")
)
def valid_verification_cooldown_value_ldap_unavailable(self, server, rbac=False, timeout=20):
"""Check that we can perform requests without contacting the LDAP server
after successful authentication when the verification_cooldown parameter
is set, even when the LDAP server is offline.
"""
error_message = "DB::Exception: testVCD: Authentication failed: password is incorrect or there is no user with such name"
error_exitcode = 4
user = None
with Given("I have an LDAP configuration that sets verification_cooldown parameter to 2 sec"):
servers = { "openldap1": {
"host": "openldap1",
"port": "389",
"enable_tls": "no",
"auth_dn_prefix": "cn=",
"auth_dn_suffix": ",ou=users,dc=company,dc=com",
"verification_cooldown": "2"
}}
self.context.ldap_node = self.context.cluster.node(server)
try:
with Given("I add a new user to LDAP"):
user = {"cn": "testVCD", "userpassword": "testVCD"}
user = add_user_to_ldap(**user)
with ldap_servers(servers):
with rbac_roles("ldap_role") as roles:
with ldap_external_user_directory(server=server, roles=roles, restart=True):
with When("I login and execute a query"):
login_and_execute_query(username=user["cn"], password=user["userpassword"])
try:
with And("then I stop the ldap server"):
self.context.ldap_node.stop()
with Then("when I try to login again with the server offline it should work"):
login_and_execute_query(username=user["cn"], password=user["userpassword"])
with And("when I sleep for 2 seconds and try to log in, it should fail"):
time.sleep(2)
login_and_execute_query(username=user["cn"], password=user["userpassword"],
exitcode=error_exitcode, message=error_message)
finally:
with Finally("I start the ldap server back up"):
self.context.ldap_node.start()
finally:
with Finally("I make sure LDAP user is deleted"):
if user is not None:
delete_user_from_ldap(user, exitcode=None)
@TestOutline
def repeat_requests(self, server, iterations, vcd_value, rbac=False):
"""Run repeated requests from some user to the LDAP server.
"""
user = None
with Given(f"I have an LDAP configuration that sets verification_cooldown parameter to {vcd_value} sec"):
servers = { "openldap1": {
"host": "openldap1",
"port": "389",
"enable_tls": "no",
"auth_dn_prefix": "cn=",
"auth_dn_suffix": ",ou=users,dc=company,dc=com",
"verification_cooldown": vcd_value
}}
self.context.ldap_node = self.context.cluster.node(server)
try:
with And("I add a new user to LDAP"):
user = {"cn": "testVCD", "userpassword": "testVCD"}
user = add_user_to_ldap(**user)
with ldap_servers(servers):
with rbac_roles("ldap_role") as roles:
with ldap_external_user_directory(server=server, roles=roles, restart=True):
with When(f"I login and execute some query {iterations} times"):
start_time = time.time()
r = self.context.node.command(f"time for i in {{1..{iterations}}}; do clickhouse client -q \"SELECT 1\" --user {user['cn']} --password {user['userpassword']} > /dev/null; done")
end_time = time.time()
return end_time - start_time
finally:
with Finally("I make sure LDAP user is deleted"):
if user is not None:
delete_user_from_ldap(user, exitcode=None)
@TestScenario
@Tags("verification_cooldown")
@Requirements(
RQ_SRS_009_LDAP_ExternalUserDirectory_Authentication_VerificationCooldown_Performance("1.0")
)
def verification_cooldown_performance(self, server, rbac=False, iterations=5000):
"""Check that login performance is better when the verification cooldown
parameter is set to a positive value when comparing to the case when
the verification cooldown parameter is turned off.
"""
vcd_time = 0
no_vcd_time = 0
with Example(f"Repeated requests with verification cooldown parameter set to 600 seconds, {iterations} iterations"):
vcd_time = repeat_requests(server=server, iterations=iterations, vcd_value="600", rbac=rbac)
metric("login_with_vcd_value_600", units="seconds", value=vcd_time)
with Example(f"Repeated requests with verification cooldown parameter set to 0 seconds, {iterations} iterations"):
no_vcd_time = repeat_requests(server=server, iterations=iterations, vcd_value="0", rbac=rbac)
metric("login_with_vcd_value_0", units="seconds", value=no_vcd_time)
with Then("The performance with verification cooldown parameter set is better than the performance with no verification cooldown parameter."):
assert no_vcd_time > vcd_time, error()
with And("Log the performance improvement as a percentage."):
metric("percentage_improvement", units="%", value=100*(no_vcd_time - vcd_time)/vcd_time)
@TestOutline
def check_verification_cooldown_reset_on_core_server_parameter_change(self, server,
parameter_name, parameter_value, rbac=False):
"""Check that the LDAP login cache is reset for all the LDAP authentication users
when verification_cooldown parameter is set after one of the core server
parameters is changed in the LDAP server configuration.
"""
config_d_dir="/etc/clickhouse-server/config.d"
config_file="ldap_servers.xml"
error_message = "DB::Exception: {user}: Authentication failed: password is incorrect or there is no user with such name"
error_exitcode = 4
user = None
config=None
updated_config=None
with Given("I have an LDAP configuration that sets verification_cooldown parameter to 600 sec"):
servers = { "openldap1": {
"host": "openldap1",
"port": "389",
"enable_tls": "no",
"auth_dn_prefix": "cn=",
"auth_dn_suffix": ",ou=users,dc=company,dc=com",
"verification_cooldown": "600"
}}
self.context.ldap_node = self.context.cluster.node(server)
with And("LDAP authenticated user"):
users = [
{"cn": f"testVCD_0", "userpassword": "testVCD_0"},
{"cn": f"testVCD_1", "userpassword": "testVCD_1"}
]
with And("I create LDAP servers configuration file"):
config = create_ldap_servers_config_content(servers, config_d_dir, config_file)
with ldap_users(*users) as users:
with ldap_servers(servers=None, restart=False, config=config):
with rbac_roles("ldap_role") as roles:
with ldap_external_user_directory(server=server, roles=roles, restart=True):
with When("I login and execute a query"):
for user in users:
with By(f"as user {user['cn']}"):
login_and_execute_query(username=user["cn"], password=user["userpassword"])
with And("I change user password in LDAP"):
for user in users:
with By(f"for user {user['cn']}"):
change_user_password_in_ldap(user, "newpassword")
with And(f"I change the server {parameter_name} core parameter", description=f"{parameter_value}"):
servers["openldap1"][parameter_name] = parameter_value
with And("I create an updated the config file that has a different server host name"):
updated_config = create_ldap_servers_config_content(servers, config_d_dir, config_file)
with modify_config(updated_config, restart=False):
with Then("when I try to log in it should fail as cache should have been reset"):
for user in users:
with By(f"as user {user['cn']}"):
login_and_execute_query(username=user["cn"], password=user["userpassword"],
exitcode=error_exitcode, message=error_message.format(user=user["cn"]))
@TestScenario
@Tags("verification_cooldown")
@Requirements(
RQ_SRS_009_LDAP_ExternalUserDirectory_Authentication_VerificationCooldown_Reset_ChangeInCoreServerParameters("1.0")
)
def verification_cooldown_reset_on_server_host_parameter_change(self, server, rbac=False):
"""Check that the LDAP login cache is reset for all the LDAP authentication users
when verification_cooldown parameter is set after server host name
is changed in the LDAP server configuration.
"""
check_verification_cooldown_reset_on_core_server_parameter_change(server=server,
parameter_name="host", parameter_value="openldap2", rbac=rbac)
@TestScenario
@Tags("verification_cooldown")
@Requirements(
RQ_SRS_009_LDAP_ExternalUserDirectory_Authentication_VerificationCooldown_Reset_ChangeInCoreServerParameters("1.0")
)
def verification_cooldown_reset_on_server_port_parameter_change(self, server, rbac=False):
"""Check that the LDAP login cache is reset for all the LDAP authentication users
when verification_cooldown parameter is set after server port is changed in the
LDAP server configuration.
"""
check_verification_cooldown_reset_on_core_server_parameter_change(server=server,
parameter_name="port", parameter_value="9006", rbac=rbac)
@TestScenario
@Tags("verification_cooldown")
@Requirements(
RQ_SRS_009_LDAP_ExternalUserDirectory_Authentication_VerificationCooldown_Reset_ChangeInCoreServerParameters("1.0")
)
def verification_cooldown_reset_on_server_auth_dn_prefix_parameter_change(self, server, rbac=False):
"""Check that the LDAP login cache is reset for all the LDAP authentication users
when verification_cooldown parameter is set after server auth_dn_prefix
is changed in the LDAP server configuration.
"""
check_verification_cooldown_reset_on_core_server_parameter_change(server=server,
parameter_name="auth_dn_prefix", parameter_value="cxx=", rbac=rbac)
@TestScenario
@Tags("verification_cooldown")
@Requirements(
RQ_SRS_009_LDAP_ExternalUserDirectory_Authentication_VerificationCooldown_Reset_ChangeInCoreServerParameters("1.0")
)
def verification_cooldown_reset_on_server_auth_dn_suffix_parameter_change(self, server, rbac=False):
"""Check that the LDAP login cache is reset for all the LDAP authentication users
when verification_cooldown parameter is set after server auth_dn_suffix
is changed in the LDAP server configuration.
"""
check_verification_cooldown_reset_on_core_server_parameter_change(server=server,
parameter_name="auth_dn_suffix",
parameter_value=",ou=company,dc=users,dc=com", rbac=rbac)
@TestScenario
@Name("verification cooldown reset when invalid password is provided")
@Tags("verification_cooldown")
@Requirements(
RQ_SRS_009_LDAP_ExternalUserDirectory_Authentication_VerificationCooldown_Reset_InvalidPassword("1.0")
)
def scenario(self, server, rbac=False):
"""Check that cached bind requests for the user are discarded when
the user provides invalid login credentials.
"""
user = None
error_exitcode = 4
error_message = "DB::Exception: testVCD: Authentication failed: password is incorrect or there is no user with such name"
with Given("I have an LDAP configuration that sets verification_cooldown parameter to 600 sec"):
servers = { "openldap1": {
"host": "openldap1",
"port": "389",
"enable_tls": "no",
"auth_dn_prefix": "cn=",
"auth_dn_suffix": ",ou=users,dc=company,dc=com",
"verification_cooldown": "600"
}}
self.context.ldap_node = self.context.cluster.node(server)
try:
with Given("I add a new user to LDAP"):
user = {"cn": "testVCD", "userpassword": "testVCD"}
user = add_user_to_ldap(**user)
with ldap_servers(servers):
with rbac_roles("ldap_role") as roles:
with ldap_external_user_directory(server=server, roles=roles, restart=True):
with When("I login and execute a query"):
login_and_execute_query(username=user["cn"], password=user["userpassword"])
with And("I change user password in LDAP"):
change_user_password_in_ldap(user, "newpassword")
with Then("When I try to log in with the cached password it should work"):
login_and_execute_query(username=user["cn"], password=user["userpassword"])
with And("When I try to log in with an incorrect password it should fail"):
login_and_execute_query(username=user["cn"], password="incorrect", exitcode=error_exitcode,
message=error_message)
with And("When I try to log in with the cached password it should fail"):
login_and_execute_query(username=user["cn"], password="incorrect", exitcode=error_exitcode,
message=error_message)
finally:
with Finally("I make sure LDAP user is deleted"):
if user is not None:
delete_user_from_ldap(user, exitcode=None)
@TestScenario
@Requirements(
RQ_SRS_009_LDAP_ExternalUserDirectory_Users_Lookup_Priority("2.0")
@ -734,7 +1188,6 @@ def user_lookup_priority(self, server):
with When("I try to login as 'ldap' user defined only in LDAP it should work"):
login_and_execute_query(**users["ldap"])
@TestOutline(Feature)
@Name("user authentications")
@Requirements(
@ -755,8 +1208,11 @@ def feature(self, servers=None, server=None, node="clickhouse1"):
with ldap_servers(servers):
with rbac_roles("ldap_role") as roles:
with ldap_external_user_directory(server=server, roles=roles, restart=True):
for scenario in loads(current_module(), Scenario, filter=~has.tag("custom config")):
Scenario(test=scenario, flags=TE)(server=server)
for scenario in loads(current_module(), Scenario, filter=~has.tag("custom config") & ~has.tag("verification_cooldown")):
Scenario(test=scenario)(server=server)
for scenario in loads(current_module(), Scenario, filter=has.tag("custom config")):
Scenario(test=scenario, flags=TE)(server=server)
Scenario(test=scenario)(server=server)
for scenario in loads(current_module(), Scenario, filter=has.tag("verification_cooldown")):
Scenario(test=scenario)(server=server)

View File

@ -5,10 +5,11 @@ from contextlib import contextmanager
import testflows.settings as settings
from testflows.core import *
from testflows.asserts import error
from ldap.authentication.tests.common import getuid, Config, ldap_servers, add_config, restart
from ldap.authentication.tests.common import getuid, Config, ldap_servers, add_config, modify_config, restart
from ldap.authentication.tests.common import xmltree, xml_indent, xml_append, xml_with_utf8
from ldap.authentication.tests.common import ldap_user, ldap_users, add_user_to_ldap, delete_user_from_ldap
from ldap.authentication.tests.common import change_user_password_in_ldap, change_user_cn_in_ldap
from ldap.authentication.tests.common import create_ldap_servers_config_content
from ldap.authentication.tests.common import randomword
def join(tasks, timeout):

View File

@ -99,7 +99,6 @@ def invalid_port(self):
}]
login(servers, "openldap1", *users)
@TestScenario
@Requirements(
RQ_SRS_009_LDAP_ExternalUserDirectory_Configuration_Server_Invalid("1.0"),
@ -232,9 +231,39 @@ def auth_dn_value(self):
login(servers, "openldap1", user)
@TestOutline(Scenario)
@Examples("invalid_value", [
("-1", Name("negative int")),
("foo", Name("string")),
("", Name("empty string")),
("36893488147419103232", Name("overflow with extremely large int value")),
("-36893488147419103232", Name("overflow with extremely large negative int value")),
("@#", Name("special characters"))
])
@Requirements(
RQ_SRS_009_LDAP_ExternalUserDirectory_Configuration_Server_VerificationCooldown_Invalid("1.0")
)
def invalid_verification_cooldown_value(self, invalid_value, timeout=20):
"""Check that server returns an error when LDAP server
verification cooldown parameter is invalid.
"""
error_message = ("<Error> Access(user directories): Could not parse LDAP server"
" \\`openldap1\\`: Poco::Exception. Code: 1000, e.code() = 0,"
f" e.displayText() = Syntax error: Not a valid unsigned integer{': ' + invalid_value if invalid_value else invalid_value}")
with Given("LDAP server configuration that uses a negative integer for the verification_cooldown parameter"):
servers = {"openldap1": {"host": "openldap1", "port": "389", "enable_tls": "no",
"auth_dn_prefix": "cn=", "auth_dn_suffix": ",ou=users,dc=company,dc=com",
"verification_cooldown": f"{invalid_value}"
}}
with When("I try to use this configuration then it should not work"):
invalid_server_config(servers, message=error_message, tail=17, timeout=timeout)
@TestScenario
@Requirements(
RQ_SRS_009_LDAP_ExternalUserDirectory_Configuration_Server_Syntax("1.0")
RQ_SRS_009_LDAP_ExternalUserDirectory_Configuration_Server_Syntax("2.0")
)
def syntax(self):
"""Check that server configuration with valid syntax can be loaded.
@ -245,6 +274,7 @@ def syntax(self):
<port>636</port>
<auth_dn_prefix>cn=</auth_dn_prefix>
<auth_dn_suffix>, ou=users, dc=example, dc=com</auth_dn_suffix>
<verification_cooldown>0</verification_cooldown>
<enable_tls>yes</enable_tls>
<tls_minimum_protocol_version>tls1.2</tls_minimum_protocol_version>
<tls_require_cert>demand</tls_require_cert>
@ -263,6 +293,7 @@ def syntax(self):
"port": "389",
"auth_dn_prefix": "cn=",
"auth_dn_suffix": ",ou=users,dc=company,dc=com",
"verification_cooldown": "0",
"enable_tls": "yes",
"tls_minimum_protocol_version": "tls1.2" ,
"tls_require_cert": "demand",