From f2a6696362f2249ed607a65f2d48289f67aeb51b Mon Sep 17 00:00:00 2001 From: Denis Glazachev Date: Thu, 15 Oct 2020 02:39:31 +0400 Subject: [PATCH 01/16] Implement verification_cooldown LDAP server connection param --- programs/server/config.xml | 4 ++++ src/Access/Authentication.cpp | 31 ++++++++++++++++++++++++++- src/Access/Authentication.h | 5 +++++ src/Access/ExternalAuthenticators.cpp | 4 ++++ src/Access/LDAPParams.h | 18 ++++++++++++++++ 5 files changed, 61 insertions(+), 1 deletion(-) diff --git a/programs/server/config.xml b/programs/server/config.xml index 5bdec5377fd..f295221452a 100644 --- a/programs/server/config.xml +++ b/programs/server/config.xml @@ -222,6 +222,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). @@ -241,6 +244,7 @@ 636 uid= ,ou=users,dc=example,dc=com + 300 yes tls1.2 demand diff --git a/src/Access/Authentication.cpp b/src/Access/Authentication.cpp index d29e2f897e8..7476e6eb2ea 100644 --- a/src/Access/Authentication.cpp +++ b/src/Access/Authentication.cpp @@ -86,8 +86,37 @@ bool Authentication::isCorrectPassword(const String & password_, const String & ldap_server_params.user = user_; ldap_server_params.password = password_; + const auto current_params_hash = ldap_server_params.getCoreHash(); + const auto last_check_period = std::chrono::steady_clock::now() - last_successful_password_check_timestamp; + + if ( + // Forbid the initial values explicitly. + last_successful_password_check_params_hash != 0 && + last_successful_password_check_timestamp != std::chrono::steady_clock::time_point{} && + + // Check if we can "reuse" the result of the previous successful password verification. + current_params_hash == last_successful_password_check_params_hash && + last_check_period <= ldap_server_params.verification_cooldown + ) + { + return true; + } + LDAPSimpleAuthClient ldap_client(ldap_server_params); - return ldap_client.check(); + const auto result = ldap_client.check(); + + if (result) + { + last_successful_password_check_params_hash = current_params_hash; + last_successful_password_check_timestamp = std::chrono::steady_clock::now(); + } + else + { + last_successful_password_check_params_hash = 0; + last_successful_password_check_timestamp = std::chrono::steady_clock::time_point{}; + } + + return result; } case MAX_TYPE: diff --git a/src/Access/Authentication.h b/src/Access/Authentication.h index 38714339221..8584c9f7325 100644 --- a/src/Access/Authentication.h +++ b/src/Access/Authentication.h @@ -6,6 +6,7 @@ #include #include #include +#include namespace DB @@ -103,7 +104,11 @@ private: Type type = Type::NO_PASSWORD; Digest password_hash; + + // Used and maintained only for LDAP. String server_name; + mutable std::size_t last_successful_password_check_params_hash = 0; + mutable std::chrono::steady_clock::time_point last_successful_password_check_timestamp; }; diff --git a/src/Access/ExternalAuthenticators.cpp b/src/Access/ExternalAuthenticators.cpp index 3ed1b21c3c2..9b908841576 100644 --- a/src/Access/ExternalAuthenticators.cpp +++ b/src/Access/ExternalAuthenticators.cpp @@ -29,6 +29,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 +53,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"); diff --git a/src/Access/LDAPParams.h b/src/Access/LDAPParams.h index eeadba6bc01..28dcc5fe50f 100644 --- a/src/Access/LDAPParams.h +++ b/src/Access/LDAPParams.h @@ -2,6 +2,8 @@ #include +#include + #include @@ -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; + } }; } From 1eb7c31011ed25738da65fc169816dd5720e721a Mon Sep 17 00:00:00 2001 From: Denis Glazachev Date: Thu, 15 Oct 2020 03:05:33 +0400 Subject: [PATCH 02/16] Add "verification_cooldown enabled" check --- src/Access/Authentication.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Access/Authentication.cpp b/src/Access/Authentication.cpp index 7476e6eb2ea..b8328057161 100644 --- a/src/Access/Authentication.cpp +++ b/src/Access/Authentication.cpp @@ -94,8 +94,12 @@ bool Authentication::isCorrectPassword(const String & password_, const String & last_successful_password_check_params_hash != 0 && last_successful_password_check_timestamp != std::chrono::steady_clock::time_point{} && + // Check if the caching is enabled at all. + ldap_server_params.verification_cooldown > std::chrono::seconds{0} && + // Check if we can "reuse" the result of the previous successful password verification. current_params_hash == last_successful_password_check_params_hash && + last_check_period >= std::chrono::seconds{0} && last_check_period <= ldap_server_params.verification_cooldown ) { From 92840dd7171bace7f41f57578a84c5c68e2e20f2 Mon Sep 17 00:00:00 2001 From: Denis Glazachev Date: Tue, 20 Oct 2020 15:36:22 +0400 Subject: [PATCH 03/16] Trigger CI From 1567a3976b9ca7893c8e730f8f6db8de3057c519 Mon Sep 17 00:00:00 2001 From: Vitaliy Zakaznikov Date: Mon, 26 Oct 2020 09:28:46 -0400 Subject: [PATCH 04/16] Adding requirements related to the LDAP login cache feature. Updated syntax tests. Linked specifications to ldap/authentication and ldap/external_user_directory features. --- docker/test/testflows/runner/Dockerfile | 2 +- .../ldap/authentication/regression.py | 5 +- .../requirements/requirements.md | 83 +- .../requirements/requirements.py | 958 +++++++++++++++--- .../authentication/tests/server_config.py | 4 +- .../external_user_directory/regression.py | 5 +- .../requirements/requirements.md | 80 +- .../requirements/requirements.py | 893 +++++++++++++++- .../tests/server_config.py | 4 +- 9 files changed, 1859 insertions(+), 175 deletions(-) diff --git a/docker/test/testflows/runner/Dockerfile b/docker/test/testflows/runner/Dockerfile index 9565e39598c..06241d6d497 100644 --- a/docker/test/testflows/runner/Dockerfile +++ b/docker/test/testflows/runner/Dockerfile @@ -35,7 +35,7 @@ RUN apt-get update \ ENV TZ=Europe/Moscow RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone -RUN pip3 install urllib3 testflows==1.6.57 docker-compose docker dicttoxml kazoo tzlocal +RUN pip3 install urllib3 testflows==1.6.59 docker-compose docker dicttoxml kazoo tzlocal ENV DOCKER_CHANNEL stable ENV DOCKER_VERSION 17.09.1-ce diff --git a/tests/testflows/ldap/authentication/regression.py b/tests/testflows/ldap/authentication/regression.py index 9d0a5ca743f..97b63dddd33 100755 --- a/tests/testflows/ldap/authentication/regression.py +++ b/tests/testflows/ldap/authentication/regression.py @@ -29,9 +29,8 @@ xfails = { @TestFeature @Name("authentication") @ArgumentParser(argparser) -@Requirements( - RQ_SRS_007_LDAP_Authentication("1.0") -) +@Specifications(SRS_007_ClickHouse_Authentication_of_Users_via_LDAP) +@Requirements(RQ_SRS_007_LDAP_Authentication("1.0")) @XFails(xfails) def regression(self, local, clickhouse_binary_path): """ClickHouse integration with LDAP regression module. diff --git a/tests/testflows/ldap/authentication/requirements/requirements.md b/tests/testflows/ldap/authentication/requirements/requirements.md index d322db70330..683dae4ead4 100644 --- a/tests/testflows/ldap/authentication/requirements/requirements.md +++ b/tests/testflows/ldap/authentication/requirements/requirements.md @@ -57,22 +57,27 @@ * 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.Syntax](#rqsrs-007ldapconfigurationserversyntax) + * 4.2.31 [RQ.SRS-007.LDAP.Configuration.User.RBAC](#rqsrs-007ldapconfigurationuserrbac) + * 4.2.32 [RQ.SRS-007.LDAP.Configuration.User.Syntax](#rqsrs-007ldapconfigurationusersyntax) + * 4.2.33 [RQ.SRS-007.LDAP.Configuration.User.Name.Empty](#rqsrs-007ldapconfigurationusernameempty) + * 4.2.34 [RQ.SRS-007.LDAP.Configuration.User.BothPasswordAndLDAP](#rqsrs-007ldapconfigurationuserbothpasswordandldap) + * 4.2.35 [RQ.SRS-007.LDAP.Configuration.User.LDAP.InvalidServerName.NotDefined](#rqsrs-007ldapconfigurationuserldapinvalidservernamenotdefined) + * 4.2.36 [RQ.SRS-007.LDAP.Configuration.User.LDAP.InvalidServerName.Empty](#rqsrs-007ldapconfigurationuserldapinvalidservernameempty) + * 4.2.37 [RQ.SRS-007.LDAP.Configuration.User.OnlyOneServer](#rqsrs-007ldapconfigurationuseronlyoneserver) + * 4.2.38 [RQ.SRS-007.LDAP.Configuration.User.Name.Long](#rqsrs-007ldapconfigurationusernamelong) + * 4.2.39 [RQ.SRS-007.LDAP.Configuration.User.Name.UTF8](#rqsrs-007ldapconfigurationusernameutf8) + * 4.2.40 [RQ.SRS-007.LDAP.Authentication.Username.Empty](#rqsrs-007ldapauthenticationusernameempty) + * 4.2.41 [RQ.SRS-007.LDAP.Authentication.Username.Long](#rqsrs-007ldapauthenticationusernamelong) + * 4.2.42 [RQ.SRS-007.LDAP.Authentication.Username.UTF8](#rqsrs-007ldapauthenticationusernameutf8) + * 4.2.43 [RQ.SRS-007.LDAP.Authentication.Password.Empty](#rqsrs-007ldapauthenticationpasswordempty) + * 4.2.44 [RQ.SRS-007.LDAP.Authentication.Password.Long](#rqsrs-007ldapauthenticationpasswordlong) + * 4.2.45 [RQ.SRS-007.LDAP.Authentication.Password.UTF8](#rqsrs-007ldapauthenticationpasswordutf8) + * 4.2.46 [RQ.SRS-007.LDAP.Authentication.VerificationCooldown.Performance](#rqsrs-007ldapauthenticationverificationcooldownperformance) + * 4.2.47 [RQ.SRS-007.LDAP.Authentication.VerificationCooldown.Reset.ChangeInCoreServerParameters](#rqsrs-007ldapauthenticationverificationcooldownresetchangeincoreserverparameters) + * 4.2.48 [RQ.SRS-007.LDAP.Authentication.VerificationCooldown.Reset.InvalidPassword](#rqsrs-007ldapauthenticationverificationcooldownresetinvalidpassword) * 5 [References](#references) ## Revision History @@ -393,9 +398,25 @@ 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.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 +427,7 @@ configuration file or of any configuration file inside the `config.d` directory. 636 cn= , ou=users, dc=example, dc=com + 0 yes tls1.2 demand @@ -521,6 +543,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 diff --git a/tests/testflows/ldap/authentication/requirements/requirements.py b/tests/testflows/ldap/authentication/requirements/requirements.py index 967e097d1fa..afdc497e75c 100644 --- a/tests/testflows/ldap/authentication/requirements/requirements.py +++ b/tests/testflows/ldap/authentication/requirements/requirements.py @@ -1,10 +1,620 @@ # These requirements were auto generated # from software requirements specification (SRS) -# document by TestFlows v1.6.200811.1124123. +# document by TestFlows v1.6.201021.1163815. # Do not edit by hand but re-generate instead # using 'tfs requirements generate' command. +from testflows.core import Specification from testflows.core import Requirement +SRS_007_ClickHouse_Authentication_of_Users_via_LDAP = Specification( + name='SRS-007 ClickHouse Authentication of Users via LDAP', + description=None, + author=None, + date=None, + status=None, + approved_by=None, + approved_date=None, + approved_version=None, + version=None, + group=None, + type=None, + link=None, + uid=None, + parent=None, + children=None, + content=''' +# SRS-007 ClickHouse Authentication of Users via LDAP + +## Table of Contents + +* 1 [Revision History](#revision-history) +* 2 [Introduction](#introduction) +* 3 [Terminology](#terminology) +* 4 [Requirements](#requirements) + * 4.1 [Generic](#generic) + * 4.1.1 [RQ.SRS-007.LDAP.Authentication](#rqsrs-007ldapauthentication) + * 4.1.2 [RQ.SRS-007.LDAP.Authentication.MultipleServers](#rqsrs-007ldapauthenticationmultipleservers) + * 4.1.3 [RQ.SRS-007.LDAP.Authentication.Protocol.PlainText](#rqsrs-007ldapauthenticationprotocolplaintext) + * 4.1.4 [RQ.SRS-007.LDAP.Authentication.Protocol.TLS](#rqsrs-007ldapauthenticationprotocoltls) + * 4.1.5 [RQ.SRS-007.LDAP.Authentication.Protocol.StartTLS](#rqsrs-007ldapauthenticationprotocolstarttls) + * 4.1.6 [RQ.SRS-007.LDAP.Authentication.TLS.Certificate.Validation](#rqsrs-007ldapauthenticationtlscertificatevalidation) + * 4.1.7 [RQ.SRS-007.LDAP.Authentication.TLS.Certificate.SelfSigned](#rqsrs-007ldapauthenticationtlscertificateselfsigned) + * 4.1.8 [RQ.SRS-007.LDAP.Authentication.TLS.Certificate.SpecificCertificationAuthority](#rqsrs-007ldapauthenticationtlscertificatespecificcertificationauthority) + * 4.1.9 [RQ.SRS-007.LDAP.Server.Configuration.Invalid](#rqsrs-007ldapserverconfigurationinvalid) + * 4.1.10 [RQ.SRS-007.LDAP.User.Configuration.Invalid](#rqsrs-007ldapuserconfigurationinvalid) + * 4.1.11 [RQ.SRS-007.LDAP.Authentication.Mechanism.Anonymous](#rqsrs-007ldapauthenticationmechanismanonymous) + * 4.1.12 [RQ.SRS-007.LDAP.Authentication.Mechanism.Unauthenticated](#rqsrs-007ldapauthenticationmechanismunauthenticated) + * 4.1.13 [RQ.SRS-007.LDAP.Authentication.Mechanism.NamePassword](#rqsrs-007ldapauthenticationmechanismnamepassword) + * 4.1.14 [RQ.SRS-007.LDAP.Authentication.Valid](#rqsrs-007ldapauthenticationvalid) + * 4.1.15 [RQ.SRS-007.LDAP.Authentication.Invalid](#rqsrs-007ldapauthenticationinvalid) + * 4.1.16 [RQ.SRS-007.LDAP.Authentication.Invalid.DeletedUser](#rqsrs-007ldapauthenticationinvaliddeleteduser) + * 4.1.17 [RQ.SRS-007.LDAP.Authentication.UsernameChanged](#rqsrs-007ldapauthenticationusernamechanged) + * 4.1.18 [RQ.SRS-007.LDAP.Authentication.PasswordChanged](#rqsrs-007ldapauthenticationpasswordchanged) + * 4.1.19 [RQ.SRS-007.LDAP.Authentication.LDAPServerRestart](#rqsrs-007ldapauthenticationldapserverrestart) + * 4.1.20 [RQ.SRS-007.LDAP.Authentication.ClickHouseServerRestart](#rqsrs-007ldapauthenticationclickhouseserverrestart) + * 4.1.21 [RQ.SRS-007.LDAP.Authentication.Parallel](#rqsrs-007ldapauthenticationparallel) + * 4.1.22 [RQ.SRS-007.LDAP.Authentication.Parallel.ValidAndInvalid](#rqsrs-007ldapauthenticationparallelvalidandinvalid) + * 4.2 [Specific](#specific) + * 4.2.1 [RQ.SRS-007.LDAP.UnreachableServer](#rqsrs-007ldapunreachableserver) + * 4.2.2 [RQ.SRS-007.LDAP.Configuration.Server.Name](#rqsrs-007ldapconfigurationservername) + * 4.2.3 [RQ.SRS-007.LDAP.Configuration.Server.Host](#rqsrs-007ldapconfigurationserverhost) + * 4.2.4 [RQ.SRS-007.LDAP.Configuration.Server.Port](#rqsrs-007ldapconfigurationserverport) + * 4.2.5 [RQ.SRS-007.LDAP.Configuration.Server.Port.Default](#rqsrs-007ldapconfigurationserverportdefault) + * 4.2.6 [RQ.SRS-007.LDAP.Configuration.Server.AuthDN.Prefix](#rqsrs-007ldapconfigurationserverauthdnprefix) + * 4.2.7 [RQ.SRS-007.LDAP.Configuration.Server.AuthDN.Suffix](#rqsrs-007ldapconfigurationserverauthdnsuffix) + * 4.2.8 [RQ.SRS-007.LDAP.Configuration.Server.AuthDN.Value](#rqsrs-007ldapconfigurationserverauthdnvalue) + * 4.2.9 [RQ.SRS-007.LDAP.Configuration.Server.EnableTLS](#rqsrs-007ldapconfigurationserverenabletls) + * 4.2.10 [RQ.SRS-007.LDAP.Configuration.Server.EnableTLS.Options.Default](#rqsrs-007ldapconfigurationserverenabletlsoptionsdefault) + * 4.2.11 [RQ.SRS-007.LDAP.Configuration.Server.EnableTLS.Options.No](#rqsrs-007ldapconfigurationserverenabletlsoptionsno) + * 4.2.12 [RQ.SRS-007.LDAP.Configuration.Server.EnableTLS.Options.Yes](#rqsrs-007ldapconfigurationserverenabletlsoptionsyes) + * 4.2.13 [RQ.SRS-007.LDAP.Configuration.Server.EnableTLS.Options.StartTLS](#rqsrs-007ldapconfigurationserverenabletlsoptionsstarttls) + * 4.2.14 [RQ.SRS-007.LDAP.Configuration.Server.TLSMinimumProtocolVersion](#rqsrs-007ldapconfigurationservertlsminimumprotocolversion) + * 4.2.15 [RQ.SRS-007.LDAP.Configuration.Server.TLSMinimumProtocolVersion.Values](#rqsrs-007ldapconfigurationservertlsminimumprotocolversionvalues) + * 4.2.16 [RQ.SRS-007.LDAP.Configuration.Server.TLSMinimumProtocolVersion.Default](#rqsrs-007ldapconfigurationservertlsminimumprotocolversiondefault) + * 4.2.17 [RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert](#rqsrs-007ldapconfigurationservertlsrequirecert) + * 4.2.18 [RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert.Options.Default](#rqsrs-007ldapconfigurationservertlsrequirecertoptionsdefault) + * 4.2.19 [RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert.Options.Demand](#rqsrs-007ldapconfigurationservertlsrequirecertoptionsdemand) + * 4.2.20 [RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert.Options.Allow](#rqsrs-007ldapconfigurationservertlsrequirecertoptionsallow) + * 4.2.21 [RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert.Options.Try](#rqsrs-007ldapconfigurationservertlsrequirecertoptionstry) + * 4.2.22 [RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert.Options.Never](#rqsrs-007ldapconfigurationservertlsrequirecertoptionsnever) + * 4.2.23 [RQ.SRS-007.LDAP.Configuration.Server.TLSCertFile](#rqsrs-007ldapconfigurationservertlscertfile) + * 4.2.24 [RQ.SRS-007.LDAP.Configuration.Server.TLSKeyFile](#rqsrs-007ldapconfigurationservertlskeyfile) + * 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.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.Syntax](#rqsrs-007ldapconfigurationserversyntax) + * 4.2.31 [RQ.SRS-007.LDAP.Configuration.User.RBAC](#rqsrs-007ldapconfigurationuserrbac) + * 4.2.32 [RQ.SRS-007.LDAP.Configuration.User.Syntax](#rqsrs-007ldapconfigurationusersyntax) + * 4.2.33 [RQ.SRS-007.LDAP.Configuration.User.Name.Empty](#rqsrs-007ldapconfigurationusernameempty) + * 4.2.34 [RQ.SRS-007.LDAP.Configuration.User.BothPasswordAndLDAP](#rqsrs-007ldapconfigurationuserbothpasswordandldap) + * 4.2.35 [RQ.SRS-007.LDAP.Configuration.User.LDAP.InvalidServerName.NotDefined](#rqsrs-007ldapconfigurationuserldapinvalidservernamenotdefined) + * 4.2.36 [RQ.SRS-007.LDAP.Configuration.User.LDAP.InvalidServerName.Empty](#rqsrs-007ldapconfigurationuserldapinvalidservernameempty) + * 4.2.37 [RQ.SRS-007.LDAP.Configuration.User.OnlyOneServer](#rqsrs-007ldapconfigurationuseronlyoneserver) + * 4.2.38 [RQ.SRS-007.LDAP.Configuration.User.Name.Long](#rqsrs-007ldapconfigurationusernamelong) + * 4.2.39 [RQ.SRS-007.LDAP.Configuration.User.Name.UTF8](#rqsrs-007ldapconfigurationusernameutf8) + * 4.2.40 [RQ.SRS-007.LDAP.Authentication.Username.Empty](#rqsrs-007ldapauthenticationusernameempty) + * 4.2.41 [RQ.SRS-007.LDAP.Authentication.Username.Long](#rqsrs-007ldapauthenticationusernamelong) + * 4.2.42 [RQ.SRS-007.LDAP.Authentication.Username.UTF8](#rqsrs-007ldapauthenticationusernameutf8) + * 4.2.43 [RQ.SRS-007.LDAP.Authentication.Password.Empty](#rqsrs-007ldapauthenticationpasswordempty) + * 4.2.44 [RQ.SRS-007.LDAP.Authentication.Password.Long](#rqsrs-007ldapauthenticationpasswordlong) + * 4.2.45 [RQ.SRS-007.LDAP.Authentication.Password.UTF8](#rqsrs-007ldapauthenticationpasswordutf8) + * 4.2.46 [RQ.SRS-007.LDAP.Authentication.VerificationCooldown.Performance](#rqsrs-007ldapauthenticationverificationcooldownperformance) + * 4.2.47 [RQ.SRS-007.LDAP.Authentication.VerificationCooldown.Reset.ChangeInCoreServerParameters](#rqsrs-007ldapauthenticationverificationcooldownresetchangeincoreserverparameters) + * 4.2.48 [RQ.SRS-007.LDAP.Authentication.VerificationCooldown.Reset.InvalidPassword](#rqsrs-007ldapauthenticationverificationcooldownresetinvalidpassword) +* 5 [References](#references) + +## Revision History + +This document is stored in an electronic form using [Git] source control management software +hosted in a [GitHub Repository]. +All the updates are tracked using the [Git]'s [Revision History]. + +## Introduction + +[ClickHouse] currently does not have any integration with [LDAP]. +As the initial step in integrating with [LDAP] this software requirements specification covers +only the requirements to enable authentication of users using an [LDAP] server. + +## Terminology + +* **CA** - + Certificate Authority ([CA]) + +* **LDAP** - + Lightweight Directory Access Protocol ([LDAP]) + +## Requirements + +### Generic + +#### RQ.SRS-007.LDAP.Authentication +version: 1.0 + +[ClickHouse] SHALL support user authentication via an [LDAP] server. + +#### RQ.SRS-007.LDAP.Authentication.MultipleServers +version: 1.0 + +[ClickHouse] SHALL support specifying multiple [LDAP] servers that can be used to authenticate +users. + +#### RQ.SRS-007.LDAP.Authentication.Protocol.PlainText +version: 1.0 + +[ClickHouse] SHALL support user authentication using plain text `ldap://` non secure protocol. + +#### RQ.SRS-007.LDAP.Authentication.Protocol.TLS +version: 1.0 + +[ClickHouse] SHALL support user authentication using `SSL/TLS` `ldaps://` secure protocol. + +#### RQ.SRS-007.LDAP.Authentication.Protocol.StartTLS +version: 1.0 + +[ClickHouse] SHALL support user authentication using legacy `StartTLS` protocol which is a +plain text `ldap://` protocol that is upgraded to [TLS]. + +#### RQ.SRS-007.LDAP.Authentication.TLS.Certificate.Validation +version: 1.0 + +[ClickHouse] SHALL support certificate validation used for [TLS] connections. + +#### RQ.SRS-007.LDAP.Authentication.TLS.Certificate.SelfSigned +version: 1.0 + +[ClickHouse] SHALL support self-signed certificates for [TLS] connections. + +#### RQ.SRS-007.LDAP.Authentication.TLS.Certificate.SpecificCertificationAuthority +version: 1.0 + +[ClickHouse] SHALL support certificates signed by specific Certification Authority for [TLS] connections. + +#### RQ.SRS-007.LDAP.Server.Configuration.Invalid +version: 1.0 + +[ClickHouse] SHALL return an error and prohibit user login if [LDAP] server configuration is not valid. + +#### RQ.SRS-007.LDAP.User.Configuration.Invalid +version: 1.0 + +[ClickHouse] SHALL return an error and prohibit user login if user configuration is not valid. + +#### RQ.SRS-007.LDAP.Authentication.Mechanism.Anonymous +version: 1.0 + +[ClickHouse] SHALL return an error and prohibit authentication using [Anonymous Authentication Mechanism of Simple Bind] +authentication mechanism. + +#### RQ.SRS-007.LDAP.Authentication.Mechanism.Unauthenticated +version: 1.0 + +[ClickHouse] SHALL return an error and prohibit authentication using [Unauthenticated Authentication Mechanism of Simple Bind] +authentication mechanism. + +#### RQ.SRS-007.LDAP.Authentication.Mechanism.NamePassword +version: 1.0 + +[ClickHouse] SHALL allow authentication using only [Name/Password Authentication Mechanism of Simple Bind] +authentication mechanism. + +#### RQ.SRS-007.LDAP.Authentication.Valid +version: 1.0 + +[ClickHouse] SHALL only allow user authentication using [LDAP] server if and only if +user name and password match [LDAP] server records for the user. + +#### RQ.SRS-007.LDAP.Authentication.Invalid +version: 1.0 + +[ClickHouse] SHALL return an error and prohibit authentication if either user name or password +do not match [LDAP] server records for the user. + +#### RQ.SRS-007.LDAP.Authentication.Invalid.DeletedUser +version: 1.0 + +[ClickHouse] SHALL return an error and prohibit authentication if the user +has been deleted from the [LDAP] server. + +#### RQ.SRS-007.LDAP.Authentication.UsernameChanged +version: 1.0 + +[ClickHouse] SHALL return an error and prohibit authentication if the username is changed +on the [LDAP] server. + +#### RQ.SRS-007.LDAP.Authentication.PasswordChanged +version: 1.0 + +[ClickHouse] SHALL return an error and prohibit authentication if the password +for the user is changed on the [LDAP] server. + +#### RQ.SRS-007.LDAP.Authentication.LDAPServerRestart +version: 1.0 + +[ClickHouse] SHALL support authenticating users after [LDAP] server is restarted. + +#### RQ.SRS-007.LDAP.Authentication.ClickHouseServerRestart +version: 1.0 + +[ClickHouse] SHALL support authenticating users after server is restarted. + +#### RQ.SRS-007.LDAP.Authentication.Parallel +version: 1.0 + +[ClickHouse] SHALL support parallel authentication of users using [LDAP] server. + +#### RQ.SRS-007.LDAP.Authentication.Parallel.ValidAndInvalid +version: 1.0 + +[ClickHouse] SHALL support authentication of valid users and +prohibit authentication of invalid users using [LDAP] server +in parallel without having invalid attempts affecting valid authentications. + +### Specific + +#### RQ.SRS-007.LDAP.UnreachableServer +version: 1.0 + +[ClickHouse] SHALL return an error and prohibit user login if [LDAP] server is unreachable. + +#### RQ.SRS-007.LDAP.Configuration.Server.Name +version: 1.0 + +[ClickHouse] SHALL not support empty string as a server name. + +#### RQ.SRS-007.LDAP.Configuration.Server.Host +version: 1.0 + +[ClickHouse] SHALL support `` parameter to specify [LDAP] +server hostname or IP, this parameter SHALL be mandatory and SHALL not be empty. + +#### RQ.SRS-007.LDAP.Configuration.Server.Port +version: 1.0 + +[ClickHouse] SHALL support `` parameter to specify [LDAP] server port. + +#### RQ.SRS-007.LDAP.Configuration.Server.Port.Default +version: 1.0 + +[ClickHouse] SHALL use default port number `636` if `enable_tls` is set to `yes` or `389` otherwise. + +#### RQ.SRS-007.LDAP.Configuration.Server.AuthDN.Prefix +version: 1.0 + +[ClickHouse] SHALL support `` parameter to specify the prefix +of value used to construct the DN to bound to during authentication via [LDAP] server. + +#### RQ.SRS-007.LDAP.Configuration.Server.AuthDN.Suffix +version: 1.0 + +[ClickHouse] SHALL support `` parameter to specify the suffix +of value used to construct the DN to bound to during authentication via [LDAP] server. + +#### RQ.SRS-007.LDAP.Configuration.Server.AuthDN.Value +version: 1.0 + +[ClickHouse] SHALL construct DN as `auth_dn_prefix + escape(user_name) + auth_dn_suffix` string. + +> This implies that auth_dn_suffix should usually have comma ',' as its first non-space character. + +#### RQ.SRS-007.LDAP.Configuration.Server.EnableTLS +version: 1.0 + +[ClickHouse] SHALL support `` parameter to trigger the use of secure connection to the [LDAP] server. + +#### RQ.SRS-007.LDAP.Configuration.Server.EnableTLS.Options.Default +version: 1.0 + +[ClickHouse] SHALL use `yes` value as the default for `` parameter +to enable SSL/TLS `ldaps://` protocol. + +#### RQ.SRS-007.LDAP.Configuration.Server.EnableTLS.Options.No +version: 1.0 + +[ClickHouse] SHALL support specifying `no` as the value of `` parameter to enable +plain text `ldap://` protocol. + +#### RQ.SRS-007.LDAP.Configuration.Server.EnableTLS.Options.Yes +version: 1.0 + +[ClickHouse] SHALL support specifying `yes` as the value of `` parameter to enable +SSL/TLS `ldaps://` protocol. + +#### RQ.SRS-007.LDAP.Configuration.Server.EnableTLS.Options.StartTLS +version: 1.0 + +[ClickHouse] SHALL support specifying `starttls` as the value of `` parameter to enable +legacy `StartTLS` protocol that used plain text `ldap://` protocol, upgraded to [TLS]. + +#### RQ.SRS-007.LDAP.Configuration.Server.TLSMinimumProtocolVersion +version: 1.0 + +[ClickHouse] SHALL support `` parameter to specify +the minimum protocol version of SSL/TLS. + +#### RQ.SRS-007.LDAP.Configuration.Server.TLSMinimumProtocolVersion.Values +version: 1.0 + +[ClickHouse] SHALL support specifying `ssl2`, `ssl3`, `tls1.0`, `tls1.1`, and `tls1.2` +as a value of the `` parameter. + +#### RQ.SRS-007.LDAP.Configuration.Server.TLSMinimumProtocolVersion.Default +version: 1.0 + +[ClickHouse] SHALL set `tls1.2` as the default value of the `` parameter. + +#### RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert +version: 1.0 + +[ClickHouse] SHALL support `` parameter to specify [TLS] peer +certificate verification behavior. + +#### RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert.Options.Default +version: 1.0 + +[ClickHouse] SHALL use `demand` value as the default for the `` parameter. + +#### RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert.Options.Demand +version: 1.0 + +[ClickHouse] SHALL support specifying `demand` as the value of `` parameter to +enable requesting of client certificate. If no certificate is provided, or a bad certificate is +provided, the session SHALL be immediately terminated. + +#### RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert.Options.Allow +version: 1.0 + +[ClickHouse] SHALL support specifying `allow` as the value of `` parameter to +enable requesting of client certificate. If no +certificate is provided, the session SHALL proceed normally. +If a bad certificate is provided, it SHALL be ignored and the session SHALL proceed normally. + +#### RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert.Options.Try +version: 1.0 + +[ClickHouse] SHALL support specifying `try` as the value of `` parameter to +enable requesting of client certificate. If no certificate is provided, the session +SHALL proceed normally. If a bad certificate is provided, the session SHALL be +immediately terminated. + +#### RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert.Options.Never +version: 1.0 + +[ClickHouse] SHALL support specifying `never` as the value of `` parameter to +disable requesting of client certificate. + +#### RQ.SRS-007.LDAP.Configuration.Server.TLSCertFile +version: 1.0 + +[ClickHouse] SHALL support `` to specify the path to certificate file used by +[ClickHouse] to establish connection with the [LDAP] server. + +#### RQ.SRS-007.LDAP.Configuration.Server.TLSKeyFile +version: 1.0 + +[ClickHouse] SHALL support `` to specify the path to key file for the certificate +specified by the `` parameter. + +#### RQ.SRS-007.LDAP.Configuration.Server.TLSCACertDir +version: 1.0 + +[ClickHouse] SHALL support `` parameter to specify to a path to +the directory containing [CA] certificates used to verify certificates provided by the [LDAP] server. + +#### RQ.SRS-007.LDAP.Configuration.Server.TLSCACertFile +version: 1.0 + +[ClickHouse] SHALL support `` parameter to specify a path to a specific +[CA] certificate file used to verify certificates provided by the [LDAP] server. + +#### RQ.SRS-007.LDAP.Configuration.Server.TLSCipherSuite +version: 1.0 + +[ClickHouse] SHALL support `tls_cipher_suite` parameter to specify allowed cipher suites. +The value SHALL use the same format as the `ciphersuites` in the [OpenSSL Ciphers]. + +For example, + +```xml +ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:AES256-GCM-SHA384 +``` + +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.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.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. + +```xml + + + localhost + 636 + cn= + , ou=users, dc=example, dc=com + 0 + yes + tls1.2 + demand + /path/to/tls_cert_file + /path/to/tls_key_file + /path/to/tls_ca_cert_file + /path/to/tls_ca_cert_dir + ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:AES256-GCM-SHA384 + + +``` + +#### RQ.SRS-007.LDAP.Configuration.User.RBAC +version: 1.0 + +[ClickHouse] SHALL support creating users identified using an [LDAP] server using +the following RBAC command + +```sql +CREATE USER name IDENTIFIED WITH ldap_server BY 'server_name' +``` + +#### RQ.SRS-007.LDAP.Configuration.User.Syntax +version: 1.0 + +[ClickHouse] SHALL support the following example syntax to create a user that is authenticated using +an [LDAP] server inside the `users.xml` file or any configuration file inside the `users.d` directory. + +```xml + + + + + my_ldap_server + + + + +``` + +#### RQ.SRS-007.LDAP.Configuration.User.Name.Empty +version: 1.0 + +[ClickHouse] SHALL not support empty string as a user name. + +#### RQ.SRS-007.LDAP.Configuration.User.BothPasswordAndLDAP +version: 1.0 + +[ClickHouse] SHALL throw an error if `` is specified for the user and at the same +time user configuration contains any of the `` entries. + +#### RQ.SRS-007.LDAP.Configuration.User.LDAP.InvalidServerName.NotDefined +version: 1.0 + +[ClickHouse] SHALL throw an error during any authentification attempt +if the name of the [LDAP] server used inside the `` entry +is not defined in the `` section. + +#### RQ.SRS-007.LDAP.Configuration.User.LDAP.InvalidServerName.Empty +version: 1.0 + +[ClickHouse] SHALL throw an error during any authentification attempt +if the name of the [LDAP] server used inside the `` entry +is empty. + +#### RQ.SRS-007.LDAP.Configuration.User.OnlyOneServer +version: 1.0 + +[ClickHouse] SHALL support specifying only one [LDAP] server for a given user. + +#### RQ.SRS-007.LDAP.Configuration.User.Name.Long +version: 1.0 + +[ClickHouse] SHALL support long user names of at least 256 bytes +to specify users that can be authenticated using an [LDAP] server. + +#### RQ.SRS-007.LDAP.Configuration.User.Name.UTF8 +version: 1.0 + +[ClickHouse] SHALL support user names that contain [UTF-8] characters. + +#### RQ.SRS-007.LDAP.Authentication.Username.Empty +version: 1.0 + +[ClickHouse] SHALL not support authenticating users with empty username. + +#### RQ.SRS-007.LDAP.Authentication.Username.Long +version: 1.0 + +[ClickHouse] SHALL support authenticating users with a long username of at least 256 bytes. + +#### RQ.SRS-007.LDAP.Authentication.Username.UTF8 +version: 1.0 + +[ClickHouse] SHALL support authentication users with a username that contains [UTF-8] characters. + +#### RQ.SRS-007.LDAP.Authentication.Password.Empty +version: 1.0 + +[ClickHouse] SHALL not support authenticating users with empty passwords +even if an empty password is valid for the user and +is allowed by the [LDAP] server. + +#### RQ.SRS-007.LDAP.Authentication.Password.Long +version: 1.0 + +[ClickHouse] SHALL support long password of at least 256 bytes +that can be used to authenticate users using an [LDAP] server. + +#### RQ.SRS-007.LDAP.Authentication.Password.UTF8 +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 + +[Anonymous Authentication Mechanism of Simple Bind]: https://ldapwiki.com/wiki/Simple%20Authentication#section-Simple+Authentication-AnonymousAuthenticationMechanismOfSimpleBind +[Unauthenticated Authentication Mechanism of Simple Bind]: https://ldapwiki.com/wiki/Simple%20Authentication#section-Simple+Authentication-UnauthenticatedAuthenticationMechanismOfSimpleBind +[Name/Password Authentication Mechanism of Simple Bind]: https://ldapwiki.com/wiki/Simple%20Authentication#section-Simple+Authentication-NamePasswordAuthenticationMechanismOfSimpleBind +[UTF-8]: https://en.wikipedia.org/wiki/UTF-8 +[OpenSSL]: https://www.openssl.org/ +[OpenSSL Ciphers]: https://www.openssl.org/docs/manmaster/man1/openssl-ciphers.html +[CA]: https://en.wikipedia.org/wiki/Certificate_authority +[TLS]: https://en.wikipedia.org/wiki/Transport_Layer_Security +[LDAP]: https://en.wikipedia.org/wiki/Lightweight_Directory_Access_Protocol +[ClickHouse]: https://clickhouse.tech +[GitHub]: https://github.com +[GitHub Repository]: https://github.com/ClickHouse/ClickHouse/blob/master/tests/testflows/ldap/authentication/requirements/requirements.md +[Revision History]: https://github.com/ClickHouse/ClickHouse/commits/master/tests/testflows/ldap/authentication/requirements/requirements.md +[Git]: https://git-scm.com/ +''') + RQ_SRS_007_LDAP_Authentication = Requirement( name='RQ.SRS-007.LDAP.Authentication', version='1.0', @@ -14,9 +624,9 @@ RQ_SRS_007_LDAP_Authentication = Requirement( uid=None, description=( '[ClickHouse] SHALL support user authentication via an [LDAP] server.\n' + '\n' ), - link=None - ) + link=None) RQ_SRS_007_LDAP_Authentication_MultipleServers = Requirement( name='RQ.SRS-007.LDAP.Authentication.MultipleServers', @@ -28,9 +638,9 @@ RQ_SRS_007_LDAP_Authentication_MultipleServers = Requirement( description=( '[ClickHouse] SHALL support specifying multiple [LDAP] servers that can be used to authenticate\n' 'users.\n' + '\n' ), - link=None - ) + link=None) RQ_SRS_007_LDAP_Authentication_Protocol_PlainText = Requirement( name='RQ.SRS-007.LDAP.Authentication.Protocol.PlainText', @@ -41,9 +651,9 @@ RQ_SRS_007_LDAP_Authentication_Protocol_PlainText = Requirement( uid=None, description=( '[ClickHouse] SHALL support user authentication using plain text `ldap://` non secure protocol.\n' + '\n' ), - link=None - ) + link=None) RQ_SRS_007_LDAP_Authentication_Protocol_TLS = Requirement( name='RQ.SRS-007.LDAP.Authentication.Protocol.TLS', @@ -54,9 +664,9 @@ RQ_SRS_007_LDAP_Authentication_Protocol_TLS = Requirement( uid=None, description=( '[ClickHouse] SHALL support user authentication using `SSL/TLS` `ldaps://` secure protocol.\n' + '\n' ), - link=None - ) + link=None) RQ_SRS_007_LDAP_Authentication_Protocol_StartTLS = Requirement( name='RQ.SRS-007.LDAP.Authentication.Protocol.StartTLS', @@ -68,9 +678,9 @@ RQ_SRS_007_LDAP_Authentication_Protocol_StartTLS = Requirement( description=( '[ClickHouse] SHALL support user authentication using legacy `StartTLS` protocol which is a\n' 'plain text `ldap://` protocol that is upgraded to [TLS].\n' + '\n' ), - link=None - ) + link=None) RQ_SRS_007_LDAP_Authentication_TLS_Certificate_Validation = Requirement( name='RQ.SRS-007.LDAP.Authentication.TLS.Certificate.Validation', @@ -81,9 +691,9 @@ RQ_SRS_007_LDAP_Authentication_TLS_Certificate_Validation = Requirement( uid=None, description=( '[ClickHouse] SHALL support certificate validation used for [TLS] connections.\n' + '\n' ), - link=None - ) + link=None) RQ_SRS_007_LDAP_Authentication_TLS_Certificate_SelfSigned = Requirement( name='RQ.SRS-007.LDAP.Authentication.TLS.Certificate.SelfSigned', @@ -94,9 +704,9 @@ RQ_SRS_007_LDAP_Authentication_TLS_Certificate_SelfSigned = Requirement( uid=None, description=( '[ClickHouse] SHALL support self-signed certificates for [TLS] connections.\n' + '\n' ), - link=None - ) + link=None) RQ_SRS_007_LDAP_Authentication_TLS_Certificate_SpecificCertificationAuthority = Requirement( name='RQ.SRS-007.LDAP.Authentication.TLS.Certificate.SpecificCertificationAuthority', @@ -107,9 +717,9 @@ RQ_SRS_007_LDAP_Authentication_TLS_Certificate_SpecificCertificationAuthority = uid=None, description=( '[ClickHouse] SHALL support certificates signed by specific Certification Authority for [TLS] connections.\n' + '\n' ), - link=None - ) + link=None) RQ_SRS_007_LDAP_Server_Configuration_Invalid = Requirement( name='RQ.SRS-007.LDAP.Server.Configuration.Invalid', @@ -120,9 +730,9 @@ RQ_SRS_007_LDAP_Server_Configuration_Invalid = Requirement( uid=None, description=( '[ClickHouse] SHALL return an error and prohibit user login if [LDAP] server configuration is not valid.\n' + '\n' ), - link=None - ) + link=None) RQ_SRS_007_LDAP_User_Configuration_Invalid = Requirement( name='RQ.SRS-007.LDAP.User.Configuration.Invalid', @@ -133,9 +743,9 @@ RQ_SRS_007_LDAP_User_Configuration_Invalid = Requirement( uid=None, description=( '[ClickHouse] SHALL return an error and prohibit user login if user configuration is not valid.\n' + '\n' ), - link=None - ) + link=None) RQ_SRS_007_LDAP_Authentication_Mechanism_Anonymous = Requirement( name='RQ.SRS-007.LDAP.Authentication.Mechanism.Anonymous', @@ -147,9 +757,9 @@ RQ_SRS_007_LDAP_Authentication_Mechanism_Anonymous = Requirement( description=( '[ClickHouse] SHALL return an error and prohibit authentication using [Anonymous Authentication Mechanism of Simple Bind]\n' 'authentication mechanism.\n' + '\n' ), - link=None - ) + link=None) RQ_SRS_007_LDAP_Authentication_Mechanism_Unauthenticated = Requirement( name='RQ.SRS-007.LDAP.Authentication.Mechanism.Unauthenticated', @@ -161,9 +771,9 @@ RQ_SRS_007_LDAP_Authentication_Mechanism_Unauthenticated = Requirement( description=( '[ClickHouse] SHALL return an error and prohibit authentication using [Unauthenticated Authentication Mechanism of Simple Bind]\n' 'authentication mechanism.\n' + '\n' ), - link=None - ) + link=None) RQ_SRS_007_LDAP_Authentication_Mechanism_NamePassword = Requirement( name='RQ.SRS-007.LDAP.Authentication.Mechanism.NamePassword', @@ -175,9 +785,9 @@ RQ_SRS_007_LDAP_Authentication_Mechanism_NamePassword = Requirement( description=( '[ClickHouse] SHALL allow authentication using only [Name/Password Authentication Mechanism of Simple Bind]\n' 'authentication mechanism.\n' + '\n' ), - link=None - ) + link=None) RQ_SRS_007_LDAP_Authentication_Valid = Requirement( name='RQ.SRS-007.LDAP.Authentication.Valid', @@ -189,9 +799,9 @@ RQ_SRS_007_LDAP_Authentication_Valid = Requirement( description=( '[ClickHouse] SHALL only allow user authentication using [LDAP] server if and only if\n' 'user name and password match [LDAP] server records for the user.\n' + '\n' ), - link=None - ) + link=None) RQ_SRS_007_LDAP_Authentication_Invalid = Requirement( name='RQ.SRS-007.LDAP.Authentication.Invalid', @@ -203,9 +813,9 @@ RQ_SRS_007_LDAP_Authentication_Invalid = Requirement( description=( '[ClickHouse] SHALL return an error and prohibit authentication if either user name or password\n' 'do not match [LDAP] server records for the user.\n' + '\n' ), - link=None - ) + link=None) RQ_SRS_007_LDAP_Authentication_Invalid_DeletedUser = Requirement( name='RQ.SRS-007.LDAP.Authentication.Invalid.DeletedUser', @@ -217,9 +827,9 @@ RQ_SRS_007_LDAP_Authentication_Invalid_DeletedUser = Requirement( description=( '[ClickHouse] SHALL return an error and prohibit authentication if the user\n' 'has been deleted from the [LDAP] server.\n' + '\n' ), - link=None - ) + link=None) RQ_SRS_007_LDAP_Authentication_UsernameChanged = Requirement( name='RQ.SRS-007.LDAP.Authentication.UsernameChanged', @@ -231,9 +841,9 @@ RQ_SRS_007_LDAP_Authentication_UsernameChanged = Requirement( description=( '[ClickHouse] SHALL return an error and prohibit authentication if the username is changed\n' 'on the [LDAP] server.\n' + '\n' ), - link=None - ) + link=None) RQ_SRS_007_LDAP_Authentication_PasswordChanged = Requirement( name='RQ.SRS-007.LDAP.Authentication.PasswordChanged', @@ -245,9 +855,9 @@ RQ_SRS_007_LDAP_Authentication_PasswordChanged = Requirement( description=( '[ClickHouse] SHALL return an error and prohibit authentication if the password\n' 'for the user is changed on the [LDAP] server.\n' + '\n' ), - link=None - ) + link=None) RQ_SRS_007_LDAP_Authentication_LDAPServerRestart = Requirement( name='RQ.SRS-007.LDAP.Authentication.LDAPServerRestart', @@ -258,9 +868,9 @@ RQ_SRS_007_LDAP_Authentication_LDAPServerRestart = Requirement( uid=None, description=( '[ClickHouse] SHALL support authenticating users after [LDAP] server is restarted.\n' + '\n' ), - link=None - ) + link=None) RQ_SRS_007_LDAP_Authentication_ClickHouseServerRestart = Requirement( name='RQ.SRS-007.LDAP.Authentication.ClickHouseServerRestart', @@ -271,9 +881,9 @@ RQ_SRS_007_LDAP_Authentication_ClickHouseServerRestart = Requirement( uid=None, description=( '[ClickHouse] SHALL support authenticating users after server is restarted.\n' + '\n' ), - link=None - ) + link=None) RQ_SRS_007_LDAP_Authentication_Parallel = Requirement( name='RQ.SRS-007.LDAP.Authentication.Parallel', @@ -284,9 +894,9 @@ RQ_SRS_007_LDAP_Authentication_Parallel = Requirement( uid=None, description=( '[ClickHouse] SHALL support parallel authentication of users using [LDAP] server.\n' + '\n' ), - link=None - ) + link=None) RQ_SRS_007_LDAP_Authentication_Parallel_ValidAndInvalid = Requirement( name='RQ.SRS-007.LDAP.Authentication.Parallel.ValidAndInvalid', @@ -299,9 +909,9 @@ RQ_SRS_007_LDAP_Authentication_Parallel_ValidAndInvalid = Requirement( '[ClickHouse] SHALL support authentication of valid users and\n' 'prohibit authentication of invalid users using [LDAP] server\n' 'in parallel without having invalid attempts affecting valid authentications.\n' + '\n' ), - link=None - ) + link=None) RQ_SRS_007_LDAP_UnreachableServer = Requirement( name='RQ.SRS-007.LDAP.UnreachableServer', @@ -312,9 +922,9 @@ RQ_SRS_007_LDAP_UnreachableServer = Requirement( uid=None, description=( '[ClickHouse] SHALL return an error and prohibit user login if [LDAP] server is unreachable.\n' + '\n' ), - link=None - ) + link=None) RQ_SRS_007_LDAP_Configuration_Server_Name = Requirement( name='RQ.SRS-007.LDAP.Configuration.Server.Name', @@ -325,9 +935,9 @@ RQ_SRS_007_LDAP_Configuration_Server_Name = Requirement( uid=None, description=( '[ClickHouse] SHALL not support empty string as a server name.\n' + '\n' ), - link=None - ) + link=None) RQ_SRS_007_LDAP_Configuration_Server_Host = Requirement( name='RQ.SRS-007.LDAP.Configuration.Server.Host', @@ -339,9 +949,9 @@ RQ_SRS_007_LDAP_Configuration_Server_Host = Requirement( description=( '[ClickHouse] SHALL support `` parameter to specify [LDAP]\n' 'server hostname or IP, this parameter SHALL be mandatory and SHALL not be empty.\n' + '\n' ), - link=None - ) + link=None) RQ_SRS_007_LDAP_Configuration_Server_Port = Requirement( name='RQ.SRS-007.LDAP.Configuration.Server.Port', @@ -352,9 +962,9 @@ RQ_SRS_007_LDAP_Configuration_Server_Port = Requirement( uid=None, description=( '[ClickHouse] SHALL support `` parameter to specify [LDAP] server port.\n' + '\n' ), - link=None - ) + link=None) RQ_SRS_007_LDAP_Configuration_Server_Port_Default = Requirement( name='RQ.SRS-007.LDAP.Configuration.Server.Port.Default', @@ -365,9 +975,9 @@ RQ_SRS_007_LDAP_Configuration_Server_Port_Default = Requirement( uid=None, description=( '[ClickHouse] SHALL use default port number `636` if `enable_tls` is set to `yes` or `389` otherwise.\n' + '\n' ), - link=None - ) + link=None) RQ_SRS_007_LDAP_Configuration_Server_AuthDN_Prefix = Requirement( name='RQ.SRS-007.LDAP.Configuration.Server.AuthDN.Prefix', @@ -379,9 +989,9 @@ RQ_SRS_007_LDAP_Configuration_Server_AuthDN_Prefix = Requirement( description=( '[ClickHouse] SHALL support `` parameter to specify the prefix\n' 'of value used to construct the DN to bound to during authentication via [LDAP] server.\n' + '\n' ), - link=None - ) + link=None) RQ_SRS_007_LDAP_Configuration_Server_AuthDN_Suffix = Requirement( name='RQ.SRS-007.LDAP.Configuration.Server.AuthDN.Suffix', @@ -393,9 +1003,9 @@ RQ_SRS_007_LDAP_Configuration_Server_AuthDN_Suffix = Requirement( description=( '[ClickHouse] SHALL support `` parameter to specify the suffix\n' 'of value used to construct the DN to bound to during authentication via [LDAP] server.\n' + '\n' ), - link=None - ) + link=None) RQ_SRS_007_LDAP_Configuration_Server_AuthDN_Value = Requirement( name='RQ.SRS-007.LDAP.Configuration.Server.AuthDN.Value', @@ -408,9 +1018,9 @@ RQ_SRS_007_LDAP_Configuration_Server_AuthDN_Value = Requirement( '[ClickHouse] SHALL construct DN as `auth_dn_prefix + escape(user_name) + auth_dn_suffix` string.\n' '\n' "> This implies that auth_dn_suffix should usually have comma ',' as its first non-space character.\n" + '\n' ), - link=None - ) + link=None) RQ_SRS_007_LDAP_Configuration_Server_EnableTLS = Requirement( name='RQ.SRS-007.LDAP.Configuration.Server.EnableTLS', @@ -421,9 +1031,9 @@ RQ_SRS_007_LDAP_Configuration_Server_EnableTLS = Requirement( uid=None, description=( '[ClickHouse] SHALL support `` parameter to trigger the use of secure connection to the [LDAP] server.\n' + '\n' ), - link=None - ) + link=None) RQ_SRS_007_LDAP_Configuration_Server_EnableTLS_Options_Default = Requirement( name='RQ.SRS-007.LDAP.Configuration.Server.EnableTLS.Options.Default', @@ -435,9 +1045,9 @@ RQ_SRS_007_LDAP_Configuration_Server_EnableTLS_Options_Default = Requirement( description=( '[ClickHouse] SHALL use `yes` value as the default for `` parameter\n' 'to enable SSL/TLS `ldaps://` protocol.\n' + '\n' ), - link=None - ) + link=None) RQ_SRS_007_LDAP_Configuration_Server_EnableTLS_Options_No = Requirement( name='RQ.SRS-007.LDAP.Configuration.Server.EnableTLS.Options.No', @@ -449,9 +1059,9 @@ RQ_SRS_007_LDAP_Configuration_Server_EnableTLS_Options_No = Requirement( description=( '[ClickHouse] SHALL support specifying `no` as the value of `` parameter to enable\n' 'plain text `ldap://` protocol.\n' + '\n' ), - link=None - ) + link=None) RQ_SRS_007_LDAP_Configuration_Server_EnableTLS_Options_Yes = Requirement( name='RQ.SRS-007.LDAP.Configuration.Server.EnableTLS.Options.Yes', @@ -463,9 +1073,9 @@ RQ_SRS_007_LDAP_Configuration_Server_EnableTLS_Options_Yes = Requirement( description=( '[ClickHouse] SHALL support specifying `yes` as the value of `` parameter to enable\n' 'SSL/TLS `ldaps://` protocol.\n' + '\n' ), - link=None - ) + link=None) RQ_SRS_007_LDAP_Configuration_Server_EnableTLS_Options_StartTLS = Requirement( name='RQ.SRS-007.LDAP.Configuration.Server.EnableTLS.Options.StartTLS', @@ -477,9 +1087,9 @@ RQ_SRS_007_LDAP_Configuration_Server_EnableTLS_Options_StartTLS = Requirement( description=( '[ClickHouse] SHALL support specifying `starttls` as the value of `` parameter to enable\n' 'legacy `StartTLS` protocol that used plain text `ldap://` protocol, upgraded to [TLS].\n' + '\n' ), - link=None - ) + link=None) RQ_SRS_007_LDAP_Configuration_Server_TLSMinimumProtocolVersion = Requirement( name='RQ.SRS-007.LDAP.Configuration.Server.TLSMinimumProtocolVersion', @@ -491,9 +1101,9 @@ RQ_SRS_007_LDAP_Configuration_Server_TLSMinimumProtocolVersion = Requirement( description=( '[ClickHouse] SHALL support `` parameter to specify\n' 'the minimum protocol version of SSL/TLS.\n' + '\n' ), - link=None - ) + link=None) RQ_SRS_007_LDAP_Configuration_Server_TLSMinimumProtocolVersion_Values = Requirement( name='RQ.SRS-007.LDAP.Configuration.Server.TLSMinimumProtocolVersion.Values', @@ -505,9 +1115,9 @@ RQ_SRS_007_LDAP_Configuration_Server_TLSMinimumProtocolVersion_Values = Requirem description=( '[ClickHouse] SHALL support specifying `ssl2`, `ssl3`, `tls1.0`, `tls1.1`, and `tls1.2`\n' 'as a value of the `` parameter.\n' + '\n' ), - link=None - ) + link=None) RQ_SRS_007_LDAP_Configuration_Server_TLSMinimumProtocolVersion_Default = Requirement( name='RQ.SRS-007.LDAP.Configuration.Server.TLSMinimumProtocolVersion.Default', @@ -518,9 +1128,9 @@ RQ_SRS_007_LDAP_Configuration_Server_TLSMinimumProtocolVersion_Default = Require uid=None, description=( '[ClickHouse] SHALL set `tls1.2` as the default value of the `` parameter.\n' + '\n' ), - link=None - ) + link=None) RQ_SRS_007_LDAP_Configuration_Server_TLSRequireCert = Requirement( name='RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert', @@ -532,9 +1142,9 @@ RQ_SRS_007_LDAP_Configuration_Server_TLSRequireCert = Requirement( description=( '[ClickHouse] SHALL support `` parameter to specify [TLS] peer\n' 'certificate verification behavior.\n' + '\n' ), - link=None - ) + link=None) RQ_SRS_007_LDAP_Configuration_Server_TLSRequireCert_Options_Default = Requirement( name='RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert.Options.Default', @@ -545,9 +1155,9 @@ RQ_SRS_007_LDAP_Configuration_Server_TLSRequireCert_Options_Default = Requiremen uid=None, description=( '[ClickHouse] SHALL use `demand` value as the default for the `` parameter.\n' + '\n' ), - link=None - ) + link=None) RQ_SRS_007_LDAP_Configuration_Server_TLSRequireCert_Options_Demand = Requirement( name='RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert.Options.Demand', @@ -560,9 +1170,9 @@ RQ_SRS_007_LDAP_Configuration_Server_TLSRequireCert_Options_Demand = Requirement '[ClickHouse] SHALL support specifying `demand` as the value of `` parameter to\n' 'enable requesting of client certificate. If no certificate is provided, or a bad certificate is\n' 'provided, the session SHALL be immediately terminated.\n' + '\n' ), - link=None - ) + link=None) RQ_SRS_007_LDAP_Configuration_Server_TLSRequireCert_Options_Allow = Requirement( name='RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert.Options.Allow', @@ -576,9 +1186,9 @@ RQ_SRS_007_LDAP_Configuration_Server_TLSRequireCert_Options_Allow = Requirement( 'enable requesting of client certificate. If no\n' 'certificate is provided, the session SHALL proceed normally.\n' 'If a bad certificate is provided, it SHALL be ignored and the session SHALL proceed normally.\n' + '\n' ), - link=None - ) + link=None) RQ_SRS_007_LDAP_Configuration_Server_TLSRequireCert_Options_Try = Requirement( name='RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert.Options.Try', @@ -592,9 +1202,9 @@ RQ_SRS_007_LDAP_Configuration_Server_TLSRequireCert_Options_Try = Requirement( 'enable requesting of client certificate. If no certificate is provided, the session\n' 'SHALL proceed normally. If a bad certificate is provided, the session SHALL be\n' 'immediately terminated.\n' + '\n' ), - link=None - ) + link=None) RQ_SRS_007_LDAP_Configuration_Server_TLSRequireCert_Options_Never = Requirement( name='RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert.Options.Never', @@ -606,9 +1216,9 @@ RQ_SRS_007_LDAP_Configuration_Server_TLSRequireCert_Options_Never = Requirement( description=( '[ClickHouse] SHALL support specifying `never` as the value of `` parameter to\n' 'disable requesting of client certificate.\n' + '\n' ), - link=None - ) + link=None) RQ_SRS_007_LDAP_Configuration_Server_TLSCertFile = Requirement( name='RQ.SRS-007.LDAP.Configuration.Server.TLSCertFile', @@ -620,9 +1230,9 @@ RQ_SRS_007_LDAP_Configuration_Server_TLSCertFile = Requirement( description=( '[ClickHouse] SHALL support `` to specify the path to certificate file used by\n' '[ClickHouse] to establish connection with the [LDAP] server.\n' + '\n' ), - link=None - ) + link=None) RQ_SRS_007_LDAP_Configuration_Server_TLSKeyFile = Requirement( name='RQ.SRS-007.LDAP.Configuration.Server.TLSKeyFile', @@ -634,9 +1244,9 @@ RQ_SRS_007_LDAP_Configuration_Server_TLSKeyFile = Requirement( description=( '[ClickHouse] SHALL support `` to specify the path to key file for the certificate\n' 'specified by the `` parameter.\n' + '\n' ), - link=None - ) + link=None) RQ_SRS_007_LDAP_Configuration_Server_TLSCACertDir = Requirement( name='RQ.SRS-007.LDAP.Configuration.Server.TLSCACertDir', @@ -648,9 +1258,9 @@ RQ_SRS_007_LDAP_Configuration_Server_TLSCACertDir = Requirement( description=( '[ClickHouse] SHALL support `` parameter to specify to a path to\n' 'the directory containing [CA] certificates used to verify certificates provided by the [LDAP] server.\n' + '\n' ), - link=None - ) + link=None) RQ_SRS_007_LDAP_Configuration_Server_TLSCACertFile = Requirement( name='RQ.SRS-007.LDAP.Configuration.Server.TLSCACertFile', @@ -662,9 +1272,9 @@ RQ_SRS_007_LDAP_Configuration_Server_TLSCACertFile = Requirement( description=( '[ClickHouse] SHALL support `` parameter to specify a path to a specific\n' '[CA] certificate file used to verify certificates provided by the [LDAP] server.\n' + '\n' ), - link=None - ) + link=None) RQ_SRS_007_LDAP_Configuration_Server_TLSCipherSuite = Requirement( name='RQ.SRS-007.LDAP.Configuration.Server.TLSCipherSuite', @@ -685,13 +1295,45 @@ RQ_SRS_007_LDAP_Configuration_Server_TLSCipherSuite = Requirement( '\n' 'The available suites SHALL depend on the [OpenSSL] library version and variant used to build\n' '[ClickHouse] and therefore might change.\n' + '\n' ), - link=None - ) + link=None) + +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) + +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) 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, @@ -707,6 +1349,7 @@ RQ_SRS_007_LDAP_Configuration_Server_Syntax = Requirement( ' 636\n' ' cn=\n' ' , ou=users, dc=example, dc=com\n' + ' 0\n' ' yes\n' ' tls1.2\n' ' demand\n' @@ -718,9 +1361,9 @@ RQ_SRS_007_LDAP_Configuration_Server_Syntax = Requirement( ' \n' '\n' '```\n' + '\n' ), - link=None - ) + link=None) RQ_SRS_007_LDAP_Configuration_User_RBAC = Requirement( name='RQ.SRS-007.LDAP.Configuration.User.RBAC', @@ -736,9 +1379,9 @@ RQ_SRS_007_LDAP_Configuration_User_RBAC = Requirement( '```sql\n' "CREATE USER name IDENTIFIED WITH ldap_server BY 'server_name'\n" '```\n' + '\n' ), - link=None - ) + link=None) RQ_SRS_007_LDAP_Configuration_User_Syntax = Requirement( name='RQ.SRS-007.LDAP.Configuration.User.Syntax', @@ -762,9 +1405,9 @@ RQ_SRS_007_LDAP_Configuration_User_Syntax = Requirement( ' \n' '\n' '```\n' + '\n' ), - link=None - ) + link=None) RQ_SRS_007_LDAP_Configuration_User_Name_Empty = Requirement( name='RQ.SRS-007.LDAP.Configuration.User.Name.Empty', @@ -775,9 +1418,9 @@ RQ_SRS_007_LDAP_Configuration_User_Name_Empty = Requirement( uid=None, description=( '[ClickHouse] SHALL not support empty string as a user name.\n' + '\n' ), - link=None - ) + link=None) RQ_SRS_007_LDAP_Configuration_User_BothPasswordAndLDAP = Requirement( name='RQ.SRS-007.LDAP.Configuration.User.BothPasswordAndLDAP', @@ -789,9 +1432,9 @@ RQ_SRS_007_LDAP_Configuration_User_BothPasswordAndLDAP = Requirement( description=( '[ClickHouse] SHALL throw an error if `` is specified for the user and at the same\n' 'time user configuration contains any of the `` entries.\n' + '\n' ), - link=None - ) + link=None) RQ_SRS_007_LDAP_Configuration_User_LDAP_InvalidServerName_NotDefined = Requirement( name='RQ.SRS-007.LDAP.Configuration.User.LDAP.InvalidServerName.NotDefined', @@ -804,9 +1447,9 @@ RQ_SRS_007_LDAP_Configuration_User_LDAP_InvalidServerName_NotDefined = Requireme '[ClickHouse] SHALL throw an error during any authentification attempt\n' 'if the name of the [LDAP] server used inside the `` entry\n' 'is not defined in the `` section.\n' + '\n' ), - link=None - ) + link=None) RQ_SRS_007_LDAP_Configuration_User_LDAP_InvalidServerName_Empty = Requirement( name='RQ.SRS-007.LDAP.Configuration.User.LDAP.InvalidServerName.Empty', @@ -819,9 +1462,9 @@ RQ_SRS_007_LDAP_Configuration_User_LDAP_InvalidServerName_Empty = Requirement( '[ClickHouse] SHALL throw an error during any authentification attempt\n' 'if the name of the [LDAP] server used inside the `` entry\n' 'is empty.\n' + '\n' ), - link=None - ) + link=None) RQ_SRS_007_LDAP_Configuration_User_OnlyOneServer = Requirement( name='RQ.SRS-007.LDAP.Configuration.User.OnlyOneServer', @@ -832,9 +1475,9 @@ RQ_SRS_007_LDAP_Configuration_User_OnlyOneServer = Requirement( uid=None, description=( '[ClickHouse] SHALL support specifying only one [LDAP] server for a given user.\n' + '\n' ), - link=None - ) + link=None) RQ_SRS_007_LDAP_Configuration_User_Name_Long = Requirement( name='RQ.SRS-007.LDAP.Configuration.User.Name.Long', @@ -846,9 +1489,9 @@ RQ_SRS_007_LDAP_Configuration_User_Name_Long = Requirement( description=( '[ClickHouse] SHALL support long user names of at least 256 bytes\n' 'to specify users that can be authenticated using an [LDAP] server.\n' + '\n' ), - link=None - ) + link=None) RQ_SRS_007_LDAP_Configuration_User_Name_UTF8 = Requirement( name='RQ.SRS-007.LDAP.Configuration.User.Name.UTF8', @@ -859,9 +1502,9 @@ RQ_SRS_007_LDAP_Configuration_User_Name_UTF8 = Requirement( uid=None, description=( '[ClickHouse] SHALL support user names that contain [UTF-8] characters.\n' + '\n' ), - link=None - ) + link=None) RQ_SRS_007_LDAP_Authentication_Username_Empty = Requirement( name='RQ.SRS-007.LDAP.Authentication.Username.Empty', @@ -872,9 +1515,9 @@ RQ_SRS_007_LDAP_Authentication_Username_Empty = Requirement( uid=None, description=( '[ClickHouse] SHALL not support authenticating users with empty username.\n' + '\n' ), - link=None - ) + link=None) RQ_SRS_007_LDAP_Authentication_Username_Long = Requirement( name='RQ.SRS-007.LDAP.Authentication.Username.Long', @@ -885,9 +1528,9 @@ RQ_SRS_007_LDAP_Authentication_Username_Long = Requirement( uid=None, description=( '[ClickHouse] SHALL support authenticating users with a long username of at least 256 bytes.\n' + '\n' ), - link=None - ) + link=None) RQ_SRS_007_LDAP_Authentication_Username_UTF8 = Requirement( name='RQ.SRS-007.LDAP.Authentication.Username.UTF8', @@ -898,9 +1541,9 @@ RQ_SRS_007_LDAP_Authentication_Username_UTF8 = Requirement( uid=None, description=( '[ClickHouse] SHALL support authentication users with a username that contains [UTF-8] characters.\n' + '\n' ), - link=None - ) + link=None) RQ_SRS_007_LDAP_Authentication_Password_Empty = Requirement( name='RQ.SRS-007.LDAP.Authentication.Password.Empty', @@ -913,9 +1556,9 @@ RQ_SRS_007_LDAP_Authentication_Password_Empty = Requirement( '[ClickHouse] SHALL not support authenticating users with empty passwords\n' 'even if an empty password is valid for the user and\n' 'is allowed by the [LDAP] server.\n' + '\n' ), - link=None - ) + link=None) RQ_SRS_007_LDAP_Authentication_Password_Long = Requirement( name='RQ.SRS-007.LDAP.Authentication.Password.Long', @@ -927,9 +1570,9 @@ RQ_SRS_007_LDAP_Authentication_Password_Long = Requirement( description=( '[ClickHouse] SHALL support long password of at least 256 bytes\n' 'that can be used to authenticate users using an [LDAP] server.\n' + '\n' ), - link=None - ) + link=None) RQ_SRS_007_LDAP_Authentication_Password_UTF8 = Requirement( name='RQ.SRS-007.LDAP.Authentication.Password.UTF8', @@ -941,6 +1584,57 @@ RQ_SRS_007_LDAP_Authentication_Password_UTF8 = Requirement( description=( '[ClickHouse] SHALL support [UTF-8] characters in passwords\n' 'used to authenticate users using an [LDAP] server.\n' + '\n' ), - link=None - ) + link=None) + +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) + +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) + +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) diff --git a/tests/testflows/ldap/authentication/tests/server_config.py b/tests/testflows/ldap/authentication/tests/server_config.py index f62fda0bbf7..5b3a96caa9c 100644 --- a/tests/testflows/ldap/authentication/tests/server_config.py +++ b/tests/testflows/ldap/authentication/tests/server_config.py @@ -219,7 +219,7 @@ def auth_dn_value(self): @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 +230,7 @@ def syntax(self): 636 cn= , ou=users, dc=example, dc=com + 0 yes tls1.2 demand @@ -248,6 +249,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", diff --git a/tests/testflows/ldap/external_user_directory/regression.py b/tests/testflows/ldap/external_user_directory/regression.py index 6ce860a6fd2..2ad2bd7b1d2 100755 --- a/tests/testflows/ldap/external_user_directory/regression.py +++ b/tests/testflows/ldap/external_user_directory/regression.py @@ -29,9 +29,8 @@ xfails = { @TestFeature @Name("external user directory") @ArgumentParser(argparser) -@Requirements( - RQ_SRS_009_LDAP_ExternalUserDirectory_Authentication("1.0") -) +@Specifications(SRS_009_ClickHouse_LDAP_External_User_Directory) +@Requirements(RQ_SRS_009_LDAP_ExternalUserDirectory_Authentication("1.0")) @XFails(xfails) def regression(self, local, clickhouse_binary_path): """ClickHouse LDAP external user directory regression module. diff --git a/tests/testflows/ldap/external_user_directory/requirements/requirements.md b/tests/testflows/ldap/external_user_directory/requirements/requirements.md index 46532c3945d..74248196998 100644 --- a/tests/testflows/ldap/external_user_directory/requirements/requirements.md +++ b/tests/testflows/ldap/external_user_directory/requirements/requirements.md @@ -80,20 +80,22 @@ * 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.Syntax](#rqsrs-009ldapexternaluserdirectoryconfigurationserversyntax) + * 4.2.3.32 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.LDAPUserDirectory](#rqsrs-009ldapexternaluserdirectoryconfigurationusersldapuserdirectory) + * 4.2.3.33 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.LDAPUserDirectory.MoreThanOne](#rqsrs-009ldapexternaluserdirectoryconfigurationusersldapuserdirectorymorethanone) + * 4.2.3.34 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Syntax](#rqsrs-009ldapexternaluserdirectoryconfigurationuserssyntax) + * 4.2.3.35 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Server](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersserver) + * 4.2.3.36 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Server.Empty](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersserverempty) + * 4.2.3.37 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Server.Missing](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersservermissing) + * 4.2.3.38 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Server.MoreThanOne](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersservermorethanone) + * 4.2.3.39 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Server.Invalid](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersserverinvalid) + * 4.2.3.40 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Roles](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersroles) + * 4.2.3.41 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Roles.MoreThanOne](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersrolesmorethanone) + * 4.2.3.42 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Roles.Invalid](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersrolesinvalid) + * 4.2.3.43 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Roles.Empty](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersrolesempty) + * 4.2.3.44 [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 +103,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 +561,25 @@ 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.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 +590,7 @@ configuration file or of any configuration file inside the `config.d` directory. 636 cn= , ou=users, dc=example, dc=com + 0 yes tls1.2 demand @@ -717,6 +739,34 @@ 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/ diff --git a/tests/testflows/ldap/external_user_directory/requirements/requirements.py b/tests/testflows/ldap/external_user_directory/requirements/requirements.py index 4c4b17d01dc..3354d2b5dd7 100644 --- a/tests/testflows/ldap/external_user_directory/requirements/requirements.py +++ b/tests/testflows/ldap/external_user_directory/requirements/requirements.py @@ -1,10 +1,814 @@ # These requirements were auto generated # from software requirements specification (SRS) -# document by TestFlows v1.6.201009.1190249. +# document by TestFlows v1.6.201025.1200805. # Do not edit by hand but re-generate instead # using 'tfs requirements generate' command. +from testflows.core import Specification from testflows.core import Requirement +SRS_009_ClickHouse_LDAP_External_User_Directory = Specification( + name='SRS-009 ClickHouse LDAP External User Directory', + description=None, + author=None, + date=None, + status=None, + approved_by=None, + approved_date=None, + approved_version=None, + version=None, + group=None, + type=None, + link=None, + uid=None, + parent=None, + children=None, + content=''' +# SRS-009 ClickHouse LDAP External User Directory +# Software Requirements Specification + +## Table of Contents + +* 1 [Revision History](#revision-history) +* 2 [Introduction](#introduction) +* 3 [Terminology](#terminology) + * 3.1 [LDAP](#ldap) +* 4 [Requirements](#requirements) + * 4.1 [Generic](#generic) + * 4.1.1 [User Authentication](#user-authentication) + * 4.1.1.1 [RQ.SRS-009.LDAP.ExternalUserDirectory.Authentication](#rqsrs-009ldapexternaluserdirectoryauthentication) + * 4.1.1.2 [RQ.SRS-009.LDAP.ExternalUserDirectory.MultipleUserDirectories](#rqsrs-009ldapexternaluserdirectorymultipleuserdirectories) + * 4.1.1.3 [RQ.SRS-009.LDAP.ExternalUserDirectory.MultipleUserDirectories.Lookup](#rqsrs-009ldapexternaluserdirectorymultipleuserdirectorieslookup) + * 4.1.1.4 [RQ.SRS-009.LDAP.ExternalUserDirectory.Users.Authentication.NewUsers](#rqsrs-009ldapexternaluserdirectoryusersauthenticationnewusers) + * 4.1.1.5 [RQ.SRS-009.LDAP.ExternalUserDirectory.Authentication.DeletedUsers](#rqsrs-009ldapexternaluserdirectoryauthenticationdeletedusers) + * 4.1.1.6 [RQ.SRS-009.LDAP.ExternalUserDirectory.Authentication.Valid](#rqsrs-009ldapexternaluserdirectoryauthenticationvalid) + * 4.1.1.7 [RQ.SRS-009.LDAP.ExternalUserDirectory.Authentication.Invalid](#rqsrs-009ldapexternaluserdirectoryauthenticationinvalid) + * 4.1.1.8 [RQ.SRS-009.LDAP.ExternalUserDirectory.Authentication.UsernameChanged](#rqsrs-009ldapexternaluserdirectoryauthenticationusernamechanged) + * 4.1.1.9 [RQ.SRS-009.LDAP.ExternalUserDirectory.Authentication.PasswordChanged](#rqsrs-009ldapexternaluserdirectoryauthenticationpasswordchanged) + * 4.1.1.10 [RQ.SRS-009.LDAP.ExternalUserDirectory.Authentication.LDAPServerRestart](#rqsrs-009ldapexternaluserdirectoryauthenticationldapserverrestart) + * 4.1.1.11 [RQ.SRS-009.LDAP.ExternalUserDirectory.Authentication.ClickHouseServerRestart](#rqsrs-009ldapexternaluserdirectoryauthenticationclickhouseserverrestart) + * 4.1.1.12 [RQ.SRS-009.LDAP.ExternalUserDirectory.Authentication.Parallel](#rqsrs-009ldapexternaluserdirectoryauthenticationparallel) + * 4.1.1.13 [RQ.SRS-009.LDAP.ExternalUserDirectory.Authentication.Parallel.ValidAndInvalid](#rqsrs-009ldapexternaluserdirectoryauthenticationparallelvalidandinvalid) + * 4.1.1.14 [RQ.SRS-009.LDAP.ExternalUserDirectory.Authentication.Parallel.MultipleServers](#rqsrs-009ldapexternaluserdirectoryauthenticationparallelmultipleservers) + * 4.1.1.15 [RQ.SRS-009.LDAP.ExternalUserDirectory.Authentication.Parallel.LocalOnly](#rqsrs-009ldapexternaluserdirectoryauthenticationparallellocalonly) + * 4.1.1.16 [RQ.SRS-009.LDAP.ExternalUserDirectory.Authentication.Parallel.LocalAndMultipleLDAP](#rqsrs-009ldapexternaluserdirectoryauthenticationparallellocalandmultipleldap) + * 4.1.1.17 [RQ.SRS-009.LDAP.ExternalUserDirectory.Authentication.Parallel.SameUser](#rqsrs-009ldapexternaluserdirectoryauthenticationparallelsameuser) + * 4.1.1.18 [RQ.SRS-009.LDAP.ExternalUserDirectory.Authentication.Parallel.DynamicallyAddedAndRemovedUsers](#rqsrs-009ldapexternaluserdirectoryauthenticationparalleldynamicallyaddedandremovedusers) + * 4.1.2 [Connection](#connection) + * 4.1.2.1 [RQ.SRS-009.LDAP.ExternalUserDirectory.Connection.Protocol.PlainText](#rqsrs-009ldapexternaluserdirectoryconnectionprotocolplaintext) + * 4.1.2.2 [RQ.SRS-009.LDAP.ExternalUserDirectory.Connection.Protocol.TLS](#rqsrs-009ldapexternaluserdirectoryconnectionprotocoltls) + * 4.1.2.3 [RQ.SRS-009.LDAP.ExternalUserDirectory.Connection.Protocol.StartTLS](#rqsrs-009ldapexternaluserdirectoryconnectionprotocolstarttls) + * 4.1.2.4 [RQ.SRS-009.LDAP.ExternalUserDirectory.Connection.Protocol.TLS.Certificate.Validation](#rqsrs-009ldapexternaluserdirectoryconnectionprotocoltlscertificatevalidation) + * 4.1.2.5 [RQ.SRS-009.LDAP.ExternalUserDirectory.Connection.Protocol.TLS.Certificate.SelfSigned](#rqsrs-009ldapexternaluserdirectoryconnectionprotocoltlscertificateselfsigned) + * 4.1.2.6 [RQ.SRS-009.LDAP.ExternalUserDirectory.Connection.Protocol.TLS.Certificate.SpecificCertificationAuthority](#rqsrs-009ldapexternaluserdirectoryconnectionprotocoltlscertificatespecificcertificationauthority) + * 4.1.2.7 [RQ.SRS-009.LDAP.ExternalUserDirectory.Connection.Authentication.Mechanism.Anonymous](#rqsrs-009ldapexternaluserdirectoryconnectionauthenticationmechanismanonymous) + * 4.1.2.8 [RQ.SRS-009.LDAP.ExternalUserDirectory.Connection.Authentication.Mechanism.Unauthenticated](#rqsrs-009ldapexternaluserdirectoryconnectionauthenticationmechanismunauthenticated) + * 4.1.2.9 [RQ.SRS-009.LDAP.ExternalUserDirectory.Connection.Authentication.Mechanism.NamePassword](#rqsrs-009ldapexternaluserdirectoryconnectionauthenticationmechanismnamepassword) + * 4.1.2.10 [RQ.SRS-009.LDAP.ExternalUserDirectory.Connection.Authentication.UnreachableServer](#rqsrs-009ldapexternaluserdirectoryconnectionauthenticationunreachableserver) + * 4.2 [Specific](#specific) + * 4.2.1 [User Discovery](#user-discovery) + * 4.2.1.1 [RQ.SRS-009.LDAP.ExternalUserDirectory.Users.Lookup.Priority](#rqsrs-009ldapexternaluserdirectoryuserslookuppriority) + * 4.2.1.2 [RQ.SRS-009.LDAP.ExternalUserDirectory.Restart.Server](#rqsrs-009ldapexternaluserdirectoryrestartserver) + * 4.2.1.3 [RQ.SRS-009.LDAP.ExternalUserDirectory.Restart.Server.ParallelLogins](#rqsrs-009ldapexternaluserdirectoryrestartserverparallellogins) + * 4.2.2 [Roles](#roles) + * 4.2.2.1 [RQ.SRS-009.LDAP.ExternalUserDirectory.Role.Removed](#rqsrs-009ldapexternaluserdirectoryroleremoved) + * 4.2.2.2 [RQ.SRS-009.LDAP.ExternalUserDirectory.Role.Removed.Privileges](#rqsrs-009ldapexternaluserdirectoryroleremovedprivileges) + * 4.2.2.3 [RQ.SRS-009.LDAP.ExternalUserDirectory.Role.Readded.Privileges](#rqsrs-009ldapexternaluserdirectoryrolereaddedprivileges) + * 4.2.2.4 [RQ.SRS-009.LDAP.ExternalUserDirectory.Role.New](#rqsrs-009ldapexternaluserdirectoryrolenew) + * 4.2.2.5 [RQ.SRS-009.LDAP.ExternalUserDirectory.Role.NewPrivilege](#rqsrs-009ldapexternaluserdirectoryrolenewprivilege) + * 4.2.2.6 [RQ.SRS-009.LDAP.ExternalUserDirectory.Role.RemovedPrivilege](#rqsrs-009ldapexternaluserdirectoryroleremovedprivilege) + * 4.2.3 [Configuration](#configuration) + * 4.2.3.1 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.Invalid](#rqsrs-009ldapexternaluserdirectoryconfigurationserverinvalid) + * 4.2.3.2 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.Definition](#rqsrs-009ldapexternaluserdirectoryconfigurationserverdefinition) + * 4.2.3.3 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.Name](#rqsrs-009ldapexternaluserdirectoryconfigurationservername) + * 4.2.3.4 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.Host](#rqsrs-009ldapexternaluserdirectoryconfigurationserverhost) + * 4.2.3.5 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.Port](#rqsrs-009ldapexternaluserdirectoryconfigurationserverport) + * 4.2.3.6 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.Port.Default](#rqsrs-009ldapexternaluserdirectoryconfigurationserverportdefault) + * 4.2.3.7 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.AuthDN.Prefix](#rqsrs-009ldapexternaluserdirectoryconfigurationserverauthdnprefix) + * 4.2.3.8 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.AuthDN.Suffix](#rqsrs-009ldapexternaluserdirectoryconfigurationserverauthdnsuffix) + * 4.2.3.9 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.AuthDN.Value](#rqsrs-009ldapexternaluserdirectoryconfigurationserverauthdnvalue) + * 4.2.3.10 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.EnableTLS](#rqsrs-009ldapexternaluserdirectoryconfigurationserverenabletls) + * 4.2.3.11 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.EnableTLS.Options.Default](#rqsrs-009ldapexternaluserdirectoryconfigurationserverenabletlsoptionsdefault) + * 4.2.3.12 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.EnableTLS.Options.No](#rqsrs-009ldapexternaluserdirectoryconfigurationserverenabletlsoptionsno) + * 4.2.3.13 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.EnableTLS.Options.Yes](#rqsrs-009ldapexternaluserdirectoryconfigurationserverenabletlsoptionsyes) + * 4.2.3.14 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.EnableTLS.Options.StartTLS](#rqsrs-009ldapexternaluserdirectoryconfigurationserverenabletlsoptionsstarttls) + * 4.2.3.15 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.TLSMinimumProtocolVersion](#rqsrs-009ldapexternaluserdirectoryconfigurationservertlsminimumprotocolversion) + * 4.2.3.16 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.TLSMinimumProtocolVersion.Values](#rqsrs-009ldapexternaluserdirectoryconfigurationservertlsminimumprotocolversionvalues) + * 4.2.3.17 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.TLSMinimumProtocolVersion.Default](#rqsrs-009ldapexternaluserdirectoryconfigurationservertlsminimumprotocolversiondefault) + * 4.2.3.18 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.TLSRequireCert](#rqsrs-009ldapexternaluserdirectoryconfigurationservertlsrequirecert) + * 4.2.3.19 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.TLSRequireCert.Options.Default](#rqsrs-009ldapexternaluserdirectoryconfigurationservertlsrequirecertoptionsdefault) + * 4.2.3.20 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.TLSRequireCert.Options.Demand](#rqsrs-009ldapexternaluserdirectoryconfigurationservertlsrequirecertoptionsdemand) + * 4.2.3.21 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.TLSRequireCert.Options.Allow](#rqsrs-009ldapexternaluserdirectoryconfigurationservertlsrequirecertoptionsallow) + * 4.2.3.22 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.TLSRequireCert.Options.Try](#rqsrs-009ldapexternaluserdirectoryconfigurationservertlsrequirecertoptionstry) + * 4.2.3.23 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.TLSRequireCert.Options.Never](#rqsrs-009ldapexternaluserdirectoryconfigurationservertlsrequirecertoptionsnever) + * 4.2.3.24 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.TLSCertFile](#rqsrs-009ldapexternaluserdirectoryconfigurationservertlscertfile) + * 4.2.3.25 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.TLSKeyFile](#rqsrs-009ldapexternaluserdirectoryconfigurationservertlskeyfile) + * 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.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.Syntax](#rqsrs-009ldapexternaluserdirectoryconfigurationserversyntax) + * 4.2.3.32 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.LDAPUserDirectory](#rqsrs-009ldapexternaluserdirectoryconfigurationusersldapuserdirectory) + * 4.2.3.33 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.LDAPUserDirectory.MoreThanOne](#rqsrs-009ldapexternaluserdirectoryconfigurationusersldapuserdirectorymorethanone) + * 4.2.3.34 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Syntax](#rqsrs-009ldapexternaluserdirectoryconfigurationuserssyntax) + * 4.2.3.35 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Server](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersserver) + * 4.2.3.36 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Server.Empty](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersserverempty) + * 4.2.3.37 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Server.Missing](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersservermissing) + * 4.2.3.38 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Server.MoreThanOne](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersservermorethanone) + * 4.2.3.39 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Server.Invalid](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersserverinvalid) + * 4.2.3.40 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Roles](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersroles) + * 4.2.3.41 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Roles.MoreThanOne](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersrolesmorethanone) + * 4.2.3.42 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Roles.Invalid](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersrolesinvalid) + * 4.2.3.43 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Roles.Empty](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersrolesempty) + * 4.2.3.44 [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) + * 4.2.4.3 [RQ.SRS-009.LDAP.ExternalUserDirectory.Authentication.Username.UTF8](#rqsrs-009ldapexternaluserdirectoryauthenticationusernameutf8) + * 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 + +This document is stored in an electronic form using [Git] source control management software +hosted in a [GitHub Repository]. +All the updates are tracked using the [Revision History]. + +## Introduction + +The [QA-SRS007 ClickHouse Authentication of Users via LDAP] enables support for authenticating +users using an [LDAP] server. This requirements specifications add addition functionality +for integrating [LDAP] with [ClickHouse]. + +This document will cover requirements to allow authenticatoin of users stored in the +external user discovery using an [LDAP] server without having to explicitly define users in [ClickHouse]'s +`users.xml` configuration file. + +## Terminology + +### LDAP + +* Lightweight Directory Access Protocol + +## Requirements + +### Generic + +#### User Authentication + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Authentication +version: 1.0 + +[ClickHouse] SHALL support authenticating users that are defined only on the [LDAP] server. + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.MultipleUserDirectories +version: 1.0 + +[ClickHouse] SHALL support authenticating users using multiple [LDAP] external user directories. + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.MultipleUserDirectories.Lookup +version: 1.0 + +[ClickHouse] SHALL attempt to authenticate external [LDAP] user +using [LDAP] external user directory in the same order +in which user directories are specified in the `config.xml` file. +If a user cannot be authenticated using the first [LDAP] external user directory +then the next user directory in the list SHALL be used. + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Users.Authentication.NewUsers +version: 1.0 + +[ClickHouse] SHALL support authenticating users that are defined only on the [LDAP] server +as soon as they are added to the [LDAP] server. + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Authentication.DeletedUsers +version: 1.0 + +[ClickHouse] SHALL not allow authentication of users that +were previously defined only on the [LDAP] server but were removed +from the [LDAP] server. + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Authentication.Valid +version: 1.0 + +[ClickHouse] SHALL only allow user authentication using [LDAP] server if and only if +user name and password match [LDAP] server records for the user +when using [LDAP] external user directory. + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Authentication.Invalid +version: 1.0 + +[ClickHouse] SHALL return an error and prohibit authentication if either user name or password +do not match [LDAP] server records for the user +when using [LDAP] external user directory. + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Authentication.UsernameChanged +version: 1.0 + +[ClickHouse] SHALL return an error and prohibit authentication if the username is changed +on the [LDAP] server when using [LDAP] external user directory. + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Authentication.PasswordChanged +version: 1.0 + +[ClickHouse] SHALL return an error and prohibit authentication if the password +for the user is changed on the [LDAP] server when using [LDAP] external user directory. + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Authentication.LDAPServerRestart +version: 1.0 + +[ClickHouse] SHALL support authenticating users after [LDAP] server is restarted +when using [LDAP] external user directory. + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Authentication.ClickHouseServerRestart +version: 1.0 + +[ClickHouse] SHALL support authenticating users after server is restarted +when using [LDAP] external user directory. + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Authentication.Parallel +version: 1.0 + +[ClickHouse] SHALL support parallel authentication of users using [LDAP] server +when using [LDAP] external user directory. + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Authentication.Parallel.ValidAndInvalid +version: 1.0 + +[ClickHouse] SHALL support authentication of valid users and +prohibit authentication of invalid users using [LDAP] server +in parallel without having invalid attempts affecting valid authentications +when using [LDAP] external user directory. + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Authentication.Parallel.MultipleServers +version: 1.0 + +[ClickHouse] SHALL support parallel authentication of external [LDAP] users +authenticated using multiple [LDAP] external user directories. + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Authentication.Parallel.LocalOnly +version: 1.0 + +[ClickHouse] SHALL support parallel authentication of users defined only locally +when one or more [LDAP] external user directories are specified in the configuration file. + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Authentication.Parallel.LocalAndMultipleLDAP +version: 1.0 + +[ClickHouse] SHALL support parallel authentication of local and external [LDAP] users +authenticated using multiple [LDAP] external user directories. + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Authentication.Parallel.SameUser +version: 1.0 + +[ClickHouse] SHALL support parallel authentication of the same external [LDAP] user +authenticated using the same [LDAP] external user directory. + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Authentication.Parallel.DynamicallyAddedAndRemovedUsers +version: 1.0 + +[ClickHouse] SHALL support parallel authentication of users using +[LDAP] external user directory when [LDAP] users are dynamically added and +removed. + +#### Connection + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Connection.Protocol.PlainText +version: 1.0 + +[ClickHouse] SHALL support user authentication using plain text `ldap://` non secure protocol +while connecting to the [LDAP] server when using [LDAP] external user directory. + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Connection.Protocol.TLS +version: 1.0 + +[ClickHouse] SHALL support user authentication using `SSL/TLS` `ldaps://` secure protocol +while connecting to the [LDAP] server when using [LDAP] external user directory. + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Connection.Protocol.StartTLS +version: 1.0 + +[ClickHouse] SHALL support user authentication using legacy `StartTLS` protocol which is a +plain text `ldap://` protocol that is upgraded to [TLS] when connecting to the [LDAP] server +when using [LDAP] external user directory. + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Connection.Protocol.TLS.Certificate.Validation +version: 1.0 + +[ClickHouse] SHALL support certificate validation used for [TLS] connections +to the [LDAP] server when using [LDAP] external user directory. + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Connection.Protocol.TLS.Certificate.SelfSigned +version: 1.0 + +[ClickHouse] SHALL support self-signed certificates for [TLS] connections +to the [LDAP] server when using [LDAP] external user directory. + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Connection.Protocol.TLS.Certificate.SpecificCertificationAuthority +version: 1.0 + +[ClickHouse] SHALL support certificates signed by specific Certification Authority for [TLS] connections +to the [LDAP] server when using [LDAP] external user directory. + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Connection.Authentication.Mechanism.Anonymous +version: 1.0 + +[ClickHouse] SHALL return an error and prohibit authentication using [Anonymous Authentication Mechanism of Simple Bind] +authentication mechanism when connecting to the [LDAP] server when using [LDAP] external server directory. + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Connection.Authentication.Mechanism.Unauthenticated +version: 1.0 + +[ClickHouse] SHALL return an error and prohibit authentication using [Unauthenticated Authentication Mechanism of Simple Bind] +authentication mechanism when connecting to the [LDAP] server when using [LDAP] external server directory. + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Connection.Authentication.Mechanism.NamePassword +version: 1.0 + +[ClickHouse] SHALL allow authentication using only [Name/Password Authentication Mechanism of Simple Bind] +authentication mechanism when connecting to the [LDAP] server when using [LDAP] external server directory. + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Connection.Authentication.UnreachableServer +version: 1.0 + +[ClickHouse] SHALL return an error and prohibit user login if [LDAP] server is unreachable +when using [LDAP] external user directory. + +### Specific + +#### User Discovery + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Users.Lookup.Priority +version: 2.0 + +[ClickHouse] SHALL lookup user presence in the same order +as user directories are defined in the `config.xml`. + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Restart.Server +version: 1.0 + +[ClickHouse] SHALL support restarting server when one or more LDAP external directories +are configured. + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Restart.Server.ParallelLogins +version: 1.0 + +[ClickHouse] SHALL support restarting server when one or more LDAP external directories +are configured during parallel [LDAP] user logins. + +#### Roles + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Role.Removed +version: 1.0 + +[ClickHouse] SHALL reject authentication attempt if any of the roles that are specified in the configuration +of the external user directory are not defined at the time of the authentication attempt +with an exception that if a user was able to authenticate in past and its internal user object was created and cached +then the user SHALL be able to authenticate again, even if one of the roles is missing. + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Role.Removed.Privileges +version: 1.0 + +[ClickHouse] SHALL remove the privileges provided by the role from all the LDAP +users authenticated using external user directory if it is removed +including currently cached users that are still able to authenticated where the removed +role is specified in the configuration of the external user directory. + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Role.Readded.Privileges +version: 1.0 + +[ClickHouse] SHALL reassign the role and add the privileges provided by the role +when it is re-added after removal for all LDAP users authenticated using external user directory +including any cached users where the re-added role was specified in the configuration of the external user directory. + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Role.New +version: 1.0 + +[ClickHouse] SHALL not allow any new roles to be assigned to any LDAP +users authenticated using external user directory unless the role is specified +in the configuration of the external user directory. + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Role.NewPrivilege +version: 1.0 + +[ClickHouse] SHALL add new privilege to all the LDAP users authenticated using external user directory +including cached users when new privilege is added to one of the roles specified +in the configuration of the external user directory. + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Role.RemovedPrivilege +version: 1.0 + +[ClickHouse] SHALL remove privilege from all the LDAP users authenticated using external user directory +including cached users when privilege is removed from all the roles specified +in the configuration of the external user directory. + +#### Configuration + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.Invalid +version: 1.0 + +[ClickHouse] SHALL return an error and prohibit user login if [LDAP] server configuration is not valid. + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.Definition +version: 1.0 + +[ClickHouse] SHALL support using the [LDAP] servers defined in the +`ldap_servers` section of the `config.xml` as the server to be used +for a external user directory that uses an [LDAP] server as a source of user definitions. + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.Name +version: 1.0 + +[ClickHouse] SHALL not support empty string as a server name. + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.Host +version: 1.0 + +[ClickHouse] SHALL support `` parameter to specify [LDAP] +server hostname or IP, this parameter SHALL be mandatory and SHALL not be empty. + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.Port +version: 1.0 + +[ClickHouse] SHALL support `` parameter to specify [LDAP] server port. + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.Port.Default +version: 1.0 + +[ClickHouse] SHALL use default port number `636` if `enable_tls` is set to `yes` or `389` otherwise. + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.AuthDN.Prefix +version: 1.0 + +[ClickHouse] SHALL support `` parameter to specify the prefix +of value used to construct the DN to bound to during authentication via [LDAP] server. + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.AuthDN.Suffix +version: 1.0 + +[ClickHouse] SHALL support `` parameter to specify the suffix +of value used to construct the DN to bound to during authentication via [LDAP] server. + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.AuthDN.Value +version: 1.0 + +[ClickHouse] SHALL construct DN as `auth_dn_prefix + escape(user_name) + auth_dn_suffix` string. + +> This implies that auth_dn_suffix should usually have comma ',' as its first non-space character. + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.EnableTLS +version: 1.0 + +[ClickHouse] SHALL support `` parameter to trigger the use of secure connection to the [LDAP] server. + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.EnableTLS.Options.Default +version: 1.0 + +[ClickHouse] SHALL use `yes` value as the default for `` parameter +to enable SSL/TLS `ldaps://` protocol. + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.EnableTLS.Options.No +version: 1.0 + +[ClickHouse] SHALL support specifying `no` as the value of `` parameter to enable +plain text `ldap://` protocol. + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.EnableTLS.Options.Yes +version: 1.0 + +[ClickHouse] SHALL support specifying `yes` as the value of `` parameter to enable +SSL/TLS `ldaps://` protocol. + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.EnableTLS.Options.StartTLS +version: 1.0 + +[ClickHouse] SHALL support specifying `starttls` as the value of `` parameter to enable +legacy `StartTLS` protocol that used plain text `ldap://` protocol, upgraded to [TLS]. + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.TLSMinimumProtocolVersion +version: 1.0 + +[ClickHouse] SHALL support `` parameter to specify +the minimum protocol version of SSL/TLS. + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.TLSMinimumProtocolVersion.Values +version: 1.0 + +[ClickHouse] SHALL support specifying `ssl2`, `ssl3`, `tls1.0`, `tls1.1`, and `tls1.2` +as a value of the `` parameter. + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.TLSMinimumProtocolVersion.Default +version: 1.0 + +[ClickHouse] SHALL set `tls1.2` as the default value of the `` parameter. + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.TLSRequireCert +version: 1.0 + +[ClickHouse] SHALL support `` parameter to specify [TLS] peer +certificate verification behavior. + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.TLSRequireCert.Options.Default +version: 1.0 + +[ClickHouse] SHALL use `demand` value as the default for the `` parameter. + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.TLSRequireCert.Options.Demand +version: 1.0 + +[ClickHouse] SHALL support specifying `demand` as the value of `` parameter to +enable requesting of client certificate. If no certificate is provided, or a bad certificate is +provided, the session SHALL be immediately terminated. + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.TLSRequireCert.Options.Allow +version: 1.0 + +[ClickHouse] SHALL support specifying `allow` as the value of `` parameter to +enable requesting of client certificate. If no +certificate is provided, the session SHALL proceed normally. +If a bad certificate is provided, it SHALL be ignored and the session SHALL proceed normally. + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.TLSRequireCert.Options.Try +version: 1.0 + +[ClickHouse] SHALL support specifying `try` as the value of `` parameter to +enable requesting of client certificate. If no certificate is provided, the session +SHALL proceed normally. If a bad certificate is provided, the session SHALL be +immediately terminated. + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.TLSRequireCert.Options.Never +version: 1.0 + +[ClickHouse] SHALL support specifying `never` as the value of `` parameter to +disable requesting of client certificate. + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.TLSCertFile +version: 1.0 + +[ClickHouse] SHALL support `` to specify the path to certificate file used by +[ClickHouse] to establish connection with the [LDAP] server. + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.TLSKeyFile +version: 1.0 + +[ClickHouse] SHALL support `` to specify the path to key file for the certificate +specified by the `` parameter. + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.TLSCACertDir +version: 1.0 + +[ClickHouse] SHALL support `` parameter to specify to a path to +the directory containing [CA] certificates used to verify certificates provided by the [LDAP] server. + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.TLSCACertFile +version: 1.0 + +[ClickHouse] SHALL support `` parameter to specify a path to a specific +[CA] certificate file used to verify certificates provided by the [LDAP] server. + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.TLSCipherSuite +version: 1.0 + +[ClickHouse] SHALL support `tls_cipher_suite` parameter to specify allowed cipher suites. +The value SHALL use the same format as the `ciphersuites` in the [OpenSSL Ciphers]. + +For example, + +```xml +ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:AES256-GCM-SHA384 +``` + +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.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.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. + +```xml + + + localhost + 636 + cn= + , ou=users, dc=example, dc=com + 0 + yes + tls1.2 + demand + /path/to/tls_cert_file + /path/to/tls_key_file + /path/to/tls_ca_cert_file + /path/to/tls_ca_cert_dir + ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:AES256-GCM-SHA384 + + +``` + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.LDAPUserDirectory +version: 1.0 + +[ClickHouse] SHALL support `` sub-section in the `` section of the `config.xml` +that SHALL define a external user directory that uses an [LDAP] server as a source of user definitions. + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.LDAPUserDirectory.MoreThanOne +version: 2.0 + +[ClickHouse] SHALL support more than one `` sub-sections in the `` section of the `config.xml` +that SHALL allow to define more than one external user directory that use an [LDAP] server as a source +of user definitions. + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Syntax +version: 1.0 + +[ClickHouse] SHALL support `` section with the following syntax + +```xml + + + + my_ldap_server + + + + + + + +``` + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Server +version: 1.0 + +[ClickHouse] SHALL support `server` parameter in the `` sub-section in the `` +section of the `config.xml` that SHALL specify one of LDAP server names +defined in `` section. + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Server.Empty +version: 1.0 + +[ClickHouse] SHALL return an error if the `server` parameter in the `` sub-section in the `` +is empty. + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Server.Missing +version: 1.0 + +[ClickHouse] SHALL return an error if the `server` parameter in the `` sub-section in the `` +is missing. + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Server.MoreThanOne +version: 1.0 + +[ClickHouse] SHALL only use the first definitition of the `server` parameter in the `` sub-section in the `` +if more than one `server` parameter is defined in the configuration. + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Server.Invalid +version: 1.0 + +[ClickHouse] SHALL return an error if the server specified as the value of the `` +parameter is not defined. + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Roles +version: 1.0 + +[ClickHouse] SHALL support `roles` parameter in the `` sub-section in the `` +section of the `config.xml` that SHALL specify the names of a locally defined roles that SHALL +be assigned to all users retrieved from the [LDAP] server. + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Roles.MoreThanOne +version: 1.0 + +[ClickHouse] SHALL only use the first definitition of the `roles` parameter +in the `` sub-section in the `` +if more than one `roles` parameter is defined in the configuration. + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Roles.Invalid +version: 1.0 + +[ClickHouse] SHALL return an error if the role specified in the `` +parameter does not exist locally. + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Roles.Empty +version: 1.0 + +[ClickHouse] SHALL not allow users authenticated using LDAP external user directory +to perform any action if the `roles` parameter in the `` sub-section in the `` +section is empty. + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Roles.Missing +version: 1.0 + +[ClickHouse] SHALL not allow users authenticated using LDAP external user directory +to perform any action if the `roles` parameter in the `` sub-section in the `` +section is missing. + +#### Authentication + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Authentication.Username.Empty +version: 1.0 + +[ClickHouse] SHALL not support authenticating users with empty username +when using [LDAP] external user directory. + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Authentication.Username.Long +version: 1.0 + +[ClickHouse] SHALL support authenticating users with a long username of at least 256 bytes +when using [LDAP] external user directory. + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Authentication.Username.UTF8 +version: 1.0 + +[ClickHouse] SHALL support authentication users with a username that contains [UTF-8] characters +when using [LDAP] external user directory. + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Authentication.Password.Empty +version: 1.0 + +[ClickHouse] SHALL not support authenticating users with empty passwords +even if an empty password is valid for the user and +is allowed by the [LDAP] server when using [LDAP] external user directory. + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Authentication.Password.Long +version: 1.0 + +[ClickHouse] SHALL support long password of at least 256 bytes +that can be used to authenticate users when using [LDAP] external user directory. + +##### RQ.SRS-009.LDAP.ExternalUserDirectory.Authentication.Password.UTF8 +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/ +* **LDAP**: https://en.wikipedia.org/wiki/Lightweight_Directory_Access_Protocol +* **ClickHouse:** https://clickhouse.tech + +[SRS]: #srs +[Access Control and Account Management]: https://clickhouse.tech/docs/en/operations/access-rights/ +[SRS-007 ClickHouse Authentication of Users via LDAP]: https://github.com/ClickHouse/ClickHouse/blob/master/tests/testflows/ldap/authentication/requirements/requirements.md +[LDAP]: https://en.wikipedia.org/wiki/Lightweight_Directory_Access_Protocol +[ClickHouse]: https://clickhouse.tech +[GitHub Repository]: https://github.com/ClickHouse/ClickHouse/blob/master/tests/testflows/ldap/external_user_directory/requirements/requirements.md +[Revision History]: https://github.com/ClickHouse/ClickHouse/commits/master/tests/testflows/ldap/external_user_directory/requirements/requirements.md +[Git]: https://git-scm.com/ +[GitHub]: https://github.com +''') + RQ_SRS_009_LDAP_ExternalUserDirectory_Authentication = Requirement( name='RQ.SRS-009.LDAP.ExternalUserDirectory.Authentication', version='1.0', @@ -940,9 +1744,41 @@ RQ_SRS_009_LDAP_ExternalUserDirectory_Configuration_Server_TLSCipherSuite = Requ ), link=None) +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) + +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) + 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, @@ -958,6 +1794,7 @@ RQ_SRS_009_LDAP_ExternalUserDirectory_Configuration_Server_Syntax = Requirement( ' 636\n' ' cn=\n' ' , ou=users, dc=example, dc=com\n' + ' 0\n' ' yes\n' ' tls1.2\n' ' demand\n' @@ -1256,5 +2093,57 @@ RQ_SRS_009_LDAP_ExternalUserDirectory_Authentication_Password_UTF8 = Requirement '[ClickHouse] SHALL support [UTF-8] characters in passwords\n' 'used to authenticate users when using [LDAP] external user directory.\n' '\n' + '\n' + ), + link=None) + +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) + +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) + +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) diff --git a/tests/testflows/ldap/external_user_directory/tests/server_config.py b/tests/testflows/ldap/external_user_directory/tests/server_config.py index 5df343b53df..2512a4d88de 100644 --- a/tests/testflows/ldap/external_user_directory/tests/server_config.py +++ b/tests/testflows/ldap/external_user_directory/tests/server_config.py @@ -234,7 +234,7 @@ def auth_dn_value(self): @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 +245,7 @@ def syntax(self): 636 cn= , ou=users, dc=example, dc=com + 0 yes tls1.2 demand @@ -263,6 +264,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", From a35088d681f8df4071e3427e8565bc928c48f222 Mon Sep 17 00:00:00 2001 From: Denis Glazachev Date: Tue, 10 Nov 2020 00:20:34 +0400 Subject: [PATCH 05/16] Add ldap_ prefix to var names --- src/Access/Authentication.cpp | 16 ++++++++-------- src/Access/Authentication.h | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Access/Authentication.cpp b/src/Access/Authentication.cpp index b8328057161..b55ffad2dfa 100644 --- a/src/Access/Authentication.cpp +++ b/src/Access/Authentication.cpp @@ -87,18 +87,18 @@ bool Authentication::isCorrectPassword(const String & password_, const String & ldap_server_params.password = password_; const auto current_params_hash = ldap_server_params.getCoreHash(); - const auto last_check_period = std::chrono::steady_clock::now() - last_successful_password_check_timestamp; + const auto last_check_period = std::chrono::steady_clock::now() - ldap_last_successful_password_check_timestamp; if ( // Forbid the initial values explicitly. - last_successful_password_check_params_hash != 0 && - last_successful_password_check_timestamp != std::chrono::steady_clock::time_point{} && + ldap_last_successful_password_check_params_hash != 0 && + ldap_last_successful_password_check_timestamp != std::chrono::steady_clock::time_point{} && // Check if the caching is enabled at all. ldap_server_params.verification_cooldown > std::chrono::seconds{0} && // Check if we can "reuse" the result of the previous successful password verification. - current_params_hash == last_successful_password_check_params_hash && + current_params_hash == ldap_last_successful_password_check_params_hash && last_check_period >= std::chrono::seconds{0} && last_check_period <= ldap_server_params.verification_cooldown ) @@ -111,13 +111,13 @@ bool Authentication::isCorrectPassword(const String & password_, const String & if (result) { - last_successful_password_check_params_hash = current_params_hash; - last_successful_password_check_timestamp = std::chrono::steady_clock::now(); + ldap_last_successful_password_check_params_hash = current_params_hash; + ldap_last_successful_password_check_timestamp = std::chrono::steady_clock::now(); } else { - last_successful_password_check_params_hash = 0; - last_successful_password_check_timestamp = std::chrono::steady_clock::time_point{}; + ldap_last_successful_password_check_params_hash = 0; + ldap_last_successful_password_check_timestamp = std::chrono::steady_clock::time_point{}; } return result; diff --git a/src/Access/Authentication.h b/src/Access/Authentication.h index 8584c9f7325..65bbcd94720 100644 --- a/src/Access/Authentication.h +++ b/src/Access/Authentication.h @@ -107,8 +107,8 @@ private: // Used and maintained only for LDAP. String server_name; - mutable std::size_t last_successful_password_check_params_hash = 0; - mutable std::chrono::steady_clock::time_point last_successful_password_check_timestamp; + mutable std::size_t ldap_last_successful_password_check_params_hash = 0; + mutable std::chrono::steady_clock::time_point ldap_last_successful_password_check_timestamp; }; From 29c86da543b24dae06cb5c83e012bcdb73ac3147 Mon Sep 17 00:00:00 2001 From: Tai White Date: Tue, 17 Nov 2020 17:32:51 +0100 Subject: [PATCH 06/16] Updated requirements (markdown and python objects), verification_cooldown parameter tests written in authentications.py and server_config.py, helper functions written in common.py --- .../requirements/requirements.md | 610 ++++++ .../requirements/requirements.py | 1687 +++++++++++++++++ ldap/authentication/tests/authentications.py | 969 ++++++++++ ldap/authentication/tests/common.py | 466 +++++ ldap/authentication/tests/server_config.py | 304 +++ 5 files changed, 4036 insertions(+) create mode 100644 ldap/authentication/requirements/requirements.md create mode 100644 ldap/authentication/requirements/requirements.py create mode 100644 ldap/authentication/tests/authentications.py create mode 100644 ldap/authentication/tests/common.py create mode 100644 ldap/authentication/tests/server_config.py diff --git a/ldap/authentication/requirements/requirements.md b/ldap/authentication/requirements/requirements.md new file mode 100644 index 00000000000..17d46584772 --- /dev/null +++ b/ldap/authentication/requirements/requirements.md @@ -0,0 +1,610 @@ +# SRS-007 ClickHouse Authentication of Users via LDAP + +## Table of Contents + +* 1 [Revision History](#revision-history) +* 2 [Introduction](#introduction) +* 3 [Terminology](#terminology) +* 4 [Requirements](#requirements) + * 4.1 [Generic](#generic) + * 4.1.1 [RQ.SRS-007.LDAP.Authentication](#rqsrs-007ldapauthentication) + * 4.1.2 [RQ.SRS-007.LDAP.Authentication.MultipleServers](#rqsrs-007ldapauthenticationmultipleservers) + * 4.1.3 [RQ.SRS-007.LDAP.Authentication.Protocol.PlainText](#rqsrs-007ldapauthenticationprotocolplaintext) + * 4.1.4 [RQ.SRS-007.LDAP.Authentication.Protocol.TLS](#rqsrs-007ldapauthenticationprotocoltls) + * 4.1.5 [RQ.SRS-007.LDAP.Authentication.Protocol.StartTLS](#rqsrs-007ldapauthenticationprotocolstarttls) + * 4.1.6 [RQ.SRS-007.LDAP.Authentication.TLS.Certificate.Validation](#rqsrs-007ldapauthenticationtlscertificatevalidation) + * 4.1.7 [RQ.SRS-007.LDAP.Authentication.TLS.Certificate.SelfSigned](#rqsrs-007ldapauthenticationtlscertificateselfsigned) + * 4.1.8 [RQ.SRS-007.LDAP.Authentication.TLS.Certificate.SpecificCertificationAuthority](#rqsrs-007ldapauthenticationtlscertificatespecificcertificationauthority) + * 4.1.9 [RQ.SRS-007.LDAP.Server.Configuration.Invalid](#rqsrs-007ldapserverconfigurationinvalid) + * 4.1.10 [RQ.SRS-007.LDAP.User.Configuration.Invalid](#rqsrs-007ldapuserconfigurationinvalid) + * 4.1.11 [RQ.SRS-007.LDAP.Authentication.Mechanism.Anonymous](#rqsrs-007ldapauthenticationmechanismanonymous) + * 4.1.12 [RQ.SRS-007.LDAP.Authentication.Mechanism.Unauthenticated](#rqsrs-007ldapauthenticationmechanismunauthenticated) + * 4.1.13 [RQ.SRS-007.LDAP.Authentication.Mechanism.NamePassword](#rqsrs-007ldapauthenticationmechanismnamepassword) + * 4.1.14 [RQ.SRS-007.LDAP.Authentication.Valid](#rqsrs-007ldapauthenticationvalid) + * 4.1.15 [RQ.SRS-007.LDAP.Authentication.Invalid](#rqsrs-007ldapauthenticationinvalid) + * 4.1.16 [RQ.SRS-007.LDAP.Authentication.Invalid.DeletedUser](#rqsrs-007ldapauthenticationinvaliddeleteduser) + * 4.1.17 [RQ.SRS-007.LDAP.Authentication.UsernameChanged](#rqsrs-007ldapauthenticationusernamechanged) + * 4.1.18 [RQ.SRS-007.LDAP.Authentication.PasswordChanged](#rqsrs-007ldapauthenticationpasswordchanged) + * 4.1.19 [RQ.SRS-007.LDAP.Authentication.LDAPServerRestart](#rqsrs-007ldapauthenticationldapserverrestart) + * 4.1.20 [RQ.SRS-007.LDAP.Authentication.ClickHouseServerRestart](#rqsrs-007ldapauthenticationclickhouseserverrestart) + * 4.1.21 [RQ.SRS-007.LDAP.Authentication.Parallel](#rqsrs-007ldapauthenticationparallel) + * 4.1.22 [RQ.SRS-007.LDAP.Authentication.Parallel.ValidAndInvalid](#rqsrs-007ldapauthenticationparallelvalidandinvalid) + * 4.2 [Specific](#specific) + * 4.2.1 [RQ.SRS-007.LDAP.UnreachableServer](#rqsrs-007ldapunreachableserver) + * 4.2.2 [RQ.SRS-007.LDAP.Configuration.Server.Name](#rqsrs-007ldapconfigurationservername) + * 4.2.3 [RQ.SRS-007.LDAP.Configuration.Server.Host](#rqsrs-007ldapconfigurationserverhost) + * 4.2.4 [RQ.SRS-007.LDAP.Configuration.Server.Port](#rqsrs-007ldapconfigurationserverport) + * 4.2.5 [RQ.SRS-007.LDAP.Configuration.Server.Port.Default](#rqsrs-007ldapconfigurationserverportdefault) + * 4.2.6 [RQ.SRS-007.LDAP.Configuration.Server.AuthDN.Prefix](#rqsrs-007ldapconfigurationserverauthdnprefix) + * 4.2.7 [RQ.SRS-007.LDAP.Configuration.Server.AuthDN.Suffix](#rqsrs-007ldapconfigurationserverauthdnsuffix) + * 4.2.8 [RQ.SRS-007.LDAP.Configuration.Server.AuthDN.Value](#rqsrs-007ldapconfigurationserverauthdnvalue) + * 4.2.9 [RQ.SRS-007.LDAP.Configuration.Server.EnableTLS](#rqsrs-007ldapconfigurationserverenabletls) + * 4.2.10 [RQ.SRS-007.LDAP.Configuration.Server.EnableTLS.Options.Default](#rqsrs-007ldapconfigurationserverenabletlsoptionsdefault) + * 4.2.11 [RQ.SRS-007.LDAP.Configuration.Server.EnableTLS.Options.No](#rqsrs-007ldapconfigurationserverenabletlsoptionsno) + * 4.2.12 [RQ.SRS-007.LDAP.Configuration.Server.EnableTLS.Options.Yes](#rqsrs-007ldapconfigurationserverenabletlsoptionsyes) + * 4.2.13 [RQ.SRS-007.LDAP.Configuration.Server.EnableTLS.Options.StartTLS](#rqsrs-007ldapconfigurationserverenabletlsoptionsstarttls) + * 4.2.14 [RQ.SRS-007.LDAP.Configuration.Server.TLSMinimumProtocolVersion](#rqsrs-007ldapconfigurationservertlsminimumprotocolversion) + * 4.2.15 [RQ.SRS-007.LDAP.Configuration.Server.TLSMinimumProtocolVersion.Values](#rqsrs-007ldapconfigurationservertlsminimumprotocolversionvalues) + * 4.2.16 [RQ.SRS-007.LDAP.Configuration.Server.TLSMinimumProtocolVersion.Default](#rqsrs-007ldapconfigurationservertlsminimumprotocolversiondefault) + * 4.2.17 [RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert](#rqsrs-007ldapconfigurationservertlsrequirecert) + * 4.2.18 [RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert.Options.Default](#rqsrs-007ldapconfigurationservertlsrequirecertoptionsdefault) + * 4.2.19 [RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert.Options.Demand](#rqsrs-007ldapconfigurationservertlsrequirecertoptionsdemand) + * 4.2.20 [RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert.Options.Allow](#rqsrs-007ldapconfigurationservertlsrequirecertoptionsallow) + * 4.2.21 [RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert.Options.Try](#rqsrs-007ldapconfigurationservertlsrequirecertoptionstry) + * 4.2.22 [RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert.Options.Never](#rqsrs-007ldapconfigurationservertlsrequirecertoptionsnever) + * 4.2.23 [RQ.SRS-007.LDAP.Configuration.Server.TLSCertFile](#rqsrs-007ldapconfigurationservertlscertfile) + * 4.2.24 [RQ.SRS-007.LDAP.Configuration.Server.TLSKeyFile](#rqsrs-007ldapconfigurationservertlskeyfile) + * 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.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 + +This document is stored in an electronic form using [Git] source control management software +hosted in a [GitHub Repository]. +All the updates are tracked using the [Git]'s [Revision History]. + +## Introduction + +[ClickHouse] currently does not have any integration with [LDAP]. +As the initial step in integrating with [LDAP] this software requirements specification covers +only the requirements to enable authentication of users using an [LDAP] server. + +## Terminology + +* **CA** - + Certificate Authority ([CA]) + +* **LDAP** - + Lightweight Directory Access Protocol ([LDAP]) + +## Requirements + +### Generic + +#### RQ.SRS-007.LDAP.Authentication +version: 1.0 + +[ClickHouse] SHALL support user authentication via an [LDAP] server. + +#### RQ.SRS-007.LDAP.Authentication.MultipleServers +version: 1.0 + +[ClickHouse] SHALL support specifying multiple [LDAP] servers that can be used to authenticate +users. + +#### RQ.SRS-007.LDAP.Authentication.Protocol.PlainText +version: 1.0 + +[ClickHouse] SHALL support user authentication using plain text `ldap://` non secure protocol. + +#### RQ.SRS-007.LDAP.Authentication.Protocol.TLS +version: 1.0 + +[ClickHouse] SHALL support user authentication using `SSL/TLS` `ldaps://` secure protocol. + +#### RQ.SRS-007.LDAP.Authentication.Protocol.StartTLS +version: 1.0 + +[ClickHouse] SHALL support user authentication using legacy `StartTLS` protocol which is a +plain text `ldap://` protocol that is upgraded to [TLS]. + +#### RQ.SRS-007.LDAP.Authentication.TLS.Certificate.Validation +version: 1.0 + +[ClickHouse] SHALL support certificate validation used for [TLS] connections. + +#### RQ.SRS-007.LDAP.Authentication.TLS.Certificate.SelfSigned +version: 1.0 + +[ClickHouse] SHALL support self-signed certificates for [TLS] connections. + +#### RQ.SRS-007.LDAP.Authentication.TLS.Certificate.SpecificCertificationAuthority +version: 1.0 + +[ClickHouse] SHALL support certificates signed by specific Certification Authority for [TLS] connections. + +#### RQ.SRS-007.LDAP.Server.Configuration.Invalid +version: 1.0 + +[ClickHouse] SHALL return an error and prohibit user login if [LDAP] server configuration is not valid. + +#### RQ.SRS-007.LDAP.User.Configuration.Invalid +version: 1.0 + +[ClickHouse] SHALL return an error and prohibit user login if user configuration is not valid. + +#### RQ.SRS-007.LDAP.Authentication.Mechanism.Anonymous +version: 1.0 + +[ClickHouse] SHALL return an error and prohibit authentication using [Anonymous Authentication Mechanism of Simple Bind] +authentication mechanism. + +#### RQ.SRS-007.LDAP.Authentication.Mechanism.Unauthenticated +version: 1.0 + +[ClickHouse] SHALL return an error and prohibit authentication using [Unauthenticated Authentication Mechanism of Simple Bind] +authentication mechanism. + +#### RQ.SRS-007.LDAP.Authentication.Mechanism.NamePassword +version: 1.0 + +[ClickHouse] SHALL allow authentication using only [Name/Password Authentication Mechanism of Simple Bind] +authentication mechanism. + +#### RQ.SRS-007.LDAP.Authentication.Valid +version: 1.0 + +[ClickHouse] SHALL only allow user authentication using [LDAP] server if and only if +user name and password match [LDAP] server records for the user. + +#### RQ.SRS-007.LDAP.Authentication.Invalid +version: 1.0 + +[ClickHouse] SHALL return an error and prohibit authentication if either user name or password +do not match [LDAP] server records for the user. + +#### RQ.SRS-007.LDAP.Authentication.Invalid.DeletedUser +version: 1.0 + +[ClickHouse] SHALL return an error and prohibit authentication if the user +has been deleted from the [LDAP] server. + +#### RQ.SRS-007.LDAP.Authentication.UsernameChanged +version: 1.0 + +[ClickHouse] SHALL return an error and prohibit authentication if the username is changed +on the [LDAP] server. + +#### RQ.SRS-007.LDAP.Authentication.PasswordChanged +version: 1.0 + +[ClickHouse] SHALL return an error and prohibit authentication if the password +for the user is changed on the [LDAP] server. + +#### RQ.SRS-007.LDAP.Authentication.LDAPServerRestart +version: 1.0 + +[ClickHouse] SHALL support authenticating users after [LDAP] server is restarted. + +#### RQ.SRS-007.LDAP.Authentication.ClickHouseServerRestart +version: 1.0 + +[ClickHouse] SHALL support authenticating users after server is restarted. + +#### RQ.SRS-007.LDAP.Authentication.Parallel +version: 1.0 + +[ClickHouse] SHALL support parallel authentication of users using [LDAP] server. + +#### RQ.SRS-007.LDAP.Authentication.Parallel.ValidAndInvalid +version: 1.0 + +[ClickHouse] SHALL support authentication of valid users and +prohibit authentication of invalid users using [LDAP] server +in parallel without having invalid attempts affecting valid authentications. + +### Specific + +#### RQ.SRS-007.LDAP.UnreachableServer +version: 1.0 + +[ClickHouse] SHALL return an error and prohibit user login if [LDAP] server is unreachable. + +#### RQ.SRS-007.LDAP.Configuration.Server.Name +version: 1.0 + +[ClickHouse] SHALL not support empty string as a server name. + +#### RQ.SRS-007.LDAP.Configuration.Server.Host +version: 1.0 + +[ClickHouse] SHALL support `` parameter to specify [LDAP] +server hostname or IP, this parameter SHALL be mandatory and SHALL not be empty. + +#### RQ.SRS-007.LDAP.Configuration.Server.Port +version: 1.0 + +[ClickHouse] SHALL support `` parameter to specify [LDAP] server port. + +#### RQ.SRS-007.LDAP.Configuration.Server.Port.Default +version: 1.0 + +[ClickHouse] SHALL use default port number `636` if `enable_tls` is set to `yes` or `389` otherwise. + +#### RQ.SRS-007.LDAP.Configuration.Server.AuthDN.Prefix +version: 1.0 + +[ClickHouse] SHALL support `` parameter to specify the prefix +of value used to construct the DN to bound to during authentication via [LDAP] server. + +#### RQ.SRS-007.LDAP.Configuration.Server.AuthDN.Suffix +version: 1.0 + +[ClickHouse] SHALL support `` parameter to specify the suffix +of value used to construct the DN to bound to during authentication via [LDAP] server. + +#### RQ.SRS-007.LDAP.Configuration.Server.AuthDN.Value +version: 1.0 + +[ClickHouse] SHALL construct DN as `auth_dn_prefix + escape(user_name) + auth_dn_suffix` string. + +> This implies that auth_dn_suffix should usually have comma ',' as its first non-space character. + +#### RQ.SRS-007.LDAP.Configuration.Server.EnableTLS +version: 1.0 + +[ClickHouse] SHALL support `` parameter to trigger the use of secure connection to the [LDAP] server. + +#### RQ.SRS-007.LDAP.Configuration.Server.EnableTLS.Options.Default +version: 1.0 + +[ClickHouse] SHALL use `yes` value as the default for `` parameter +to enable SSL/TLS `ldaps://` protocol. + +#### RQ.SRS-007.LDAP.Configuration.Server.EnableTLS.Options.No +version: 1.0 + +[ClickHouse] SHALL support specifying `no` as the value of `` parameter to enable +plain text `ldap://` protocol. + +#### RQ.SRS-007.LDAP.Configuration.Server.EnableTLS.Options.Yes +version: 1.0 + +[ClickHouse] SHALL support specifying `yes` as the value of `` parameter to enable +SSL/TLS `ldaps://` protocol. + +#### RQ.SRS-007.LDAP.Configuration.Server.EnableTLS.Options.StartTLS +version: 1.0 + +[ClickHouse] SHALL support specifying `starttls` as the value of `` parameter to enable +legacy `StartTLS` protocol that used plain text `ldap://` protocol, upgraded to [TLS]. + +#### RQ.SRS-007.LDAP.Configuration.Server.TLSMinimumProtocolVersion +version: 1.0 + +[ClickHouse] SHALL support `` parameter to specify +the minimum protocol version of SSL/TLS. + +#### RQ.SRS-007.LDAP.Configuration.Server.TLSMinimumProtocolVersion.Values +version: 1.0 + +[ClickHouse] SHALL support specifying `ssl2`, `ssl3`, `tls1.0`, `tls1.1`, and `tls1.2` +as a value of the `` parameter. + +#### RQ.SRS-007.LDAP.Configuration.Server.TLSMinimumProtocolVersion.Default +version: 1.0 + +[ClickHouse] SHALL set `tls1.2` as the default value of the `` parameter. + +#### RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert +version: 1.0 + +[ClickHouse] SHALL support `` parameter to specify [TLS] peer +certificate verification behavior. + +#### RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert.Options.Default +version: 1.0 + +[ClickHouse] SHALL use `demand` value as the default for the `` parameter. + +#### RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert.Options.Demand +version: 1.0 + +[ClickHouse] SHALL support specifying `demand` as the value of `` parameter to +enable requesting of client certificate. If no certificate is provided, or a bad certificate is +provided, the session SHALL be immediately terminated. + +#### RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert.Options.Allow +version: 1.0 + +[ClickHouse] SHALL support specifying `allow` as the value of `` parameter to +enable requesting of client certificate. If no +certificate is provided, the session SHALL proceed normally. +If a bad certificate is provided, it SHALL be ignored and the session SHALL proceed normally. + +#### RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert.Options.Try +version: 1.0 + +[ClickHouse] SHALL support specifying `try` as the value of `` parameter to +enable requesting of client certificate. If no certificate is provided, the session +SHALL proceed normally. If a bad certificate is provided, the session SHALL be +immediately terminated. + +#### RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert.Options.Never +version: 1.0 + +[ClickHouse] SHALL support specifying `never` as the value of `` parameter to +disable requesting of client certificate. + +#### RQ.SRS-007.LDAP.Configuration.Server.TLSCertFile +version: 1.0 + +[ClickHouse] SHALL support `` to specify the path to certificate file used by +[ClickHouse] to establish connection with the [LDAP] server. + +#### RQ.SRS-007.LDAP.Configuration.Server.TLSKeyFile +version: 1.0 + +[ClickHouse] SHALL support `` to specify the path to key file for the certificate +specified by the `` parameter. + +#### RQ.SRS-007.LDAP.Configuration.Server.TLSCACertDir +version: 1.0 + +[ClickHouse] SHALL support `` parameter to specify to a path to +the directory containing [CA] certificates used to verify certificates provided by the [LDAP] server. + +#### RQ.SRS-007.LDAP.Configuration.Server.TLSCACertFile +version: 1.0 + +[ClickHouse] SHALL support `` parameter to specify a path to a specific +[CA] certificate file used to verify certificates provided by the [LDAP] server. + +#### RQ.SRS-007.LDAP.Configuration.Server.TLSCipherSuite +version: 1.0 + +[ClickHouse] SHALL support `tls_cipher_suite` parameter to specify allowed cipher suites. +The value SHALL use the same format as the `ciphersuites` in the [OpenSSL Ciphers]. + +For example, + +```xml +ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:AES256-GCM-SHA384 +``` + +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.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 + 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. + +```xml + + + localhost + 636 + cn= + , ou=users, dc=example, dc=com + 0 + yes + tls1.2 + demand + /path/to/tls_cert_file + /path/to/tls_key_file + /path/to/tls_ca_cert_file + /path/to/tls_ca_cert_dir + ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:AES256-GCM-SHA384 + + +``` + +#### RQ.SRS-007.LDAP.Configuration.User.RBAC +version: 1.0 + +[ClickHouse] SHALL support creating users identified using an [LDAP] server using +the following RBAC command + +```sql +CREATE USER name IDENTIFIED WITH ldap_server BY 'server_name' +``` + +#### RQ.SRS-007.LDAP.Configuration.User.Syntax +version: 1.0 + +[ClickHouse] SHALL support the following example syntax to create a user that is authenticated using +an [LDAP] server inside the `users.xml` file or any configuration file inside the `users.d` directory. + +```xml + + + + + my_ldap_server + + + + +``` + +#### RQ.SRS-007.LDAP.Configuration.User.Name.Empty +version: 1.0 + +[ClickHouse] SHALL not support empty string as a user name. + +#### RQ.SRS-007.LDAP.Configuration.User.BothPasswordAndLDAP +version: 1.0 + +[ClickHouse] SHALL throw an error if `` is specified for the user and at the same +time user configuration contains any of the `` entries. + +#### RQ.SRS-007.LDAP.Configuration.User.LDAP.InvalidServerName.NotDefined +version: 1.0 + +[ClickHouse] SHALL throw an error during any authentification attempt +if the name of the [LDAP] server used inside the `` entry +is not defined in the `` section. + +#### RQ.SRS-007.LDAP.Configuration.User.LDAP.InvalidServerName.Empty +version: 1.0 + +[ClickHouse] SHALL throw an error during any authentification attempt +if the name of the [LDAP] server used inside the `` entry +is empty. + +#### RQ.SRS-007.LDAP.Configuration.User.OnlyOneServer +version: 1.0 + +[ClickHouse] SHALL support specifying only one [LDAP] server for a given user. + +#### RQ.SRS-007.LDAP.Configuration.User.Name.Long +version: 1.0 + +[ClickHouse] SHALL support long user names of at least 256 bytes +to specify users that can be authenticated using an [LDAP] server. + +#### RQ.SRS-007.LDAP.Configuration.User.Name.UTF8 +version: 1.0 + +[ClickHouse] SHALL support user names that contain [UTF-8] characters. + +#### RQ.SRS-007.LDAP.Authentication.Username.Empty +version: 1.0 + +[ClickHouse] SHALL not support authenticating users with empty username. + +#### RQ.SRS-007.LDAP.Authentication.Username.Long +version: 1.0 + +[ClickHouse] SHALL support authenticating users with a long username of at least 256 bytes. + +#### RQ.SRS-007.LDAP.Authentication.Username.UTF8 +version: 1.0 + +[ClickHouse] SHALL support authentication users with a username that contains [UTF-8] characters. + +#### RQ.SRS-007.LDAP.Authentication.Password.Empty +version: 1.0 + +[ClickHouse] SHALL not support authenticating users with empty passwords +even if an empty password is valid for the user and +is allowed by the [LDAP] server. + +#### RQ.SRS-007.LDAP.Authentication.Password.Long +version: 1.0 + +[ClickHouse] SHALL support long password of at least 256 bytes +that can be used to authenticate users using an [LDAP] server. + +#### RQ.SRS-007.LDAP.Authentication.Password.UTF8 +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 + +[Anonymous Authentication Mechanism of Simple Bind]: https://ldapwiki.com/wiki/Simple%20Authentication#section-Simple+Authentication-AnonymousAuthenticationMechanismOfSimpleBind +[Unauthenticated Authentication Mechanism of Simple Bind]: https://ldapwiki.com/wiki/Simple%20Authentication#section-Simple+Authentication-UnauthenticatedAuthenticationMechanismOfSimpleBind +[Name/Password Authentication Mechanism of Simple Bind]: https://ldapwiki.com/wiki/Simple%20Authentication#section-Simple+Authentication-NamePasswordAuthenticationMechanismOfSimpleBind +[UTF-8]: https://en.wikipedia.org/wiki/UTF-8 +[OpenSSL]: https://www.openssl.org/ +[OpenSSL Ciphers]: https://www.openssl.org/docs/manmaster/man1/openssl-ciphers.html +[CA]: https://en.wikipedia.org/wiki/Certificate_authority +[TLS]: https://en.wikipedia.org/wiki/Transport_Layer_Security +[LDAP]: https://en.wikipedia.org/wiki/Lightweight_Directory_Access_Protocol +[ClickHouse]: https://clickhouse.tech +[GitHub]: https://github.com +[GitHub Repository]: https://github.com/ClickHouse/ClickHouse/blob/master/tests/testflows/ldap/authentication/requirements/requirements.md +[Revision History]: https://github.com/ClickHouse/ClickHouse/commits/master/tests/testflows/ldap/authentication/requirements/requirements.md +[Git]: https://git-scm.com/ diff --git a/ldap/authentication/requirements/requirements.py b/ldap/authentication/requirements/requirements.py new file mode 100644 index 00000000000..f934e6c7a99 --- /dev/null +++ b/ldap/authentication/requirements/requirements.py @@ -0,0 +1,1687 @@ +# These requirements were auto generated +# from software requirements specification (SRS) +# document by TestFlows v1.6.201101.1131719. +# Do not edit by hand but re-generate instead +# using 'tfs requirements generate' command. +from testflows.core import Specification +from testflows.core import Requirement + +SRS_007_ClickHouse_Authentication_of_Users_via_LDAP = Specification( + name='SRS-007 ClickHouse Authentication of Users via LDAP', + description=None, + author=None, + date=None, + status=None, + approved_by=None, + approved_date=None, + approved_version=None, + version=None, + group=None, + type=None, + link=None, + uid=None, + parent=None, + children=None, + content=''' +# SRS-007 ClickHouse Authentication of Users via LDAP + +## Table of Contents + +* 1 [Revision History](#revision-history) +* 2 [Introduction](#introduction) +* 3 [Terminology](#terminology) +* 4 [Requirements](#requirements) + * 4.1 [Generic](#generic) + * 4.1.1 [RQ.SRS-007.LDAP.Authentication](#rqsrs-007ldapauthentication) + * 4.1.2 [RQ.SRS-007.LDAP.Authentication.MultipleServers](#rqsrs-007ldapauthenticationmultipleservers) + * 4.1.3 [RQ.SRS-007.LDAP.Authentication.Protocol.PlainText](#rqsrs-007ldapauthenticationprotocolplaintext) + * 4.1.4 [RQ.SRS-007.LDAP.Authentication.Protocol.TLS](#rqsrs-007ldapauthenticationprotocoltls) + * 4.1.5 [RQ.SRS-007.LDAP.Authentication.Protocol.StartTLS](#rqsrs-007ldapauthenticationprotocolstarttls) + * 4.1.6 [RQ.SRS-007.LDAP.Authentication.TLS.Certificate.Validation](#rqsrs-007ldapauthenticationtlscertificatevalidation) + * 4.1.7 [RQ.SRS-007.LDAP.Authentication.TLS.Certificate.SelfSigned](#rqsrs-007ldapauthenticationtlscertificateselfsigned) + * 4.1.8 [RQ.SRS-007.LDAP.Authentication.TLS.Certificate.SpecificCertificationAuthority](#rqsrs-007ldapauthenticationtlscertificatespecificcertificationauthority) + * 4.1.9 [RQ.SRS-007.LDAP.Server.Configuration.Invalid](#rqsrs-007ldapserverconfigurationinvalid) + * 4.1.10 [RQ.SRS-007.LDAP.User.Configuration.Invalid](#rqsrs-007ldapuserconfigurationinvalid) + * 4.1.11 [RQ.SRS-007.LDAP.Authentication.Mechanism.Anonymous](#rqsrs-007ldapauthenticationmechanismanonymous) + * 4.1.12 [RQ.SRS-007.LDAP.Authentication.Mechanism.Unauthenticated](#rqsrs-007ldapauthenticationmechanismunauthenticated) + * 4.1.13 [RQ.SRS-007.LDAP.Authentication.Mechanism.NamePassword](#rqsrs-007ldapauthenticationmechanismnamepassword) + * 4.1.14 [RQ.SRS-007.LDAP.Authentication.Valid](#rqsrs-007ldapauthenticationvalid) + * 4.1.15 [RQ.SRS-007.LDAP.Authentication.Invalid](#rqsrs-007ldapauthenticationinvalid) + * 4.1.16 [RQ.SRS-007.LDAP.Authentication.Invalid.DeletedUser](#rqsrs-007ldapauthenticationinvaliddeleteduser) + * 4.1.17 [RQ.SRS-007.LDAP.Authentication.UsernameChanged](#rqsrs-007ldapauthenticationusernamechanged) + * 4.1.18 [RQ.SRS-007.LDAP.Authentication.PasswordChanged](#rqsrs-007ldapauthenticationpasswordchanged) + * 4.1.19 [RQ.SRS-007.LDAP.Authentication.LDAPServerRestart](#rqsrs-007ldapauthenticationldapserverrestart) + * 4.1.20 [RQ.SRS-007.LDAP.Authentication.ClickHouseServerRestart](#rqsrs-007ldapauthenticationclickhouseserverrestart) + * 4.1.21 [RQ.SRS-007.LDAP.Authentication.Parallel](#rqsrs-007ldapauthenticationparallel) + * 4.1.22 [RQ.SRS-007.LDAP.Authentication.Parallel.ValidAndInvalid](#rqsrs-007ldapauthenticationparallelvalidandinvalid) + * 4.2 [Specific](#specific) + * 4.2.1 [RQ.SRS-007.LDAP.UnreachableServer](#rqsrs-007ldapunreachableserver) + * 4.2.2 [RQ.SRS-007.LDAP.Configuration.Server.Name](#rqsrs-007ldapconfigurationservername) + * 4.2.3 [RQ.SRS-007.LDAP.Configuration.Server.Host](#rqsrs-007ldapconfigurationserverhost) + * 4.2.4 [RQ.SRS-007.LDAP.Configuration.Server.Port](#rqsrs-007ldapconfigurationserverport) + * 4.2.5 [RQ.SRS-007.LDAP.Configuration.Server.Port.Default](#rqsrs-007ldapconfigurationserverportdefault) + * 4.2.6 [RQ.SRS-007.LDAP.Configuration.Server.AuthDN.Prefix](#rqsrs-007ldapconfigurationserverauthdnprefix) + * 4.2.7 [RQ.SRS-007.LDAP.Configuration.Server.AuthDN.Suffix](#rqsrs-007ldapconfigurationserverauthdnsuffix) + * 4.2.8 [RQ.SRS-007.LDAP.Configuration.Server.AuthDN.Value](#rqsrs-007ldapconfigurationserverauthdnvalue) + * 4.2.9 [RQ.SRS-007.LDAP.Configuration.Server.EnableTLS](#rqsrs-007ldapconfigurationserverenabletls) + * 4.2.10 [RQ.SRS-007.LDAP.Configuration.Server.EnableTLS.Options.Default](#rqsrs-007ldapconfigurationserverenabletlsoptionsdefault) + * 4.2.11 [RQ.SRS-007.LDAP.Configuration.Server.EnableTLS.Options.No](#rqsrs-007ldapconfigurationserverenabletlsoptionsno) + * 4.2.12 [RQ.SRS-007.LDAP.Configuration.Server.EnableTLS.Options.Yes](#rqsrs-007ldapconfigurationserverenabletlsoptionsyes) + * 4.2.13 [RQ.SRS-007.LDAP.Configuration.Server.EnableTLS.Options.StartTLS](#rqsrs-007ldapconfigurationserverenabletlsoptionsstarttls) + * 4.2.14 [RQ.SRS-007.LDAP.Configuration.Server.TLSMinimumProtocolVersion](#rqsrs-007ldapconfigurationservertlsminimumprotocolversion) + * 4.2.15 [RQ.SRS-007.LDAP.Configuration.Server.TLSMinimumProtocolVersion.Values](#rqsrs-007ldapconfigurationservertlsminimumprotocolversionvalues) + * 4.2.16 [RQ.SRS-007.LDAP.Configuration.Server.TLSMinimumProtocolVersion.Default](#rqsrs-007ldapconfigurationservertlsminimumprotocolversiondefault) + * 4.2.17 [RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert](#rqsrs-007ldapconfigurationservertlsrequirecert) + * 4.2.18 [RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert.Options.Default](#rqsrs-007ldapconfigurationservertlsrequirecertoptionsdefault) + * 4.2.19 [RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert.Options.Demand](#rqsrs-007ldapconfigurationservertlsrequirecertoptionsdemand) + * 4.2.20 [RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert.Options.Allow](#rqsrs-007ldapconfigurationservertlsrequirecertoptionsallow) + * 4.2.21 [RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert.Options.Try](#rqsrs-007ldapconfigurationservertlsrequirecertoptionstry) + * 4.2.22 [RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert.Options.Never](#rqsrs-007ldapconfigurationservertlsrequirecertoptionsnever) + * 4.2.23 [RQ.SRS-007.LDAP.Configuration.Server.TLSCertFile](#rqsrs-007ldapconfigurationservertlscertfile) + * 4.2.24 [RQ.SRS-007.LDAP.Configuration.Server.TLSKeyFile](#rqsrs-007ldapconfigurationservertlskeyfile) + * 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.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 + +This document is stored in an electronic form using [Git] source control management software +hosted in a [GitHub Repository]. +All the updates are tracked using the [Git]'s [Revision History]. + +## Introduction + +[ClickHouse] currently does not have any integration with [LDAP]. +As the initial step in integrating with [LDAP] this software requirements specification covers +only the requirements to enable authentication of users using an [LDAP] server. + +## Terminology + +* **CA** - + Certificate Authority ([CA]) + +* **LDAP** - + Lightweight Directory Access Protocol ([LDAP]) + +## Requirements + +### Generic + +#### RQ.SRS-007.LDAP.Authentication +version: 1.0 + +[ClickHouse] SHALL support user authentication via an [LDAP] server. + +#### RQ.SRS-007.LDAP.Authentication.MultipleServers +version: 1.0 + +[ClickHouse] SHALL support specifying multiple [LDAP] servers that can be used to authenticate +users. + +#### RQ.SRS-007.LDAP.Authentication.Protocol.PlainText +version: 1.0 + +[ClickHouse] SHALL support user authentication using plain text `ldap://` non secure protocol. + +#### RQ.SRS-007.LDAP.Authentication.Protocol.TLS +version: 1.0 + +[ClickHouse] SHALL support user authentication using `SSL/TLS` `ldaps://` secure protocol. + +#### RQ.SRS-007.LDAP.Authentication.Protocol.StartTLS +version: 1.0 + +[ClickHouse] SHALL support user authentication using legacy `StartTLS` protocol which is a +plain text `ldap://` protocol that is upgraded to [TLS]. + +#### RQ.SRS-007.LDAP.Authentication.TLS.Certificate.Validation +version: 1.0 + +[ClickHouse] SHALL support certificate validation used for [TLS] connections. + +#### RQ.SRS-007.LDAP.Authentication.TLS.Certificate.SelfSigned +version: 1.0 + +[ClickHouse] SHALL support self-signed certificates for [TLS] connections. + +#### RQ.SRS-007.LDAP.Authentication.TLS.Certificate.SpecificCertificationAuthority +version: 1.0 + +[ClickHouse] SHALL support certificates signed by specific Certification Authority for [TLS] connections. + +#### RQ.SRS-007.LDAP.Server.Configuration.Invalid +version: 1.0 + +[ClickHouse] SHALL return an error and prohibit user login if [LDAP] server configuration is not valid. + +#### RQ.SRS-007.LDAP.User.Configuration.Invalid +version: 1.0 + +[ClickHouse] SHALL return an error and prohibit user login if user configuration is not valid. + +#### RQ.SRS-007.LDAP.Authentication.Mechanism.Anonymous +version: 1.0 + +[ClickHouse] SHALL return an error and prohibit authentication using [Anonymous Authentication Mechanism of Simple Bind] +authentication mechanism. + +#### RQ.SRS-007.LDAP.Authentication.Mechanism.Unauthenticated +version: 1.0 + +[ClickHouse] SHALL return an error and prohibit authentication using [Unauthenticated Authentication Mechanism of Simple Bind] +authentication mechanism. + +#### RQ.SRS-007.LDAP.Authentication.Mechanism.NamePassword +version: 1.0 + +[ClickHouse] SHALL allow authentication using only [Name/Password Authentication Mechanism of Simple Bind] +authentication mechanism. + +#### RQ.SRS-007.LDAP.Authentication.Valid +version: 1.0 + +[ClickHouse] SHALL only allow user authentication using [LDAP] server if and only if +user name and password match [LDAP] server records for the user. + +#### RQ.SRS-007.LDAP.Authentication.Invalid +version: 1.0 + +[ClickHouse] SHALL return an error and prohibit authentication if either user name or password +do not match [LDAP] server records for the user. + +#### RQ.SRS-007.LDAP.Authentication.Invalid.DeletedUser +version: 1.0 + +[ClickHouse] SHALL return an error and prohibit authentication if the user +has been deleted from the [LDAP] server. + +#### RQ.SRS-007.LDAP.Authentication.UsernameChanged +version: 1.0 + +[ClickHouse] SHALL return an error and prohibit authentication if the username is changed +on the [LDAP] server. + +#### RQ.SRS-007.LDAP.Authentication.PasswordChanged +version: 1.0 + +[ClickHouse] SHALL return an error and prohibit authentication if the password +for the user is changed on the [LDAP] server. + +#### RQ.SRS-007.LDAP.Authentication.LDAPServerRestart +version: 1.0 + +[ClickHouse] SHALL support authenticating users after [LDAP] server is restarted. + +#### RQ.SRS-007.LDAP.Authentication.ClickHouseServerRestart +version: 1.0 + +[ClickHouse] SHALL support authenticating users after server is restarted. + +#### RQ.SRS-007.LDAP.Authentication.Parallel +version: 1.0 + +[ClickHouse] SHALL support parallel authentication of users using [LDAP] server. + +#### RQ.SRS-007.LDAP.Authentication.Parallel.ValidAndInvalid +version: 1.0 + +[ClickHouse] SHALL support authentication of valid users and +prohibit authentication of invalid users using [LDAP] server +in parallel without having invalid attempts affecting valid authentications. + +### Specific + +#### RQ.SRS-007.LDAP.UnreachableServer +version: 1.0 + +[ClickHouse] SHALL return an error and prohibit user login if [LDAP] server is unreachable. + +#### RQ.SRS-007.LDAP.Configuration.Server.Name +version: 1.0 + +[ClickHouse] SHALL not support empty string as a server name. + +#### RQ.SRS-007.LDAP.Configuration.Server.Host +version: 1.0 + +[ClickHouse] SHALL support `` parameter to specify [LDAP] +server hostname or IP, this parameter SHALL be mandatory and SHALL not be empty. + +#### RQ.SRS-007.LDAP.Configuration.Server.Port +version: 1.0 + +[ClickHouse] SHALL support `` parameter to specify [LDAP] server port. + +#### RQ.SRS-007.LDAP.Configuration.Server.Port.Default +version: 1.0 + +[ClickHouse] SHALL use default port number `636` if `enable_tls` is set to `yes` or `389` otherwise. + +#### RQ.SRS-007.LDAP.Configuration.Server.AuthDN.Prefix +version: 1.0 + +[ClickHouse] SHALL support `` parameter to specify the prefix +of value used to construct the DN to bound to during authentication via [LDAP] server. + +#### RQ.SRS-007.LDAP.Configuration.Server.AuthDN.Suffix +version: 1.0 + +[ClickHouse] SHALL support `` parameter to specify the suffix +of value used to construct the DN to bound to during authentication via [LDAP] server. + +#### RQ.SRS-007.LDAP.Configuration.Server.AuthDN.Value +version: 1.0 + +[ClickHouse] SHALL construct DN as `auth_dn_prefix + escape(user_name) + auth_dn_suffix` string. + +> This implies that auth_dn_suffix should usually have comma ',' as its first non-space character. + +#### RQ.SRS-007.LDAP.Configuration.Server.EnableTLS +version: 1.0 + +[ClickHouse] SHALL support `` parameter to trigger the use of secure connection to the [LDAP] server. + +#### RQ.SRS-007.LDAP.Configuration.Server.EnableTLS.Options.Default +version: 1.0 + +[ClickHouse] SHALL use `yes` value as the default for `` parameter +to enable SSL/TLS `ldaps://` protocol. + +#### RQ.SRS-007.LDAP.Configuration.Server.EnableTLS.Options.No +version: 1.0 + +[ClickHouse] SHALL support specifying `no` as the value of `` parameter to enable +plain text `ldap://` protocol. + +#### RQ.SRS-007.LDAP.Configuration.Server.EnableTLS.Options.Yes +version: 1.0 + +[ClickHouse] SHALL support specifying `yes` as the value of `` parameter to enable +SSL/TLS `ldaps://` protocol. + +#### RQ.SRS-007.LDAP.Configuration.Server.EnableTLS.Options.StartTLS +version: 1.0 + +[ClickHouse] SHALL support specifying `starttls` as the value of `` parameter to enable +legacy `StartTLS` protocol that used plain text `ldap://` protocol, upgraded to [TLS]. + +#### RQ.SRS-007.LDAP.Configuration.Server.TLSMinimumProtocolVersion +version: 1.0 + +[ClickHouse] SHALL support `` parameter to specify +the minimum protocol version of SSL/TLS. + +#### RQ.SRS-007.LDAP.Configuration.Server.TLSMinimumProtocolVersion.Values +version: 1.0 + +[ClickHouse] SHALL support specifying `ssl2`, `ssl3`, `tls1.0`, `tls1.1`, and `tls1.2` +as a value of the `` parameter. + +#### RQ.SRS-007.LDAP.Configuration.Server.TLSMinimumProtocolVersion.Default +version: 1.0 + +[ClickHouse] SHALL set `tls1.2` as the default value of the `` parameter. + +#### RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert +version: 1.0 + +[ClickHouse] SHALL support `` parameter to specify [TLS] peer +certificate verification behavior. + +#### RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert.Options.Default +version: 1.0 + +[ClickHouse] SHALL use `demand` value as the default for the `` parameter. + +#### RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert.Options.Demand +version: 1.0 + +[ClickHouse] SHALL support specifying `demand` as the value of `` parameter to +enable requesting of client certificate. If no certificate is provided, or a bad certificate is +provided, the session SHALL be immediately terminated. + +#### RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert.Options.Allow +version: 1.0 + +[ClickHouse] SHALL support specifying `allow` as the value of `` parameter to +enable requesting of client certificate. If no +certificate is provided, the session SHALL proceed normally. +If a bad certificate is provided, it SHALL be ignored and the session SHALL proceed normally. + +#### RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert.Options.Try +version: 1.0 + +[ClickHouse] SHALL support specifying `try` as the value of `` parameter to +enable requesting of client certificate. If no certificate is provided, the session +SHALL proceed normally. If a bad certificate is provided, the session SHALL be +immediately terminated. + +#### RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert.Options.Never +version: 1.0 + +[ClickHouse] SHALL support specifying `never` as the value of `` parameter to +disable requesting of client certificate. + +#### RQ.SRS-007.LDAP.Configuration.Server.TLSCertFile +version: 1.0 + +[ClickHouse] SHALL support `` to specify the path to certificate file used by +[ClickHouse] to establish connection with the [LDAP] server. + +#### RQ.SRS-007.LDAP.Configuration.Server.TLSKeyFile +version: 1.0 + +[ClickHouse] SHALL support `` to specify the path to key file for the certificate +specified by the `` parameter. + +#### RQ.SRS-007.LDAP.Configuration.Server.TLSCACertDir +version: 1.0 + +[ClickHouse] SHALL support `` parameter to specify to a path to +the directory containing [CA] certificates used to verify certificates provided by the [LDAP] server. + +#### RQ.SRS-007.LDAP.Configuration.Server.TLSCACertFile +version: 1.0 + +[ClickHouse] SHALL support `` parameter to specify a path to a specific +[CA] certificate file used to verify certificates provided by the [LDAP] server. + +#### RQ.SRS-007.LDAP.Configuration.Server.TLSCipherSuite +version: 1.0 + +[ClickHouse] SHALL support `tls_cipher_suite` parameter to specify allowed cipher suites. +The value SHALL use the same format as the `ciphersuites` in the [OpenSSL Ciphers]. + +For example, + +```xml +ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:AES256-GCM-SHA384 +``` + +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.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 + 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. + +```xml + + + localhost + 636 + cn= + , ou=users, dc=example, dc=com + 0 + yes + tls1.2 + demand + /path/to/tls_cert_file + /path/to/tls_key_file + /path/to/tls_ca_cert_file + /path/to/tls_ca_cert_dir + ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:AES256-GCM-SHA384 + + +``` + +#### RQ.SRS-007.LDAP.Configuration.User.RBAC +version: 1.0 + +[ClickHouse] SHALL support creating users identified using an [LDAP] server using +the following RBAC command + +```sql +CREATE USER name IDENTIFIED WITH ldap_server BY 'server_name' +``` + +#### RQ.SRS-007.LDAP.Configuration.User.Syntax +version: 1.0 + +[ClickHouse] SHALL support the following example syntax to create a user that is authenticated using +an [LDAP] server inside the `users.xml` file or any configuration file inside the `users.d` directory. + +```xml + + + + + my_ldap_server + + + + +``` + +#### RQ.SRS-007.LDAP.Configuration.User.Name.Empty +version: 1.0 + +[ClickHouse] SHALL not support empty string as a user name. + +#### RQ.SRS-007.LDAP.Configuration.User.BothPasswordAndLDAP +version: 1.0 + +[ClickHouse] SHALL throw an error if `` is specified for the user and at the same +time user configuration contains any of the `` entries. + +#### RQ.SRS-007.LDAP.Configuration.User.LDAP.InvalidServerName.NotDefined +version: 1.0 + +[ClickHouse] SHALL throw an error during any authentification attempt +if the name of the [LDAP] server used inside the `` entry +is not defined in the `` section. + +#### RQ.SRS-007.LDAP.Configuration.User.LDAP.InvalidServerName.Empty +version: 1.0 + +[ClickHouse] SHALL throw an error during any authentification attempt +if the name of the [LDAP] server used inside the `` entry +is empty. + +#### RQ.SRS-007.LDAP.Configuration.User.OnlyOneServer +version: 1.0 + +[ClickHouse] SHALL support specifying only one [LDAP] server for a given user. + +#### RQ.SRS-007.LDAP.Configuration.User.Name.Long +version: 1.0 + +[ClickHouse] SHALL support long user names of at least 256 bytes +to specify users that can be authenticated using an [LDAP] server. + +#### RQ.SRS-007.LDAP.Configuration.User.Name.UTF8 +version: 1.0 + +[ClickHouse] SHALL support user names that contain [UTF-8] characters. + +#### RQ.SRS-007.LDAP.Authentication.Username.Empty +version: 1.0 + +[ClickHouse] SHALL not support authenticating users with empty username. + +#### RQ.SRS-007.LDAP.Authentication.Username.Long +version: 1.0 + +[ClickHouse] SHALL support authenticating users with a long username of at least 256 bytes. + +#### RQ.SRS-007.LDAP.Authentication.Username.UTF8 +version: 1.0 + +[ClickHouse] SHALL support authentication users with a username that contains [UTF-8] characters. + +#### RQ.SRS-007.LDAP.Authentication.Password.Empty +version: 1.0 + +[ClickHouse] SHALL not support authenticating users with empty passwords +even if an empty password is valid for the user and +is allowed by the [LDAP] server. + +#### RQ.SRS-007.LDAP.Authentication.Password.Long +version: 1.0 + +[ClickHouse] SHALL support long password of at least 256 bytes +that can be used to authenticate users using an [LDAP] server. + +#### RQ.SRS-007.LDAP.Authentication.Password.UTF8 +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 + +[Anonymous Authentication Mechanism of Simple Bind]: https://ldapwiki.com/wiki/Simple%20Authentication#section-Simple+Authentication-AnonymousAuthenticationMechanismOfSimpleBind +[Unauthenticated Authentication Mechanism of Simple Bind]: https://ldapwiki.com/wiki/Simple%20Authentication#section-Simple+Authentication-UnauthenticatedAuthenticationMechanismOfSimpleBind +[Name/Password Authentication Mechanism of Simple Bind]: https://ldapwiki.com/wiki/Simple%20Authentication#section-Simple+Authentication-NamePasswordAuthenticationMechanismOfSimpleBind +[UTF-8]: https://en.wikipedia.org/wiki/UTF-8 +[OpenSSL]: https://www.openssl.org/ +[OpenSSL Ciphers]: https://www.openssl.org/docs/manmaster/man1/openssl-ciphers.html +[CA]: https://en.wikipedia.org/wiki/Certificate_authority +[TLS]: https://en.wikipedia.org/wiki/Transport_Layer_Security +[LDAP]: https://en.wikipedia.org/wiki/Lightweight_Directory_Access_Protocol +[ClickHouse]: https://clickhouse.tech +[GitHub]: https://github.com +[GitHub Repository]: https://github.com/ClickHouse/ClickHouse/blob/master/tests/testflows/ldap/authentication/requirements/requirements.md +[Revision History]: https://github.com/ClickHouse/ClickHouse/commits/master/tests/testflows/ldap/authentication/requirements/requirements.md +[Git]: https://git-scm.com/ +''') + +RQ_SRS_007_LDAP_Authentication = Requirement( + name='RQ.SRS-007.LDAP.Authentication', + version='1.0', + priority=None, + group=None, + type=None, + uid=None, + description=( + '[ClickHouse] SHALL support user authentication via an [LDAP] server.\n' + '\n' + ), + link=None) + +RQ_SRS_007_LDAP_Authentication_MultipleServers = Requirement( + name='RQ.SRS-007.LDAP.Authentication.MultipleServers', + version='1.0', + priority=None, + group=None, + type=None, + uid=None, + description=( + '[ClickHouse] SHALL support specifying multiple [LDAP] servers that can be used to authenticate\n' + 'users.\n' + '\n' + ), + link=None) + +RQ_SRS_007_LDAP_Authentication_Protocol_PlainText = Requirement( + name='RQ.SRS-007.LDAP.Authentication.Protocol.PlainText', + version='1.0', + priority=None, + group=None, + type=None, + uid=None, + description=( + '[ClickHouse] SHALL support user authentication using plain text `ldap://` non secure protocol.\n' + '\n' + ), + link=None) + +RQ_SRS_007_LDAP_Authentication_Protocol_TLS = Requirement( + name='RQ.SRS-007.LDAP.Authentication.Protocol.TLS', + version='1.0', + priority=None, + group=None, + type=None, + uid=None, + description=( + '[ClickHouse] SHALL support user authentication using `SSL/TLS` `ldaps://` secure protocol.\n' + '\n' + ), + link=None) + +RQ_SRS_007_LDAP_Authentication_Protocol_StartTLS = Requirement( + name='RQ.SRS-007.LDAP.Authentication.Protocol.StartTLS', + version='1.0', + priority=None, + group=None, + type=None, + uid=None, + description=( + '[ClickHouse] SHALL support user authentication using legacy `StartTLS` protocol which is a\n' + 'plain text `ldap://` protocol that is upgraded to [TLS].\n' + '\n' + ), + link=None) + +RQ_SRS_007_LDAP_Authentication_TLS_Certificate_Validation = Requirement( + name='RQ.SRS-007.LDAP.Authentication.TLS.Certificate.Validation', + version='1.0', + priority=None, + group=None, + type=None, + uid=None, + description=( + '[ClickHouse] SHALL support certificate validation used for [TLS] connections.\n' + '\n' + ), + link=None) + +RQ_SRS_007_LDAP_Authentication_TLS_Certificate_SelfSigned = Requirement( + name='RQ.SRS-007.LDAP.Authentication.TLS.Certificate.SelfSigned', + version='1.0', + priority=None, + group=None, + type=None, + uid=None, + description=( + '[ClickHouse] SHALL support self-signed certificates for [TLS] connections.\n' + '\n' + ), + link=None) + +RQ_SRS_007_LDAP_Authentication_TLS_Certificate_SpecificCertificationAuthority = Requirement( + name='RQ.SRS-007.LDAP.Authentication.TLS.Certificate.SpecificCertificationAuthority', + version='1.0', + priority=None, + group=None, + type=None, + uid=None, + description=( + '[ClickHouse] SHALL support certificates signed by specific Certification Authority for [TLS] connections.\n' + '\n' + ), + link=None) + +RQ_SRS_007_LDAP_Server_Configuration_Invalid = Requirement( + name='RQ.SRS-007.LDAP.Server.Configuration.Invalid', + version='1.0', + priority=None, + group=None, + type=None, + uid=None, + description=( + '[ClickHouse] SHALL return an error and prohibit user login if [LDAP] server configuration is not valid.\n' + '\n' + ), + link=None) + +RQ_SRS_007_LDAP_User_Configuration_Invalid = Requirement( + name='RQ.SRS-007.LDAP.User.Configuration.Invalid', + version='1.0', + priority=None, + group=None, + type=None, + uid=None, + description=( + '[ClickHouse] SHALL return an error and prohibit user login if user configuration is not valid.\n' + '\n' + ), + link=None) + +RQ_SRS_007_LDAP_Authentication_Mechanism_Anonymous = Requirement( + name='RQ.SRS-007.LDAP.Authentication.Mechanism.Anonymous', + version='1.0', + priority=None, + group=None, + type=None, + uid=None, + description=( + '[ClickHouse] SHALL return an error and prohibit authentication using [Anonymous Authentication Mechanism of Simple Bind]\n' + 'authentication mechanism.\n' + '\n' + ), + link=None) + +RQ_SRS_007_LDAP_Authentication_Mechanism_Unauthenticated = Requirement( + name='RQ.SRS-007.LDAP.Authentication.Mechanism.Unauthenticated', + version='1.0', + priority=None, + group=None, + type=None, + uid=None, + description=( + '[ClickHouse] SHALL return an error and prohibit authentication using [Unauthenticated Authentication Mechanism of Simple Bind]\n' + 'authentication mechanism.\n' + '\n' + ), + link=None) + +RQ_SRS_007_LDAP_Authentication_Mechanism_NamePassword = Requirement( + name='RQ.SRS-007.LDAP.Authentication.Mechanism.NamePassword', + version='1.0', + priority=None, + group=None, + type=None, + uid=None, + description=( + '[ClickHouse] SHALL allow authentication using only [Name/Password Authentication Mechanism of Simple Bind]\n' + 'authentication mechanism.\n' + '\n' + ), + link=None) + +RQ_SRS_007_LDAP_Authentication_Valid = Requirement( + name='RQ.SRS-007.LDAP.Authentication.Valid', + version='1.0', + priority=None, + group=None, + type=None, + uid=None, + description=( + '[ClickHouse] SHALL only allow user authentication using [LDAP] server if and only if\n' + 'user name and password match [LDAP] server records for the user.\n' + '\n' + ), + link=None) + +RQ_SRS_007_LDAP_Authentication_Invalid = Requirement( + name='RQ.SRS-007.LDAP.Authentication.Invalid', + version='1.0', + priority=None, + group=None, + type=None, + uid=None, + description=( + '[ClickHouse] SHALL return an error and prohibit authentication if either user name or password\n' + 'do not match [LDAP] server records for the user.\n' + '\n' + ), + link=None) + +RQ_SRS_007_LDAP_Authentication_Invalid_DeletedUser = Requirement( + name='RQ.SRS-007.LDAP.Authentication.Invalid.DeletedUser', + version='1.0', + priority=None, + group=None, + type=None, + uid=None, + description=( + '[ClickHouse] SHALL return an error and prohibit authentication if the user\n' + 'has been deleted from the [LDAP] server.\n' + '\n' + ), + link=None) + +RQ_SRS_007_LDAP_Authentication_UsernameChanged = Requirement( + name='RQ.SRS-007.LDAP.Authentication.UsernameChanged', + version='1.0', + priority=None, + group=None, + type=None, + uid=None, + description=( + '[ClickHouse] SHALL return an error and prohibit authentication if the username is changed\n' + 'on the [LDAP] server.\n' + '\n' + ), + link=None) + +RQ_SRS_007_LDAP_Authentication_PasswordChanged = Requirement( + name='RQ.SRS-007.LDAP.Authentication.PasswordChanged', + version='1.0', + priority=None, + group=None, + type=None, + uid=None, + description=( + '[ClickHouse] SHALL return an error and prohibit authentication if the password\n' + 'for the user is changed on the [LDAP] server.\n' + '\n' + ), + link=None) + +RQ_SRS_007_LDAP_Authentication_LDAPServerRestart = Requirement( + name='RQ.SRS-007.LDAP.Authentication.LDAPServerRestart', + version='1.0', + priority=None, + group=None, + type=None, + uid=None, + description=( + '[ClickHouse] SHALL support authenticating users after [LDAP] server is restarted.\n' + '\n' + ), + link=None) + +RQ_SRS_007_LDAP_Authentication_ClickHouseServerRestart = Requirement( + name='RQ.SRS-007.LDAP.Authentication.ClickHouseServerRestart', + version='1.0', + priority=None, + group=None, + type=None, + uid=None, + description=( + '[ClickHouse] SHALL support authenticating users after server is restarted.\n' + '\n' + ), + link=None) + +RQ_SRS_007_LDAP_Authentication_Parallel = Requirement( + name='RQ.SRS-007.LDAP.Authentication.Parallel', + version='1.0', + priority=None, + group=None, + type=None, + uid=None, + description=( + '[ClickHouse] SHALL support parallel authentication of users using [LDAP] server.\n' + '\n' + ), + link=None) + +RQ_SRS_007_LDAP_Authentication_Parallel_ValidAndInvalid = Requirement( + name='RQ.SRS-007.LDAP.Authentication.Parallel.ValidAndInvalid', + version='1.0', + priority=None, + group=None, + type=None, + uid=None, + description=( + '[ClickHouse] SHALL support authentication of valid users and\n' + 'prohibit authentication of invalid users using [LDAP] server\n' + 'in parallel without having invalid attempts affecting valid authentications.\n' + '\n' + ), + link=None) + +RQ_SRS_007_LDAP_UnreachableServer = Requirement( + name='RQ.SRS-007.LDAP.UnreachableServer', + version='1.0', + priority=None, + group=None, + type=None, + uid=None, + description=( + '[ClickHouse] SHALL return an error and prohibit user login if [LDAP] server is unreachable.\n' + '\n' + ), + link=None) + +RQ_SRS_007_LDAP_Configuration_Server_Name = Requirement( + name='RQ.SRS-007.LDAP.Configuration.Server.Name', + version='1.0', + priority=None, + group=None, + type=None, + uid=None, + description=( + '[ClickHouse] SHALL not support empty string as a server name.\n' + '\n' + ), + link=None) + +RQ_SRS_007_LDAP_Configuration_Server_Host = Requirement( + name='RQ.SRS-007.LDAP.Configuration.Server.Host', + version='1.0', + priority=None, + group=None, + type=None, + uid=None, + description=( + '[ClickHouse] SHALL support `` parameter to specify [LDAP]\n' + 'server hostname or IP, this parameter SHALL be mandatory and SHALL not be empty.\n' + '\n' + ), + link=None) + +RQ_SRS_007_LDAP_Configuration_Server_Port = Requirement( + name='RQ.SRS-007.LDAP.Configuration.Server.Port', + version='1.0', + priority=None, + group=None, + type=None, + uid=None, + description=( + '[ClickHouse] SHALL support `` parameter to specify [LDAP] server port.\n' + '\n' + ), + link=None) + +RQ_SRS_007_LDAP_Configuration_Server_Port_Default = Requirement( + name='RQ.SRS-007.LDAP.Configuration.Server.Port.Default', + version='1.0', + priority=None, + group=None, + type=None, + uid=None, + description=( + '[ClickHouse] SHALL use default port number `636` if `enable_tls` is set to `yes` or `389` otherwise.\n' + '\n' + ), + link=None) + +RQ_SRS_007_LDAP_Configuration_Server_AuthDN_Prefix = Requirement( + name='RQ.SRS-007.LDAP.Configuration.Server.AuthDN.Prefix', + version='1.0', + priority=None, + group=None, + type=None, + uid=None, + description=( + '[ClickHouse] SHALL support `` parameter to specify the prefix\n' + 'of value used to construct the DN to bound to during authentication via [LDAP] server.\n' + '\n' + ), + link=None) + +RQ_SRS_007_LDAP_Configuration_Server_AuthDN_Suffix = Requirement( + name='RQ.SRS-007.LDAP.Configuration.Server.AuthDN.Suffix', + version='1.0', + priority=None, + group=None, + type=None, + uid=None, + description=( + '[ClickHouse] SHALL support `` parameter to specify the suffix\n' + 'of value used to construct the DN to bound to during authentication via [LDAP] server.\n' + '\n' + ), + link=None) + +RQ_SRS_007_LDAP_Configuration_Server_AuthDN_Value = Requirement( + name='RQ.SRS-007.LDAP.Configuration.Server.AuthDN.Value', + version='1.0', + priority=None, + group=None, + type=None, + uid=None, + description=( + '[ClickHouse] SHALL construct DN as `auth_dn_prefix + escape(user_name) + auth_dn_suffix` string.\n' + '\n' + "> This implies that auth_dn_suffix should usually have comma ',' as its first non-space character.\n" + '\n' + ), + link=None) + +RQ_SRS_007_LDAP_Configuration_Server_EnableTLS = Requirement( + name='RQ.SRS-007.LDAP.Configuration.Server.EnableTLS', + version='1.0', + priority=None, + group=None, + type=None, + uid=None, + description=( + '[ClickHouse] SHALL support `` parameter to trigger the use of secure connection to the [LDAP] server.\n' + '\n' + ), + link=None) + +RQ_SRS_007_LDAP_Configuration_Server_EnableTLS_Options_Default = Requirement( + name='RQ.SRS-007.LDAP.Configuration.Server.EnableTLS.Options.Default', + version='1.0', + priority=None, + group=None, + type=None, + uid=None, + description=( + '[ClickHouse] SHALL use `yes` value as the default for `` parameter\n' + 'to enable SSL/TLS `ldaps://` protocol.\n' + '\n' + ), + link=None) + +RQ_SRS_007_LDAP_Configuration_Server_EnableTLS_Options_No = Requirement( + name='RQ.SRS-007.LDAP.Configuration.Server.EnableTLS.Options.No', + version='1.0', + priority=None, + group=None, + type=None, + uid=None, + description=( + '[ClickHouse] SHALL support specifying `no` as the value of `` parameter to enable\n' + 'plain text `ldap://` protocol.\n' + '\n' + ), + link=None) + +RQ_SRS_007_LDAP_Configuration_Server_EnableTLS_Options_Yes = Requirement( + name='RQ.SRS-007.LDAP.Configuration.Server.EnableTLS.Options.Yes', + version='1.0', + priority=None, + group=None, + type=None, + uid=None, + description=( + '[ClickHouse] SHALL support specifying `yes` as the value of `` parameter to enable\n' + 'SSL/TLS `ldaps://` protocol.\n' + '\n' + ), + link=None) + +RQ_SRS_007_LDAP_Configuration_Server_EnableTLS_Options_StartTLS = Requirement( + name='RQ.SRS-007.LDAP.Configuration.Server.EnableTLS.Options.StartTLS', + version='1.0', + priority=None, + group=None, + type=None, + uid=None, + description=( + '[ClickHouse] SHALL support specifying `starttls` as the value of `` parameter to enable\n' + 'legacy `StartTLS` protocol that used plain text `ldap://` protocol, upgraded to [TLS].\n' + '\n' + ), + link=None) + +RQ_SRS_007_LDAP_Configuration_Server_TLSMinimumProtocolVersion = Requirement( + name='RQ.SRS-007.LDAP.Configuration.Server.TLSMinimumProtocolVersion', + version='1.0', + priority=None, + group=None, + type=None, + uid=None, + description=( + '[ClickHouse] SHALL support `` parameter to specify\n' + 'the minimum protocol version of SSL/TLS.\n' + '\n' + ), + link=None) + +RQ_SRS_007_LDAP_Configuration_Server_TLSMinimumProtocolVersion_Values = Requirement( + name='RQ.SRS-007.LDAP.Configuration.Server.TLSMinimumProtocolVersion.Values', + version='1.0', + priority=None, + group=None, + type=None, + uid=None, + description=( + '[ClickHouse] SHALL support specifying `ssl2`, `ssl3`, `tls1.0`, `tls1.1`, and `tls1.2`\n' + 'as a value of the `` parameter.\n' + '\n' + ), + link=None) + +RQ_SRS_007_LDAP_Configuration_Server_TLSMinimumProtocolVersion_Default = Requirement( + name='RQ.SRS-007.LDAP.Configuration.Server.TLSMinimumProtocolVersion.Default', + version='1.0', + priority=None, + group=None, + type=None, + uid=None, + description=( + '[ClickHouse] SHALL set `tls1.2` as the default value of the `` parameter.\n' + '\n' + ), + link=None) + +RQ_SRS_007_LDAP_Configuration_Server_TLSRequireCert = Requirement( + name='RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert', + version='1.0', + priority=None, + group=None, + type=None, + uid=None, + description=( + '[ClickHouse] SHALL support `` parameter to specify [TLS] peer\n' + 'certificate verification behavior.\n' + '\n' + ), + link=None) + +RQ_SRS_007_LDAP_Configuration_Server_TLSRequireCert_Options_Default = Requirement( + name='RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert.Options.Default', + version='1.0', + priority=None, + group=None, + type=None, + uid=None, + description=( + '[ClickHouse] SHALL use `demand` value as the default for the `` parameter.\n' + '\n' + ), + link=None) + +RQ_SRS_007_LDAP_Configuration_Server_TLSRequireCert_Options_Demand = Requirement( + name='RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert.Options.Demand', + version='1.0', + priority=None, + group=None, + type=None, + uid=None, + description=( + '[ClickHouse] SHALL support specifying `demand` as the value of `` parameter to\n' + 'enable requesting of client certificate. If no certificate is provided, or a bad certificate is\n' + 'provided, the session SHALL be immediately terminated.\n' + '\n' + ), + link=None) + +RQ_SRS_007_LDAP_Configuration_Server_TLSRequireCert_Options_Allow = Requirement( + name='RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert.Options.Allow', + version='1.0', + priority=None, + group=None, + type=None, + uid=None, + description=( + '[ClickHouse] SHALL support specifying `allow` as the value of `` parameter to\n' + 'enable requesting of client certificate. If no\n' + 'certificate is provided, the session SHALL proceed normally.\n' + 'If a bad certificate is provided, it SHALL be ignored and the session SHALL proceed normally.\n' + '\n' + ), + link=None) + +RQ_SRS_007_LDAP_Configuration_Server_TLSRequireCert_Options_Try = Requirement( + name='RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert.Options.Try', + version='1.0', + priority=None, + group=None, + type=None, + uid=None, + description=( + '[ClickHouse] SHALL support specifying `try` as the value of `` parameter to\n' + 'enable requesting of client certificate. If no certificate is provided, the session\n' + 'SHALL proceed normally. If a bad certificate is provided, the session SHALL be\n' + 'immediately terminated.\n' + '\n' + ), + link=None) + +RQ_SRS_007_LDAP_Configuration_Server_TLSRequireCert_Options_Never = Requirement( + name='RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert.Options.Never', + version='1.0', + priority=None, + group=None, + type=None, + uid=None, + description=( + '[ClickHouse] SHALL support specifying `never` as the value of `` parameter to\n' + 'disable requesting of client certificate.\n' + '\n' + ), + link=None) + +RQ_SRS_007_LDAP_Configuration_Server_TLSCertFile = Requirement( + name='RQ.SRS-007.LDAP.Configuration.Server.TLSCertFile', + version='1.0', + priority=None, + group=None, + type=None, + uid=None, + description=( + '[ClickHouse] SHALL support `` to specify the path to certificate file used by\n' + '[ClickHouse] to establish connection with the [LDAP] server.\n' + '\n' + ), + link=None) + +RQ_SRS_007_LDAP_Configuration_Server_TLSKeyFile = Requirement( + name='RQ.SRS-007.LDAP.Configuration.Server.TLSKeyFile', + version='1.0', + priority=None, + group=None, + type=None, + uid=None, + description=( + '[ClickHouse] SHALL support `` to specify the path to key file for the certificate\n' + 'specified by the `` parameter.\n' + '\n' + ), + link=None) + +RQ_SRS_007_LDAP_Configuration_Server_TLSCACertDir = Requirement( + name='RQ.SRS-007.LDAP.Configuration.Server.TLSCACertDir', + version='1.0', + priority=None, + group=None, + type=None, + uid=None, + description=( + '[ClickHouse] SHALL support `` parameter to specify to a path to\n' + 'the directory containing [CA] certificates used to verify certificates provided by the [LDAP] server.\n' + '\n' + ), + link=None) + +RQ_SRS_007_LDAP_Configuration_Server_TLSCACertFile = Requirement( + name='RQ.SRS-007.LDAP.Configuration.Server.TLSCACertFile', + version='1.0', + priority=None, + group=None, + type=None, + uid=None, + description=( + '[ClickHouse] SHALL support `` parameter to specify a path to a specific\n' + '[CA] certificate file used to verify certificates provided by the [LDAP] server.\n' + '\n' + ), + link=None) + +RQ_SRS_007_LDAP_Configuration_Server_TLSCipherSuite = Requirement( + name='RQ.SRS-007.LDAP.Configuration.Server.TLSCipherSuite', + version='1.0', + priority=None, + group=None, + type=None, + uid=None, + description=( + '[ClickHouse] SHALL support `tls_cipher_suite` parameter to specify allowed cipher suites.\n' + 'The value SHALL use the same format as the `ciphersuites` in the [OpenSSL Ciphers].\n' + '\n' + 'For example,\n' + '\n' + '```xml\n' + 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:AES256-GCM-SHA384\n' + '```\n' + '\n' + 'The available suites SHALL depend on the [OpenSSL] library version and variant used to build\n' + '[ClickHouse] and therefore might change.\n' + '\n' + ), + link=None) + +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) + +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) + +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' + ' 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) + +RQ_SRS_007_LDAP_Configuration_Server_Syntax = Requirement( + name='RQ.SRS-007.LDAP.Configuration.Server.Syntax', + version='2.0', + priority=None, + group=None, + type=None, + uid=None, + description=( + '[ClickHouse] SHALL support the following example syntax to create an entry for an [LDAP] server inside the `config.xml`\n' + 'configuration file or of any configuration file inside the `config.d` directory.\n' + '\n' + '```xml\n' + '\n' + ' \n' + ' localhost\n' + ' 636\n' + ' cn=\n' + ' , ou=users, dc=example, dc=com\n' + ' 0\n' + ' yes\n' + ' tls1.2\n' + ' demand\n' + ' /path/to/tls_cert_file\n' + ' /path/to/tls_key_file\n' + ' /path/to/tls_ca_cert_file\n' + ' /path/to/tls_ca_cert_dir\n' + ' ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:AES256-GCM-SHA384\n' + ' \n' + '\n' + '```\n' + '\n' + ), + link=None) + +RQ_SRS_007_LDAP_Configuration_User_RBAC = Requirement( + name='RQ.SRS-007.LDAP.Configuration.User.RBAC', + version='1.0', + priority=None, + group=None, + type=None, + uid=None, + description=( + '[ClickHouse] SHALL support creating users identified using an [LDAP] server using\n' + 'the following RBAC command\n' + '\n' + '```sql\n' + "CREATE USER name IDENTIFIED WITH ldap_server BY 'server_name'\n" + '```\n' + '\n' + ), + link=None) + +RQ_SRS_007_LDAP_Configuration_User_Syntax = Requirement( + name='RQ.SRS-007.LDAP.Configuration.User.Syntax', + version='1.0', + priority=None, + group=None, + type=None, + uid=None, + description=( + '[ClickHouse] SHALL support the following example syntax to create a user that is authenticated using\n' + 'an [LDAP] server inside the `users.xml` file or any configuration file inside the `users.d` directory.\n' + '\n' + '```xml\n' + '\n' + ' \n' + ' \n' + ' \n' + ' my_ldap_server\n' + ' \n' + ' \n' + ' \n' + '\n' + '```\n' + '\n' + ), + link=None) + +RQ_SRS_007_LDAP_Configuration_User_Name_Empty = Requirement( + name='RQ.SRS-007.LDAP.Configuration.User.Name.Empty', + version='1.0', + priority=None, + group=None, + type=None, + uid=None, + description=( + '[ClickHouse] SHALL not support empty string as a user name.\n' + '\n' + ), + link=None) + +RQ_SRS_007_LDAP_Configuration_User_BothPasswordAndLDAP = Requirement( + name='RQ.SRS-007.LDAP.Configuration.User.BothPasswordAndLDAP', + version='1.0', + priority=None, + group=None, + type=None, + uid=None, + description=( + '[ClickHouse] SHALL throw an error if `` is specified for the user and at the same\n' + 'time user configuration contains any of the `` entries.\n' + '\n' + ), + link=None) + +RQ_SRS_007_LDAP_Configuration_User_LDAP_InvalidServerName_NotDefined = Requirement( + name='RQ.SRS-007.LDAP.Configuration.User.LDAP.InvalidServerName.NotDefined', + version='1.0', + priority=None, + group=None, + type=None, + uid=None, + description=( + '[ClickHouse] SHALL throw an error during any authentification attempt\n' + 'if the name of the [LDAP] server used inside the `` entry\n' + 'is not defined in the `` section.\n' + '\n' + ), + link=None) + +RQ_SRS_007_LDAP_Configuration_User_LDAP_InvalidServerName_Empty = Requirement( + name='RQ.SRS-007.LDAP.Configuration.User.LDAP.InvalidServerName.Empty', + version='1.0', + priority=None, + group=None, + type=None, + uid=None, + description=( + '[ClickHouse] SHALL throw an error during any authentification attempt\n' + 'if the name of the [LDAP] server used inside the `` entry\n' + 'is empty.\n' + '\n' + ), + link=None) + +RQ_SRS_007_LDAP_Configuration_User_OnlyOneServer = Requirement( + name='RQ.SRS-007.LDAP.Configuration.User.OnlyOneServer', + version='1.0', + priority=None, + group=None, + type=None, + uid=None, + description=( + '[ClickHouse] SHALL support specifying only one [LDAP] server for a given user.\n' + '\n' + ), + link=None) + +RQ_SRS_007_LDAP_Configuration_User_Name_Long = Requirement( + name='RQ.SRS-007.LDAP.Configuration.User.Name.Long', + version='1.0', + priority=None, + group=None, + type=None, + uid=None, + description=( + '[ClickHouse] SHALL support long user names of at least 256 bytes\n' + 'to specify users that can be authenticated using an [LDAP] server.\n' + '\n' + ), + link=None) + +RQ_SRS_007_LDAP_Configuration_User_Name_UTF8 = Requirement( + name='RQ.SRS-007.LDAP.Configuration.User.Name.UTF8', + version='1.0', + priority=None, + group=None, + type=None, + uid=None, + description=( + '[ClickHouse] SHALL support user names that contain [UTF-8] characters.\n' + '\n' + ), + link=None) + +RQ_SRS_007_LDAP_Authentication_Username_Empty = Requirement( + name='RQ.SRS-007.LDAP.Authentication.Username.Empty', + version='1.0', + priority=None, + group=None, + type=None, + uid=None, + description=( + '[ClickHouse] SHALL not support authenticating users with empty username.\n' + '\n' + ), + link=None) + +RQ_SRS_007_LDAP_Authentication_Username_Long = Requirement( + name='RQ.SRS-007.LDAP.Authentication.Username.Long', + version='1.0', + priority=None, + group=None, + type=None, + uid=None, + description=( + '[ClickHouse] SHALL support authenticating users with a long username of at least 256 bytes.\n' + '\n' + ), + link=None) + +RQ_SRS_007_LDAP_Authentication_Username_UTF8 = Requirement( + name='RQ.SRS-007.LDAP.Authentication.Username.UTF8', + version='1.0', + priority=None, + group=None, + type=None, + uid=None, + description=( + '[ClickHouse] SHALL support authentication users with a username that contains [UTF-8] characters.\n' + '\n' + ), + link=None) + +RQ_SRS_007_LDAP_Authentication_Password_Empty = Requirement( + name='RQ.SRS-007.LDAP.Authentication.Password.Empty', + version='1.0', + priority=None, + group=None, + type=None, + uid=None, + description=( + '[ClickHouse] SHALL not support authenticating users with empty passwords\n' + 'even if an empty password is valid for the user and\n' + 'is allowed by the [LDAP] server.\n' + '\n' + ), + link=None) + +RQ_SRS_007_LDAP_Authentication_Password_Long = Requirement( + name='RQ.SRS-007.LDAP.Authentication.Password.Long', + version='1.0', + priority=None, + group=None, + type=None, + uid=None, + description=( + '[ClickHouse] SHALL support long password of at least 256 bytes\n' + 'that can be used to authenticate users using an [LDAP] server.\n' + '\n' + ), + link=None) + +RQ_SRS_007_LDAP_Authentication_Password_UTF8 = Requirement( + name='RQ.SRS-007.LDAP.Authentication.Password.UTF8', + version='1.0', + priority=None, + group=None, + type=None, + uid=None, + description=( + '[ClickHouse] SHALL support [UTF-8] characters in passwords\n' + 'used to authenticate users using an [LDAP] server.\n' + '\n' + ), + link=None) + +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) + +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) + +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) diff --git a/ldap/authentication/tests/authentications.py b/ldap/authentication/tests/authentications.py new file mode 100644 index 00000000000..b1a109f87ce --- /dev/null +++ b/ldap/authentication/tests/authentications.py @@ -0,0 +1,969 @@ +# -*- coding: utf-8 -*- +import random +import time + +from multiprocessing.dummy import Pool +from testflows.core import * +from testflows.asserts import error +from ldap.authentication.tests.common import * +from ldap.authentication.requirements import * + +servers = { + "openldap1": { + "host": "openldap1", + "port": "389", + "enable_tls": "no", + "auth_dn_prefix": "cn=", + "auth_dn_suffix": ",ou=users,dc=company,dc=com" + }, + "openldap2": { + "host": "openldap2", + "port": "636", + "enable_tls": "yes", + "auth_dn_prefix": "cn=", + "auth_dn_suffix": ",ou=users,dc=company,dc=com", + "tls_require_cert": "never", + } +} + +@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. + """ + self.context.node.query("SELECT 1", + settings=[("user", username), ("password", password)], + exitcode=exitcode or 0, + message=message, steps=steps) + +@TestScenario +def add_user_to_ldap_and_login(self, server, user=None, ch_user=None, login=None, exitcode=None, message=None, rbac=False): + """Add user to LDAP and ClickHouse and then try to login. + """ + self.context.ldap_node = self.context.cluster.node(server) + + if ch_user is None: + ch_user = {} + if login is None: + login = {} + if user is None: + user = {"cn": "myuser", "userpassword": "myuser"} + + with ldap_user(**user) as user: + ch_user["username"] = ch_user.get("username", user["cn"]) + ch_user["server"] = ch_user.get("server", user["_server"]) + + with ldap_authenticated_users(ch_user, config_file=f"ldap_users_{getuid()}.xml", restart=True, rbac=rbac): + username = login.get("username", user["cn"]) + password = login.get("password", user["userpassword"]) + login_and_execute_query(username=username, password=password, exitcode=exitcode, message=message) + +@TestScenario +@Requirements( + RQ_SRS_007_LDAP_Authentication_Parallel("1.0"), + RQ_SRS_007_LDAP_Authentication_Parallel_ValidAndInvalid("1.0") +) +def parallel_login(self, server, user_count=10, timeout=200, rbac=False): + """Check that login of valid and invalid LDAP authenticated users works in parallel. + """ + self.context.ldap_node = self.context.cluster.node(server) + user = None + + users = [{"cn": f"parallel_user{i}", "userpassword": randomword(20)} for i in range(user_count)] + + with ldap_users(*users): + with ldap_authenticated_users(*[{"username": user["cn"], "server": server} for user in users], rbac=rbac): + + def login_with_valid_username_and_password(users, i, iterations=10): + with When(f"valid users try to login #{i}"): + for i in range(iterations): + random_user = users[random.randint(0, len(users)-1)] + login_and_execute_query(username=random_user["cn"], password=random_user["userpassword"], steps=False) + + def login_with_valid_username_and_invalid_password(users, i, iterations=10): + with When(f"users try to login with valid username and invalid password #{i}"): + for i in range(iterations): + random_user = users[random.randint(0, len(users)-1)] + login_and_execute_query(username=random_user["cn"], + password=(random_user["userpassword"] + randomword(1)), + exitcode=4, + message=f"DB::Exception: {random_user['cn']}: Authentication failed: password is incorrect or there is no user with such name", + steps=False) + + def login_with_invalid_username_and_valid_password(users, i, iterations=10): + with When(f"users try to login with invalid username and valid password #{i}"): + for i in range(iterations): + random_user = dict(users[random.randint(0, len(users)-1)]) + random_user["cn"] += randomword(1) + login_and_execute_query(username=random_user["cn"], + password=random_user["userpassword"], + exitcode=4, + message=f"DB::Exception: {random_user['cn']}: Authentication failed: password is incorrect or there is no user with such name", + steps=False) + + with When("I login in parallel"): + p = Pool(15) + tasks = [] + for i in range(5): + tasks.append(p.apply_async(login_with_valid_username_and_password, (users, i, 50,))) + tasks.append(p.apply_async(login_with_valid_username_and_invalid_password, (users, i, 50,))) + tasks.append(p.apply_async(login_with_invalid_username_and_valid_password, (users, i, 50,))) + + with Then("it should work"): + for task in tasks: + task.get(timeout=timeout) + +@TestScenario +@Requirements( + RQ_SRS_007_LDAP_Authentication_Invalid("1.0"), + RQ_SRS_007_LDAP_Authentication_Invalid_DeletedUser("1.0") +) +def login_after_user_is_deleted_from_ldap(self, server, rbac=False): + """Check that login fails after user is deleted from LDAP. + """ + self.context.ldap_node = self.context.cluster.node(server) + user = None + + try: + with Given(f"I add user to LDAP"): + user = {"cn": "myuser", "userpassword": "myuser"} + 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): + login_and_execute_query(username=user["cn"], password=user["userpassword"]) + + with When("I delete this user from LDAP"): + delete_user_from_ldap(user) + + with Then("when I try to login again it should fail"): + login_and_execute_query(username=user["cn"], password=user["userpassword"], + exitcode=4, + message=f"DB::Exception: {user['cn']}: Authentication failed: password is incorrect or there is no user with such name" + ) + 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_007_LDAP_Authentication_Invalid("1.0"), + RQ_SRS_007_LDAP_Authentication_PasswordChanged("1.0") +) +def login_after_user_password_changed_in_ldap(self, server, rbac=False): + """Check that login fails after user password is changed in LDAP. + """ + self.context.ldap_node = self.context.cluster.node(server) + user = None + + try: + with Given(f"I add user to LDAP"): + user = {"cn": "myuser", "userpassword": "myuser"} + 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): + login_and_execute_query(username=user["cn"], password=user["userpassword"]) + + with When("I change user password in LDAP"): + change_user_password_in_ldap(user, "newpassword") + + with Then("when I try to login again it should fail"): + login_and_execute_query(username=user["cn"], password=user["userpassword"], + exitcode=4, + message=f"DB::Exception: {user['cn']}: Authentication failed: password is incorrect or there is no user with such name" + ) + + with And("when I try to login with the new password it should work"): + login_and_execute_query(username=user["cn"], password="newpassword") + + 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_007_LDAP_Authentication_Invalid("1.0"), + RQ_SRS_007_LDAP_Authentication_UsernameChanged("1.0") +) +def login_after_user_cn_changed_in_ldap(self, server, rbac=False): + """Check that login fails after user cn is changed in LDAP. + """ + self.context.ldap_node = self.context.cluster.node(server) + user = None + new_user = None + + try: + with Given(f"I add user to LDAP"): + user = {"cn": "myuser", "userpassword": "myuser"} + 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): + login_and_execute_query(username=user["cn"], password=user["userpassword"]) + + with When("I change user password in LDAP"): + new_user = change_user_cn_in_ldap(user, "myuser2") + + with Then("when I try to login again it should fail"): + login_and_execute_query(username=user["cn"], password=user["userpassword"], + exitcode=4, + message=f"DB::Exception: {user['cn']}: Authentication failed: password is incorrect or there is no user with such name" + ) + 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 +@Requirements( + RQ_SRS_007_LDAP_Authentication_Valid("1.0"), + RQ_SRS_007_LDAP_Authentication_LDAPServerRestart("1.0") +) +def login_after_ldap_server_is_restarted(self, server, timeout=60, rbac=False): + """Check that login succeeds after LDAP server is restarted. + """ + self.context.ldap_node = self.context.cluster.node(server) + user = None + + try: + with Given(f"I add user to LDAP"): + user = {"cn": "myuser", "userpassword": getuid()} + user = add_user_to_ldap(**user) + + with ldap_authenticated_users({"username": user["cn"], "server": server}, rbac=rbac): + login_and_execute_query(username=user["cn"], password=user["userpassword"]) + + with When("I restart LDAP server"): + self.context.ldap_node.restart() + + with Then("I try to login until it works", description=f"timeout {timeout} sec"): + started = time.time() + while True: + r = self.context.node.query("SELECT 1", + settings=[("user", user["cn"]), ("password", user["userpassword"])], + no_checks=True) + if r.exitcode == 0: + break + assert time.time() - started < timeout, error(r.output) + 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_007_LDAP_Authentication_Valid("1.0"), + RQ_SRS_007_LDAP_Authentication_ClickHouseServerRestart("1.0") +) +def login_after_clickhouse_server_is_restarted(self, server, timeout=60, rbac=False): + """Check that login succeeds after ClickHouse server is restarted. + """ + self.context.ldap_node = self.context.cluster.node(server) + user = None + + try: + with Given(f"I add user to LDAP"): + user = {"cn": "myuser", "userpassword": getuid()} + user = add_user_to_ldap(**user) + + with ldap_authenticated_users({"username": user["cn"], "server": server}, rbac=rbac): + login_and_execute_query(username=user["cn"], password=user["userpassword"]) + + with When("I restart ClickHouse server"): + self.context.node.restart() + + with Then("I try to login until it works", description=f"timeout {timeout} sec"): + started = time.time() + while True: + r = self.context.node.query("SELECT 1", + settings=[("user", user["cn"]), ("password", user["userpassword"])], + no_checks=True) + if r.exitcode == 0: + break + assert time.time() - started < timeout, error(r.output) + 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_007_LDAP_Authentication_Invalid("1.0"), + RQ_SRS_007_LDAP_Authentication_Password_Empty("1.0") +) +def valid_username_with_valid_empty_password(self, server, rbac=False): + """Check that we can't login using valid username that has empty password. + """ + user = {"cn": "empty_password", "userpassword": ""} + exitcode = 4 + message = f"DB::Exception: {user['cn']}: Authentication failed: password is incorrect or there is no user with such name" + + add_user_to_ldap_and_login(user=user, exitcode=exitcode, message=message, server=server, rbac=rbac) + +@TestScenario +@Requirements( + RQ_SRS_007_LDAP_Authentication_Invalid("1.0"), + RQ_SRS_007_LDAP_Authentication_Password_Empty("1.0") +) +def valid_username_and_invalid_empty_password(self, server, rbac=False): + """Check that we can't login using valid username but invalid empty password. + """ + username = "user_non_empty_password" + user = {"cn": username, "userpassword": username} + login = {"password": ""} + + exitcode = 4 + message = f"DB::Exception: {username}: Authentication failed: password is incorrect or there is no user with such name" + + add_user_to_ldap_and_login(user=user, login=login, exitcode=exitcode, message=message, server=server, rbac=rbac) + +@TestScenario +@Requirements( + RQ_SRS_007_LDAP_Authentication_Valid("1.0") +) +def valid_username_and_password(self, server, rbac=False): + """Check that we can login using valid username and password. + """ + username = "valid_username_and_password" + user = {"cn": username, "userpassword": username} + + with When(f"I add user {username} to LDAP and try to login"): + add_user_to_ldap_and_login(user=user, server=server, rbac=rbac) + +@TestScenario +@Requirements( + RQ_SRS_007_LDAP_Authentication_Invalid("1.0") +) +def valid_username_and_password_invalid_server(self, server=None, rbac=False): + """Check that we can't login using valid username and valid + password but for a different server. + """ + self.context.ldap_node = self.context.cluster.node("openldap1") + + user = {"username": "user2", "userpassword": "user2", "server": "openldap1"} + + exitcode = 4 + message = f"DB::Exception: user2: Authentication failed: password is incorrect or there is no user with such name" + + with ldap_authenticated_users(user, config_file=f"ldap_users_{getuid()}.xml", restart=True, rbac=rbac): + login_and_execute_query(username="user2", password="user2", exitcode=exitcode, message=message) + +@TestScenario +@Requirements( + RQ_SRS_007_LDAP_Authentication_Valid("1.0"), + RQ_SRS_007_LDAP_Authentication_Username_Long("1.0"), + RQ_SRS_007_LDAP_Configuration_User_Name_Long("1.0") +) +def valid_long_username_and_short_password(self, server, rbac=False): + """Check that we can login using valid very long username and short password. + """ + username = "long_username_12345678901234567890123456789012345678901234567890123456789012345678901234567890" + user = {"cn": username, "userpassword": "long_username"} + + add_user_to_ldap_and_login(user=user, server=server, rbac=rbac) + +@TestScenario +@Requirements( + RQ_SRS_007_LDAP_Authentication_Invalid("1.0") +) +def invalid_long_username_and_valid_short_password(self, server, rbac=False): + """Check that we can't login using slightly invalid long username but valid password. + """ + username = "long_username_12345678901234567890123456789012345678901234567890123456789012345678901234567890" + user = {"cn": username, "userpassword": "long_username"} + login = {"username": f"{username}?"} + + exitcode = 4 + message=f"DB::Exception: {login['username']}: Authentication failed: password is incorrect or there is no user with such name" + + add_user_to_ldap_and_login(user=user, login=login, exitcode=exitcode, message=message, server=server, rbac=rbac) + +@TestScenario +@Requirements( + RQ_SRS_007_LDAP_Authentication_Valid("1.0"), + RQ_SRS_007_LDAP_Authentication_Password_Long("1.0") +) +def valid_short_username_and_long_password(self, server, rbac=False): + """Check that we can login using valid short username with very long password. + """ + username = "long_password" + user = {"cn": username, "userpassword": "long_password_12345678901234567890123456789012345678901234567890123456789012345678901234567890"} + add_user_to_ldap_and_login(user=user, server=server, rbac=rbac) + +@TestScenario +@Requirements( + RQ_SRS_007_LDAP_Authentication_Invalid("1.0") +) +def valid_short_username_and_invalid_long_password(self, server, rbac=False): + """Check that we can't login using valid short username and invalid long password. + """ + username = "long_password" + user = {"cn": username, "userpassword": "long_password_12345678901234567890123456789012345678901234567890123456789012345678901234567890"} + login = {"password": user["userpassword"] + "1"} + + exitcode = 4 + message=f"DB::Exception: {username}: Authentication failed: password is incorrect or there is no user with such name" + + add_user_to_ldap_and_login(user=user, login=login, exitcode=exitcode, message=message, server=server, rbac=rbac) + +@TestScenario +@Requirements( + RQ_SRS_007_LDAP_Authentication_Invalid("1.0") +) +def valid_username_and_invalid_password(self, server, rbac=False): + """Check that we can't login using valid username and invalid password. + """ + username = "valid_username_and_invalid_password" + user = {"cn": username, "userpassword": username} + login = {"password": user["userpassword"] + "1"} + + exitcode = 4 + message=f"DB::Exception: {username}: Authentication failed: password is incorrect or there is no user with such name" + + add_user_to_ldap_and_login(user=user, login=login, exitcode=exitcode, message=message, server=server, rbac=rbac) + +@TestScenario +@Requirements( + RQ_SRS_007_LDAP_Authentication_Invalid("1.0") +) +def invalid_username_and_valid_password(self, server, rbac=False): + """Check that we can't login using slightly invalid username but valid password. + """ + username = "invalid_username_and_valid_password" + user = {"cn": username, "userpassword": username} + login = {"username": user["cn"] + "1"} + + exitcode = 4 + message=f"DB::Exception: {login['username']}: Authentication failed: password is incorrect or there is no user with such name" + + add_user_to_ldap_and_login(user=user, login=login, exitcode=exitcode, message=message, server=server, rbac=rbac) + +@TestScenario +@Requirements( + RQ_SRS_007_LDAP_Authentication_Valid("1.0"), + RQ_SRS_007_LDAP_Authentication_Username_UTF8("1.0"), + RQ_SRS_007_LDAP_Configuration_User_Name_UTF8("1.0") +) +def valid_utf8_username_and_ascii_password(self, server, rbac=False): + """Check that we can login using valid utf-8 username with ascii password. + """ + username = "utf8_username_Gãńdåłf_Thê_Gręât" + user = {"cn": username, "userpassword": "utf8_username"} + + add_user_to_ldap_and_login(user=user, server=server, rbac=rbac) + +@TestScenario +@Requirements( + RQ_SRS_007_LDAP_Authentication_Valid("1.0"), + RQ_SRS_007_LDAP_Authentication_Password_UTF8("1.0") +) +def valid_ascii_username_and_utf8_password(self, server, rbac=False): + """Check that we can login using valid ascii username with utf-8 password. + """ + username = "utf8_password" + user = {"cn": username, "userpassword": "utf8_password_Gãńdåłf_Thê_Gręât"} + + add_user_to_ldap_and_login(user=user, server=server, rbac=rbac) + +@TestScenario +def empty_username_and_empty_password(self, server=None, rbac=False): + """Check that we can login using empty username and empty password as + it will use the default user and that has an empty password. + """ + 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( + RQ_SRS_007_LDAP_Authentication_Mechanism_NamePassword("1.0") +) +@Examples("rbac", [ + (False,), + (True, Requirements(RQ_SRS_007_LDAP_Configuration_User_RBAC("1.0"))) +]) +def feature(self, rbac, servers=None, node="clickhouse1"): + """Check that users can be authenticated using an LDAP server when + users are configured either using an XML configuration file or RBAC. + """ + self.context.node = self.context.cluster.node(node) + + if servers is None: + servers = globals()["servers"] + + with ldap_servers(servers): + 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) + + + + diff --git a/ldap/authentication/tests/common.py b/ldap/authentication/tests/common.py new file mode 100644 index 00000000000..8efb389a23f --- /dev/null +++ b/ldap/authentication/tests/common.py @@ -0,0 +1,466 @@ +import os +import uuid +import time +import string +import random +import textwrap +import xml.etree.ElementTree as xmltree + +from collections import namedtuple +from contextlib import contextmanager + +import testflows.settings as settings + +from testflows.core import * +from testflows.asserts import error + +def getuid(): + return str(uuid.uuid1()).replace('-', '_') + +xml_with_utf8 = '\n' + +def xml_indent(elem, level=0, by=" "): + i = "\n" + level * by + if len(elem): + if not elem.text or not elem.text.strip(): + elem.text = i + by + if not elem.tail or not elem.tail.strip(): + elem.tail = i + for elem in elem: + xml_indent(elem, level + 1) + if not elem.tail or not elem.tail.strip(): + elem.tail = i + else: + if level and (not elem.tail or not elem.tail.strip()): + elem.tail = i + +def xml_append(root, tag, text): + element = xmltree.Element(tag) + element.text = text + root.append(element) + return element + +Config = namedtuple("Config", "content path name uid preprocessed_name") + +ASCII_CHARS = string.ascii_lowercase + string.ascii_uppercase + string.digits + +def randomword(length, chars=ASCII_CHARS): + return ''.join(random.choice(chars) for i in range(length)) + +def restart(node=None, safe=False, timeout=60): + """Restart ClickHouse server and wait for config to be reloaded. + """ + with When("I restart ClickHouse server node"): + if node is None: + node = current().context.node + + with node.cluster.shell(node.name) as bash: + bash.expect(bash.prompt) + + with By("closing terminal to the node to be restarted"): + bash.close() + + with And("getting current log size"): + logsize = \ + node.command("stat --format=%s /var/log/clickhouse-server/clickhouse-server.log").output.split(" ")[ + 0].strip() + + with And("restarting ClickHouse server"): + node.restart(safe=safe) + + with Then("tailing the log file from using previous log size as the offset"): + bash.prompt = bash.__class__.prompt + bash.open() + bash.send(f"tail -c +{logsize} -f /var/log/clickhouse-server/clickhouse-server.log") + + with And("waiting for config reload message in the log file"): + bash.expect( + f"ConfigReloader: Loaded config '/etc/clickhouse-server/config.xml', performed update on configuration", + timeout=timeout) + +def add_config(config, timeout=60, restart=False, modify=False): + """Add dynamic configuration file to ClickHouse. + + :param node: node + :param config: configuration file description + :param timeout: timeout, default: 20 sec + """ + node = current().context.node + + def check_preprocessed_config_is_updated(after_removal=False): + """Check that preprocessed config is updated. + """ + started = time.time() + command = f"cat /var/lib/clickhouse/preprocessed_configs/{config.preprocessed_name} | grep {config.uid}{' > /dev/null' if not settings.debug else ''}" + + while time.time() - started < timeout: + exitcode = node.command(command, steps=False).exitcode + if after_removal: + if exitcode == 1: + break + else: + if exitcode == 0: + break + time.sleep(1) + + if settings.debug: + node.command(f"cat /var/lib/clickhouse/preprocessed_configs/{config.preprocessed_name}") + + if after_removal: + assert exitcode == 1, error() + else: + assert exitcode == 0, error() + + def wait_for_config_to_be_loaded(): + """Wait for config to be loaded. + """ + if restart: + with When("I close terminal to the node to be restarted"): + bash.close() + + with And("I stop ClickHouse to apply the config changes"): + node.stop(safe=False) + + with And("I get the current log size"): + cmd = node.cluster.command(None, + f"stat --format=%s {os.environ['CLICKHOUSE_TESTS_DIR']}/_instances/{node.name}/logs/clickhouse-server.log") + logsize = cmd.output.split(" ")[0].strip() + + with And("I start ClickHouse back up"): + node.start() + + with Then("I tail the log file from using previous log size as the offset"): + bash.prompt = bash.__class__.prompt + bash.open() + bash.send(f"tail -c +{logsize} -f /var/log/clickhouse-server/clickhouse-server.log") + + with Then("I wait for config reload message in the log file"): + if restart: + bash.expect( + f"ConfigReloader: Loaded config '/etc/clickhouse-server/config.xml', performed update on configuration", + timeout=timeout) + else: + bash.expect( + f"ConfigReloader: Loaded config '/etc/clickhouse-server/{config.preprocessed_name}', performed update on configuration", + timeout=timeout) + + try: + with Given(f"{config.name}"): + if settings.debug: + with When("I output the content of the config"): + debug(config.content) + + 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 When("I add the config", description=config.path): + command = f"cat < {config.path}\n{config.content}\nHEREDOC" + node.command(command, steps=False, exitcode=0) + + with Then(f"{config.preprocessed_name} should be updated", description=f"timeout {timeout}"): + check_preprocessed_config_is_updated() + + with And("I wait for config to be reloaded"): + wait_for_config_to_be_loaded() + yield + finally: + 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 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() + +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. + """ + uid = getuid() + path = os.path.join(config_d_dir, config_file) + name = config_file + + root = xmltree.fromstring("") + xml_servers = root.find("ldap_servers") + xml_servers.append(xmltree.Comment(text=f"LDAP servers {uid}")) + + for _name, server in list(servers.items()): + xml_server = xmltree.Element(_name) + for key, value in list(server.items()): + xml_append(xml_server, key, value) + xml_servers.append(xml_server) + + xml_indent(root) + content = xml_with_utf8 + str(xmltree.tostring(root, short_empty_elements=False, encoding="utf-8"), "utf-8") + + 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, config=None): + """Add LDAP servers configuration. + """ + 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"): + """Create LDAP users configuration file content. + """ + uid = getuid() + path = os.path.join(config_d_dir, config_file) + name = config_file + + root = xmltree.fromstring("") + xml_users = root.find("users") + xml_users.append(xmltree.Comment(text=f"LDAP users {uid}")) + + for user in users: + xml_user = xmltree.Element(user['username']) + xml_user_server = xmltree.Element("ldap") + xml_append(xml_user_server, "server", user["server"]) + xml_user.append(xml_user_server) + xml_users.append(xml_user) + + xml_indent(root) + content = xml_with_utf8 + str(xmltree.tostring(root, short_empty_elements=False, encoding="utf-8"), "utf-8") + + return Config(content, path, name, uid, "users.xml") + +def add_users_identified_with_ldap(*users): + """Add one or more users that are identified via + an ldap server using RBAC. + """ + node = current().context.node + try: + with Given("I create users"): + for user in users: + node.query(f"CREATE USER '{user['username']}' IDENTIFIED WITH ldap_server BY '{user['server']}'") + yield + finally: + with Finally("I remove users"): + for user in users: + with By(f"dropping user {user['username']}", flags=TE): + node.query(f"DROP USER IF EXISTS '{user['username']}'") + +@contextmanager +def ldap_authenticated_users(*users, config_d_dir="/etc/clickhouse-server/users.d", + config_file=None, timeout=60, restart=True, config=None, rbac=False): + """Add LDAP authenticated users. + """ + if rbac: + return add_users_identified_with_ldap(*users) + else: + if config_file is None: + config_file = f"ldap_users_{getuid()}.xml" + if config is None: + config = create_ldap_users_config_content(*users, config_d_dir=config_d_dir, config_file=config_file) + return add_config(config, restart=restart) + +def invalid_server_config(servers, message=None, tail=13, timeout=60): + """Check that ClickHouse errors when trying to load invalid LDAP servers configuration file. + """ + node = current().context.node + if message is None: + message = "Exception: Failed to merge config with '/etc/clickhouse-server/config.d/ldap_servers.xml'" + + config = create_ldap_servers_config_content(servers) + try: + node.command("echo -e \"%s\" > /var/log/clickhouse-server/clickhouse-server.err.log" % ("-\\n" * tail)) + + with When("I add the config", description=config.path): + command = f"cat < {config.path}\n{config.content}\nHEREDOC" + node.command(command, steps=False, exitcode=0) + + with Then("server shall fail to merge the new config"): + started = time.time() + command = f"tail -n {tail} /var/log/clickhouse-server/clickhouse-server.err.log | grep \"{message}\"" + while time.time() - started < timeout: + exitcode = node.command(command, steps=False).exitcode + if exitcode == 0: + break + time.sleep(1) + assert exitcode == 0, error() + finally: + with Finally(f"I remove {config.name}"): + with By("removing the config file", description=config.path): + node.command(f"rm -rf {config.path}", exitcode=0) + +def invalid_user_config(servers, config, message=None, tail=13, timeout=60): + """Check that ClickHouse errors when trying to load invalid LDAP users configuration file. + """ + node = current().context.node + if message is None: + message = "Exception: Failed to merge config with '/etc/clickhouse-server/users.d/ldap_users.xml'" + + with ldap_servers(servers): + try: + node.command("echo -e \"%s\" > /var/log/clickhouse-server/clickhouse-server.err.log" % ("\\n" * tail)) + with When("I add the config", description=config.path): + command = f"cat < {config.path}\n{config.content}\nHEREDOC" + node.command(command, steps=False, exitcode=0) + + with Then("server shall fail to merge the new config"): + started = time.time() + command = f"tail -n {tail} /var/log/clickhouse-server/clickhouse-server.err.log | grep \"{message}\"" + while time.time() - started < timeout: + exitcode = node.command(command, steps=False).exitcode + if exitcode == 0: + break + time.sleep(1) + assert exitcode == 0, error() + finally: + with Finally(f"I remove {config.name}"): + with By("removing the config file", description=config.path): + node.command(f"rm -rf {config.path}", exitcode=0) + +def add_user_to_ldap(cn, userpassword, givenname=None, homedirectory=None, sn=None, uid=None, uidnumber=None, node=None): + """Add user entry to LDAP.""" + if node is None: + node = current().context.ldap_node + if uid is None: + uid = cn + if givenname is None: + givenname = "John" + if homedirectory is None: + homedirectory = "/home/users" + if sn is None: + sn = "User" + if uidnumber is None: + uidnumber = 2000 + + user = { + "dn": f"cn={cn},ou=users,dc=company,dc=com", + "cn": cn, + "gidnumber": 501, + "givenname": givenname, + "homedirectory": homedirectory, + "objectclass": ["inetOrgPerson", "posixAccount", "top"], + "sn": sn, + "uid": uid, + "uidnumber": uidnumber, + "userpassword": userpassword, + "_server": node.name + } + + lines = [] + + for key, value in list(user.items()): + if key.startswith("_"): + continue + elif key == "objectclass": + for cls in value: + lines.append(f"objectclass: {cls}") + else: + lines.append(f"{key}: {value}") + + ldif = "\n".join(lines) + + r = node.command( + f"echo -e \"{ldif}\" | ldapadd -x -H ldap://localhost -D \"cn=admin,dc=company,dc=com\" -w admin") + assert r.exitcode == 0, error() + + return user + +def delete_user_from_ldap(user, node=None, exitcode=0): + """Delete user entry from LDAP.""" + if node is None: + node = current().context.ldap_node + r = node.command( + f"ldapdelete -x -H ldap://localhost -D \"cn=admin,dc=company,dc=com\" -w admin \"{user['dn']}\"") + if exitcode is not None: + assert r.exitcode == exitcode, error() + +def change_user_password_in_ldap(user, new_password, node=None, exitcode=0): + """Change user password in LDAP.""" + if node is None: + node = current().context.ldap_node + + ldif = (f"dn: {user['dn']}\n" + "changetype: modify\n" + "replace: userpassword\n" + f"userpassword: {new_password}") + + r = node.command( + f"echo -e \"{ldif}\" | ldapmodify -x -H ldap://localhost -D \"cn=admin,dc=company,dc=com\" -w admin") + + if exitcode is not None: + assert r.exitcode == exitcode, error() + +def change_user_cn_in_ldap(user, new_cn, node=None, exitcode=0): + """Change user password in LDAP.""" + if node is None: + node = current().context.ldap_node + + new_user = dict(user) + new_user['dn'] = f"cn={new_cn},ou=users,dc=company,dc=com" + new_user['cn'] = new_cn + + ldif = ( + f"dn: {user['dn']}\n" + "changetype: modrdn\n" + f"newrdn: cn = {new_user['cn']}\n" + f"deleteoldrdn: 1\n" + ) + + r = node.command( + f"echo -e \"{ldif}\" | ldapmodify -x -H ldap://localhost -D \"cn=admin,dc=company,dc=com\" -w admin") + + if exitcode is not None: + assert r.exitcode == exitcode, error() + + return new_user + +@contextmanager +def ldap_user(cn, userpassword, givenname=None, homedirectory=None, sn=None, uid=None, uidnumber=None, node=None): + """Add new user to the LDAP server.""" + try: + user = None + with Given(f"I add user {cn} to LDAP"): + user = add_user_to_ldap(cn, userpassword, givenname, homedirectory, sn, uid, uidnumber, node=node) + yield user + finally: + with Finally(f"I delete user {cn} from LDAP"): + if user is not None: + delete_user_from_ldap(user, node=node) + +@contextmanager +def ldap_users(*users, node=None): + """Add multiple new users to the LDAP server.""" + try: + _users = [] + with Given("I add users to LDAP"): + for user in users: + with By(f"adding user {user['cn']}"): + _users.append(add_user_to_ldap(**user, node=node)) + yield _users + finally: + with Finally(f"I delete users from LDAP"): + for _user in _users: + delete_user_from_ldap(_user, node=node) + +def login(servers, *users, config=None): + """Configure LDAP server and LDAP authenticated users and + try to login and execute a query""" + with ldap_servers(servers): + with ldap_authenticated_users(*users, restart=True, config=config): + for user in users: + if user.get("login", False): + with When(f"I login as {user['username']} and execute query"): + current().context.node.query("SELECT 1", + settings=[("user", user["username"]), ("password", user["password"])], + exitcode=user.get("exitcode", None), + message=user.get("message", None)) diff --git a/ldap/authentication/tests/server_config.py b/ldap/authentication/tests/server_config.py new file mode 100644 index 00000000000..38ec859226b --- /dev/null +++ b/ldap/authentication/tests/server_config.py @@ -0,0 +1,304 @@ +from testflows.core import * + +from ldap.authentication.tests.common import * +from ldap.authentication.requirements import * + +@TestScenario +@Requirements( + RQ_SRS_007_LDAP_Server_Configuration_Invalid("1.0"), + RQ_SRS_007_LDAP_Configuration_Server_Name("1.0") +) +def empty_server_name(self, timeout=20): + """Check that empty string as a server name is not allowed. + """ + servers = {"": {"host": "foo", "port": "389", "enable_tls": "no", + "auth_dn_prefix": "cn=", "auth_dn_suffix": ",ou=users,dc=company,dc=com" + }} + invalid_server_config(servers, timeout=timeout) + +@TestScenario +@Requirements( + RQ_SRS_007_LDAP_Server_Configuration_Invalid("1.0"), + RQ_SRS_007_LDAP_UnreachableServer("1.0") +) +def invalid_host(self): + """Check that server returns an error when LDAP server + host name is invalid. + """ + servers = {"foo": {"host": "foo", "port": "389", "enable_tls": "no"}} + users = [{ + "server": "foo", "username": "user1", "password": "user1", "login": True, + "exitcode": 4, + "message": "DB::Exception: user1: Authentication failed: password is incorrect or there is no user with such name" + }] + login(servers, *users) + +@TestScenario +@Requirements( + RQ_SRS_007_LDAP_Server_Configuration_Invalid("1.0"), + RQ_SRS_007_LDAP_Configuration_Server_Host("1.0") +) +def empty_host(self): + """Check that server returns an error when LDAP server + host value is empty. + """ + servers = {"foo": {"host": "", "port": "389", "enable_tls": "no"}} + users = [{ + "server": "foo", "username": "user1", "password": "user1", "login": True, + "exitcode": 4, + "message": "DB::Exception: user1: Authentication failed: password is incorrect or there is no user with such name" + }] + login(servers, *users) + +@TestScenario +@Requirements( + RQ_SRS_007_LDAP_Server_Configuration_Invalid("1.0"), + RQ_SRS_007_LDAP_Configuration_Server_Host("1.0") +) +def missing_host(self): + """Check that server returns an error when LDAP server + host is missing. + """ + servers = {"foo": {"port": "389", "enable_tls": "no"}} + users = [{ + "server": "foo", "username": "user1", "password": "user1", "login": True, + "exitcode": 4, + "message": "DB::Exception: user1: Authentication failed: password is incorrect or there is no user with such name" + }] + login(servers, *users) + +@TestScenario +@Requirements( + RQ_SRS_007_LDAP_Server_Configuration_Invalid("1.0") +) +def invalid_port(self): + """Check that server returns an error when LDAP server + port is not valid. + """ + servers = {"openldap1": {"host": "openldap1", "port": "3890", "enable_tls": "no"}} + users = [{ + "server": "openldap1", "username": "user1", "password": "user1", "login": True, + "exitcode": 4, + "message": "DB::Exception: user1: Authentication failed: password is incorrect or there is no user with such name" + }] + login(servers, *users) + + +@TestScenario +@Requirements( + RQ_SRS_007_LDAP_Server_Configuration_Invalid("1.0") +) +def invalid_auth_dn_prefix(self): + """Check that server returns an error when LDAP server + port is not valid. + """ + servers = {"openldap1": {"host": "openldap1", "port": "389", "enable_tls": "no", + "auth_dn_prefix": "foo=", "auth_dn_suffix": ",ou=users,dc=company,dc=com" + }} + users = [{ + "server": "openldap1", "username": "user1", "password": "user1", "login": True, + "exitcode": 4, + "message": "DB::Exception: user1: Authentication failed: password is incorrect or there is no user with such name" + }] + login(servers, *users) + +@TestScenario +@Requirements( + RQ_SRS_007_LDAP_Server_Configuration_Invalid("1.0") +) +def invalid_auth_dn_suffix(self): + """Check that server returns an error when LDAP server + port is not valid. + """ + servers = {"openldap1": {"host": "openldap1", "port": "389", "enable_tls": "no", + "auth_dn_prefix": "cn=", "auth_dn_suffix": ",foo=users,dc=company,dc=com" + }} + users = [{ + "server": "openldap1", "username": "user1", "password": "user1", "login": True, + "exitcode": 4, + "message": "DB::Exception: user1: Authentication failed: password is incorrect or there is no user with such name" + }] + login(servers, *users) + +@TestScenario +@Requirements( + RQ_SRS_007_LDAP_Server_Configuration_Invalid("1.0") +) +def invalid_enable_tls_value(self): + """Check that server returns an error when enable_tls + option has invalid value. + """ + servers = {"openldap1": {"host": "openldap1", "port": "389", "enable_tls": "foo", + "auth_dn_prefix": "cn=", "auth_dn_suffix": ",ou=users,dc=company,dc=com" + }} + users = [{ + "server": "openldap1", "username": "user1", "password": "user1", "login": True, + "exitcode": 4, + "message": "DB::Exception: user1: Authentication failed: password is incorrect or there is no user with such name" + }] + login(servers, *users) + +@TestScenario +@Requirements( + RQ_SRS_007_LDAP_Server_Configuration_Invalid("1.0") +) +def invalid_tls_require_cert_value(self): + """Check that server returns an error when tls_require_cert + option has invalid value. + """ + servers = {"openldap2": { + "host": "openldap2", "port": "636", "enable_tls": "yes", + "auth_dn_prefix": "cn=", "auth_dn_suffix": ",ou=users,dc=company,dc=com", + "tls_require_cert": "foo", + "ca_cert_dir": "/container/service/slapd/assets/certs/", + "ca_cert_file": "/container/service/slapd/assets/certs/ca.crt" + }} + users = [{ + "server": "openldap2", "username": "user2", "password": "user2", "login": True, + "exitcode": 4, + "message": "DB::Exception: user2: Authentication failed: password is incorrect or there is no user with such name" + }] + login(servers, *users) + +@TestScenario +@Requirements( + RQ_SRS_007_LDAP_Server_Configuration_Invalid("1.0") +) +def empty_ca_cert_dir(self): + """Check that server returns an error when ca_cert_dir is empty. + """ + servers = {"openldap2": {"host": "openldap2", "port": "636", "enable_tls": "yes", + "auth_dn_prefix": "cn=", "auth_dn_suffix": ",ou=users,dc=company,dc=com", + "tls_require_cert": "demand", + "ca_cert_dir": "", + "ca_cert_file": "/container/service/slapd/assets/certs/ca.crt" + }} + users = [{ + "server": "openldap2", "username": "user2", "password": "user2", "login": True, + "exitcode": 4, + "message": "DB::Exception: user2: Authentication failed: password is incorrect or there is no user with such name" + }] + login(servers, *users) + +@TestScenario +@Requirements( + RQ_SRS_007_LDAP_Server_Configuration_Invalid("1.0") +) +def empty_ca_cert_file(self): + """Check that server returns an error when ca_cert_file is empty. + """ + servers = {"openldap2": {"host": "openldap2", "port": "636", "enable_tls": "yes", + "auth_dn_prefix": "cn=", "auth_dn_suffix": ",ou=users,dc=company,dc=com", + "tls_require_cert": "demand", + "ca_cert_dir": "/container/service/slapd/assets/certs/", + "ca_cert_file": "" + }} + users = [{ + "server": "openldap2", "username": "user2", "password": "user2", "login": True, + "exitcode": 4, + "message": "DB::Exception: user2: Authentication failed: password is incorrect or there is no user with such name" + }] + login(servers, *users) + +@TestScenario +@Requirements( + RQ_SRS_007_LDAP_Configuration_Server_AuthDN_Value("1.0"), + RQ_SRS_007_LDAP_Configuration_Server_AuthDN_Prefix("1.0"), + RQ_SRS_007_LDAP_Configuration_Server_AuthDN_Suffix("1.0") +) +def auth_dn_value(self): + """Check that server configuration can properly define the `dn` value of the user.""" + servers = { + "openldap1": { + "host": "openldap1", "port": "389", "enable_tls": "no", + "auth_dn_prefix": "cn=", "auth_dn_suffix": ",ou=users,dc=company,dc=com" + }} + user = {"server": "openldap1", "username": "user1", "password": "user1", "login": True} + + 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 = (" 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("2.0") +) +def syntax(self): + """Check that server configuration with valid syntax can be loaded. + ```xml + + + localhost + 636 + cn= + , ou=users, dc=example, dc=com + 0 + yes + tls1.2 + demand + /path/to/tls_cert_file + /path/to/tls_key_file + /path/to/tls_ca_cert_file + /path/to/tls_ca_cert_dir + ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:AES256-GCM-SHA384 + + + ``` + """ + servers = { + "openldap2": { + "host": "openldap2", + "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", + "tls_cert_file": "/container/service/slapd/assets/certs/ldap.crt", + "tls_key_file": "/container/service/slapd/assets/certs/ldap.key", + "tls_ca_cert_file": "/container/service/slapd/assets/certs/ca.crt", + "tls_ca_cert_dir": "/container/service/slapd/assets/certs/", + "tls_cipher_suite": "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:AES256-GCM-SHA384" + } + } + with ldap_servers(servers): + pass + +@TestFeature +@Name("server config") +def feature(self, node="clickhouse1"): + """Check that LDAP server configuration. + """ + self.context.node = self.context.cluster.node(node) + + for scenario in loads(current_module(), Scenario): + scenario() From e42ea7b8b935fce14ce41a07c6c4e7fdfa9a7266 Mon Sep 17 00:00:00 2001 From: Tai White Date: Tue, 17 Nov 2020 19:41:28 +0100 Subject: [PATCH 07/16] Deleted ldap directory added by mistake. Added verification cooldown tests to the ldap/authentication feature. --- .../requirements/requirements.md | 610 ------ .../requirements/requirements.py | 1687 ----------------- ldap/authentication/tests/authentications.py | 969 ---------- ldap/authentication/tests/common.py | 466 ----- ldap/authentication/tests/server_config.py | 304 --- tests/testflows/helpers/cluster.py | 17 + .../ldap/authentication/regression.py | 5 +- .../requirements/requirements.md | 58 +- .../requirements/requirements.py | 85 +- .../authentication/tests/authentications.py | 478 ++++- .../ldap/authentication/tests/common.py | 34 +- .../authentication/tests/server_config.py | 30 + 12 files changed, 651 insertions(+), 4092 deletions(-) delete mode 100644 ldap/authentication/requirements/requirements.md delete mode 100644 ldap/authentication/requirements/requirements.py delete mode 100644 ldap/authentication/tests/authentications.py delete mode 100644 ldap/authentication/tests/common.py delete mode 100644 ldap/authentication/tests/server_config.py diff --git a/ldap/authentication/requirements/requirements.md b/ldap/authentication/requirements/requirements.md deleted file mode 100644 index 17d46584772..00000000000 --- a/ldap/authentication/requirements/requirements.md +++ /dev/null @@ -1,610 +0,0 @@ -# SRS-007 ClickHouse Authentication of Users via LDAP - -## Table of Contents - -* 1 [Revision History](#revision-history) -* 2 [Introduction](#introduction) -* 3 [Terminology](#terminology) -* 4 [Requirements](#requirements) - * 4.1 [Generic](#generic) - * 4.1.1 [RQ.SRS-007.LDAP.Authentication](#rqsrs-007ldapauthentication) - * 4.1.2 [RQ.SRS-007.LDAP.Authentication.MultipleServers](#rqsrs-007ldapauthenticationmultipleservers) - * 4.1.3 [RQ.SRS-007.LDAP.Authentication.Protocol.PlainText](#rqsrs-007ldapauthenticationprotocolplaintext) - * 4.1.4 [RQ.SRS-007.LDAP.Authentication.Protocol.TLS](#rqsrs-007ldapauthenticationprotocoltls) - * 4.1.5 [RQ.SRS-007.LDAP.Authentication.Protocol.StartTLS](#rqsrs-007ldapauthenticationprotocolstarttls) - * 4.1.6 [RQ.SRS-007.LDAP.Authentication.TLS.Certificate.Validation](#rqsrs-007ldapauthenticationtlscertificatevalidation) - * 4.1.7 [RQ.SRS-007.LDAP.Authentication.TLS.Certificate.SelfSigned](#rqsrs-007ldapauthenticationtlscertificateselfsigned) - * 4.1.8 [RQ.SRS-007.LDAP.Authentication.TLS.Certificate.SpecificCertificationAuthority](#rqsrs-007ldapauthenticationtlscertificatespecificcertificationauthority) - * 4.1.9 [RQ.SRS-007.LDAP.Server.Configuration.Invalid](#rqsrs-007ldapserverconfigurationinvalid) - * 4.1.10 [RQ.SRS-007.LDAP.User.Configuration.Invalid](#rqsrs-007ldapuserconfigurationinvalid) - * 4.1.11 [RQ.SRS-007.LDAP.Authentication.Mechanism.Anonymous](#rqsrs-007ldapauthenticationmechanismanonymous) - * 4.1.12 [RQ.SRS-007.LDAP.Authentication.Mechanism.Unauthenticated](#rqsrs-007ldapauthenticationmechanismunauthenticated) - * 4.1.13 [RQ.SRS-007.LDAP.Authentication.Mechanism.NamePassword](#rqsrs-007ldapauthenticationmechanismnamepassword) - * 4.1.14 [RQ.SRS-007.LDAP.Authentication.Valid](#rqsrs-007ldapauthenticationvalid) - * 4.1.15 [RQ.SRS-007.LDAP.Authentication.Invalid](#rqsrs-007ldapauthenticationinvalid) - * 4.1.16 [RQ.SRS-007.LDAP.Authentication.Invalid.DeletedUser](#rqsrs-007ldapauthenticationinvaliddeleteduser) - * 4.1.17 [RQ.SRS-007.LDAP.Authentication.UsernameChanged](#rqsrs-007ldapauthenticationusernamechanged) - * 4.1.18 [RQ.SRS-007.LDAP.Authentication.PasswordChanged](#rqsrs-007ldapauthenticationpasswordchanged) - * 4.1.19 [RQ.SRS-007.LDAP.Authentication.LDAPServerRestart](#rqsrs-007ldapauthenticationldapserverrestart) - * 4.1.20 [RQ.SRS-007.LDAP.Authentication.ClickHouseServerRestart](#rqsrs-007ldapauthenticationclickhouseserverrestart) - * 4.1.21 [RQ.SRS-007.LDAP.Authentication.Parallel](#rqsrs-007ldapauthenticationparallel) - * 4.1.22 [RQ.SRS-007.LDAP.Authentication.Parallel.ValidAndInvalid](#rqsrs-007ldapauthenticationparallelvalidandinvalid) - * 4.2 [Specific](#specific) - * 4.2.1 [RQ.SRS-007.LDAP.UnreachableServer](#rqsrs-007ldapunreachableserver) - * 4.2.2 [RQ.SRS-007.LDAP.Configuration.Server.Name](#rqsrs-007ldapconfigurationservername) - * 4.2.3 [RQ.SRS-007.LDAP.Configuration.Server.Host](#rqsrs-007ldapconfigurationserverhost) - * 4.2.4 [RQ.SRS-007.LDAP.Configuration.Server.Port](#rqsrs-007ldapconfigurationserverport) - * 4.2.5 [RQ.SRS-007.LDAP.Configuration.Server.Port.Default](#rqsrs-007ldapconfigurationserverportdefault) - * 4.2.6 [RQ.SRS-007.LDAP.Configuration.Server.AuthDN.Prefix](#rqsrs-007ldapconfigurationserverauthdnprefix) - * 4.2.7 [RQ.SRS-007.LDAP.Configuration.Server.AuthDN.Suffix](#rqsrs-007ldapconfigurationserverauthdnsuffix) - * 4.2.8 [RQ.SRS-007.LDAP.Configuration.Server.AuthDN.Value](#rqsrs-007ldapconfigurationserverauthdnvalue) - * 4.2.9 [RQ.SRS-007.LDAP.Configuration.Server.EnableTLS](#rqsrs-007ldapconfigurationserverenabletls) - * 4.2.10 [RQ.SRS-007.LDAP.Configuration.Server.EnableTLS.Options.Default](#rqsrs-007ldapconfigurationserverenabletlsoptionsdefault) - * 4.2.11 [RQ.SRS-007.LDAP.Configuration.Server.EnableTLS.Options.No](#rqsrs-007ldapconfigurationserverenabletlsoptionsno) - * 4.2.12 [RQ.SRS-007.LDAP.Configuration.Server.EnableTLS.Options.Yes](#rqsrs-007ldapconfigurationserverenabletlsoptionsyes) - * 4.2.13 [RQ.SRS-007.LDAP.Configuration.Server.EnableTLS.Options.StartTLS](#rqsrs-007ldapconfigurationserverenabletlsoptionsstarttls) - * 4.2.14 [RQ.SRS-007.LDAP.Configuration.Server.TLSMinimumProtocolVersion](#rqsrs-007ldapconfigurationservertlsminimumprotocolversion) - * 4.2.15 [RQ.SRS-007.LDAP.Configuration.Server.TLSMinimumProtocolVersion.Values](#rqsrs-007ldapconfigurationservertlsminimumprotocolversionvalues) - * 4.2.16 [RQ.SRS-007.LDAP.Configuration.Server.TLSMinimumProtocolVersion.Default](#rqsrs-007ldapconfigurationservertlsminimumprotocolversiondefault) - * 4.2.17 [RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert](#rqsrs-007ldapconfigurationservertlsrequirecert) - * 4.2.18 [RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert.Options.Default](#rqsrs-007ldapconfigurationservertlsrequirecertoptionsdefault) - * 4.2.19 [RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert.Options.Demand](#rqsrs-007ldapconfigurationservertlsrequirecertoptionsdemand) - * 4.2.20 [RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert.Options.Allow](#rqsrs-007ldapconfigurationservertlsrequirecertoptionsallow) - * 4.2.21 [RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert.Options.Try](#rqsrs-007ldapconfigurationservertlsrequirecertoptionstry) - * 4.2.22 [RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert.Options.Never](#rqsrs-007ldapconfigurationservertlsrequirecertoptionsnever) - * 4.2.23 [RQ.SRS-007.LDAP.Configuration.Server.TLSCertFile](#rqsrs-007ldapconfigurationservertlscertfile) - * 4.2.24 [RQ.SRS-007.LDAP.Configuration.Server.TLSKeyFile](#rqsrs-007ldapconfigurationservertlskeyfile) - * 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.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 - -This document is stored in an electronic form using [Git] source control management software -hosted in a [GitHub Repository]. -All the updates are tracked using the [Git]'s [Revision History]. - -## Introduction - -[ClickHouse] currently does not have any integration with [LDAP]. -As the initial step in integrating with [LDAP] this software requirements specification covers -only the requirements to enable authentication of users using an [LDAP] server. - -## Terminology - -* **CA** - - Certificate Authority ([CA]) - -* **LDAP** - - Lightweight Directory Access Protocol ([LDAP]) - -## Requirements - -### Generic - -#### RQ.SRS-007.LDAP.Authentication -version: 1.0 - -[ClickHouse] SHALL support user authentication via an [LDAP] server. - -#### RQ.SRS-007.LDAP.Authentication.MultipleServers -version: 1.0 - -[ClickHouse] SHALL support specifying multiple [LDAP] servers that can be used to authenticate -users. - -#### RQ.SRS-007.LDAP.Authentication.Protocol.PlainText -version: 1.0 - -[ClickHouse] SHALL support user authentication using plain text `ldap://` non secure protocol. - -#### RQ.SRS-007.LDAP.Authentication.Protocol.TLS -version: 1.0 - -[ClickHouse] SHALL support user authentication using `SSL/TLS` `ldaps://` secure protocol. - -#### RQ.SRS-007.LDAP.Authentication.Protocol.StartTLS -version: 1.0 - -[ClickHouse] SHALL support user authentication using legacy `StartTLS` protocol which is a -plain text `ldap://` protocol that is upgraded to [TLS]. - -#### RQ.SRS-007.LDAP.Authentication.TLS.Certificate.Validation -version: 1.0 - -[ClickHouse] SHALL support certificate validation used for [TLS] connections. - -#### RQ.SRS-007.LDAP.Authentication.TLS.Certificate.SelfSigned -version: 1.0 - -[ClickHouse] SHALL support self-signed certificates for [TLS] connections. - -#### RQ.SRS-007.LDAP.Authentication.TLS.Certificate.SpecificCertificationAuthority -version: 1.0 - -[ClickHouse] SHALL support certificates signed by specific Certification Authority for [TLS] connections. - -#### RQ.SRS-007.LDAP.Server.Configuration.Invalid -version: 1.0 - -[ClickHouse] SHALL return an error and prohibit user login if [LDAP] server configuration is not valid. - -#### RQ.SRS-007.LDAP.User.Configuration.Invalid -version: 1.0 - -[ClickHouse] SHALL return an error and prohibit user login if user configuration is not valid. - -#### RQ.SRS-007.LDAP.Authentication.Mechanism.Anonymous -version: 1.0 - -[ClickHouse] SHALL return an error and prohibit authentication using [Anonymous Authentication Mechanism of Simple Bind] -authentication mechanism. - -#### RQ.SRS-007.LDAP.Authentication.Mechanism.Unauthenticated -version: 1.0 - -[ClickHouse] SHALL return an error and prohibit authentication using [Unauthenticated Authentication Mechanism of Simple Bind] -authentication mechanism. - -#### RQ.SRS-007.LDAP.Authentication.Mechanism.NamePassword -version: 1.0 - -[ClickHouse] SHALL allow authentication using only [Name/Password Authentication Mechanism of Simple Bind] -authentication mechanism. - -#### RQ.SRS-007.LDAP.Authentication.Valid -version: 1.0 - -[ClickHouse] SHALL only allow user authentication using [LDAP] server if and only if -user name and password match [LDAP] server records for the user. - -#### RQ.SRS-007.LDAP.Authentication.Invalid -version: 1.0 - -[ClickHouse] SHALL return an error and prohibit authentication if either user name or password -do not match [LDAP] server records for the user. - -#### RQ.SRS-007.LDAP.Authentication.Invalid.DeletedUser -version: 1.0 - -[ClickHouse] SHALL return an error and prohibit authentication if the user -has been deleted from the [LDAP] server. - -#### RQ.SRS-007.LDAP.Authentication.UsernameChanged -version: 1.0 - -[ClickHouse] SHALL return an error and prohibit authentication if the username is changed -on the [LDAP] server. - -#### RQ.SRS-007.LDAP.Authentication.PasswordChanged -version: 1.0 - -[ClickHouse] SHALL return an error and prohibit authentication if the password -for the user is changed on the [LDAP] server. - -#### RQ.SRS-007.LDAP.Authentication.LDAPServerRestart -version: 1.0 - -[ClickHouse] SHALL support authenticating users after [LDAP] server is restarted. - -#### RQ.SRS-007.LDAP.Authentication.ClickHouseServerRestart -version: 1.0 - -[ClickHouse] SHALL support authenticating users after server is restarted. - -#### RQ.SRS-007.LDAP.Authentication.Parallel -version: 1.0 - -[ClickHouse] SHALL support parallel authentication of users using [LDAP] server. - -#### RQ.SRS-007.LDAP.Authentication.Parallel.ValidAndInvalid -version: 1.0 - -[ClickHouse] SHALL support authentication of valid users and -prohibit authentication of invalid users using [LDAP] server -in parallel without having invalid attempts affecting valid authentications. - -### Specific - -#### RQ.SRS-007.LDAP.UnreachableServer -version: 1.0 - -[ClickHouse] SHALL return an error and prohibit user login if [LDAP] server is unreachable. - -#### RQ.SRS-007.LDAP.Configuration.Server.Name -version: 1.0 - -[ClickHouse] SHALL not support empty string as a server name. - -#### RQ.SRS-007.LDAP.Configuration.Server.Host -version: 1.0 - -[ClickHouse] SHALL support `` parameter to specify [LDAP] -server hostname or IP, this parameter SHALL be mandatory and SHALL not be empty. - -#### RQ.SRS-007.LDAP.Configuration.Server.Port -version: 1.0 - -[ClickHouse] SHALL support `` parameter to specify [LDAP] server port. - -#### RQ.SRS-007.LDAP.Configuration.Server.Port.Default -version: 1.0 - -[ClickHouse] SHALL use default port number `636` if `enable_tls` is set to `yes` or `389` otherwise. - -#### RQ.SRS-007.LDAP.Configuration.Server.AuthDN.Prefix -version: 1.0 - -[ClickHouse] SHALL support `` parameter to specify the prefix -of value used to construct the DN to bound to during authentication via [LDAP] server. - -#### RQ.SRS-007.LDAP.Configuration.Server.AuthDN.Suffix -version: 1.0 - -[ClickHouse] SHALL support `` parameter to specify the suffix -of value used to construct the DN to bound to during authentication via [LDAP] server. - -#### RQ.SRS-007.LDAP.Configuration.Server.AuthDN.Value -version: 1.0 - -[ClickHouse] SHALL construct DN as `auth_dn_prefix + escape(user_name) + auth_dn_suffix` string. - -> This implies that auth_dn_suffix should usually have comma ',' as its first non-space character. - -#### RQ.SRS-007.LDAP.Configuration.Server.EnableTLS -version: 1.0 - -[ClickHouse] SHALL support `` parameter to trigger the use of secure connection to the [LDAP] server. - -#### RQ.SRS-007.LDAP.Configuration.Server.EnableTLS.Options.Default -version: 1.0 - -[ClickHouse] SHALL use `yes` value as the default for `` parameter -to enable SSL/TLS `ldaps://` protocol. - -#### RQ.SRS-007.LDAP.Configuration.Server.EnableTLS.Options.No -version: 1.0 - -[ClickHouse] SHALL support specifying `no` as the value of `` parameter to enable -plain text `ldap://` protocol. - -#### RQ.SRS-007.LDAP.Configuration.Server.EnableTLS.Options.Yes -version: 1.0 - -[ClickHouse] SHALL support specifying `yes` as the value of `` parameter to enable -SSL/TLS `ldaps://` protocol. - -#### RQ.SRS-007.LDAP.Configuration.Server.EnableTLS.Options.StartTLS -version: 1.0 - -[ClickHouse] SHALL support specifying `starttls` as the value of `` parameter to enable -legacy `StartTLS` protocol that used plain text `ldap://` protocol, upgraded to [TLS]. - -#### RQ.SRS-007.LDAP.Configuration.Server.TLSMinimumProtocolVersion -version: 1.0 - -[ClickHouse] SHALL support `` parameter to specify -the minimum protocol version of SSL/TLS. - -#### RQ.SRS-007.LDAP.Configuration.Server.TLSMinimumProtocolVersion.Values -version: 1.0 - -[ClickHouse] SHALL support specifying `ssl2`, `ssl3`, `tls1.0`, `tls1.1`, and `tls1.2` -as a value of the `` parameter. - -#### RQ.SRS-007.LDAP.Configuration.Server.TLSMinimumProtocolVersion.Default -version: 1.0 - -[ClickHouse] SHALL set `tls1.2` as the default value of the `` parameter. - -#### RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert -version: 1.0 - -[ClickHouse] SHALL support `` parameter to specify [TLS] peer -certificate verification behavior. - -#### RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert.Options.Default -version: 1.0 - -[ClickHouse] SHALL use `demand` value as the default for the `` parameter. - -#### RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert.Options.Demand -version: 1.0 - -[ClickHouse] SHALL support specifying `demand` as the value of `` parameter to -enable requesting of client certificate. If no certificate is provided, or a bad certificate is -provided, the session SHALL be immediately terminated. - -#### RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert.Options.Allow -version: 1.0 - -[ClickHouse] SHALL support specifying `allow` as the value of `` parameter to -enable requesting of client certificate. If no -certificate is provided, the session SHALL proceed normally. -If a bad certificate is provided, it SHALL be ignored and the session SHALL proceed normally. - -#### RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert.Options.Try -version: 1.0 - -[ClickHouse] SHALL support specifying `try` as the value of `` parameter to -enable requesting of client certificate. If no certificate is provided, the session -SHALL proceed normally. If a bad certificate is provided, the session SHALL be -immediately terminated. - -#### RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert.Options.Never -version: 1.0 - -[ClickHouse] SHALL support specifying `never` as the value of `` parameter to -disable requesting of client certificate. - -#### RQ.SRS-007.LDAP.Configuration.Server.TLSCertFile -version: 1.0 - -[ClickHouse] SHALL support `` to specify the path to certificate file used by -[ClickHouse] to establish connection with the [LDAP] server. - -#### RQ.SRS-007.LDAP.Configuration.Server.TLSKeyFile -version: 1.0 - -[ClickHouse] SHALL support `` to specify the path to key file for the certificate -specified by the `` parameter. - -#### RQ.SRS-007.LDAP.Configuration.Server.TLSCACertDir -version: 1.0 - -[ClickHouse] SHALL support `` parameter to specify to a path to -the directory containing [CA] certificates used to verify certificates provided by the [LDAP] server. - -#### RQ.SRS-007.LDAP.Configuration.Server.TLSCACertFile -version: 1.0 - -[ClickHouse] SHALL support `` parameter to specify a path to a specific -[CA] certificate file used to verify certificates provided by the [LDAP] server. - -#### RQ.SRS-007.LDAP.Configuration.Server.TLSCipherSuite -version: 1.0 - -[ClickHouse] SHALL support `tls_cipher_suite` parameter to specify allowed cipher suites. -The value SHALL use the same format as the `ciphersuites` in the [OpenSSL Ciphers]. - -For example, - -```xml -ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:AES256-GCM-SHA384 -``` - -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.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 - 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. - -```xml - - - localhost - 636 - cn= - , ou=users, dc=example, dc=com - 0 - yes - tls1.2 - demand - /path/to/tls_cert_file - /path/to/tls_key_file - /path/to/tls_ca_cert_file - /path/to/tls_ca_cert_dir - ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:AES256-GCM-SHA384 - - -``` - -#### RQ.SRS-007.LDAP.Configuration.User.RBAC -version: 1.0 - -[ClickHouse] SHALL support creating users identified using an [LDAP] server using -the following RBAC command - -```sql -CREATE USER name IDENTIFIED WITH ldap_server BY 'server_name' -``` - -#### RQ.SRS-007.LDAP.Configuration.User.Syntax -version: 1.0 - -[ClickHouse] SHALL support the following example syntax to create a user that is authenticated using -an [LDAP] server inside the `users.xml` file or any configuration file inside the `users.d` directory. - -```xml - - - - - my_ldap_server - - - - -``` - -#### RQ.SRS-007.LDAP.Configuration.User.Name.Empty -version: 1.0 - -[ClickHouse] SHALL not support empty string as a user name. - -#### RQ.SRS-007.LDAP.Configuration.User.BothPasswordAndLDAP -version: 1.0 - -[ClickHouse] SHALL throw an error if `` is specified for the user and at the same -time user configuration contains any of the `` entries. - -#### RQ.SRS-007.LDAP.Configuration.User.LDAP.InvalidServerName.NotDefined -version: 1.0 - -[ClickHouse] SHALL throw an error during any authentification attempt -if the name of the [LDAP] server used inside the `` entry -is not defined in the `` section. - -#### RQ.SRS-007.LDAP.Configuration.User.LDAP.InvalidServerName.Empty -version: 1.0 - -[ClickHouse] SHALL throw an error during any authentification attempt -if the name of the [LDAP] server used inside the `` entry -is empty. - -#### RQ.SRS-007.LDAP.Configuration.User.OnlyOneServer -version: 1.0 - -[ClickHouse] SHALL support specifying only one [LDAP] server for a given user. - -#### RQ.SRS-007.LDAP.Configuration.User.Name.Long -version: 1.0 - -[ClickHouse] SHALL support long user names of at least 256 bytes -to specify users that can be authenticated using an [LDAP] server. - -#### RQ.SRS-007.LDAP.Configuration.User.Name.UTF8 -version: 1.0 - -[ClickHouse] SHALL support user names that contain [UTF-8] characters. - -#### RQ.SRS-007.LDAP.Authentication.Username.Empty -version: 1.0 - -[ClickHouse] SHALL not support authenticating users with empty username. - -#### RQ.SRS-007.LDAP.Authentication.Username.Long -version: 1.0 - -[ClickHouse] SHALL support authenticating users with a long username of at least 256 bytes. - -#### RQ.SRS-007.LDAP.Authentication.Username.UTF8 -version: 1.0 - -[ClickHouse] SHALL support authentication users with a username that contains [UTF-8] characters. - -#### RQ.SRS-007.LDAP.Authentication.Password.Empty -version: 1.0 - -[ClickHouse] SHALL not support authenticating users with empty passwords -even if an empty password is valid for the user and -is allowed by the [LDAP] server. - -#### RQ.SRS-007.LDAP.Authentication.Password.Long -version: 1.0 - -[ClickHouse] SHALL support long password of at least 256 bytes -that can be used to authenticate users using an [LDAP] server. - -#### RQ.SRS-007.LDAP.Authentication.Password.UTF8 -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 - -[Anonymous Authentication Mechanism of Simple Bind]: https://ldapwiki.com/wiki/Simple%20Authentication#section-Simple+Authentication-AnonymousAuthenticationMechanismOfSimpleBind -[Unauthenticated Authentication Mechanism of Simple Bind]: https://ldapwiki.com/wiki/Simple%20Authentication#section-Simple+Authentication-UnauthenticatedAuthenticationMechanismOfSimpleBind -[Name/Password Authentication Mechanism of Simple Bind]: https://ldapwiki.com/wiki/Simple%20Authentication#section-Simple+Authentication-NamePasswordAuthenticationMechanismOfSimpleBind -[UTF-8]: https://en.wikipedia.org/wiki/UTF-8 -[OpenSSL]: https://www.openssl.org/ -[OpenSSL Ciphers]: https://www.openssl.org/docs/manmaster/man1/openssl-ciphers.html -[CA]: https://en.wikipedia.org/wiki/Certificate_authority -[TLS]: https://en.wikipedia.org/wiki/Transport_Layer_Security -[LDAP]: https://en.wikipedia.org/wiki/Lightweight_Directory_Access_Protocol -[ClickHouse]: https://clickhouse.tech -[GitHub]: https://github.com -[GitHub Repository]: https://github.com/ClickHouse/ClickHouse/blob/master/tests/testflows/ldap/authentication/requirements/requirements.md -[Revision History]: https://github.com/ClickHouse/ClickHouse/commits/master/tests/testflows/ldap/authentication/requirements/requirements.md -[Git]: https://git-scm.com/ diff --git a/ldap/authentication/requirements/requirements.py b/ldap/authentication/requirements/requirements.py deleted file mode 100644 index f934e6c7a99..00000000000 --- a/ldap/authentication/requirements/requirements.py +++ /dev/null @@ -1,1687 +0,0 @@ -# These requirements were auto generated -# from software requirements specification (SRS) -# document by TestFlows v1.6.201101.1131719. -# Do not edit by hand but re-generate instead -# using 'tfs requirements generate' command. -from testflows.core import Specification -from testflows.core import Requirement - -SRS_007_ClickHouse_Authentication_of_Users_via_LDAP = Specification( - name='SRS-007 ClickHouse Authentication of Users via LDAP', - description=None, - author=None, - date=None, - status=None, - approved_by=None, - approved_date=None, - approved_version=None, - version=None, - group=None, - type=None, - link=None, - uid=None, - parent=None, - children=None, - content=''' -# SRS-007 ClickHouse Authentication of Users via LDAP - -## Table of Contents - -* 1 [Revision History](#revision-history) -* 2 [Introduction](#introduction) -* 3 [Terminology](#terminology) -* 4 [Requirements](#requirements) - * 4.1 [Generic](#generic) - * 4.1.1 [RQ.SRS-007.LDAP.Authentication](#rqsrs-007ldapauthentication) - * 4.1.2 [RQ.SRS-007.LDAP.Authentication.MultipleServers](#rqsrs-007ldapauthenticationmultipleservers) - * 4.1.3 [RQ.SRS-007.LDAP.Authentication.Protocol.PlainText](#rqsrs-007ldapauthenticationprotocolplaintext) - * 4.1.4 [RQ.SRS-007.LDAP.Authentication.Protocol.TLS](#rqsrs-007ldapauthenticationprotocoltls) - * 4.1.5 [RQ.SRS-007.LDAP.Authentication.Protocol.StartTLS](#rqsrs-007ldapauthenticationprotocolstarttls) - * 4.1.6 [RQ.SRS-007.LDAP.Authentication.TLS.Certificate.Validation](#rqsrs-007ldapauthenticationtlscertificatevalidation) - * 4.1.7 [RQ.SRS-007.LDAP.Authentication.TLS.Certificate.SelfSigned](#rqsrs-007ldapauthenticationtlscertificateselfsigned) - * 4.1.8 [RQ.SRS-007.LDAP.Authentication.TLS.Certificate.SpecificCertificationAuthority](#rqsrs-007ldapauthenticationtlscertificatespecificcertificationauthority) - * 4.1.9 [RQ.SRS-007.LDAP.Server.Configuration.Invalid](#rqsrs-007ldapserverconfigurationinvalid) - * 4.1.10 [RQ.SRS-007.LDAP.User.Configuration.Invalid](#rqsrs-007ldapuserconfigurationinvalid) - * 4.1.11 [RQ.SRS-007.LDAP.Authentication.Mechanism.Anonymous](#rqsrs-007ldapauthenticationmechanismanonymous) - * 4.1.12 [RQ.SRS-007.LDAP.Authentication.Mechanism.Unauthenticated](#rqsrs-007ldapauthenticationmechanismunauthenticated) - * 4.1.13 [RQ.SRS-007.LDAP.Authentication.Mechanism.NamePassword](#rqsrs-007ldapauthenticationmechanismnamepassword) - * 4.1.14 [RQ.SRS-007.LDAP.Authentication.Valid](#rqsrs-007ldapauthenticationvalid) - * 4.1.15 [RQ.SRS-007.LDAP.Authentication.Invalid](#rqsrs-007ldapauthenticationinvalid) - * 4.1.16 [RQ.SRS-007.LDAP.Authentication.Invalid.DeletedUser](#rqsrs-007ldapauthenticationinvaliddeleteduser) - * 4.1.17 [RQ.SRS-007.LDAP.Authentication.UsernameChanged](#rqsrs-007ldapauthenticationusernamechanged) - * 4.1.18 [RQ.SRS-007.LDAP.Authentication.PasswordChanged](#rqsrs-007ldapauthenticationpasswordchanged) - * 4.1.19 [RQ.SRS-007.LDAP.Authentication.LDAPServerRestart](#rqsrs-007ldapauthenticationldapserverrestart) - * 4.1.20 [RQ.SRS-007.LDAP.Authentication.ClickHouseServerRestart](#rqsrs-007ldapauthenticationclickhouseserverrestart) - * 4.1.21 [RQ.SRS-007.LDAP.Authentication.Parallel](#rqsrs-007ldapauthenticationparallel) - * 4.1.22 [RQ.SRS-007.LDAP.Authentication.Parallel.ValidAndInvalid](#rqsrs-007ldapauthenticationparallelvalidandinvalid) - * 4.2 [Specific](#specific) - * 4.2.1 [RQ.SRS-007.LDAP.UnreachableServer](#rqsrs-007ldapunreachableserver) - * 4.2.2 [RQ.SRS-007.LDAP.Configuration.Server.Name](#rqsrs-007ldapconfigurationservername) - * 4.2.3 [RQ.SRS-007.LDAP.Configuration.Server.Host](#rqsrs-007ldapconfigurationserverhost) - * 4.2.4 [RQ.SRS-007.LDAP.Configuration.Server.Port](#rqsrs-007ldapconfigurationserverport) - * 4.2.5 [RQ.SRS-007.LDAP.Configuration.Server.Port.Default](#rqsrs-007ldapconfigurationserverportdefault) - * 4.2.6 [RQ.SRS-007.LDAP.Configuration.Server.AuthDN.Prefix](#rqsrs-007ldapconfigurationserverauthdnprefix) - * 4.2.7 [RQ.SRS-007.LDAP.Configuration.Server.AuthDN.Suffix](#rqsrs-007ldapconfigurationserverauthdnsuffix) - * 4.2.8 [RQ.SRS-007.LDAP.Configuration.Server.AuthDN.Value](#rqsrs-007ldapconfigurationserverauthdnvalue) - * 4.2.9 [RQ.SRS-007.LDAP.Configuration.Server.EnableTLS](#rqsrs-007ldapconfigurationserverenabletls) - * 4.2.10 [RQ.SRS-007.LDAP.Configuration.Server.EnableTLS.Options.Default](#rqsrs-007ldapconfigurationserverenabletlsoptionsdefault) - * 4.2.11 [RQ.SRS-007.LDAP.Configuration.Server.EnableTLS.Options.No](#rqsrs-007ldapconfigurationserverenabletlsoptionsno) - * 4.2.12 [RQ.SRS-007.LDAP.Configuration.Server.EnableTLS.Options.Yes](#rqsrs-007ldapconfigurationserverenabletlsoptionsyes) - * 4.2.13 [RQ.SRS-007.LDAP.Configuration.Server.EnableTLS.Options.StartTLS](#rqsrs-007ldapconfigurationserverenabletlsoptionsstarttls) - * 4.2.14 [RQ.SRS-007.LDAP.Configuration.Server.TLSMinimumProtocolVersion](#rqsrs-007ldapconfigurationservertlsminimumprotocolversion) - * 4.2.15 [RQ.SRS-007.LDAP.Configuration.Server.TLSMinimumProtocolVersion.Values](#rqsrs-007ldapconfigurationservertlsminimumprotocolversionvalues) - * 4.2.16 [RQ.SRS-007.LDAP.Configuration.Server.TLSMinimumProtocolVersion.Default](#rqsrs-007ldapconfigurationservertlsminimumprotocolversiondefault) - * 4.2.17 [RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert](#rqsrs-007ldapconfigurationservertlsrequirecert) - * 4.2.18 [RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert.Options.Default](#rqsrs-007ldapconfigurationservertlsrequirecertoptionsdefault) - * 4.2.19 [RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert.Options.Demand](#rqsrs-007ldapconfigurationservertlsrequirecertoptionsdemand) - * 4.2.20 [RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert.Options.Allow](#rqsrs-007ldapconfigurationservertlsrequirecertoptionsallow) - * 4.2.21 [RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert.Options.Try](#rqsrs-007ldapconfigurationservertlsrequirecertoptionstry) - * 4.2.22 [RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert.Options.Never](#rqsrs-007ldapconfigurationservertlsrequirecertoptionsnever) - * 4.2.23 [RQ.SRS-007.LDAP.Configuration.Server.TLSCertFile](#rqsrs-007ldapconfigurationservertlscertfile) - * 4.2.24 [RQ.SRS-007.LDAP.Configuration.Server.TLSKeyFile](#rqsrs-007ldapconfigurationservertlskeyfile) - * 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.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 - -This document is stored in an electronic form using [Git] source control management software -hosted in a [GitHub Repository]. -All the updates are tracked using the [Git]'s [Revision History]. - -## Introduction - -[ClickHouse] currently does not have any integration with [LDAP]. -As the initial step in integrating with [LDAP] this software requirements specification covers -only the requirements to enable authentication of users using an [LDAP] server. - -## Terminology - -* **CA** - - Certificate Authority ([CA]) - -* **LDAP** - - Lightweight Directory Access Protocol ([LDAP]) - -## Requirements - -### Generic - -#### RQ.SRS-007.LDAP.Authentication -version: 1.0 - -[ClickHouse] SHALL support user authentication via an [LDAP] server. - -#### RQ.SRS-007.LDAP.Authentication.MultipleServers -version: 1.0 - -[ClickHouse] SHALL support specifying multiple [LDAP] servers that can be used to authenticate -users. - -#### RQ.SRS-007.LDAP.Authentication.Protocol.PlainText -version: 1.0 - -[ClickHouse] SHALL support user authentication using plain text `ldap://` non secure protocol. - -#### RQ.SRS-007.LDAP.Authentication.Protocol.TLS -version: 1.0 - -[ClickHouse] SHALL support user authentication using `SSL/TLS` `ldaps://` secure protocol. - -#### RQ.SRS-007.LDAP.Authentication.Protocol.StartTLS -version: 1.0 - -[ClickHouse] SHALL support user authentication using legacy `StartTLS` protocol which is a -plain text `ldap://` protocol that is upgraded to [TLS]. - -#### RQ.SRS-007.LDAP.Authentication.TLS.Certificate.Validation -version: 1.0 - -[ClickHouse] SHALL support certificate validation used for [TLS] connections. - -#### RQ.SRS-007.LDAP.Authentication.TLS.Certificate.SelfSigned -version: 1.0 - -[ClickHouse] SHALL support self-signed certificates for [TLS] connections. - -#### RQ.SRS-007.LDAP.Authentication.TLS.Certificate.SpecificCertificationAuthority -version: 1.0 - -[ClickHouse] SHALL support certificates signed by specific Certification Authority for [TLS] connections. - -#### RQ.SRS-007.LDAP.Server.Configuration.Invalid -version: 1.0 - -[ClickHouse] SHALL return an error and prohibit user login if [LDAP] server configuration is not valid. - -#### RQ.SRS-007.LDAP.User.Configuration.Invalid -version: 1.0 - -[ClickHouse] SHALL return an error and prohibit user login if user configuration is not valid. - -#### RQ.SRS-007.LDAP.Authentication.Mechanism.Anonymous -version: 1.0 - -[ClickHouse] SHALL return an error and prohibit authentication using [Anonymous Authentication Mechanism of Simple Bind] -authentication mechanism. - -#### RQ.SRS-007.LDAP.Authentication.Mechanism.Unauthenticated -version: 1.0 - -[ClickHouse] SHALL return an error and prohibit authentication using [Unauthenticated Authentication Mechanism of Simple Bind] -authentication mechanism. - -#### RQ.SRS-007.LDAP.Authentication.Mechanism.NamePassword -version: 1.0 - -[ClickHouse] SHALL allow authentication using only [Name/Password Authentication Mechanism of Simple Bind] -authentication mechanism. - -#### RQ.SRS-007.LDAP.Authentication.Valid -version: 1.0 - -[ClickHouse] SHALL only allow user authentication using [LDAP] server if and only if -user name and password match [LDAP] server records for the user. - -#### RQ.SRS-007.LDAP.Authentication.Invalid -version: 1.0 - -[ClickHouse] SHALL return an error and prohibit authentication if either user name or password -do not match [LDAP] server records for the user. - -#### RQ.SRS-007.LDAP.Authentication.Invalid.DeletedUser -version: 1.0 - -[ClickHouse] SHALL return an error and prohibit authentication if the user -has been deleted from the [LDAP] server. - -#### RQ.SRS-007.LDAP.Authentication.UsernameChanged -version: 1.0 - -[ClickHouse] SHALL return an error and prohibit authentication if the username is changed -on the [LDAP] server. - -#### RQ.SRS-007.LDAP.Authentication.PasswordChanged -version: 1.0 - -[ClickHouse] SHALL return an error and prohibit authentication if the password -for the user is changed on the [LDAP] server. - -#### RQ.SRS-007.LDAP.Authentication.LDAPServerRestart -version: 1.0 - -[ClickHouse] SHALL support authenticating users after [LDAP] server is restarted. - -#### RQ.SRS-007.LDAP.Authentication.ClickHouseServerRestart -version: 1.0 - -[ClickHouse] SHALL support authenticating users after server is restarted. - -#### RQ.SRS-007.LDAP.Authentication.Parallel -version: 1.0 - -[ClickHouse] SHALL support parallel authentication of users using [LDAP] server. - -#### RQ.SRS-007.LDAP.Authentication.Parallel.ValidAndInvalid -version: 1.0 - -[ClickHouse] SHALL support authentication of valid users and -prohibit authentication of invalid users using [LDAP] server -in parallel without having invalid attempts affecting valid authentications. - -### Specific - -#### RQ.SRS-007.LDAP.UnreachableServer -version: 1.0 - -[ClickHouse] SHALL return an error and prohibit user login if [LDAP] server is unreachable. - -#### RQ.SRS-007.LDAP.Configuration.Server.Name -version: 1.0 - -[ClickHouse] SHALL not support empty string as a server name. - -#### RQ.SRS-007.LDAP.Configuration.Server.Host -version: 1.0 - -[ClickHouse] SHALL support `` parameter to specify [LDAP] -server hostname or IP, this parameter SHALL be mandatory and SHALL not be empty. - -#### RQ.SRS-007.LDAP.Configuration.Server.Port -version: 1.0 - -[ClickHouse] SHALL support `` parameter to specify [LDAP] server port. - -#### RQ.SRS-007.LDAP.Configuration.Server.Port.Default -version: 1.0 - -[ClickHouse] SHALL use default port number `636` if `enable_tls` is set to `yes` or `389` otherwise. - -#### RQ.SRS-007.LDAP.Configuration.Server.AuthDN.Prefix -version: 1.0 - -[ClickHouse] SHALL support `` parameter to specify the prefix -of value used to construct the DN to bound to during authentication via [LDAP] server. - -#### RQ.SRS-007.LDAP.Configuration.Server.AuthDN.Suffix -version: 1.0 - -[ClickHouse] SHALL support `` parameter to specify the suffix -of value used to construct the DN to bound to during authentication via [LDAP] server. - -#### RQ.SRS-007.LDAP.Configuration.Server.AuthDN.Value -version: 1.0 - -[ClickHouse] SHALL construct DN as `auth_dn_prefix + escape(user_name) + auth_dn_suffix` string. - -> This implies that auth_dn_suffix should usually have comma ',' as its first non-space character. - -#### RQ.SRS-007.LDAP.Configuration.Server.EnableTLS -version: 1.0 - -[ClickHouse] SHALL support `` parameter to trigger the use of secure connection to the [LDAP] server. - -#### RQ.SRS-007.LDAP.Configuration.Server.EnableTLS.Options.Default -version: 1.0 - -[ClickHouse] SHALL use `yes` value as the default for `` parameter -to enable SSL/TLS `ldaps://` protocol. - -#### RQ.SRS-007.LDAP.Configuration.Server.EnableTLS.Options.No -version: 1.0 - -[ClickHouse] SHALL support specifying `no` as the value of `` parameter to enable -plain text `ldap://` protocol. - -#### RQ.SRS-007.LDAP.Configuration.Server.EnableTLS.Options.Yes -version: 1.0 - -[ClickHouse] SHALL support specifying `yes` as the value of `` parameter to enable -SSL/TLS `ldaps://` protocol. - -#### RQ.SRS-007.LDAP.Configuration.Server.EnableTLS.Options.StartTLS -version: 1.0 - -[ClickHouse] SHALL support specifying `starttls` as the value of `` parameter to enable -legacy `StartTLS` protocol that used plain text `ldap://` protocol, upgraded to [TLS]. - -#### RQ.SRS-007.LDAP.Configuration.Server.TLSMinimumProtocolVersion -version: 1.0 - -[ClickHouse] SHALL support `` parameter to specify -the minimum protocol version of SSL/TLS. - -#### RQ.SRS-007.LDAP.Configuration.Server.TLSMinimumProtocolVersion.Values -version: 1.0 - -[ClickHouse] SHALL support specifying `ssl2`, `ssl3`, `tls1.0`, `tls1.1`, and `tls1.2` -as a value of the `` parameter. - -#### RQ.SRS-007.LDAP.Configuration.Server.TLSMinimumProtocolVersion.Default -version: 1.0 - -[ClickHouse] SHALL set `tls1.2` as the default value of the `` parameter. - -#### RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert -version: 1.0 - -[ClickHouse] SHALL support `` parameter to specify [TLS] peer -certificate verification behavior. - -#### RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert.Options.Default -version: 1.0 - -[ClickHouse] SHALL use `demand` value as the default for the `` parameter. - -#### RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert.Options.Demand -version: 1.0 - -[ClickHouse] SHALL support specifying `demand` as the value of `` parameter to -enable requesting of client certificate. If no certificate is provided, or a bad certificate is -provided, the session SHALL be immediately terminated. - -#### RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert.Options.Allow -version: 1.0 - -[ClickHouse] SHALL support specifying `allow` as the value of `` parameter to -enable requesting of client certificate. If no -certificate is provided, the session SHALL proceed normally. -If a bad certificate is provided, it SHALL be ignored and the session SHALL proceed normally. - -#### RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert.Options.Try -version: 1.0 - -[ClickHouse] SHALL support specifying `try` as the value of `` parameter to -enable requesting of client certificate. If no certificate is provided, the session -SHALL proceed normally. If a bad certificate is provided, the session SHALL be -immediately terminated. - -#### RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert.Options.Never -version: 1.0 - -[ClickHouse] SHALL support specifying `never` as the value of `` parameter to -disable requesting of client certificate. - -#### RQ.SRS-007.LDAP.Configuration.Server.TLSCertFile -version: 1.0 - -[ClickHouse] SHALL support `` to specify the path to certificate file used by -[ClickHouse] to establish connection with the [LDAP] server. - -#### RQ.SRS-007.LDAP.Configuration.Server.TLSKeyFile -version: 1.0 - -[ClickHouse] SHALL support `` to specify the path to key file for the certificate -specified by the `` parameter. - -#### RQ.SRS-007.LDAP.Configuration.Server.TLSCACertDir -version: 1.0 - -[ClickHouse] SHALL support `` parameter to specify to a path to -the directory containing [CA] certificates used to verify certificates provided by the [LDAP] server. - -#### RQ.SRS-007.LDAP.Configuration.Server.TLSCACertFile -version: 1.0 - -[ClickHouse] SHALL support `` parameter to specify a path to a specific -[CA] certificate file used to verify certificates provided by the [LDAP] server. - -#### RQ.SRS-007.LDAP.Configuration.Server.TLSCipherSuite -version: 1.0 - -[ClickHouse] SHALL support `tls_cipher_suite` parameter to specify allowed cipher suites. -The value SHALL use the same format as the `ciphersuites` in the [OpenSSL Ciphers]. - -For example, - -```xml -ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:AES256-GCM-SHA384 -``` - -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.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 - 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. - -```xml - - - localhost - 636 - cn= - , ou=users, dc=example, dc=com - 0 - yes - tls1.2 - demand - /path/to/tls_cert_file - /path/to/tls_key_file - /path/to/tls_ca_cert_file - /path/to/tls_ca_cert_dir - ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:AES256-GCM-SHA384 - - -``` - -#### RQ.SRS-007.LDAP.Configuration.User.RBAC -version: 1.0 - -[ClickHouse] SHALL support creating users identified using an [LDAP] server using -the following RBAC command - -```sql -CREATE USER name IDENTIFIED WITH ldap_server BY 'server_name' -``` - -#### RQ.SRS-007.LDAP.Configuration.User.Syntax -version: 1.0 - -[ClickHouse] SHALL support the following example syntax to create a user that is authenticated using -an [LDAP] server inside the `users.xml` file or any configuration file inside the `users.d` directory. - -```xml - - - - - my_ldap_server - - - - -``` - -#### RQ.SRS-007.LDAP.Configuration.User.Name.Empty -version: 1.0 - -[ClickHouse] SHALL not support empty string as a user name. - -#### RQ.SRS-007.LDAP.Configuration.User.BothPasswordAndLDAP -version: 1.0 - -[ClickHouse] SHALL throw an error if `` is specified for the user and at the same -time user configuration contains any of the `` entries. - -#### RQ.SRS-007.LDAP.Configuration.User.LDAP.InvalidServerName.NotDefined -version: 1.0 - -[ClickHouse] SHALL throw an error during any authentification attempt -if the name of the [LDAP] server used inside the `` entry -is not defined in the `` section. - -#### RQ.SRS-007.LDAP.Configuration.User.LDAP.InvalidServerName.Empty -version: 1.0 - -[ClickHouse] SHALL throw an error during any authentification attempt -if the name of the [LDAP] server used inside the `` entry -is empty. - -#### RQ.SRS-007.LDAP.Configuration.User.OnlyOneServer -version: 1.0 - -[ClickHouse] SHALL support specifying only one [LDAP] server for a given user. - -#### RQ.SRS-007.LDAP.Configuration.User.Name.Long -version: 1.0 - -[ClickHouse] SHALL support long user names of at least 256 bytes -to specify users that can be authenticated using an [LDAP] server. - -#### RQ.SRS-007.LDAP.Configuration.User.Name.UTF8 -version: 1.0 - -[ClickHouse] SHALL support user names that contain [UTF-8] characters. - -#### RQ.SRS-007.LDAP.Authentication.Username.Empty -version: 1.0 - -[ClickHouse] SHALL not support authenticating users with empty username. - -#### RQ.SRS-007.LDAP.Authentication.Username.Long -version: 1.0 - -[ClickHouse] SHALL support authenticating users with a long username of at least 256 bytes. - -#### RQ.SRS-007.LDAP.Authentication.Username.UTF8 -version: 1.0 - -[ClickHouse] SHALL support authentication users with a username that contains [UTF-8] characters. - -#### RQ.SRS-007.LDAP.Authentication.Password.Empty -version: 1.0 - -[ClickHouse] SHALL not support authenticating users with empty passwords -even if an empty password is valid for the user and -is allowed by the [LDAP] server. - -#### RQ.SRS-007.LDAP.Authentication.Password.Long -version: 1.0 - -[ClickHouse] SHALL support long password of at least 256 bytes -that can be used to authenticate users using an [LDAP] server. - -#### RQ.SRS-007.LDAP.Authentication.Password.UTF8 -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 - -[Anonymous Authentication Mechanism of Simple Bind]: https://ldapwiki.com/wiki/Simple%20Authentication#section-Simple+Authentication-AnonymousAuthenticationMechanismOfSimpleBind -[Unauthenticated Authentication Mechanism of Simple Bind]: https://ldapwiki.com/wiki/Simple%20Authentication#section-Simple+Authentication-UnauthenticatedAuthenticationMechanismOfSimpleBind -[Name/Password Authentication Mechanism of Simple Bind]: https://ldapwiki.com/wiki/Simple%20Authentication#section-Simple+Authentication-NamePasswordAuthenticationMechanismOfSimpleBind -[UTF-8]: https://en.wikipedia.org/wiki/UTF-8 -[OpenSSL]: https://www.openssl.org/ -[OpenSSL Ciphers]: https://www.openssl.org/docs/manmaster/man1/openssl-ciphers.html -[CA]: https://en.wikipedia.org/wiki/Certificate_authority -[TLS]: https://en.wikipedia.org/wiki/Transport_Layer_Security -[LDAP]: https://en.wikipedia.org/wiki/Lightweight_Directory_Access_Protocol -[ClickHouse]: https://clickhouse.tech -[GitHub]: https://github.com -[GitHub Repository]: https://github.com/ClickHouse/ClickHouse/blob/master/tests/testflows/ldap/authentication/requirements/requirements.md -[Revision History]: https://github.com/ClickHouse/ClickHouse/commits/master/tests/testflows/ldap/authentication/requirements/requirements.md -[Git]: https://git-scm.com/ -''') - -RQ_SRS_007_LDAP_Authentication = Requirement( - name='RQ.SRS-007.LDAP.Authentication', - version='1.0', - priority=None, - group=None, - type=None, - uid=None, - description=( - '[ClickHouse] SHALL support user authentication via an [LDAP] server.\n' - '\n' - ), - link=None) - -RQ_SRS_007_LDAP_Authentication_MultipleServers = Requirement( - name='RQ.SRS-007.LDAP.Authentication.MultipleServers', - version='1.0', - priority=None, - group=None, - type=None, - uid=None, - description=( - '[ClickHouse] SHALL support specifying multiple [LDAP] servers that can be used to authenticate\n' - 'users.\n' - '\n' - ), - link=None) - -RQ_SRS_007_LDAP_Authentication_Protocol_PlainText = Requirement( - name='RQ.SRS-007.LDAP.Authentication.Protocol.PlainText', - version='1.0', - priority=None, - group=None, - type=None, - uid=None, - description=( - '[ClickHouse] SHALL support user authentication using plain text `ldap://` non secure protocol.\n' - '\n' - ), - link=None) - -RQ_SRS_007_LDAP_Authentication_Protocol_TLS = Requirement( - name='RQ.SRS-007.LDAP.Authentication.Protocol.TLS', - version='1.0', - priority=None, - group=None, - type=None, - uid=None, - description=( - '[ClickHouse] SHALL support user authentication using `SSL/TLS` `ldaps://` secure protocol.\n' - '\n' - ), - link=None) - -RQ_SRS_007_LDAP_Authentication_Protocol_StartTLS = Requirement( - name='RQ.SRS-007.LDAP.Authentication.Protocol.StartTLS', - version='1.0', - priority=None, - group=None, - type=None, - uid=None, - description=( - '[ClickHouse] SHALL support user authentication using legacy `StartTLS` protocol which is a\n' - 'plain text `ldap://` protocol that is upgraded to [TLS].\n' - '\n' - ), - link=None) - -RQ_SRS_007_LDAP_Authentication_TLS_Certificate_Validation = Requirement( - name='RQ.SRS-007.LDAP.Authentication.TLS.Certificate.Validation', - version='1.0', - priority=None, - group=None, - type=None, - uid=None, - description=( - '[ClickHouse] SHALL support certificate validation used for [TLS] connections.\n' - '\n' - ), - link=None) - -RQ_SRS_007_LDAP_Authentication_TLS_Certificate_SelfSigned = Requirement( - name='RQ.SRS-007.LDAP.Authentication.TLS.Certificate.SelfSigned', - version='1.0', - priority=None, - group=None, - type=None, - uid=None, - description=( - '[ClickHouse] SHALL support self-signed certificates for [TLS] connections.\n' - '\n' - ), - link=None) - -RQ_SRS_007_LDAP_Authentication_TLS_Certificate_SpecificCertificationAuthority = Requirement( - name='RQ.SRS-007.LDAP.Authentication.TLS.Certificate.SpecificCertificationAuthority', - version='1.0', - priority=None, - group=None, - type=None, - uid=None, - description=( - '[ClickHouse] SHALL support certificates signed by specific Certification Authority for [TLS] connections.\n' - '\n' - ), - link=None) - -RQ_SRS_007_LDAP_Server_Configuration_Invalid = Requirement( - name='RQ.SRS-007.LDAP.Server.Configuration.Invalid', - version='1.0', - priority=None, - group=None, - type=None, - uid=None, - description=( - '[ClickHouse] SHALL return an error and prohibit user login if [LDAP] server configuration is not valid.\n' - '\n' - ), - link=None) - -RQ_SRS_007_LDAP_User_Configuration_Invalid = Requirement( - name='RQ.SRS-007.LDAP.User.Configuration.Invalid', - version='1.0', - priority=None, - group=None, - type=None, - uid=None, - description=( - '[ClickHouse] SHALL return an error and prohibit user login if user configuration is not valid.\n' - '\n' - ), - link=None) - -RQ_SRS_007_LDAP_Authentication_Mechanism_Anonymous = Requirement( - name='RQ.SRS-007.LDAP.Authentication.Mechanism.Anonymous', - version='1.0', - priority=None, - group=None, - type=None, - uid=None, - description=( - '[ClickHouse] SHALL return an error and prohibit authentication using [Anonymous Authentication Mechanism of Simple Bind]\n' - 'authentication mechanism.\n' - '\n' - ), - link=None) - -RQ_SRS_007_LDAP_Authentication_Mechanism_Unauthenticated = Requirement( - name='RQ.SRS-007.LDAP.Authentication.Mechanism.Unauthenticated', - version='1.0', - priority=None, - group=None, - type=None, - uid=None, - description=( - '[ClickHouse] SHALL return an error and prohibit authentication using [Unauthenticated Authentication Mechanism of Simple Bind]\n' - 'authentication mechanism.\n' - '\n' - ), - link=None) - -RQ_SRS_007_LDAP_Authentication_Mechanism_NamePassword = Requirement( - name='RQ.SRS-007.LDAP.Authentication.Mechanism.NamePassword', - version='1.0', - priority=None, - group=None, - type=None, - uid=None, - description=( - '[ClickHouse] SHALL allow authentication using only [Name/Password Authentication Mechanism of Simple Bind]\n' - 'authentication mechanism.\n' - '\n' - ), - link=None) - -RQ_SRS_007_LDAP_Authentication_Valid = Requirement( - name='RQ.SRS-007.LDAP.Authentication.Valid', - version='1.0', - priority=None, - group=None, - type=None, - uid=None, - description=( - '[ClickHouse] SHALL only allow user authentication using [LDAP] server if and only if\n' - 'user name and password match [LDAP] server records for the user.\n' - '\n' - ), - link=None) - -RQ_SRS_007_LDAP_Authentication_Invalid = Requirement( - name='RQ.SRS-007.LDAP.Authentication.Invalid', - version='1.0', - priority=None, - group=None, - type=None, - uid=None, - description=( - '[ClickHouse] SHALL return an error and prohibit authentication if either user name or password\n' - 'do not match [LDAP] server records for the user.\n' - '\n' - ), - link=None) - -RQ_SRS_007_LDAP_Authentication_Invalid_DeletedUser = Requirement( - name='RQ.SRS-007.LDAP.Authentication.Invalid.DeletedUser', - version='1.0', - priority=None, - group=None, - type=None, - uid=None, - description=( - '[ClickHouse] SHALL return an error and prohibit authentication if the user\n' - 'has been deleted from the [LDAP] server.\n' - '\n' - ), - link=None) - -RQ_SRS_007_LDAP_Authentication_UsernameChanged = Requirement( - name='RQ.SRS-007.LDAP.Authentication.UsernameChanged', - version='1.0', - priority=None, - group=None, - type=None, - uid=None, - description=( - '[ClickHouse] SHALL return an error and prohibit authentication if the username is changed\n' - 'on the [LDAP] server.\n' - '\n' - ), - link=None) - -RQ_SRS_007_LDAP_Authentication_PasswordChanged = Requirement( - name='RQ.SRS-007.LDAP.Authentication.PasswordChanged', - version='1.0', - priority=None, - group=None, - type=None, - uid=None, - description=( - '[ClickHouse] SHALL return an error and prohibit authentication if the password\n' - 'for the user is changed on the [LDAP] server.\n' - '\n' - ), - link=None) - -RQ_SRS_007_LDAP_Authentication_LDAPServerRestart = Requirement( - name='RQ.SRS-007.LDAP.Authentication.LDAPServerRestart', - version='1.0', - priority=None, - group=None, - type=None, - uid=None, - description=( - '[ClickHouse] SHALL support authenticating users after [LDAP] server is restarted.\n' - '\n' - ), - link=None) - -RQ_SRS_007_LDAP_Authentication_ClickHouseServerRestart = Requirement( - name='RQ.SRS-007.LDAP.Authentication.ClickHouseServerRestart', - version='1.0', - priority=None, - group=None, - type=None, - uid=None, - description=( - '[ClickHouse] SHALL support authenticating users after server is restarted.\n' - '\n' - ), - link=None) - -RQ_SRS_007_LDAP_Authentication_Parallel = Requirement( - name='RQ.SRS-007.LDAP.Authentication.Parallel', - version='1.0', - priority=None, - group=None, - type=None, - uid=None, - description=( - '[ClickHouse] SHALL support parallel authentication of users using [LDAP] server.\n' - '\n' - ), - link=None) - -RQ_SRS_007_LDAP_Authentication_Parallel_ValidAndInvalid = Requirement( - name='RQ.SRS-007.LDAP.Authentication.Parallel.ValidAndInvalid', - version='1.0', - priority=None, - group=None, - type=None, - uid=None, - description=( - '[ClickHouse] SHALL support authentication of valid users and\n' - 'prohibit authentication of invalid users using [LDAP] server\n' - 'in parallel without having invalid attempts affecting valid authentications.\n' - '\n' - ), - link=None) - -RQ_SRS_007_LDAP_UnreachableServer = Requirement( - name='RQ.SRS-007.LDAP.UnreachableServer', - version='1.0', - priority=None, - group=None, - type=None, - uid=None, - description=( - '[ClickHouse] SHALL return an error and prohibit user login if [LDAP] server is unreachable.\n' - '\n' - ), - link=None) - -RQ_SRS_007_LDAP_Configuration_Server_Name = Requirement( - name='RQ.SRS-007.LDAP.Configuration.Server.Name', - version='1.0', - priority=None, - group=None, - type=None, - uid=None, - description=( - '[ClickHouse] SHALL not support empty string as a server name.\n' - '\n' - ), - link=None) - -RQ_SRS_007_LDAP_Configuration_Server_Host = Requirement( - name='RQ.SRS-007.LDAP.Configuration.Server.Host', - version='1.0', - priority=None, - group=None, - type=None, - uid=None, - description=( - '[ClickHouse] SHALL support `` parameter to specify [LDAP]\n' - 'server hostname or IP, this parameter SHALL be mandatory and SHALL not be empty.\n' - '\n' - ), - link=None) - -RQ_SRS_007_LDAP_Configuration_Server_Port = Requirement( - name='RQ.SRS-007.LDAP.Configuration.Server.Port', - version='1.0', - priority=None, - group=None, - type=None, - uid=None, - description=( - '[ClickHouse] SHALL support `` parameter to specify [LDAP] server port.\n' - '\n' - ), - link=None) - -RQ_SRS_007_LDAP_Configuration_Server_Port_Default = Requirement( - name='RQ.SRS-007.LDAP.Configuration.Server.Port.Default', - version='1.0', - priority=None, - group=None, - type=None, - uid=None, - description=( - '[ClickHouse] SHALL use default port number `636` if `enable_tls` is set to `yes` or `389` otherwise.\n' - '\n' - ), - link=None) - -RQ_SRS_007_LDAP_Configuration_Server_AuthDN_Prefix = Requirement( - name='RQ.SRS-007.LDAP.Configuration.Server.AuthDN.Prefix', - version='1.0', - priority=None, - group=None, - type=None, - uid=None, - description=( - '[ClickHouse] SHALL support `` parameter to specify the prefix\n' - 'of value used to construct the DN to bound to during authentication via [LDAP] server.\n' - '\n' - ), - link=None) - -RQ_SRS_007_LDAP_Configuration_Server_AuthDN_Suffix = Requirement( - name='RQ.SRS-007.LDAP.Configuration.Server.AuthDN.Suffix', - version='1.0', - priority=None, - group=None, - type=None, - uid=None, - description=( - '[ClickHouse] SHALL support `` parameter to specify the suffix\n' - 'of value used to construct the DN to bound to during authentication via [LDAP] server.\n' - '\n' - ), - link=None) - -RQ_SRS_007_LDAP_Configuration_Server_AuthDN_Value = Requirement( - name='RQ.SRS-007.LDAP.Configuration.Server.AuthDN.Value', - version='1.0', - priority=None, - group=None, - type=None, - uid=None, - description=( - '[ClickHouse] SHALL construct DN as `auth_dn_prefix + escape(user_name) + auth_dn_suffix` string.\n' - '\n' - "> This implies that auth_dn_suffix should usually have comma ',' as its first non-space character.\n" - '\n' - ), - link=None) - -RQ_SRS_007_LDAP_Configuration_Server_EnableTLS = Requirement( - name='RQ.SRS-007.LDAP.Configuration.Server.EnableTLS', - version='1.0', - priority=None, - group=None, - type=None, - uid=None, - description=( - '[ClickHouse] SHALL support `` parameter to trigger the use of secure connection to the [LDAP] server.\n' - '\n' - ), - link=None) - -RQ_SRS_007_LDAP_Configuration_Server_EnableTLS_Options_Default = Requirement( - name='RQ.SRS-007.LDAP.Configuration.Server.EnableTLS.Options.Default', - version='1.0', - priority=None, - group=None, - type=None, - uid=None, - description=( - '[ClickHouse] SHALL use `yes` value as the default for `` parameter\n' - 'to enable SSL/TLS `ldaps://` protocol.\n' - '\n' - ), - link=None) - -RQ_SRS_007_LDAP_Configuration_Server_EnableTLS_Options_No = Requirement( - name='RQ.SRS-007.LDAP.Configuration.Server.EnableTLS.Options.No', - version='1.0', - priority=None, - group=None, - type=None, - uid=None, - description=( - '[ClickHouse] SHALL support specifying `no` as the value of `` parameter to enable\n' - 'plain text `ldap://` protocol.\n' - '\n' - ), - link=None) - -RQ_SRS_007_LDAP_Configuration_Server_EnableTLS_Options_Yes = Requirement( - name='RQ.SRS-007.LDAP.Configuration.Server.EnableTLS.Options.Yes', - version='1.0', - priority=None, - group=None, - type=None, - uid=None, - description=( - '[ClickHouse] SHALL support specifying `yes` as the value of `` parameter to enable\n' - 'SSL/TLS `ldaps://` protocol.\n' - '\n' - ), - link=None) - -RQ_SRS_007_LDAP_Configuration_Server_EnableTLS_Options_StartTLS = Requirement( - name='RQ.SRS-007.LDAP.Configuration.Server.EnableTLS.Options.StartTLS', - version='1.0', - priority=None, - group=None, - type=None, - uid=None, - description=( - '[ClickHouse] SHALL support specifying `starttls` as the value of `` parameter to enable\n' - 'legacy `StartTLS` protocol that used plain text `ldap://` protocol, upgraded to [TLS].\n' - '\n' - ), - link=None) - -RQ_SRS_007_LDAP_Configuration_Server_TLSMinimumProtocolVersion = Requirement( - name='RQ.SRS-007.LDAP.Configuration.Server.TLSMinimumProtocolVersion', - version='1.0', - priority=None, - group=None, - type=None, - uid=None, - description=( - '[ClickHouse] SHALL support `` parameter to specify\n' - 'the minimum protocol version of SSL/TLS.\n' - '\n' - ), - link=None) - -RQ_SRS_007_LDAP_Configuration_Server_TLSMinimumProtocolVersion_Values = Requirement( - name='RQ.SRS-007.LDAP.Configuration.Server.TLSMinimumProtocolVersion.Values', - version='1.0', - priority=None, - group=None, - type=None, - uid=None, - description=( - '[ClickHouse] SHALL support specifying `ssl2`, `ssl3`, `tls1.0`, `tls1.1`, and `tls1.2`\n' - 'as a value of the `` parameter.\n' - '\n' - ), - link=None) - -RQ_SRS_007_LDAP_Configuration_Server_TLSMinimumProtocolVersion_Default = Requirement( - name='RQ.SRS-007.LDAP.Configuration.Server.TLSMinimumProtocolVersion.Default', - version='1.0', - priority=None, - group=None, - type=None, - uid=None, - description=( - '[ClickHouse] SHALL set `tls1.2` as the default value of the `` parameter.\n' - '\n' - ), - link=None) - -RQ_SRS_007_LDAP_Configuration_Server_TLSRequireCert = Requirement( - name='RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert', - version='1.0', - priority=None, - group=None, - type=None, - uid=None, - description=( - '[ClickHouse] SHALL support `` parameter to specify [TLS] peer\n' - 'certificate verification behavior.\n' - '\n' - ), - link=None) - -RQ_SRS_007_LDAP_Configuration_Server_TLSRequireCert_Options_Default = Requirement( - name='RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert.Options.Default', - version='1.0', - priority=None, - group=None, - type=None, - uid=None, - description=( - '[ClickHouse] SHALL use `demand` value as the default for the `` parameter.\n' - '\n' - ), - link=None) - -RQ_SRS_007_LDAP_Configuration_Server_TLSRequireCert_Options_Demand = Requirement( - name='RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert.Options.Demand', - version='1.0', - priority=None, - group=None, - type=None, - uid=None, - description=( - '[ClickHouse] SHALL support specifying `demand` as the value of `` parameter to\n' - 'enable requesting of client certificate. If no certificate is provided, or a bad certificate is\n' - 'provided, the session SHALL be immediately terminated.\n' - '\n' - ), - link=None) - -RQ_SRS_007_LDAP_Configuration_Server_TLSRequireCert_Options_Allow = Requirement( - name='RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert.Options.Allow', - version='1.0', - priority=None, - group=None, - type=None, - uid=None, - description=( - '[ClickHouse] SHALL support specifying `allow` as the value of `` parameter to\n' - 'enable requesting of client certificate. If no\n' - 'certificate is provided, the session SHALL proceed normally.\n' - 'If a bad certificate is provided, it SHALL be ignored and the session SHALL proceed normally.\n' - '\n' - ), - link=None) - -RQ_SRS_007_LDAP_Configuration_Server_TLSRequireCert_Options_Try = Requirement( - name='RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert.Options.Try', - version='1.0', - priority=None, - group=None, - type=None, - uid=None, - description=( - '[ClickHouse] SHALL support specifying `try` as the value of `` parameter to\n' - 'enable requesting of client certificate. If no certificate is provided, the session\n' - 'SHALL proceed normally. If a bad certificate is provided, the session SHALL be\n' - 'immediately terminated.\n' - '\n' - ), - link=None) - -RQ_SRS_007_LDAP_Configuration_Server_TLSRequireCert_Options_Never = Requirement( - name='RQ.SRS-007.LDAP.Configuration.Server.TLSRequireCert.Options.Never', - version='1.0', - priority=None, - group=None, - type=None, - uid=None, - description=( - '[ClickHouse] SHALL support specifying `never` as the value of `` parameter to\n' - 'disable requesting of client certificate.\n' - '\n' - ), - link=None) - -RQ_SRS_007_LDAP_Configuration_Server_TLSCertFile = Requirement( - name='RQ.SRS-007.LDAP.Configuration.Server.TLSCertFile', - version='1.0', - priority=None, - group=None, - type=None, - uid=None, - description=( - '[ClickHouse] SHALL support `` to specify the path to certificate file used by\n' - '[ClickHouse] to establish connection with the [LDAP] server.\n' - '\n' - ), - link=None) - -RQ_SRS_007_LDAP_Configuration_Server_TLSKeyFile = Requirement( - name='RQ.SRS-007.LDAP.Configuration.Server.TLSKeyFile', - version='1.0', - priority=None, - group=None, - type=None, - uid=None, - description=( - '[ClickHouse] SHALL support `` to specify the path to key file for the certificate\n' - 'specified by the `` parameter.\n' - '\n' - ), - link=None) - -RQ_SRS_007_LDAP_Configuration_Server_TLSCACertDir = Requirement( - name='RQ.SRS-007.LDAP.Configuration.Server.TLSCACertDir', - version='1.0', - priority=None, - group=None, - type=None, - uid=None, - description=( - '[ClickHouse] SHALL support `` parameter to specify to a path to\n' - 'the directory containing [CA] certificates used to verify certificates provided by the [LDAP] server.\n' - '\n' - ), - link=None) - -RQ_SRS_007_LDAP_Configuration_Server_TLSCACertFile = Requirement( - name='RQ.SRS-007.LDAP.Configuration.Server.TLSCACertFile', - version='1.0', - priority=None, - group=None, - type=None, - uid=None, - description=( - '[ClickHouse] SHALL support `` parameter to specify a path to a specific\n' - '[CA] certificate file used to verify certificates provided by the [LDAP] server.\n' - '\n' - ), - link=None) - -RQ_SRS_007_LDAP_Configuration_Server_TLSCipherSuite = Requirement( - name='RQ.SRS-007.LDAP.Configuration.Server.TLSCipherSuite', - version='1.0', - priority=None, - group=None, - type=None, - uid=None, - description=( - '[ClickHouse] SHALL support `tls_cipher_suite` parameter to specify allowed cipher suites.\n' - 'The value SHALL use the same format as the `ciphersuites` in the [OpenSSL Ciphers].\n' - '\n' - 'For example,\n' - '\n' - '```xml\n' - 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:AES256-GCM-SHA384\n' - '```\n' - '\n' - 'The available suites SHALL depend on the [OpenSSL] library version and variant used to build\n' - '[ClickHouse] and therefore might change.\n' - '\n' - ), - link=None) - -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) - -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) - -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' - ' 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) - -RQ_SRS_007_LDAP_Configuration_Server_Syntax = Requirement( - name='RQ.SRS-007.LDAP.Configuration.Server.Syntax', - version='2.0', - priority=None, - group=None, - type=None, - uid=None, - description=( - '[ClickHouse] SHALL support the following example syntax to create an entry for an [LDAP] server inside the `config.xml`\n' - 'configuration file or of any configuration file inside the `config.d` directory.\n' - '\n' - '```xml\n' - '\n' - ' \n' - ' localhost\n' - ' 636\n' - ' cn=\n' - ' , ou=users, dc=example, dc=com\n' - ' 0\n' - ' yes\n' - ' tls1.2\n' - ' demand\n' - ' /path/to/tls_cert_file\n' - ' /path/to/tls_key_file\n' - ' /path/to/tls_ca_cert_file\n' - ' /path/to/tls_ca_cert_dir\n' - ' ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:AES256-GCM-SHA384\n' - ' \n' - '\n' - '```\n' - '\n' - ), - link=None) - -RQ_SRS_007_LDAP_Configuration_User_RBAC = Requirement( - name='RQ.SRS-007.LDAP.Configuration.User.RBAC', - version='1.0', - priority=None, - group=None, - type=None, - uid=None, - description=( - '[ClickHouse] SHALL support creating users identified using an [LDAP] server using\n' - 'the following RBAC command\n' - '\n' - '```sql\n' - "CREATE USER name IDENTIFIED WITH ldap_server BY 'server_name'\n" - '```\n' - '\n' - ), - link=None) - -RQ_SRS_007_LDAP_Configuration_User_Syntax = Requirement( - name='RQ.SRS-007.LDAP.Configuration.User.Syntax', - version='1.0', - priority=None, - group=None, - type=None, - uid=None, - description=( - '[ClickHouse] SHALL support the following example syntax to create a user that is authenticated using\n' - 'an [LDAP] server inside the `users.xml` file or any configuration file inside the `users.d` directory.\n' - '\n' - '```xml\n' - '\n' - ' \n' - ' \n' - ' \n' - ' my_ldap_server\n' - ' \n' - ' \n' - ' \n' - '\n' - '```\n' - '\n' - ), - link=None) - -RQ_SRS_007_LDAP_Configuration_User_Name_Empty = Requirement( - name='RQ.SRS-007.LDAP.Configuration.User.Name.Empty', - version='1.0', - priority=None, - group=None, - type=None, - uid=None, - description=( - '[ClickHouse] SHALL not support empty string as a user name.\n' - '\n' - ), - link=None) - -RQ_SRS_007_LDAP_Configuration_User_BothPasswordAndLDAP = Requirement( - name='RQ.SRS-007.LDAP.Configuration.User.BothPasswordAndLDAP', - version='1.0', - priority=None, - group=None, - type=None, - uid=None, - description=( - '[ClickHouse] SHALL throw an error if `` is specified for the user and at the same\n' - 'time user configuration contains any of the `` entries.\n' - '\n' - ), - link=None) - -RQ_SRS_007_LDAP_Configuration_User_LDAP_InvalidServerName_NotDefined = Requirement( - name='RQ.SRS-007.LDAP.Configuration.User.LDAP.InvalidServerName.NotDefined', - version='1.0', - priority=None, - group=None, - type=None, - uid=None, - description=( - '[ClickHouse] SHALL throw an error during any authentification attempt\n' - 'if the name of the [LDAP] server used inside the `` entry\n' - 'is not defined in the `` section.\n' - '\n' - ), - link=None) - -RQ_SRS_007_LDAP_Configuration_User_LDAP_InvalidServerName_Empty = Requirement( - name='RQ.SRS-007.LDAP.Configuration.User.LDAP.InvalidServerName.Empty', - version='1.0', - priority=None, - group=None, - type=None, - uid=None, - description=( - '[ClickHouse] SHALL throw an error during any authentification attempt\n' - 'if the name of the [LDAP] server used inside the `` entry\n' - 'is empty.\n' - '\n' - ), - link=None) - -RQ_SRS_007_LDAP_Configuration_User_OnlyOneServer = Requirement( - name='RQ.SRS-007.LDAP.Configuration.User.OnlyOneServer', - version='1.0', - priority=None, - group=None, - type=None, - uid=None, - description=( - '[ClickHouse] SHALL support specifying only one [LDAP] server for a given user.\n' - '\n' - ), - link=None) - -RQ_SRS_007_LDAP_Configuration_User_Name_Long = Requirement( - name='RQ.SRS-007.LDAP.Configuration.User.Name.Long', - version='1.0', - priority=None, - group=None, - type=None, - uid=None, - description=( - '[ClickHouse] SHALL support long user names of at least 256 bytes\n' - 'to specify users that can be authenticated using an [LDAP] server.\n' - '\n' - ), - link=None) - -RQ_SRS_007_LDAP_Configuration_User_Name_UTF8 = Requirement( - name='RQ.SRS-007.LDAP.Configuration.User.Name.UTF8', - version='1.0', - priority=None, - group=None, - type=None, - uid=None, - description=( - '[ClickHouse] SHALL support user names that contain [UTF-8] characters.\n' - '\n' - ), - link=None) - -RQ_SRS_007_LDAP_Authentication_Username_Empty = Requirement( - name='RQ.SRS-007.LDAP.Authentication.Username.Empty', - version='1.0', - priority=None, - group=None, - type=None, - uid=None, - description=( - '[ClickHouse] SHALL not support authenticating users with empty username.\n' - '\n' - ), - link=None) - -RQ_SRS_007_LDAP_Authentication_Username_Long = Requirement( - name='RQ.SRS-007.LDAP.Authentication.Username.Long', - version='1.0', - priority=None, - group=None, - type=None, - uid=None, - description=( - '[ClickHouse] SHALL support authenticating users with a long username of at least 256 bytes.\n' - '\n' - ), - link=None) - -RQ_SRS_007_LDAP_Authentication_Username_UTF8 = Requirement( - name='RQ.SRS-007.LDAP.Authentication.Username.UTF8', - version='1.0', - priority=None, - group=None, - type=None, - uid=None, - description=( - '[ClickHouse] SHALL support authentication users with a username that contains [UTF-8] characters.\n' - '\n' - ), - link=None) - -RQ_SRS_007_LDAP_Authentication_Password_Empty = Requirement( - name='RQ.SRS-007.LDAP.Authentication.Password.Empty', - version='1.0', - priority=None, - group=None, - type=None, - uid=None, - description=( - '[ClickHouse] SHALL not support authenticating users with empty passwords\n' - 'even if an empty password is valid for the user and\n' - 'is allowed by the [LDAP] server.\n' - '\n' - ), - link=None) - -RQ_SRS_007_LDAP_Authentication_Password_Long = Requirement( - name='RQ.SRS-007.LDAP.Authentication.Password.Long', - version='1.0', - priority=None, - group=None, - type=None, - uid=None, - description=( - '[ClickHouse] SHALL support long password of at least 256 bytes\n' - 'that can be used to authenticate users using an [LDAP] server.\n' - '\n' - ), - link=None) - -RQ_SRS_007_LDAP_Authentication_Password_UTF8 = Requirement( - name='RQ.SRS-007.LDAP.Authentication.Password.UTF8', - version='1.0', - priority=None, - group=None, - type=None, - uid=None, - description=( - '[ClickHouse] SHALL support [UTF-8] characters in passwords\n' - 'used to authenticate users using an [LDAP] server.\n' - '\n' - ), - link=None) - -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) - -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) - -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) diff --git a/ldap/authentication/tests/authentications.py b/ldap/authentication/tests/authentications.py deleted file mode 100644 index b1a109f87ce..00000000000 --- a/ldap/authentication/tests/authentications.py +++ /dev/null @@ -1,969 +0,0 @@ -# -*- coding: utf-8 -*- -import random -import time - -from multiprocessing.dummy import Pool -from testflows.core import * -from testflows.asserts import error -from ldap.authentication.tests.common import * -from ldap.authentication.requirements import * - -servers = { - "openldap1": { - "host": "openldap1", - "port": "389", - "enable_tls": "no", - "auth_dn_prefix": "cn=", - "auth_dn_suffix": ",ou=users,dc=company,dc=com" - }, - "openldap2": { - "host": "openldap2", - "port": "636", - "enable_tls": "yes", - "auth_dn_prefix": "cn=", - "auth_dn_suffix": ",ou=users,dc=company,dc=com", - "tls_require_cert": "never", - } -} - -@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. - """ - self.context.node.query("SELECT 1", - settings=[("user", username), ("password", password)], - exitcode=exitcode or 0, - message=message, steps=steps) - -@TestScenario -def add_user_to_ldap_and_login(self, server, user=None, ch_user=None, login=None, exitcode=None, message=None, rbac=False): - """Add user to LDAP and ClickHouse and then try to login. - """ - self.context.ldap_node = self.context.cluster.node(server) - - if ch_user is None: - ch_user = {} - if login is None: - login = {} - if user is None: - user = {"cn": "myuser", "userpassword": "myuser"} - - with ldap_user(**user) as user: - ch_user["username"] = ch_user.get("username", user["cn"]) - ch_user["server"] = ch_user.get("server", user["_server"]) - - with ldap_authenticated_users(ch_user, config_file=f"ldap_users_{getuid()}.xml", restart=True, rbac=rbac): - username = login.get("username", user["cn"]) - password = login.get("password", user["userpassword"]) - login_and_execute_query(username=username, password=password, exitcode=exitcode, message=message) - -@TestScenario -@Requirements( - RQ_SRS_007_LDAP_Authentication_Parallel("1.0"), - RQ_SRS_007_LDAP_Authentication_Parallel_ValidAndInvalid("1.0") -) -def parallel_login(self, server, user_count=10, timeout=200, rbac=False): - """Check that login of valid and invalid LDAP authenticated users works in parallel. - """ - self.context.ldap_node = self.context.cluster.node(server) - user = None - - users = [{"cn": f"parallel_user{i}", "userpassword": randomword(20)} for i in range(user_count)] - - with ldap_users(*users): - with ldap_authenticated_users(*[{"username": user["cn"], "server": server} for user in users], rbac=rbac): - - def login_with_valid_username_and_password(users, i, iterations=10): - with When(f"valid users try to login #{i}"): - for i in range(iterations): - random_user = users[random.randint(0, len(users)-1)] - login_and_execute_query(username=random_user["cn"], password=random_user["userpassword"], steps=False) - - def login_with_valid_username_and_invalid_password(users, i, iterations=10): - with When(f"users try to login with valid username and invalid password #{i}"): - for i in range(iterations): - random_user = users[random.randint(0, len(users)-1)] - login_and_execute_query(username=random_user["cn"], - password=(random_user["userpassword"] + randomword(1)), - exitcode=4, - message=f"DB::Exception: {random_user['cn']}: Authentication failed: password is incorrect or there is no user with such name", - steps=False) - - def login_with_invalid_username_and_valid_password(users, i, iterations=10): - with When(f"users try to login with invalid username and valid password #{i}"): - for i in range(iterations): - random_user = dict(users[random.randint(0, len(users)-1)]) - random_user["cn"] += randomword(1) - login_and_execute_query(username=random_user["cn"], - password=random_user["userpassword"], - exitcode=4, - message=f"DB::Exception: {random_user['cn']}: Authentication failed: password is incorrect or there is no user with such name", - steps=False) - - with When("I login in parallel"): - p = Pool(15) - tasks = [] - for i in range(5): - tasks.append(p.apply_async(login_with_valid_username_and_password, (users, i, 50,))) - tasks.append(p.apply_async(login_with_valid_username_and_invalid_password, (users, i, 50,))) - tasks.append(p.apply_async(login_with_invalid_username_and_valid_password, (users, i, 50,))) - - with Then("it should work"): - for task in tasks: - task.get(timeout=timeout) - -@TestScenario -@Requirements( - RQ_SRS_007_LDAP_Authentication_Invalid("1.0"), - RQ_SRS_007_LDAP_Authentication_Invalid_DeletedUser("1.0") -) -def login_after_user_is_deleted_from_ldap(self, server, rbac=False): - """Check that login fails after user is deleted from LDAP. - """ - self.context.ldap_node = self.context.cluster.node(server) - user = None - - try: - with Given(f"I add user to LDAP"): - user = {"cn": "myuser", "userpassword": "myuser"} - 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): - login_and_execute_query(username=user["cn"], password=user["userpassword"]) - - with When("I delete this user from LDAP"): - delete_user_from_ldap(user) - - with Then("when I try to login again it should fail"): - login_and_execute_query(username=user["cn"], password=user["userpassword"], - exitcode=4, - message=f"DB::Exception: {user['cn']}: Authentication failed: password is incorrect or there is no user with such name" - ) - 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_007_LDAP_Authentication_Invalid("1.0"), - RQ_SRS_007_LDAP_Authentication_PasswordChanged("1.0") -) -def login_after_user_password_changed_in_ldap(self, server, rbac=False): - """Check that login fails after user password is changed in LDAP. - """ - self.context.ldap_node = self.context.cluster.node(server) - user = None - - try: - with Given(f"I add user to LDAP"): - user = {"cn": "myuser", "userpassword": "myuser"} - 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): - login_and_execute_query(username=user["cn"], password=user["userpassword"]) - - with When("I change user password in LDAP"): - change_user_password_in_ldap(user, "newpassword") - - with Then("when I try to login again it should fail"): - login_and_execute_query(username=user["cn"], password=user["userpassword"], - exitcode=4, - message=f"DB::Exception: {user['cn']}: Authentication failed: password is incorrect or there is no user with such name" - ) - - with And("when I try to login with the new password it should work"): - login_and_execute_query(username=user["cn"], password="newpassword") - - 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_007_LDAP_Authentication_Invalid("1.0"), - RQ_SRS_007_LDAP_Authentication_UsernameChanged("1.0") -) -def login_after_user_cn_changed_in_ldap(self, server, rbac=False): - """Check that login fails after user cn is changed in LDAP. - """ - self.context.ldap_node = self.context.cluster.node(server) - user = None - new_user = None - - try: - with Given(f"I add user to LDAP"): - user = {"cn": "myuser", "userpassword": "myuser"} - 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): - login_and_execute_query(username=user["cn"], password=user["userpassword"]) - - with When("I change user password in LDAP"): - new_user = change_user_cn_in_ldap(user, "myuser2") - - with Then("when I try to login again it should fail"): - login_and_execute_query(username=user["cn"], password=user["userpassword"], - exitcode=4, - message=f"DB::Exception: {user['cn']}: Authentication failed: password is incorrect or there is no user with such name" - ) - 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 -@Requirements( - RQ_SRS_007_LDAP_Authentication_Valid("1.0"), - RQ_SRS_007_LDAP_Authentication_LDAPServerRestart("1.0") -) -def login_after_ldap_server_is_restarted(self, server, timeout=60, rbac=False): - """Check that login succeeds after LDAP server is restarted. - """ - self.context.ldap_node = self.context.cluster.node(server) - user = None - - try: - with Given(f"I add user to LDAP"): - user = {"cn": "myuser", "userpassword": getuid()} - user = add_user_to_ldap(**user) - - with ldap_authenticated_users({"username": user["cn"], "server": server}, rbac=rbac): - login_and_execute_query(username=user["cn"], password=user["userpassword"]) - - with When("I restart LDAP server"): - self.context.ldap_node.restart() - - with Then("I try to login until it works", description=f"timeout {timeout} sec"): - started = time.time() - while True: - r = self.context.node.query("SELECT 1", - settings=[("user", user["cn"]), ("password", user["userpassword"])], - no_checks=True) - if r.exitcode == 0: - break - assert time.time() - started < timeout, error(r.output) - 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_007_LDAP_Authentication_Valid("1.0"), - RQ_SRS_007_LDAP_Authentication_ClickHouseServerRestart("1.0") -) -def login_after_clickhouse_server_is_restarted(self, server, timeout=60, rbac=False): - """Check that login succeeds after ClickHouse server is restarted. - """ - self.context.ldap_node = self.context.cluster.node(server) - user = None - - try: - with Given(f"I add user to LDAP"): - user = {"cn": "myuser", "userpassword": getuid()} - user = add_user_to_ldap(**user) - - with ldap_authenticated_users({"username": user["cn"], "server": server}, rbac=rbac): - login_and_execute_query(username=user["cn"], password=user["userpassword"]) - - with When("I restart ClickHouse server"): - self.context.node.restart() - - with Then("I try to login until it works", description=f"timeout {timeout} sec"): - started = time.time() - while True: - r = self.context.node.query("SELECT 1", - settings=[("user", user["cn"]), ("password", user["userpassword"])], - no_checks=True) - if r.exitcode == 0: - break - assert time.time() - started < timeout, error(r.output) - 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_007_LDAP_Authentication_Invalid("1.0"), - RQ_SRS_007_LDAP_Authentication_Password_Empty("1.0") -) -def valid_username_with_valid_empty_password(self, server, rbac=False): - """Check that we can't login using valid username that has empty password. - """ - user = {"cn": "empty_password", "userpassword": ""} - exitcode = 4 - message = f"DB::Exception: {user['cn']}: Authentication failed: password is incorrect or there is no user with such name" - - add_user_to_ldap_and_login(user=user, exitcode=exitcode, message=message, server=server, rbac=rbac) - -@TestScenario -@Requirements( - RQ_SRS_007_LDAP_Authentication_Invalid("1.0"), - RQ_SRS_007_LDAP_Authentication_Password_Empty("1.0") -) -def valid_username_and_invalid_empty_password(self, server, rbac=False): - """Check that we can't login using valid username but invalid empty password. - """ - username = "user_non_empty_password" - user = {"cn": username, "userpassword": username} - login = {"password": ""} - - exitcode = 4 - message = f"DB::Exception: {username}: Authentication failed: password is incorrect or there is no user with such name" - - add_user_to_ldap_and_login(user=user, login=login, exitcode=exitcode, message=message, server=server, rbac=rbac) - -@TestScenario -@Requirements( - RQ_SRS_007_LDAP_Authentication_Valid("1.0") -) -def valid_username_and_password(self, server, rbac=False): - """Check that we can login using valid username and password. - """ - username = "valid_username_and_password" - user = {"cn": username, "userpassword": username} - - with When(f"I add user {username} to LDAP and try to login"): - add_user_to_ldap_and_login(user=user, server=server, rbac=rbac) - -@TestScenario -@Requirements( - RQ_SRS_007_LDAP_Authentication_Invalid("1.0") -) -def valid_username_and_password_invalid_server(self, server=None, rbac=False): - """Check that we can't login using valid username and valid - password but for a different server. - """ - self.context.ldap_node = self.context.cluster.node("openldap1") - - user = {"username": "user2", "userpassword": "user2", "server": "openldap1"} - - exitcode = 4 - message = f"DB::Exception: user2: Authentication failed: password is incorrect or there is no user with such name" - - with ldap_authenticated_users(user, config_file=f"ldap_users_{getuid()}.xml", restart=True, rbac=rbac): - login_and_execute_query(username="user2", password="user2", exitcode=exitcode, message=message) - -@TestScenario -@Requirements( - RQ_SRS_007_LDAP_Authentication_Valid("1.0"), - RQ_SRS_007_LDAP_Authentication_Username_Long("1.0"), - RQ_SRS_007_LDAP_Configuration_User_Name_Long("1.0") -) -def valid_long_username_and_short_password(self, server, rbac=False): - """Check that we can login using valid very long username and short password. - """ - username = "long_username_12345678901234567890123456789012345678901234567890123456789012345678901234567890" - user = {"cn": username, "userpassword": "long_username"} - - add_user_to_ldap_and_login(user=user, server=server, rbac=rbac) - -@TestScenario -@Requirements( - RQ_SRS_007_LDAP_Authentication_Invalid("1.0") -) -def invalid_long_username_and_valid_short_password(self, server, rbac=False): - """Check that we can't login using slightly invalid long username but valid password. - """ - username = "long_username_12345678901234567890123456789012345678901234567890123456789012345678901234567890" - user = {"cn": username, "userpassword": "long_username"} - login = {"username": f"{username}?"} - - exitcode = 4 - message=f"DB::Exception: {login['username']}: Authentication failed: password is incorrect or there is no user with such name" - - add_user_to_ldap_and_login(user=user, login=login, exitcode=exitcode, message=message, server=server, rbac=rbac) - -@TestScenario -@Requirements( - RQ_SRS_007_LDAP_Authentication_Valid("1.0"), - RQ_SRS_007_LDAP_Authentication_Password_Long("1.0") -) -def valid_short_username_and_long_password(self, server, rbac=False): - """Check that we can login using valid short username with very long password. - """ - username = "long_password" - user = {"cn": username, "userpassword": "long_password_12345678901234567890123456789012345678901234567890123456789012345678901234567890"} - add_user_to_ldap_and_login(user=user, server=server, rbac=rbac) - -@TestScenario -@Requirements( - RQ_SRS_007_LDAP_Authentication_Invalid("1.0") -) -def valid_short_username_and_invalid_long_password(self, server, rbac=False): - """Check that we can't login using valid short username and invalid long password. - """ - username = "long_password" - user = {"cn": username, "userpassword": "long_password_12345678901234567890123456789012345678901234567890123456789012345678901234567890"} - login = {"password": user["userpassword"] + "1"} - - exitcode = 4 - message=f"DB::Exception: {username}: Authentication failed: password is incorrect or there is no user with such name" - - add_user_to_ldap_and_login(user=user, login=login, exitcode=exitcode, message=message, server=server, rbac=rbac) - -@TestScenario -@Requirements( - RQ_SRS_007_LDAP_Authentication_Invalid("1.0") -) -def valid_username_and_invalid_password(self, server, rbac=False): - """Check that we can't login using valid username and invalid password. - """ - username = "valid_username_and_invalid_password" - user = {"cn": username, "userpassword": username} - login = {"password": user["userpassword"] + "1"} - - exitcode = 4 - message=f"DB::Exception: {username}: Authentication failed: password is incorrect or there is no user with such name" - - add_user_to_ldap_and_login(user=user, login=login, exitcode=exitcode, message=message, server=server, rbac=rbac) - -@TestScenario -@Requirements( - RQ_SRS_007_LDAP_Authentication_Invalid("1.0") -) -def invalid_username_and_valid_password(self, server, rbac=False): - """Check that we can't login using slightly invalid username but valid password. - """ - username = "invalid_username_and_valid_password" - user = {"cn": username, "userpassword": username} - login = {"username": user["cn"] + "1"} - - exitcode = 4 - message=f"DB::Exception: {login['username']}: Authentication failed: password is incorrect or there is no user with such name" - - add_user_to_ldap_and_login(user=user, login=login, exitcode=exitcode, message=message, server=server, rbac=rbac) - -@TestScenario -@Requirements( - RQ_SRS_007_LDAP_Authentication_Valid("1.0"), - RQ_SRS_007_LDAP_Authentication_Username_UTF8("1.0"), - RQ_SRS_007_LDAP_Configuration_User_Name_UTF8("1.0") -) -def valid_utf8_username_and_ascii_password(self, server, rbac=False): - """Check that we can login using valid utf-8 username with ascii password. - """ - username = "utf8_username_Gãńdåłf_Thê_Gręât" - user = {"cn": username, "userpassword": "utf8_username"} - - add_user_to_ldap_and_login(user=user, server=server, rbac=rbac) - -@TestScenario -@Requirements( - RQ_SRS_007_LDAP_Authentication_Valid("1.0"), - RQ_SRS_007_LDAP_Authentication_Password_UTF8("1.0") -) -def valid_ascii_username_and_utf8_password(self, server, rbac=False): - """Check that we can login using valid ascii username with utf-8 password. - """ - username = "utf8_password" - user = {"cn": username, "userpassword": "utf8_password_Gãńdåłf_Thê_Gręât"} - - add_user_to_ldap_and_login(user=user, server=server, rbac=rbac) - -@TestScenario -def empty_username_and_empty_password(self, server=None, rbac=False): - """Check that we can login using empty username and empty password as - it will use the default user and that has an empty password. - """ - 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( - RQ_SRS_007_LDAP_Authentication_Mechanism_NamePassword("1.0") -) -@Examples("rbac", [ - (False,), - (True, Requirements(RQ_SRS_007_LDAP_Configuration_User_RBAC("1.0"))) -]) -def feature(self, rbac, servers=None, node="clickhouse1"): - """Check that users can be authenticated using an LDAP server when - users are configured either using an XML configuration file or RBAC. - """ - self.context.node = self.context.cluster.node(node) - - if servers is None: - servers = globals()["servers"] - - with ldap_servers(servers): - 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) - - - - diff --git a/ldap/authentication/tests/common.py b/ldap/authentication/tests/common.py deleted file mode 100644 index 8efb389a23f..00000000000 --- a/ldap/authentication/tests/common.py +++ /dev/null @@ -1,466 +0,0 @@ -import os -import uuid -import time -import string -import random -import textwrap -import xml.etree.ElementTree as xmltree - -from collections import namedtuple -from contextlib import contextmanager - -import testflows.settings as settings - -from testflows.core import * -from testflows.asserts import error - -def getuid(): - return str(uuid.uuid1()).replace('-', '_') - -xml_with_utf8 = '\n' - -def xml_indent(elem, level=0, by=" "): - i = "\n" + level * by - if len(elem): - if not elem.text or not elem.text.strip(): - elem.text = i + by - if not elem.tail or not elem.tail.strip(): - elem.tail = i - for elem in elem: - xml_indent(elem, level + 1) - if not elem.tail or not elem.tail.strip(): - elem.tail = i - else: - if level and (not elem.tail or not elem.tail.strip()): - elem.tail = i - -def xml_append(root, tag, text): - element = xmltree.Element(tag) - element.text = text - root.append(element) - return element - -Config = namedtuple("Config", "content path name uid preprocessed_name") - -ASCII_CHARS = string.ascii_lowercase + string.ascii_uppercase + string.digits - -def randomword(length, chars=ASCII_CHARS): - return ''.join(random.choice(chars) for i in range(length)) - -def restart(node=None, safe=False, timeout=60): - """Restart ClickHouse server and wait for config to be reloaded. - """ - with When("I restart ClickHouse server node"): - if node is None: - node = current().context.node - - with node.cluster.shell(node.name) as bash: - bash.expect(bash.prompt) - - with By("closing terminal to the node to be restarted"): - bash.close() - - with And("getting current log size"): - logsize = \ - node.command("stat --format=%s /var/log/clickhouse-server/clickhouse-server.log").output.split(" ")[ - 0].strip() - - with And("restarting ClickHouse server"): - node.restart(safe=safe) - - with Then("tailing the log file from using previous log size as the offset"): - bash.prompt = bash.__class__.prompt - bash.open() - bash.send(f"tail -c +{logsize} -f /var/log/clickhouse-server/clickhouse-server.log") - - with And("waiting for config reload message in the log file"): - bash.expect( - f"ConfigReloader: Loaded config '/etc/clickhouse-server/config.xml', performed update on configuration", - timeout=timeout) - -def add_config(config, timeout=60, restart=False, modify=False): - """Add dynamic configuration file to ClickHouse. - - :param node: node - :param config: configuration file description - :param timeout: timeout, default: 20 sec - """ - node = current().context.node - - def check_preprocessed_config_is_updated(after_removal=False): - """Check that preprocessed config is updated. - """ - started = time.time() - command = f"cat /var/lib/clickhouse/preprocessed_configs/{config.preprocessed_name} | grep {config.uid}{' > /dev/null' if not settings.debug else ''}" - - while time.time() - started < timeout: - exitcode = node.command(command, steps=False).exitcode - if after_removal: - if exitcode == 1: - break - else: - if exitcode == 0: - break - time.sleep(1) - - if settings.debug: - node.command(f"cat /var/lib/clickhouse/preprocessed_configs/{config.preprocessed_name}") - - if after_removal: - assert exitcode == 1, error() - else: - assert exitcode == 0, error() - - def wait_for_config_to_be_loaded(): - """Wait for config to be loaded. - """ - if restart: - with When("I close terminal to the node to be restarted"): - bash.close() - - with And("I stop ClickHouse to apply the config changes"): - node.stop(safe=False) - - with And("I get the current log size"): - cmd = node.cluster.command(None, - f"stat --format=%s {os.environ['CLICKHOUSE_TESTS_DIR']}/_instances/{node.name}/logs/clickhouse-server.log") - logsize = cmd.output.split(" ")[0].strip() - - with And("I start ClickHouse back up"): - node.start() - - with Then("I tail the log file from using previous log size as the offset"): - bash.prompt = bash.__class__.prompt - bash.open() - bash.send(f"tail -c +{logsize} -f /var/log/clickhouse-server/clickhouse-server.log") - - with Then("I wait for config reload message in the log file"): - if restart: - bash.expect( - f"ConfigReloader: Loaded config '/etc/clickhouse-server/config.xml', performed update on configuration", - timeout=timeout) - else: - bash.expect( - f"ConfigReloader: Loaded config '/etc/clickhouse-server/{config.preprocessed_name}', performed update on configuration", - timeout=timeout) - - try: - with Given(f"{config.name}"): - if settings.debug: - with When("I output the content of the config"): - debug(config.content) - - 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 When("I add the config", description=config.path): - command = f"cat < {config.path}\n{config.content}\nHEREDOC" - node.command(command, steps=False, exitcode=0) - - with Then(f"{config.preprocessed_name} should be updated", description=f"timeout {timeout}"): - check_preprocessed_config_is_updated() - - with And("I wait for config to be reloaded"): - wait_for_config_to_be_loaded() - yield - finally: - 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 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() - -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. - """ - uid = getuid() - path = os.path.join(config_d_dir, config_file) - name = config_file - - root = xmltree.fromstring("") - xml_servers = root.find("ldap_servers") - xml_servers.append(xmltree.Comment(text=f"LDAP servers {uid}")) - - for _name, server in list(servers.items()): - xml_server = xmltree.Element(_name) - for key, value in list(server.items()): - xml_append(xml_server, key, value) - xml_servers.append(xml_server) - - xml_indent(root) - content = xml_with_utf8 + str(xmltree.tostring(root, short_empty_elements=False, encoding="utf-8"), "utf-8") - - 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, config=None): - """Add LDAP servers configuration. - """ - 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"): - """Create LDAP users configuration file content. - """ - uid = getuid() - path = os.path.join(config_d_dir, config_file) - name = config_file - - root = xmltree.fromstring("") - xml_users = root.find("users") - xml_users.append(xmltree.Comment(text=f"LDAP users {uid}")) - - for user in users: - xml_user = xmltree.Element(user['username']) - xml_user_server = xmltree.Element("ldap") - xml_append(xml_user_server, "server", user["server"]) - xml_user.append(xml_user_server) - xml_users.append(xml_user) - - xml_indent(root) - content = xml_with_utf8 + str(xmltree.tostring(root, short_empty_elements=False, encoding="utf-8"), "utf-8") - - return Config(content, path, name, uid, "users.xml") - -def add_users_identified_with_ldap(*users): - """Add one or more users that are identified via - an ldap server using RBAC. - """ - node = current().context.node - try: - with Given("I create users"): - for user in users: - node.query(f"CREATE USER '{user['username']}' IDENTIFIED WITH ldap_server BY '{user['server']}'") - yield - finally: - with Finally("I remove users"): - for user in users: - with By(f"dropping user {user['username']}", flags=TE): - node.query(f"DROP USER IF EXISTS '{user['username']}'") - -@contextmanager -def ldap_authenticated_users(*users, config_d_dir="/etc/clickhouse-server/users.d", - config_file=None, timeout=60, restart=True, config=None, rbac=False): - """Add LDAP authenticated users. - """ - if rbac: - return add_users_identified_with_ldap(*users) - else: - if config_file is None: - config_file = f"ldap_users_{getuid()}.xml" - if config is None: - config = create_ldap_users_config_content(*users, config_d_dir=config_d_dir, config_file=config_file) - return add_config(config, restart=restart) - -def invalid_server_config(servers, message=None, tail=13, timeout=60): - """Check that ClickHouse errors when trying to load invalid LDAP servers configuration file. - """ - node = current().context.node - if message is None: - message = "Exception: Failed to merge config with '/etc/clickhouse-server/config.d/ldap_servers.xml'" - - config = create_ldap_servers_config_content(servers) - try: - node.command("echo -e \"%s\" > /var/log/clickhouse-server/clickhouse-server.err.log" % ("-\\n" * tail)) - - with When("I add the config", description=config.path): - command = f"cat < {config.path}\n{config.content}\nHEREDOC" - node.command(command, steps=False, exitcode=0) - - with Then("server shall fail to merge the new config"): - started = time.time() - command = f"tail -n {tail} /var/log/clickhouse-server/clickhouse-server.err.log | grep \"{message}\"" - while time.time() - started < timeout: - exitcode = node.command(command, steps=False).exitcode - if exitcode == 0: - break - time.sleep(1) - assert exitcode == 0, error() - finally: - with Finally(f"I remove {config.name}"): - with By("removing the config file", description=config.path): - node.command(f"rm -rf {config.path}", exitcode=0) - -def invalid_user_config(servers, config, message=None, tail=13, timeout=60): - """Check that ClickHouse errors when trying to load invalid LDAP users configuration file. - """ - node = current().context.node - if message is None: - message = "Exception: Failed to merge config with '/etc/clickhouse-server/users.d/ldap_users.xml'" - - with ldap_servers(servers): - try: - node.command("echo -e \"%s\" > /var/log/clickhouse-server/clickhouse-server.err.log" % ("\\n" * tail)) - with When("I add the config", description=config.path): - command = f"cat < {config.path}\n{config.content}\nHEREDOC" - node.command(command, steps=False, exitcode=0) - - with Then("server shall fail to merge the new config"): - started = time.time() - command = f"tail -n {tail} /var/log/clickhouse-server/clickhouse-server.err.log | grep \"{message}\"" - while time.time() - started < timeout: - exitcode = node.command(command, steps=False).exitcode - if exitcode == 0: - break - time.sleep(1) - assert exitcode == 0, error() - finally: - with Finally(f"I remove {config.name}"): - with By("removing the config file", description=config.path): - node.command(f"rm -rf {config.path}", exitcode=0) - -def add_user_to_ldap(cn, userpassword, givenname=None, homedirectory=None, sn=None, uid=None, uidnumber=None, node=None): - """Add user entry to LDAP.""" - if node is None: - node = current().context.ldap_node - if uid is None: - uid = cn - if givenname is None: - givenname = "John" - if homedirectory is None: - homedirectory = "/home/users" - if sn is None: - sn = "User" - if uidnumber is None: - uidnumber = 2000 - - user = { - "dn": f"cn={cn},ou=users,dc=company,dc=com", - "cn": cn, - "gidnumber": 501, - "givenname": givenname, - "homedirectory": homedirectory, - "objectclass": ["inetOrgPerson", "posixAccount", "top"], - "sn": sn, - "uid": uid, - "uidnumber": uidnumber, - "userpassword": userpassword, - "_server": node.name - } - - lines = [] - - for key, value in list(user.items()): - if key.startswith("_"): - continue - elif key == "objectclass": - for cls in value: - lines.append(f"objectclass: {cls}") - else: - lines.append(f"{key}: {value}") - - ldif = "\n".join(lines) - - r = node.command( - f"echo -e \"{ldif}\" | ldapadd -x -H ldap://localhost -D \"cn=admin,dc=company,dc=com\" -w admin") - assert r.exitcode == 0, error() - - return user - -def delete_user_from_ldap(user, node=None, exitcode=0): - """Delete user entry from LDAP.""" - if node is None: - node = current().context.ldap_node - r = node.command( - f"ldapdelete -x -H ldap://localhost -D \"cn=admin,dc=company,dc=com\" -w admin \"{user['dn']}\"") - if exitcode is not None: - assert r.exitcode == exitcode, error() - -def change_user_password_in_ldap(user, new_password, node=None, exitcode=0): - """Change user password in LDAP.""" - if node is None: - node = current().context.ldap_node - - ldif = (f"dn: {user['dn']}\n" - "changetype: modify\n" - "replace: userpassword\n" - f"userpassword: {new_password}") - - r = node.command( - f"echo -e \"{ldif}\" | ldapmodify -x -H ldap://localhost -D \"cn=admin,dc=company,dc=com\" -w admin") - - if exitcode is not None: - assert r.exitcode == exitcode, error() - -def change_user_cn_in_ldap(user, new_cn, node=None, exitcode=0): - """Change user password in LDAP.""" - if node is None: - node = current().context.ldap_node - - new_user = dict(user) - new_user['dn'] = f"cn={new_cn},ou=users,dc=company,dc=com" - new_user['cn'] = new_cn - - ldif = ( - f"dn: {user['dn']}\n" - "changetype: modrdn\n" - f"newrdn: cn = {new_user['cn']}\n" - f"deleteoldrdn: 1\n" - ) - - r = node.command( - f"echo -e \"{ldif}\" | ldapmodify -x -H ldap://localhost -D \"cn=admin,dc=company,dc=com\" -w admin") - - if exitcode is not None: - assert r.exitcode == exitcode, error() - - return new_user - -@contextmanager -def ldap_user(cn, userpassword, givenname=None, homedirectory=None, sn=None, uid=None, uidnumber=None, node=None): - """Add new user to the LDAP server.""" - try: - user = None - with Given(f"I add user {cn} to LDAP"): - user = add_user_to_ldap(cn, userpassword, givenname, homedirectory, sn, uid, uidnumber, node=node) - yield user - finally: - with Finally(f"I delete user {cn} from LDAP"): - if user is not None: - delete_user_from_ldap(user, node=node) - -@contextmanager -def ldap_users(*users, node=None): - """Add multiple new users to the LDAP server.""" - try: - _users = [] - with Given("I add users to LDAP"): - for user in users: - with By(f"adding user {user['cn']}"): - _users.append(add_user_to_ldap(**user, node=node)) - yield _users - finally: - with Finally(f"I delete users from LDAP"): - for _user in _users: - delete_user_from_ldap(_user, node=node) - -def login(servers, *users, config=None): - """Configure LDAP server and LDAP authenticated users and - try to login and execute a query""" - with ldap_servers(servers): - with ldap_authenticated_users(*users, restart=True, config=config): - for user in users: - if user.get("login", False): - with When(f"I login as {user['username']} and execute query"): - current().context.node.query("SELECT 1", - settings=[("user", user["username"]), ("password", user["password"])], - exitcode=user.get("exitcode", None), - message=user.get("message", None)) diff --git a/ldap/authentication/tests/server_config.py b/ldap/authentication/tests/server_config.py deleted file mode 100644 index 38ec859226b..00000000000 --- a/ldap/authentication/tests/server_config.py +++ /dev/null @@ -1,304 +0,0 @@ -from testflows.core import * - -from ldap.authentication.tests.common import * -from ldap.authentication.requirements import * - -@TestScenario -@Requirements( - RQ_SRS_007_LDAP_Server_Configuration_Invalid("1.0"), - RQ_SRS_007_LDAP_Configuration_Server_Name("1.0") -) -def empty_server_name(self, timeout=20): - """Check that empty string as a server name is not allowed. - """ - servers = {"": {"host": "foo", "port": "389", "enable_tls": "no", - "auth_dn_prefix": "cn=", "auth_dn_suffix": ",ou=users,dc=company,dc=com" - }} - invalid_server_config(servers, timeout=timeout) - -@TestScenario -@Requirements( - RQ_SRS_007_LDAP_Server_Configuration_Invalid("1.0"), - RQ_SRS_007_LDAP_UnreachableServer("1.0") -) -def invalid_host(self): - """Check that server returns an error when LDAP server - host name is invalid. - """ - servers = {"foo": {"host": "foo", "port": "389", "enable_tls": "no"}} - users = [{ - "server": "foo", "username": "user1", "password": "user1", "login": True, - "exitcode": 4, - "message": "DB::Exception: user1: Authentication failed: password is incorrect or there is no user with such name" - }] - login(servers, *users) - -@TestScenario -@Requirements( - RQ_SRS_007_LDAP_Server_Configuration_Invalid("1.0"), - RQ_SRS_007_LDAP_Configuration_Server_Host("1.0") -) -def empty_host(self): - """Check that server returns an error when LDAP server - host value is empty. - """ - servers = {"foo": {"host": "", "port": "389", "enable_tls": "no"}} - users = [{ - "server": "foo", "username": "user1", "password": "user1", "login": True, - "exitcode": 4, - "message": "DB::Exception: user1: Authentication failed: password is incorrect or there is no user with such name" - }] - login(servers, *users) - -@TestScenario -@Requirements( - RQ_SRS_007_LDAP_Server_Configuration_Invalid("1.0"), - RQ_SRS_007_LDAP_Configuration_Server_Host("1.0") -) -def missing_host(self): - """Check that server returns an error when LDAP server - host is missing. - """ - servers = {"foo": {"port": "389", "enable_tls": "no"}} - users = [{ - "server": "foo", "username": "user1", "password": "user1", "login": True, - "exitcode": 4, - "message": "DB::Exception: user1: Authentication failed: password is incorrect or there is no user with such name" - }] - login(servers, *users) - -@TestScenario -@Requirements( - RQ_SRS_007_LDAP_Server_Configuration_Invalid("1.0") -) -def invalid_port(self): - """Check that server returns an error when LDAP server - port is not valid. - """ - servers = {"openldap1": {"host": "openldap1", "port": "3890", "enable_tls": "no"}} - users = [{ - "server": "openldap1", "username": "user1", "password": "user1", "login": True, - "exitcode": 4, - "message": "DB::Exception: user1: Authentication failed: password is incorrect or there is no user with such name" - }] - login(servers, *users) - - -@TestScenario -@Requirements( - RQ_SRS_007_LDAP_Server_Configuration_Invalid("1.0") -) -def invalid_auth_dn_prefix(self): - """Check that server returns an error when LDAP server - port is not valid. - """ - servers = {"openldap1": {"host": "openldap1", "port": "389", "enable_tls": "no", - "auth_dn_prefix": "foo=", "auth_dn_suffix": ",ou=users,dc=company,dc=com" - }} - users = [{ - "server": "openldap1", "username": "user1", "password": "user1", "login": True, - "exitcode": 4, - "message": "DB::Exception: user1: Authentication failed: password is incorrect or there is no user with such name" - }] - login(servers, *users) - -@TestScenario -@Requirements( - RQ_SRS_007_LDAP_Server_Configuration_Invalid("1.0") -) -def invalid_auth_dn_suffix(self): - """Check that server returns an error when LDAP server - port is not valid. - """ - servers = {"openldap1": {"host": "openldap1", "port": "389", "enable_tls": "no", - "auth_dn_prefix": "cn=", "auth_dn_suffix": ",foo=users,dc=company,dc=com" - }} - users = [{ - "server": "openldap1", "username": "user1", "password": "user1", "login": True, - "exitcode": 4, - "message": "DB::Exception: user1: Authentication failed: password is incorrect or there is no user with such name" - }] - login(servers, *users) - -@TestScenario -@Requirements( - RQ_SRS_007_LDAP_Server_Configuration_Invalid("1.0") -) -def invalid_enable_tls_value(self): - """Check that server returns an error when enable_tls - option has invalid value. - """ - servers = {"openldap1": {"host": "openldap1", "port": "389", "enable_tls": "foo", - "auth_dn_prefix": "cn=", "auth_dn_suffix": ",ou=users,dc=company,dc=com" - }} - users = [{ - "server": "openldap1", "username": "user1", "password": "user1", "login": True, - "exitcode": 4, - "message": "DB::Exception: user1: Authentication failed: password is incorrect or there is no user with such name" - }] - login(servers, *users) - -@TestScenario -@Requirements( - RQ_SRS_007_LDAP_Server_Configuration_Invalid("1.0") -) -def invalid_tls_require_cert_value(self): - """Check that server returns an error when tls_require_cert - option has invalid value. - """ - servers = {"openldap2": { - "host": "openldap2", "port": "636", "enable_tls": "yes", - "auth_dn_prefix": "cn=", "auth_dn_suffix": ",ou=users,dc=company,dc=com", - "tls_require_cert": "foo", - "ca_cert_dir": "/container/service/slapd/assets/certs/", - "ca_cert_file": "/container/service/slapd/assets/certs/ca.crt" - }} - users = [{ - "server": "openldap2", "username": "user2", "password": "user2", "login": True, - "exitcode": 4, - "message": "DB::Exception: user2: Authentication failed: password is incorrect or there is no user with such name" - }] - login(servers, *users) - -@TestScenario -@Requirements( - RQ_SRS_007_LDAP_Server_Configuration_Invalid("1.0") -) -def empty_ca_cert_dir(self): - """Check that server returns an error when ca_cert_dir is empty. - """ - servers = {"openldap2": {"host": "openldap2", "port": "636", "enable_tls": "yes", - "auth_dn_prefix": "cn=", "auth_dn_suffix": ",ou=users,dc=company,dc=com", - "tls_require_cert": "demand", - "ca_cert_dir": "", - "ca_cert_file": "/container/service/slapd/assets/certs/ca.crt" - }} - users = [{ - "server": "openldap2", "username": "user2", "password": "user2", "login": True, - "exitcode": 4, - "message": "DB::Exception: user2: Authentication failed: password is incorrect or there is no user with such name" - }] - login(servers, *users) - -@TestScenario -@Requirements( - RQ_SRS_007_LDAP_Server_Configuration_Invalid("1.0") -) -def empty_ca_cert_file(self): - """Check that server returns an error when ca_cert_file is empty. - """ - servers = {"openldap2": {"host": "openldap2", "port": "636", "enable_tls": "yes", - "auth_dn_prefix": "cn=", "auth_dn_suffix": ",ou=users,dc=company,dc=com", - "tls_require_cert": "demand", - "ca_cert_dir": "/container/service/slapd/assets/certs/", - "ca_cert_file": "" - }} - users = [{ - "server": "openldap2", "username": "user2", "password": "user2", "login": True, - "exitcode": 4, - "message": "DB::Exception: user2: Authentication failed: password is incorrect or there is no user with such name" - }] - login(servers, *users) - -@TestScenario -@Requirements( - RQ_SRS_007_LDAP_Configuration_Server_AuthDN_Value("1.0"), - RQ_SRS_007_LDAP_Configuration_Server_AuthDN_Prefix("1.0"), - RQ_SRS_007_LDAP_Configuration_Server_AuthDN_Suffix("1.0") -) -def auth_dn_value(self): - """Check that server configuration can properly define the `dn` value of the user.""" - servers = { - "openldap1": { - "host": "openldap1", "port": "389", "enable_tls": "no", - "auth_dn_prefix": "cn=", "auth_dn_suffix": ",ou=users,dc=company,dc=com" - }} - user = {"server": "openldap1", "username": "user1", "password": "user1", "login": True} - - 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 = (" 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("2.0") -) -def syntax(self): - """Check that server configuration with valid syntax can be loaded. - ```xml - - - localhost - 636 - cn= - , ou=users, dc=example, dc=com - 0 - yes - tls1.2 - demand - /path/to/tls_cert_file - /path/to/tls_key_file - /path/to/tls_ca_cert_file - /path/to/tls_ca_cert_dir - ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:AES256-GCM-SHA384 - - - ``` - """ - servers = { - "openldap2": { - "host": "openldap2", - "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", - "tls_cert_file": "/container/service/slapd/assets/certs/ldap.crt", - "tls_key_file": "/container/service/slapd/assets/certs/ldap.key", - "tls_ca_cert_file": "/container/service/slapd/assets/certs/ca.crt", - "tls_ca_cert_dir": "/container/service/slapd/assets/certs/", - "tls_cipher_suite": "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:AES256-GCM-SHA384" - } - } - with ldap_servers(servers): - pass - -@TestFeature -@Name("server config") -def feature(self, node="clickhouse1"): - """Check that LDAP server configuration. - """ - self.context.node = self.context.cluster.node(node) - - for scenario in loads(current_module(), Scenario): - scenario() diff --git a/tests/testflows/helpers/cluster.py b/tests/testflows/helpers/cluster.py index d173547a916..3be79132ec3 100755 --- a/tests/testflows/helpers/cluster.py +++ b/tests/testflows/helpers/cluster.py @@ -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) diff --git a/tests/testflows/ldap/authentication/regression.py b/tests/testflows/ldap/authentication/regression.py index ef24b29f3bb..ed75ce4fe75 100755 --- a/tests/testflows/ldap/authentication/regression.py +++ b/tests/testflows/ldap/authentication/regression.py @@ -29,8 +29,9 @@ xfails = { @TestFeature @Name("authentication") @ArgumentParser(argparser) -@Specifications(SRS_007_ClickHouse_Authentication_of_Users_via_LDAP) -@Requirements(RQ_SRS_007_LDAP_Authentication("1.0")) +@Requirements( + RQ_SRS_007_LDAP_Authentication("1.0") +) @XFails(xfails) def regression(self, local, clickhouse_binary_path, stress=None, parallel=None): """ClickHouse integration with LDAP regression module. diff --git a/tests/testflows/ldap/authentication/requirements/requirements.md b/tests/testflows/ldap/authentication/requirements/requirements.md index bade212c244..b0943d4a48b 100644 --- a/tests/testflows/ldap/authentication/requirements/requirements.md +++ b/tests/testflows/ldap/authentication/requirements/requirements.md @@ -59,25 +59,26 @@ * 4.2.27 [RQ.SRS-007.LDAP.Configuration.Server.TLSCipherSuite](#rqsrs-007ldapconfigurationservertlsciphersuite) * 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.Syntax](#rqsrs-007ldapconfigurationserversyntax) - * 4.2.31 [RQ.SRS-007.LDAP.Configuration.User.RBAC](#rqsrs-007ldapconfigurationuserrbac) - * 4.2.32 [RQ.SRS-007.LDAP.Configuration.User.Syntax](#rqsrs-007ldapconfigurationusersyntax) - * 4.2.33 [RQ.SRS-007.LDAP.Configuration.User.Name.Empty](#rqsrs-007ldapconfigurationusernameempty) - * 4.2.34 [RQ.SRS-007.LDAP.Configuration.User.BothPasswordAndLDAP](#rqsrs-007ldapconfigurationuserbothpasswordandldap) - * 4.2.35 [RQ.SRS-007.LDAP.Configuration.User.LDAP.InvalidServerName.NotDefined](#rqsrs-007ldapconfigurationuserldapinvalidservernamenotdefined) - * 4.2.36 [RQ.SRS-007.LDAP.Configuration.User.LDAP.InvalidServerName.Empty](#rqsrs-007ldapconfigurationuserldapinvalidservernameempty) - * 4.2.37 [RQ.SRS-007.LDAP.Configuration.User.OnlyOneServer](#rqsrs-007ldapconfigurationuseronlyoneserver) - * 4.2.38 [RQ.SRS-007.LDAP.Configuration.User.Name.Long](#rqsrs-007ldapconfigurationusernamelong) - * 4.2.39 [RQ.SRS-007.LDAP.Configuration.User.Name.UTF8](#rqsrs-007ldapconfigurationusernameutf8) - * 4.2.40 [RQ.SRS-007.LDAP.Authentication.Username.Empty](#rqsrs-007ldapauthenticationusernameempty) - * 4.2.41 [RQ.SRS-007.LDAP.Authentication.Username.Long](#rqsrs-007ldapauthenticationusernamelong) - * 4.2.42 [RQ.SRS-007.LDAP.Authentication.Username.UTF8](#rqsrs-007ldapauthenticationusernameutf8) - * 4.2.43 [RQ.SRS-007.LDAP.Authentication.Password.Empty](#rqsrs-007ldapauthenticationpasswordempty) - * 4.2.44 [RQ.SRS-007.LDAP.Authentication.Password.Long](#rqsrs-007ldapauthenticationpasswordlong) - * 4.2.45 [RQ.SRS-007.LDAP.Authentication.Password.UTF8](#rqsrs-007ldapauthenticationpasswordutf8) - * 4.2.46 [RQ.SRS-007.LDAP.Authentication.VerificationCooldown.Performance](#rqsrs-007ldapauthenticationverificationcooldownperformance) - * 4.2.47 [RQ.SRS-007.LDAP.Authentication.VerificationCooldown.Reset.ChangeInCoreServerParameters](#rqsrs-007ldapauthenticationverificationcooldownresetchangeincoreserverparameters) - * 4.2.48 [RQ.SRS-007.LDAP.Authentication.VerificationCooldown.Reset.InvalidPassword](#rqsrs-007ldapauthenticationverificationcooldownresetinvalidpassword) + * 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 @@ -414,6 +415,25 @@ version: 1.0 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 + 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 diff --git a/tests/testflows/ldap/authentication/requirements/requirements.py b/tests/testflows/ldap/authentication/requirements/requirements.py index 4a9f18cd648..92c3f844781 100644 --- a/tests/testflows/ldap/authentication/requirements/requirements.py +++ b/tests/testflows/ldap/authentication/requirements/requirements.py @@ -84,25 +84,26 @@ SRS_007_ClickHouse_Authentication_of_Users_via_LDAP = Specification( * 4.2.27 [RQ.SRS-007.LDAP.Configuration.Server.TLSCipherSuite](#rqsrs-007ldapconfigurationservertlsciphersuite) * 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.Syntax](#rqsrs-007ldapconfigurationserversyntax) - * 4.2.31 [RQ.SRS-007.LDAP.Configuration.User.RBAC](#rqsrs-007ldapconfigurationuserrbac) - * 4.2.32 [RQ.SRS-007.LDAP.Configuration.User.Syntax](#rqsrs-007ldapconfigurationusersyntax) - * 4.2.33 [RQ.SRS-007.LDAP.Configuration.User.Name.Empty](#rqsrs-007ldapconfigurationusernameempty) - * 4.2.34 [RQ.SRS-007.LDAP.Configuration.User.BothPasswordAndLDAP](#rqsrs-007ldapconfigurationuserbothpasswordandldap) - * 4.2.35 [RQ.SRS-007.LDAP.Configuration.User.LDAP.InvalidServerName.NotDefined](#rqsrs-007ldapconfigurationuserldapinvalidservernamenotdefined) - * 4.2.36 [RQ.SRS-007.LDAP.Configuration.User.LDAP.InvalidServerName.Empty](#rqsrs-007ldapconfigurationuserldapinvalidservernameempty) - * 4.2.37 [RQ.SRS-007.LDAP.Configuration.User.OnlyOneServer](#rqsrs-007ldapconfigurationuseronlyoneserver) - * 4.2.38 [RQ.SRS-007.LDAP.Configuration.User.Name.Long](#rqsrs-007ldapconfigurationusernamelong) - * 4.2.39 [RQ.SRS-007.LDAP.Configuration.User.Name.UTF8](#rqsrs-007ldapconfigurationusernameutf8) - * 4.2.40 [RQ.SRS-007.LDAP.Authentication.Username.Empty](#rqsrs-007ldapauthenticationusernameempty) - * 4.2.41 [RQ.SRS-007.LDAP.Authentication.Username.Long](#rqsrs-007ldapauthenticationusernamelong) - * 4.2.42 [RQ.SRS-007.LDAP.Authentication.Username.UTF8](#rqsrs-007ldapauthenticationusernameutf8) - * 4.2.43 [RQ.SRS-007.LDAP.Authentication.Password.Empty](#rqsrs-007ldapauthenticationpasswordempty) - * 4.2.44 [RQ.SRS-007.LDAP.Authentication.Password.Long](#rqsrs-007ldapauthenticationpasswordlong) - * 4.2.45 [RQ.SRS-007.LDAP.Authentication.Password.UTF8](#rqsrs-007ldapauthenticationpasswordutf8) - * 4.2.46 [RQ.SRS-007.LDAP.Authentication.VerificationCooldown.Performance](#rqsrs-007ldapauthenticationverificationcooldownperformance) - * 4.2.47 [RQ.SRS-007.LDAP.Authentication.VerificationCooldown.Reset.ChangeInCoreServerParameters](#rqsrs-007ldapauthenticationverificationcooldownresetchangeincoreserverparameters) - * 4.2.48 [RQ.SRS-007.LDAP.Authentication.VerificationCooldown.Reset.InvalidPassword](#rqsrs-007ldapauthenticationverificationcooldownresetinvalidpassword) + * 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 @@ -439,6 +440,25 @@ version: 1.0 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 + 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 @@ -1331,6 +1351,33 @@ RQ_SRS_007_LDAP_Configuration_Server_VerificationCooldown_Default = Requirement( ), link=None) +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' + ' 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) + RQ_SRS_007_LDAP_Configuration_Server_Syntax = Requirement( name='RQ.SRS-007.LDAP.Configuration.Server.Syntax', version='2.0', diff --git a/tests/testflows/ldap/authentication/tests/authentications.py b/tests/testflows/ldap/authentication/tests/authentications.py index a64a37ed686..46bcae000b8 100644 --- a/tests/testflows/ldap/authentication/tests/authentications.py +++ b/tests/testflows/ldap/authentication/tests/authentications.py @@ -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) + + + + diff --git a/tests/testflows/ldap/authentication/tests/common.py b/tests/testflows/ldap/authentication/tests/common.py index ed8d46df92b..8efb389a23f 100644 --- a/tests/testflows/ldap/authentication/tests/common.py +++ b/tests/testflows/ldap/authentication/tests/common.py @@ -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"): diff --git a/tests/testflows/ldap/authentication/tests/server_config.py b/tests/testflows/ldap/authentication/tests/server_config.py index 5b3a96caa9c..38ec859226b 100644 --- a/tests/testflows/ldap/authentication/tests/server_config.py +++ b/tests/testflows/ldap/authentication/tests/server_config.py @@ -217,6 +217,36 @@ 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 = (" 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("2.0") From 61339ea3fc6a71496ae09d4d7a63f345b7205620 Mon Sep 17 00:00:00 2001 From: Tai White Date: Wed, 18 Nov 2020 01:36:35 +0100 Subject: [PATCH 08/16] fixed header for SRS in /ldap/authentication requirements --- tests/testflows/ldap/authentication/requirements/requirements.md | 1 + tests/testflows/ldap/authentication/requirements/requirements.py | 1 + 2 files changed, 2 insertions(+) diff --git a/tests/testflows/ldap/authentication/requirements/requirements.md b/tests/testflows/ldap/authentication/requirements/requirements.md index b0943d4a48b..27ce8c921a0 100644 --- a/tests/testflows/ldap/authentication/requirements/requirements.md +++ b/tests/testflows/ldap/authentication/requirements/requirements.md @@ -1,4 +1,5 @@ # SRS-007 ClickHouse Authentication of Users via LDAP +# Software Requirements Specification ## Table of Contents diff --git a/tests/testflows/ldap/authentication/requirements/requirements.py b/tests/testflows/ldap/authentication/requirements/requirements.py index 92c3f844781..41d443c5df2 100644 --- a/tests/testflows/ldap/authentication/requirements/requirements.py +++ b/tests/testflows/ldap/authentication/requirements/requirements.py @@ -24,6 +24,7 @@ SRS_007_ClickHouse_Authentication_of_Users_via_LDAP = Specification( children=None, content=''' # SRS-007 ClickHouse Authentication of Users via LDAP +# Software Requirements Specification ## Table of Contents From 2bc32fe29298e828f10c87014d1892f468aa0a5c Mon Sep 17 00:00:00 2001 From: Tai White Date: Fri, 20 Nov 2020 23:21:13 +0100 Subject: [PATCH 09/16] Added verification cooldown requirements and tests to the ldap/external_user_directory SRS and test files --- .../requirements/requirements.md | 51 +- .../requirements/requirements.py | 83 +++- .../tests/authentications.py | 465 +++++++++++++++++- .../external_user_directory/tests/common.py | 3 +- .../tests/server_config.py | 31 +- 5 files changed, 591 insertions(+), 42 deletions(-) diff --git a/tests/testflows/ldap/external_user_directory/requirements/requirements.md b/tests/testflows/ldap/external_user_directory/requirements/requirements.md index 74248196998..cf9650f2cae 100644 --- a/tests/testflows/ldap/external_user_directory/requirements/requirements.md +++ b/tests/testflows/ldap/external_user_directory/requirements/requirements.md @@ -82,20 +82,21 @@ * 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.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.Syntax](#rqsrs-009ldapexternaluserdirectoryconfigurationserversyntax) - * 4.2.3.32 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.LDAPUserDirectory](#rqsrs-009ldapexternaluserdirectoryconfigurationusersldapuserdirectory) - * 4.2.3.33 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.LDAPUserDirectory.MoreThanOne](#rqsrs-009ldapexternaluserdirectoryconfigurationusersldapuserdirectorymorethanone) - * 4.2.3.34 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Syntax](#rqsrs-009ldapexternaluserdirectoryconfigurationuserssyntax) - * 4.2.3.35 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Server](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersserver) - * 4.2.3.36 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Server.Empty](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersserverempty) - * 4.2.3.37 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Server.Missing](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersservermissing) - * 4.2.3.38 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Server.MoreThanOne](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersservermorethanone) - * 4.2.3.39 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Server.Invalid](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersserverinvalid) - * 4.2.3.40 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Roles](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersroles) - * 4.2.3.41 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Roles.MoreThanOne](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersrolesmorethanone) - * 4.2.3.42 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Roles.Invalid](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersrolesinvalid) - * 4.2.3.43 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Roles.Empty](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersrolesempty) - * 4.2.3.44 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Roles.Missing](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersrolesmissing) + * 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) @@ -568,7 +569,7 @@ version: 1.0 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. +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 @@ -577,6 +578,25 @@ version: 1.0 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 + 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 @@ -739,7 +759,6 @@ 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 diff --git a/tests/testflows/ldap/external_user_directory/requirements/requirements.py b/tests/testflows/ldap/external_user_directory/requirements/requirements.py index 3354d2b5dd7..fc370de7753 100644 --- a/tests/testflows/ldap/external_user_directory/requirements/requirements.py +++ b/tests/testflows/ldap/external_user_directory/requirements/requirements.py @@ -1,6 +1,6 @@ # These requirements were auto generated # from software requirements specification (SRS) -# document by TestFlows v1.6.201025.1200805. +# document by TestFlows v1.6.201102.1235648. # Do not edit by hand but re-generate instead # using 'tfs requirements generate' command. from testflows.core import Specification @@ -107,20 +107,21 @@ SRS_009_ClickHouse_LDAP_External_User_Directory = Specification( * 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.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.Syntax](#rqsrs-009ldapexternaluserdirectoryconfigurationserversyntax) - * 4.2.3.32 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.LDAPUserDirectory](#rqsrs-009ldapexternaluserdirectoryconfigurationusersldapuserdirectory) - * 4.2.3.33 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.LDAPUserDirectory.MoreThanOne](#rqsrs-009ldapexternaluserdirectoryconfigurationusersldapuserdirectorymorethanone) - * 4.2.3.34 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Syntax](#rqsrs-009ldapexternaluserdirectoryconfigurationuserssyntax) - * 4.2.3.35 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Server](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersserver) - * 4.2.3.36 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Server.Empty](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersserverempty) - * 4.2.3.37 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Server.Missing](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersservermissing) - * 4.2.3.38 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Server.MoreThanOne](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersservermorethanone) - * 4.2.3.39 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Server.Invalid](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersserverinvalid) - * 4.2.3.40 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Roles](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersroles) - * 4.2.3.41 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Roles.MoreThanOne](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersrolesmorethanone) - * 4.2.3.42 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Roles.Invalid](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersrolesinvalid) - * 4.2.3.43 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Roles.Empty](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersrolesempty) - * 4.2.3.44 [RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Users.Parameters.Roles.Missing](#rqsrs-009ldapexternaluserdirectoryconfigurationusersparametersrolesmissing) + * 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) @@ -593,7 +594,7 @@ version: 1.0 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. +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 @@ -602,6 +603,25 @@ version: 1.0 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 + 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 @@ -764,7 +784,6 @@ 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 @@ -1756,7 +1775,7 @@ RQ_SRS_009_LDAP_ExternalUserDirectory_Configuration_Server_VerificationCooldown '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' + 'SHALL result in contacting the [LDAP] server to verify the username and password.\n' '\n' ), link=None) @@ -1776,6 +1795,33 @@ RQ_SRS_009_LDAP_ExternalUserDirectory_Configuration_Server_VerificationCooldown_ ), link=None) +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' + ' 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) + RQ_SRS_009_LDAP_ExternalUserDirectory_Configuration_Server_Syntax = Requirement( name='RQ.SRS-009.LDAP.ExternalUserDirectory.Configuration.Server.Syntax', version='2.0', @@ -2093,7 +2139,6 @@ RQ_SRS_009_LDAP_ExternalUserDirectory_Authentication_Password_UTF8 = Requirement '[ClickHouse] SHALL support [UTF-8] characters in passwords\n' 'used to authenticate users when using [LDAP] external user directory.\n' '\n' - '\n' ), link=None) diff --git a/tests/testflows/ldap/external_user_directory/tests/authentications.py b/tests/testflows/ldap/external_user_directory/tests/authentications.py index 531a1b2f3ea..8651dd9903b 100644 --- a/tests/testflows/ldap/external_user_directory/tests/authentications.py +++ b/tests/testflows/ldap/external_user_directory/tests/authentications.py @@ -144,7 +144,6 @@ def parallel_login_with_the_same_user(self, server, timeout=200): join(tasks, timeout) @TestScenario -@Tags("custom config") def login_after_ldap_external_user_directory_is_removed(self, server): """Check that ClickHouse stops authenticating LDAP users after LDAP external user directory is removed. @@ -698,6 +697,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 +1187,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 +1207,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) diff --git a/tests/testflows/ldap/external_user_directory/tests/common.py b/tests/testflows/ldap/external_user_directory/tests/common.py index 38b53ca6e9f..6d8a97e8611 100644 --- a/tests/testflows/ldap/external_user_directory/tests/common.py +++ b/tests/testflows/ldap/external_user_directory/tests/common.py @@ -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): diff --git a/tests/testflows/ldap/external_user_directory/tests/server_config.py b/tests/testflows/ldap/external_user_directory/tests/server_config.py index 2512a4d88de..617c0ee32e5 100644 --- a/tests/testflows/ldap/external_user_directory/tests/server_config.py +++ b/tests/testflows/ldap/external_user_directory/tests/server_config.py @@ -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,6 +231,36 @@ 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 = (" 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("2.0") From c1662b6a4b66af33082eaac29d0b46eacc06c4b8 Mon Sep 17 00:00:00 2001 From: Tai White Date: Mon, 23 Nov 2020 21:21:30 +0100 Subject: [PATCH 10/16] Added line removed by mistake --- .../ldap/external_user_directory/tests/authentications.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/testflows/ldap/external_user_directory/tests/authentications.py b/tests/testflows/ldap/external_user_directory/tests/authentications.py index 8651dd9903b..8229947adf7 100644 --- a/tests/testflows/ldap/external_user_directory/tests/authentications.py +++ b/tests/testflows/ldap/external_user_directory/tests/authentications.py @@ -144,6 +144,7 @@ def parallel_login_with_the_same_user(self, server, timeout=200): join(tasks, timeout) @TestScenario +@Tags("custom config") def login_after_ldap_external_user_directory_is_removed(self, server): """Check that ClickHouse stops authenticating LDAP users after LDAP external user directory is removed. From 47be319dea985b1ef170184c447909ed54b86a0d Mon Sep 17 00:00:00 2001 From: Denis Glazachev Date: Tue, 24 Nov 2020 20:17:58 +0400 Subject: [PATCH 11/16] Refactor the cached ldap info locations Add cached value access synchronization --- src/Access/Authentication.cpp | 62 +++++++++++++++++++++-------------- src/Access/Authentication.h | 9 ++--- src/Access/IAccessStorage.cpp | 2 +- src/Access/User.cpp | 16 +++++++++ src/Access/User.h | 18 ++++++++++ 5 files changed, 75 insertions(+), 32 deletions(-) diff --git a/src/Access/Authentication.cpp b/src/Access/Authentication.cpp index b55ffad2dfa..2455869f783 100644 --- a/src/Access/Authentication.cpp +++ b/src/Access/Authentication.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -49,7 +50,7 @@ Authentication::Digest Authentication::getPasswordDoubleSHA1() const } -bool Authentication::isCorrectPassword(const String & password_, const String & user_, const ExternalAuthenticators & external_authenticators) const +bool Authentication::isCorrectPassword(const User & user_, const String & password_, const ExternalAuthenticators & external_authenticators) const { switch (type) { @@ -83,41 +84,52 @@ 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.user = user_.getName(); ldap_server_params.password = password_; const auto current_params_hash = ldap_server_params.getCoreHash(); - const auto last_check_period = std::chrono::steady_clock::now() - ldap_last_successful_password_check_timestamp; + auto & ldap_last_successful_password_check_params_hash = user_.cache.ldap_last_successful_password_check_params_hash; + auto & ldap_last_successful_password_check_timestamp = user_.cache.ldap_last_successful_password_check_timestamp; - if ( - // Forbid the initial values explicitly. - ldap_last_successful_password_check_params_hash != 0 && - ldap_last_successful_password_check_timestamp != std::chrono::steady_clock::time_point{} && - - // Check if the caching is enabled at all. - ldap_server_params.verification_cooldown > std::chrono::seconds{0} && - - // Check if we can "reuse" the result of the previous successful password verification. - current_params_hash == ldap_last_successful_password_check_params_hash && - last_check_period >= std::chrono::seconds{0} && - last_check_period <= ldap_server_params.verification_cooldown - ) { - return true; + std::scoped_lock lock(user_.cache.mutex); + const auto last_check_period = std::chrono::steady_clock::now() - ldap_last_successful_password_check_timestamp; + + if ( + // Check if the caching is enabled at all. + ldap_server_params.verification_cooldown > std::chrono::seconds{0} && + + // Forbid the initial values explicitly. + ldap_last_successful_password_check_params_hash != 0 && + ldap_last_successful_password_check_timestamp != std::chrono::steady_clock::time_point{} && + + // Check if we can "reuse" the result of the previous successful password verification. + current_params_hash == ldap_last_successful_password_check_params_hash && + last_check_period >= std::chrono::seconds{0} && + last_check_period <= ldap_server_params.verification_cooldown + ) + { + return true; + } } LDAPSimpleAuthClient ldap_client(ldap_server_params); const auto result = ldap_client.check(); + const auto current_check_timestamp = std::chrono::steady_clock::now(); - if (result) { - ldap_last_successful_password_check_params_hash = current_params_hash; - ldap_last_successful_password_check_timestamp = std::chrono::steady_clock::now(); - } - else - { - ldap_last_successful_password_check_params_hash = 0; - ldap_last_successful_password_check_timestamp = std::chrono::steady_clock::time_point{}; + std::scoped_lock lock(user_.cache.mutex); + + if (result) + { + ldap_last_successful_password_check_params_hash = current_params_hash; + ldap_last_successful_password_check_timestamp = current_check_timestamp; + } + else + { + ldap_last_successful_password_check_params_hash = 0; + ldap_last_successful_password_check_timestamp = std::chrono::steady_clock::time_point{}; + } } return result; diff --git a/src/Access/Authentication.h b/src/Access/Authentication.h index 65bbcd94720..abbd43555b1 100644 --- a/src/Access/Authentication.h +++ b/src/Access/Authentication.h @@ -19,6 +19,7 @@ namespace ErrorCodes extern const int NOT_IMPLEMENTED; } +struct User; class ExternalAuthenticators; /// Authentication type and encrypted password for checking when an user logins. @@ -89,8 +90,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 instance and external authenticators' info are used only by some specific authentication type (e.g., LDAP_SERVER). + bool isCorrectPassword(const User & 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); } @@ -104,11 +105,7 @@ private: Type type = Type::NO_PASSWORD; Digest password_hash; - - // Used and maintained only for LDAP. String server_name; - mutable std::size_t ldap_last_successful_password_check_params_hash = 0; - mutable std::chrono::steady_clock::time_point ldap_last_successful_password_check_timestamp; }; diff --git a/src/Access/IAccessStorage.cpp b/src/Access/IAccessStorage.cpp index 58821e7de4b..01516017d89 100644 --- a/src/Access/IAccessStorage.cpp +++ b/src/Access/IAccessStorage.cpp @@ -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, password, external_authenticators); } diff --git a/src/Access/User.cpp b/src/Access/User.cpp index f57ec7c1359..b5ba098d0bc 100644 --- a/src/Access/User.cpp +++ b/src/Access/User.cpp @@ -14,4 +14,20 @@ bool User::equal(const IAccessEntity & other) const && (settings == other_user.settings); } +UserEtcCache & UserEtcCache::operator= (const UserEtcCache & other) +{ + std::scoped_lock lock(mutex, other.mutex); + ldap_last_successful_password_check_params_hash = other.ldap_last_successful_password_check_params_hash; + ldap_last_successful_password_check_timestamp = other.ldap_last_successful_password_check_timestamp; + return *this; +} + +UserEtcCache & UserEtcCache::operator= (const UserEtcCache && other) +{ + std::scoped_lock lock(mutex, other.mutex); + ldap_last_successful_password_check_params_hash = std::move(other.ldap_last_successful_password_check_params_hash); + ldap_last_successful_password_check_timestamp = std::move(other.ldap_last_successful_password_check_timestamp); + return *this; +} + } diff --git a/src/Access/User.h b/src/Access/User.h index 13f1e532015..0ad0068794d 100644 --- a/src/Access/User.h +++ b/src/Access/User.h @@ -8,9 +8,26 @@ #include #include +#include +#include namespace DB { + +/** Various cached data bound to a User instance. Access to any member must be synchronized via 'mutex' member. + */ +struct UserEtcCache { + mutable std::recursive_mutex mutex; + std::size_t ldap_last_successful_password_check_params_hash = 0; + std::chrono::steady_clock::time_point ldap_last_successful_password_check_timestamp; + + explicit UserEtcCache() = default; + explicit UserEtcCache(const UserEtcCache & other) { (*this) = other; } + explicit UserEtcCache(const UserEtcCache && other) { (*this) = std::move(other); } + UserEtcCache & operator= (const UserEtcCache & other); + UserEtcCache & operator= (const UserEtcCache && other); +}; + /** User and ACL. */ struct User : public IAccessEntity @@ -21,6 +38,7 @@ struct User : public IAccessEntity GrantedRoles granted_roles; RolesOrUsersSet default_roles = RolesOrUsersSet::AllTag{}; SettingsProfileElements settings; + mutable UserEtcCache cache; bool equal(const IAccessEntity & other) const override; std::shared_ptr clone() const override { return cloneImpl(); } From d12f59388a0c0dee3f5cb2201797a15f62e8b79e Mon Sep 17 00:00:00 2001 From: Denis Glazachev Date: Tue, 24 Nov 2020 22:48:15 +0400 Subject: [PATCH 12/16] Compilation fix --- src/Access/User.cpp | 2 +- src/Access/User.h | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Access/User.cpp b/src/Access/User.cpp index b5ba098d0bc..9b705fbdb5e 100644 --- a/src/Access/User.cpp +++ b/src/Access/User.cpp @@ -22,7 +22,7 @@ UserEtcCache & UserEtcCache::operator= (const UserEtcCache & other) return *this; } -UserEtcCache & UserEtcCache::operator= (const UserEtcCache && other) +UserEtcCache & UserEtcCache::operator= (UserEtcCache && other) { std::scoped_lock lock(mutex, other.mutex); ldap_last_successful_password_check_params_hash = std::move(other.ldap_last_successful_password_check_params_hash); diff --git a/src/Access/User.h b/src/Access/User.h index 0ad0068794d..ed3366b4712 100644 --- a/src/Access/User.h +++ b/src/Access/User.h @@ -16,16 +16,17 @@ namespace DB /** Various cached data bound to a User instance. Access to any member must be synchronized via 'mutex' member. */ -struct UserEtcCache { +struct UserEtcCache +{ mutable std::recursive_mutex mutex; std::size_t ldap_last_successful_password_check_params_hash = 0; std::chrono::steady_clock::time_point ldap_last_successful_password_check_timestamp; explicit UserEtcCache() = default; explicit UserEtcCache(const UserEtcCache & other) { (*this) = other; } - explicit UserEtcCache(const UserEtcCache && other) { (*this) = std::move(other); } + explicit UserEtcCache(UserEtcCache && other) { (*this) = std::move(other); } UserEtcCache & operator= (const UserEtcCache & other); - UserEtcCache & operator= (const UserEtcCache && other); + UserEtcCache & operator= (UserEtcCache && other); }; /** User and ACL. From 13e633c1ad009b474ac6a74b2748c16ec0d70fca Mon Sep 17 00:00:00 2001 From: Vitaliy Zakaznikov Date: Wed, 23 Dec 2020 13:17:18 -0500 Subject: [PATCH 13/16] Regenerating ldap/authentication/requirements/requirements.py --- .../requirements/requirements.py | 305 +++++++++++++++--- 1 file changed, 255 insertions(+), 50 deletions(-) diff --git a/tests/testflows/ldap/authentication/requirements/requirements.py b/tests/testflows/ldap/authentication/requirements/requirements.py index 4e955bf801b..25b943d18c2 100644 --- a/tests/testflows/ldap/authentication/requirements/requirements.py +++ b/tests/testflows/ldap/authentication/requirements/requirements.py @@ -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' + ' 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( ' 636\n' ' cn=\n' ' , ou=users, dc=example, dc=com\n' + ' 0\n' ' yes\n' ' tls1.2\n' ' demand\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 + 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. 636 cn= , ou=users, dc=example, dc=com + 0 yes tls1.2 demand @@ -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 From 3ce073163053abc99b8cf6180139e4a4a63290c5 Mon Sep 17 00:00:00 2001 From: Denis Glazachev Date: Fri, 25 Dec 2020 01:49:19 +0400 Subject: [PATCH 14/16] Move caching and LDAP credential verification code to ExternalAuthenticators --- src/Access/Authentication.cpp | 57 +---------- src/Access/Authentication.h | 5 +- src/Access/ExternalAuthenticators.cpp | 137 ++++++++++++++++++++------ src/Access/ExternalAuthenticators.h | 19 +++- src/Access/IAccessStorage.cpp | 2 +- src/Access/User.cpp | 16 --- src/Access/User.h | 19 ---- 7 files changed, 129 insertions(+), 126 deletions(-) diff --git a/src/Access/Authentication.cpp b/src/Access/Authentication.cpp index 2455869f783..5fab2c92624 100644 --- a/src/Access/Authentication.cpp +++ b/src/Access/Authentication.cpp @@ -1,7 +1,5 @@ #include -#include #include -#include #include #include @@ -50,7 +48,7 @@ Authentication::Digest Authentication::getPasswordDoubleSHA1() const } -bool Authentication::isCorrectPassword(const User & user_, const String & password_, const ExternalAuthenticators & external_authenticators) const +bool Authentication::isCorrectPassword(const String & user_, const String & password_, const ExternalAuthenticators & external_authenticators) const { switch (type) { @@ -82,58 +80,7 @@ bool Authentication::isCorrectPassword(const User & user_, const String & passwo } case LDAP_SERVER: - { - auto ldap_server_params = external_authenticators.getLDAPServerParams(server_name); - ldap_server_params.user = user_.getName(); - ldap_server_params.password = password_; - - const auto current_params_hash = ldap_server_params.getCoreHash(); - auto & ldap_last_successful_password_check_params_hash = user_.cache.ldap_last_successful_password_check_params_hash; - auto & ldap_last_successful_password_check_timestamp = user_.cache.ldap_last_successful_password_check_timestamp; - - { - std::scoped_lock lock(user_.cache.mutex); - const auto last_check_period = std::chrono::steady_clock::now() - ldap_last_successful_password_check_timestamp; - - if ( - // Check if the caching is enabled at all. - ldap_server_params.verification_cooldown > std::chrono::seconds{0} && - - // Forbid the initial values explicitly. - ldap_last_successful_password_check_params_hash != 0 && - ldap_last_successful_password_check_timestamp != std::chrono::steady_clock::time_point{} && - - // Check if we can "reuse" the result of the previous successful password verification. - current_params_hash == ldap_last_successful_password_check_params_hash && - last_check_period >= std::chrono::seconds{0} && - last_check_period <= ldap_server_params.verification_cooldown - ) - { - return true; - } - } - - LDAPSimpleAuthClient ldap_client(ldap_server_params); - const auto result = ldap_client.check(); - const auto current_check_timestamp = std::chrono::steady_clock::now(); - - { - std::scoped_lock lock(user_.cache.mutex); - - if (result) - { - ldap_last_successful_password_check_params_hash = current_params_hash; - ldap_last_successful_password_check_timestamp = current_check_timestamp; - } - else - { - ldap_last_successful_password_check_params_hash = 0; - ldap_last_successful_password_check_timestamp = std::chrono::steady_clock::time_point{}; - } - } - - return result; - } + return external_authenticators.checkLDAPCredentials(server_name, user_, password_); case MAX_TYPE: break; diff --git a/src/Access/Authentication.h b/src/Access/Authentication.h index abbd43555b1..e31e36531c3 100644 --- a/src/Access/Authentication.h +++ b/src/Access/Authentication.h @@ -19,7 +19,6 @@ namespace ErrorCodes extern const int NOT_IMPLEMENTED; } -struct User; class ExternalAuthenticators; /// Authentication type and encrypted password for checking when an user logins. @@ -90,8 +89,8 @@ public: void setServerName(const String & server_name_); /// Checks if the provided password is correct. Returns false if not. - /// User instance and external authenticators' info are used only by some specific authentication type (e.g., LDAP_SERVER). - bool isCorrectPassword(const User & user_, const String & password_, const ExternalAuthenticators & external_authenticators) const; + /// User name and external authenticators are used by 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); } diff --git a/src/Access/ExternalAuthenticators.cpp b/src/Access/ExternalAuthenticators.cpp index 9b908841576..71cc1c11020 100644 --- a/src/Access/ExternalAuthenticators.cpp +++ b/src/Access/ExternalAuthenticators.cpp @@ -1,9 +1,13 @@ #include +#include #include #include #include #include +#include +#include + namespace DB { @@ -134,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 (...) { @@ -152,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 params; + const auto password_hash = std::hash{}(password); -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; + + // 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_password_check_timestamp; + + if ( + // Forbid the initial values explicitly. + entry.last_successful_password_hash != 0 && + entry.last_successful_password_check_timestamp != std::chrono::steady_clock::time_point{} && + + // Check if we can safely "reuse" the result of the previous successful password verification. + password_hash == entry.last_successful_password_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 the same way. + if (result) + { + std::scoped_lock lock(mutex); + + // If the server was removed from the config while we were checking the password, we discard this 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 result. + if (params->getCoreHash() != new_params.getCoreHash()) + return false; + + auto & entry = ldap_server_caches[server][user_name]; + + if (entry.last_successful_password_check_timestamp < current_check_timestamp) + { + entry.last_successful_password_hash = password_hash; + entry.last_successful_password_check_timestamp = current_check_timestamp; + } + else if (entry.last_successful_password_hash != password_hash) + { + // Somehow a newer check succeeded but with other password, so this one is obsolete and we discard it. + return false; + } + } + + return result; } } diff --git a/src/Access/ExternalAuthenticators.h b/src/Access/ExternalAuthenticators.h index 7502409d817..16a69ec7709 100644 --- a/src/Access/ExternalAuthenticators.h +++ b/src/Access/ExternalAuthenticators.h @@ -3,9 +3,10 @@ #include #include +#include #include -#include #include +#include 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_password_hash = 0; + std::chrono::steady_clock::time_point last_successful_password_check_timestamp; + }; + + using LDAPServerCache = std::unordered_map; // user name -> cache entry + using LDAPServerCaches = std::map; // server name -> cache + using LDAPServersParams = std::map; // server name -> params private: mutable std::recursive_mutex mutex; - std::map ldap_server_params; + LDAPServersParams ldap_server_params; + mutable LDAPServerCaches ldap_server_caches; }; } diff --git a/src/Access/IAccessStorage.cpp b/src/Access/IAccessStorage.cpp index 01516017d89..c68f5f55ef5 100644 --- a/src/Access/IAccessStorage.cpp +++ b/src/Access/IAccessStorage.cpp @@ -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(user, password, external_authenticators); + return user.authentication.isCorrectPassword(user.getName(), password, external_authenticators); } diff --git a/src/Access/User.cpp b/src/Access/User.cpp index 9b705fbdb5e..f57ec7c1359 100644 --- a/src/Access/User.cpp +++ b/src/Access/User.cpp @@ -14,20 +14,4 @@ bool User::equal(const IAccessEntity & other) const && (settings == other_user.settings); } -UserEtcCache & UserEtcCache::operator= (const UserEtcCache & other) -{ - std::scoped_lock lock(mutex, other.mutex); - ldap_last_successful_password_check_params_hash = other.ldap_last_successful_password_check_params_hash; - ldap_last_successful_password_check_timestamp = other.ldap_last_successful_password_check_timestamp; - return *this; -} - -UserEtcCache & UserEtcCache::operator= (UserEtcCache && other) -{ - std::scoped_lock lock(mutex, other.mutex); - ldap_last_successful_password_check_params_hash = std::move(other.ldap_last_successful_password_check_params_hash); - ldap_last_successful_password_check_timestamp = std::move(other.ldap_last_successful_password_check_timestamp); - return *this; -} - } diff --git a/src/Access/User.h b/src/Access/User.h index ed3366b4712..13f1e532015 100644 --- a/src/Access/User.h +++ b/src/Access/User.h @@ -8,27 +8,9 @@ #include #include -#include -#include namespace DB { - -/** Various cached data bound to a User instance. Access to any member must be synchronized via 'mutex' member. - */ -struct UserEtcCache -{ - mutable std::recursive_mutex mutex; - std::size_t ldap_last_successful_password_check_params_hash = 0; - std::chrono::steady_clock::time_point ldap_last_successful_password_check_timestamp; - - explicit UserEtcCache() = default; - explicit UserEtcCache(const UserEtcCache & other) { (*this) = other; } - explicit UserEtcCache(UserEtcCache && other) { (*this) = std::move(other); } - UserEtcCache & operator= (const UserEtcCache & other); - UserEtcCache & operator= (UserEtcCache && other); -}; - /** User and ACL. */ struct User : public IAccessEntity @@ -39,7 +21,6 @@ struct User : public IAccessEntity GrantedRoles granted_roles; RolesOrUsersSet default_roles = RolesOrUsersSet::AllTag{}; SettingsProfileElements settings; - mutable UserEtcCache cache; bool equal(const IAccessEntity & other) const override; std::shared_ptr clone() const override { return cloneImpl(); } From b1e46ccef82d8bffe5eead8a10844953f9211264 Mon Sep 17 00:00:00 2001 From: Denis Glazachev Date: Fri, 25 Dec 2020 01:57:13 +0400 Subject: [PATCH 15/16] Remove unneeded include --- src/Access/Authentication.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Access/Authentication.h b/src/Access/Authentication.h index e31e36531c3..f98d2ed4679 100644 --- a/src/Access/Authentication.h +++ b/src/Access/Authentication.h @@ -6,7 +6,6 @@ #include #include #include -#include namespace DB @@ -89,7 +88,7 @@ public: void setServerName(const String & server_name_); /// Checks if the provided password is correct. Returns false if not. - /// User name and external authenticators are used by specific authentication types only (e.g., LDAP_SERVER). + /// 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); } From 07aa3fe30d952207c4e769975629c1334afc85ca Mon Sep 17 00:00:00 2001 From: Denis Glazachev Date: Fri, 25 Dec 2020 03:46:08 +0400 Subject: [PATCH 16/16] Refine the caching --- src/Access/ExternalAuthenticators.cpp | 30 +++++++++++++-------------- src/Access/ExternalAuthenticators.h | 4 ++-- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/Access/ExternalAuthenticators.cpp b/src/Access/ExternalAuthenticators.cpp index 71cc1c11020..81ab1af3b29 100644 --- a/src/Access/ExternalAuthenticators.cpp +++ b/src/Access/ExternalAuthenticators.cpp @@ -171,7 +171,7 @@ void ExternalAuthenticators::setConfiguration(const Poco::Util::AbstractConfigur bool ExternalAuthenticators::checkLDAPCredentials(const String & server, const String & user_name, const String & password) const { std::optional params; - const auto password_hash = std::hash{}(password); + std::size_t params_hash = 0; { std::scoped_lock lock(mutex); @@ -184,6 +184,7 @@ bool ExternalAuthenticators::checkLDAPCredentials(const String & server, const S 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}) @@ -197,15 +198,15 @@ bool ExternalAuthenticators::checkLDAPCredentials(const String & server, const S if (eit != cache.end()) { const auto & entry = eit->second; - const auto last_check_period = std::chrono::steady_clock::now() - entry.last_successful_password_check_timestamp; + const auto last_check_period = std::chrono::steady_clock::now() - entry.last_successful_authentication_timestamp; if ( // Forbid the initial values explicitly. - entry.last_successful_password_hash != 0 && - entry.last_successful_password_check_timestamp != std::chrono::steady_clock::time_point{} && + 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. - password_hash == entry.last_successful_password_hash && + entry.last_successful_params_hash == params_hash && last_check_period >= std::chrono::seconds{0} && last_check_period <= params->verification_cooldown ) @@ -229,12 +230,12 @@ bool ExternalAuthenticators::checkLDAPCredentials(const String & server, const S 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 the same way. + // 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 this result. + // 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; @@ -243,20 +244,19 @@ bool ExternalAuthenticators::checkLDAPCredentials(const String & server, const S 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 result. - if (params->getCoreHash() != new_params.getCoreHash()) + // 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_password_check_timestamp < current_check_timestamp) + if (entry.last_successful_authentication_timestamp < current_check_timestamp) { - entry.last_successful_password_hash = password_hash; - entry.last_successful_password_check_timestamp = current_check_timestamp; + entry.last_successful_params_hash = params_hash; + entry.last_successful_authentication_timestamp = current_check_timestamp; } - else if (entry.last_successful_password_hash != password_hash) + else if (entry.last_successful_params_hash != params_hash) { - // Somehow a newer check succeeded but with other password, so this one is obsolete and we discard it. + // Somehow a newer check with different params/password succeeded, so the current result is obsolete and we discard it. return false; } } diff --git a/src/Access/ExternalAuthenticators.h b/src/Access/ExternalAuthenticators.h index 16a69ec7709..fa618c92b3f 100644 --- a/src/Access/ExternalAuthenticators.h +++ b/src/Access/ExternalAuthenticators.h @@ -33,8 +33,8 @@ public: private: struct LDAPCacheEntry { - std::size_t last_successful_password_hash = 0; - std::chrono::steady_clock::time_point last_successful_password_check_timestamp; + std::size_t last_successful_params_hash = 0; + std::chrono::steady_clock::time_point last_successful_authentication_timestamp; }; using LDAPServerCache = std::unordered_map; // user name -> cache entry