From f2a6696362f2249ed607a65f2d48289f67aeb51b Mon Sep 17 00:00:00 2001 From: Denis Glazachev Date: Thu, 15 Oct 2020 02:39:31 +0400 Subject: [PATCH 001/294] 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 002/294] 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 003/294] 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 004/294] 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 005/294] 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 006/294] 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 007/294] 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 008/294] 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 009/294] 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 010/294] 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 011/294] 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 012/294] 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 cd8e7981e08dace480fd340646e2504301d5d96c Mon Sep 17 00:00:00 2001 From: vdimir Date: Sun, 29 Nov 2020 20:54:46 +0300 Subject: [PATCH 013/294] Speedup applyCIDRMask for IPv6 with compile-time generated mask array --- src/Common/IPv6ToBinary.cpp | 33 +++++++++++++++++++++++++++++++ src/Common/IPv6ToBinary.h | 5 +++++ src/Functions/FunctionsCoding.h | 35 ++++++++++++++++++++++----------- 3 files changed, 61 insertions(+), 12 deletions(-) diff --git a/src/Common/IPv6ToBinary.cpp b/src/Common/IPv6ToBinary.cpp index 1fd2e3312f6..da213bcb5a9 100644 --- a/src/Common/IPv6ToBinary.cpp +++ b/src/Common/IPv6ToBinary.cpp @@ -2,12 +2,18 @@ #include #include +#include + #include namespace DB { +constexpr size_t IPV6_MASKS_COUNT = 256; + +using RawMaskArray = std::array; + void IPv6ToRawBinary(const Poco::Net::IPAddress & address, char * res) { if (Poco::Net::IPAddress::IPv6 == address.family()) @@ -33,4 +39,31 @@ std::array IPv6ToBinary(const Poco::Net::IPAddress & address) return res; } +static constexpr RawMaskArray generateBitMask(size_t prefix) { + if (prefix >= 128) + prefix = 128; + RawMaskArray arr{0}; + size_t i = 0; + for (; prefix >= 8; ++i, prefix -= 8) + arr[i] = 0xff; + if (prefix > 0) + arr[i++] = ~(0xff >> prefix); + while (i < 16) + arr[i++] = 0x00; + return arr; +} + +static constexpr std::array generateBitMasks() { + std::array arr{}; + for (size_t i = 0; i < IPV6_MASKS_COUNT; ++i) + arr[i] = generateBitMask(i); + return arr; +} + +const uint8_t * getCIDRMaskIPv6(UInt8 prefix_len) +{ + static constexpr std::array IPV6_RAW_MASK_ARRAY = generateBitMasks(); + return IPV6_RAW_MASK_ARRAY[prefix_len].data(); +} + } diff --git a/src/Common/IPv6ToBinary.h b/src/Common/IPv6ToBinary.h index 2d0d4a20ecb..2e47238aeba 100644 --- a/src/Common/IPv6ToBinary.h +++ b/src/Common/IPv6ToBinary.h @@ -14,4 +14,9 @@ void IPv6ToRawBinary(const Poco::Net::IPAddress & address, char * res); /// Convert IP address to 16-byte array with IPv6 data (big endian). If it's an IPv4, map it to IPv6. std::array IPv6ToBinary(const Poco::Net::IPAddress & address); +/// Returns pointer to 16-byte array containing mask with first `prefix_len` bits set to `1` and `128 - prefix_len` to `0`. +/// Pointer is valid during all program execution time and doesn't require freeing. +/// Values of prefix_len greater than 128 interpreted as 128 exactly. +const uint8_t * getCIDRMaskIPv6(UInt8 prefix_len); + } diff --git a/src/Functions/FunctionsCoding.h b/src/Functions/FunctionsCoding.h index ac3262f5131..e07df450206 100644 --- a/src/Functions/FunctionsCoding.h +++ b/src/Functions/FunctionsCoding.h @@ -1,7 +1,8 @@ #pragma once -#include #include +#include +#include #include #include #include @@ -1617,20 +1618,28 @@ public: class FunctionIPv6CIDRToRange : public IFunction { private: - /// TODO Inefficient. + +#if defined(__SSE2__) + + #include + + static inline void applyCIDRMask(const UInt8 * __restrict src, UInt8 * __restrict dst_lower, UInt8 * __restrict dst_upper, UInt8 bits_to_keep) + { + __m128i mask = _mm_loadu_si128(reinterpret_cast(getCIDRMaskIPv6(bits_to_keep))); + __m128i lower = _mm_and_si128(_mm_loadu_si128(reinterpret_cast(src)), mask); + _mm_storeu_si128(reinterpret_cast<__m128i *>(dst_lower), lower); + + __m128i inv_mask = _mm_xor_si128(mask, _mm_cmpeq_epi32(_mm_setzero_si128(), _mm_setzero_si128())); + __m128i upper = _mm_or_si128(lower, inv_mask); + _mm_storeu_si128(reinterpret_cast<__m128i *>(dst_upper), upper); + } + +#else + /// NOTE IPv6 is stored in memory in big endian format that makes some difficulties. static void applyCIDRMask(const UInt8 * __restrict src, UInt8 * __restrict dst_lower, UInt8 * __restrict dst_upper, UInt8 bits_to_keep) { - UInt8 mask[16]{}; - - UInt8 bytes_to_keep = bits_to_keep / 8; - UInt8 bits_to_keep_in_last_byte = bits_to_keep % 8; - - for (size_t i = 0; i < bits_to_keep / 8; ++i) - mask[i] = 0xFFU; - - if (bits_to_keep_in_last_byte) - mask[bytes_to_keep] = 0xFFU << (8 - bits_to_keep_in_last_byte); + const auto * mask = getCIDRMaskIPv6(bits_to_keep); for (size_t i = 0; i < 16; ++i) { @@ -1639,6 +1648,8 @@ private: } } +#endif + public: static constexpr auto name = "IPv6CIDRToRange"; static FunctionPtr create(const Context &) { return std::make_shared(); } From 1aaff75d9ae1b38e74b5e14708bb2168d408301c Mon Sep 17 00:00:00 2001 From: vdimir Date: Tue, 1 Dec 2020 20:38:49 +0300 Subject: [PATCH 014/294] Fix style in IPv6ToBinary.cpp --- src/Common/IPv6ToBinary.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Common/IPv6ToBinary.cpp b/src/Common/IPv6ToBinary.cpp index da213bcb5a9..94b831fcc35 100644 --- a/src/Common/IPv6ToBinary.cpp +++ b/src/Common/IPv6ToBinary.cpp @@ -39,7 +39,8 @@ std::array IPv6ToBinary(const Poco::Net::IPAddress & address) return res; } -static constexpr RawMaskArray generateBitMask(size_t prefix) { +static constexpr RawMaskArray generateBitMask(size_t prefix) +{ if (prefix >= 128) prefix = 128; RawMaskArray arr{0}; @@ -53,7 +54,8 @@ static constexpr RawMaskArray generateBitMask(size_t prefix) { return arr; } -static constexpr std::array generateBitMasks() { +static constexpr std::array generateBitMasks() +{ std::array arr{}; for (size_t i = 0; i < IPV6_MASKS_COUNT; ++i) arr[i] = generateBitMask(i); From 9ce010e82cbdef46cfbd87d13c8f82cc7cfa4cc7 Mon Sep 17 00:00:00 2001 From: vdimir Date: Tue, 1 Dec 2020 22:12:11 +0300 Subject: [PATCH 015/294] Add comment for IPV6_MASKS_COUNT --- src/Common/IPv6ToBinary.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Common/IPv6ToBinary.cpp b/src/Common/IPv6ToBinary.cpp index 94b831fcc35..3c004a5a84e 100644 --- a/src/Common/IPv6ToBinary.cpp +++ b/src/Common/IPv6ToBinary.cpp @@ -10,6 +10,8 @@ namespace DB { +/// Result array could be indexed with all possible uint8 values without extra check. +/// For values greater than 128 we will store same value as for 128 (all bits set). constexpr size_t IPV6_MASKS_COUNT = 256; using RawMaskArray = std::array; From d1c7833f28976ebb62bea002889a2a4facf0f51f Mon Sep 17 00:00:00 2001 From: Daria Mozhaeva Date: Wed, 9 Dec 2020 17:03:38 +0300 Subject: [PATCH 016/294] Edit and translate. --- docs/en/operations/settings/settings.md | 2 +- docs/en/operations/system-tables/errors.md | 4 +-- .../sql-reference/functions/hash-functions.md | 4 +-- docs/ru/operations/settings/settings.md | 17 ++++++++- docs/ru/operations/system-tables/errors.md | 23 ++++++++++++ docs/ru/sql-reference/data-types/date.md | 35 +++++++++++++++++++ .../sql-reference/functions/hash-functions.md | 7 ++-- .../functions/other-functions.md | 20 +++++++++++ 8 files changed, 104 insertions(+), 8 deletions(-) create mode 100644 docs/ru/operations/system-tables/errors.md diff --git a/docs/en/operations/settings/settings.md b/docs/en/operations/settings/settings.md index c5e44d4c464..01e15f772ac 100644 --- a/docs/en/operations/settings/settings.md +++ b/docs/en/operations/settings/settings.md @@ -726,7 +726,7 @@ log_queries=1 ## log_queries_min_query_duration_ms {#settings-log-queries-min-query-duration-ms} -Minimal time for the query to run to get to the following tables: +Minimum query execution time to navigate to the following tables: - `system.query_log` - `system.query_thread_log` diff --git a/docs/en/operations/system-tables/errors.md b/docs/en/operations/system-tables/errors.md index 53e8a397217..1366855b5ce 100644 --- a/docs/en/operations/system-tables/errors.md +++ b/docs/en/operations/system-tables/errors.md @@ -1,12 +1,12 @@ # system.errors {#system_tables-errors} -Contains error codes with number of times they have been triggered. +Contains error codes with the number of times they have been triggered. Columns: - `name` ([String](../../sql-reference/data-types/string.md)) — name of the error (`errorCodeToName`). - `code` ([Int32](../../sql-reference/data-types/int-uint.md)) — code number of the error. -- `value` ([UInt64](../../sql-reference/data-types/int-uint.md)) - number of times this error has been happened. +- `value` ([UInt64](../../sql-reference/data-types/int-uint.md)) - the number of times this error has been happened. **Example** diff --git a/docs/en/sql-reference/functions/hash-functions.md b/docs/en/sql-reference/functions/hash-functions.md index 3594737c18a..9394426b20b 100644 --- a/docs/en/sql-reference/functions/hash-functions.md +++ b/docs/en/sql-reference/functions/hash-functions.md @@ -157,14 +157,14 @@ Levels are the same as in URLHierarchy. This function is specific to Yandex.Metr ## farmHash64 {#farmhash64} -Produces a 64-bit [FarmHash](https://github.com/google/farmhash) or Fingerprint value. Prefer `farmFingerprint64` for a stable and portable value. +Produces a 64-bit [FarmHash](https://github.com/google/farmhash) or Fingerprint value. `farmFingerprint64` is preferred for a stable and portable value. ``` sql farmFingerprint64(par1, ...) farmHash64(par1, ...) ``` -These functions use the `Fingerprint64` and `Hash64` method respectively from all [available methods](https://github.com/google/farmhash/blob/master/src/farmhash.h). +These functions use the `Fingerprint64` and `Hash64` methods respectively from all [available methods](https://github.com/google/farmhash/blob/master/src/farmhash.h). **Parameters** diff --git a/docs/ru/operations/settings/settings.md b/docs/ru/operations/settings/settings.md index 0de29112d1b..c027c7c170b 100644 --- a/docs/ru/operations/settings/settings.md +++ b/docs/ru/operations/settings/settings.md @@ -412,7 +412,7 @@ INSERT INTO table_with_enum_column_for_tsv_insert FORMAT TSV 102 2; - `'basic'` — используется базовый парсер. - ClickHouse может парсить только базовый формат `YYYY-MM-DD HH:MM:SS`. Например, `'2019-08-20 10:18:56'`. + ClickHouse может парсить только базовый формат `YYYY-MM-DD HH:MM:SS` или `YYYY-MM-DD`. Например, `'2019-08-20 10:18:56'` или `2019-08-20`. Значение по умолчанию: `'basic'`. @@ -691,6 +691,21 @@ ClickHouse использует этот параметр при чтении д log_queries=1 ``` +## log_queries_min_query_duration_ms {#settings-log-queries-min-query-duration-ms} + +Минимальное время выполнения запроса для перехода к следующим таблицам: + +- `system.query_log` +- `system.query_thread_log` + +В лог попадут только запросы следующего типа: + +- `QUERY_FINISH` +- `EXCEPTION_WHILE_PROCESSING` + +- Тип: milliseconds +- Значение по умолчанию: 0 (для любого запроса) + ## log_queries_min_type {#settings-log-queries-min-type} Задаёт минимальный уровень логирования в `query_log`. diff --git a/docs/ru/operations/system-tables/errors.md b/docs/ru/operations/system-tables/errors.md new file mode 100644 index 00000000000..43c5e0852f0 --- /dev/null +++ b/docs/ru/operations/system-tables/errors.md @@ -0,0 +1,23 @@ +# system.errors {#system_tables-errors} + +Содержит коды ошибок с указанием количества срабатываний. + +Столбцы: + +- `name` ([String](../../sql-reference/data-types/string.md)) — название ошибки (`errorCodeToName`). +- `code` ([Int32](../../sql-reference/data-types/int-uint.md)) — номер кода ошибки. +- `value` ([UInt64](../../sql-reference/data-types/int-uint.md)) - сколько раз возникала эта ошибка. + +**Пример** + +``` sql +SELECT * +FROM system.errors +WHERE value > 0 +ORDER BY code ASC +LIMIT 1 + +┌─name─────────────┬─code─┬─value─┐ +│ CANNOT_OPEN_FILE │ 76 │ 1 │ +└──────────────────┴──────┴───────┘ +``` diff --git a/docs/ru/sql-reference/data-types/date.md b/docs/ru/sql-reference/data-types/date.md index 9bcae2c1d72..e2162f22ff5 100644 --- a/docs/ru/sql-reference/data-types/date.md +++ b/docs/ru/sql-reference/data-types/date.md @@ -9,4 +9,39 @@ toc_title: Date Дата хранится без учёта часового пояса. +## Примеры {#examples} + +**1.** Создание таблицы со столбцом типа `DateTime` и добавление в неё данных: + +``` sql +CREATE TABLE dt +( + `timestamp` Date, + `event_id` UInt8 +) +ENGINE = TinyLog; +``` + +``` sql +INSERT INTO dt Values (1546300800, 1), ('2019-01-01', 2); +``` + +``` sql +SELECT * FROM dt; +``` + +``` text +┌──timestamp─┬─event_id─┐ +│ 2019-01-01 │ 1 │ +│ 2019-01-01 │ 2 │ +└────────────┴──────────┘ +``` + +## Смотрите также {#see-also} + +- [Функции для работы с датой и временем](../../sql-reference/functions/date-time-functions.md) +- [Операторы для работы с датой и временем](../../sql-reference/operators/index.md#operators-datetime) +- [Тип данных `DateTime`](../../sql-reference/data-types/datetime.md) + + [Оригинальная статья](https://clickhouse.tech/docs/ru/data_types/date/) diff --git a/docs/ru/sql-reference/functions/hash-functions.md b/docs/ru/sql-reference/functions/hash-functions.md index 92fc69227f4..d9c02ef9883 100644 --- a/docs/ru/sql-reference/functions/hash-functions.md +++ b/docs/ru/sql-reference/functions/hash-functions.md @@ -153,15 +153,18 @@ SELECT groupBitXor(cityHash64(*)) FROM table `URLHash(s, N)` - вычислить хэш от строки до N-го уровня в иерархии URL, без одного завершающего символа `/`, `?` или `#` на конце, если там такой есть. Уровни аналогичные URLHierarchy. Функция специфична для Яндекс.Метрики. +## farmFingerprint64 {#farmfingerprint64} + ## farmHash64 {#farmhash64} -Генерирует 64-х битное значение [FarmHash](https://github.com/google/farmhash). +Создает 64-битное значение [FarmHash](https://github.com/google/farmhash) или значение. `farmFingerprint64` предпочтительнее для стабильных и перемещаемых значений. ``` sql +farmFingerprint64(par1, ...) farmHash64(par1, ...) ``` -Из всех [доступных методов](https://github.com/google/farmhash/blob/master/src/farmhash.h) функция использует `Hash64`. +Эти функции используют методы `Fingerprint64` и `Hash64` из всех [доступных методов](https://github.com/google/farmhash/blob/master/src/farmhash.h). **Параметры** diff --git a/docs/ru/sql-reference/functions/other-functions.md b/docs/ru/sql-reference/functions/other-functions.md index 91bf0f5b3a0..587bc497da8 100644 --- a/docs/ru/sql-reference/functions/other-functions.md +++ b/docs/ru/sql-reference/functions/other-functions.md @@ -1589,4 +1589,24 @@ SELECT countDigits(toDecimal32(1, 9)), countDigits(toDecimal32(-1, 9)), 10 10 19 19 39 39 ``` +## errorCodeToName {#error-code-to-name} + +**Возвращаемое значение** + +- Название переменной для кода ошибки. + +Тип: [LowCardinality(String)](../../sql-reference/data-types/lowcardinality.md). + +**Синтаксис** + +``` sql +errorCodeToName(1) +``` + +Результат: + +``` text +UNSUPPORTED_METHOD +``` + [Оригинальная статья](https://clickhouse.tech/docs/ru/query_language/functions/other_functions/) From 843a26cbca218c12527328352cee6142e24912b1 Mon Sep 17 00:00:00 2001 From: Daria Mozhaeva Date: Wed, 9 Dec 2020 18:50:58 +0300 Subject: [PATCH 017/294] Edit and translate v2. --- docs/en/operations/system-tables/errors.md | 2 +- docs/ru/operations/settings/settings.md | 4 ++-- docs/ru/operations/system-tables/errors.md | 2 +- docs/ru/sql-reference/functions/hash-functions.md | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/en/operations/system-tables/errors.md b/docs/en/operations/system-tables/errors.md index 1366855b5ce..ec874efd711 100644 --- a/docs/en/operations/system-tables/errors.md +++ b/docs/en/operations/system-tables/errors.md @@ -6,7 +6,7 @@ Columns: - `name` ([String](../../sql-reference/data-types/string.md)) — name of the error (`errorCodeToName`). - `code` ([Int32](../../sql-reference/data-types/int-uint.md)) — code number of the error. -- `value` ([UInt64](../../sql-reference/data-types/int-uint.md)) - the number of times this error has been happened. +- `value` ([UInt64](../../sql-reference/data-types/int-uint.md)) — the number of times this error has been happened. **Example** diff --git a/docs/ru/operations/settings/settings.md b/docs/ru/operations/settings/settings.md index c027c7c170b..e7355935687 100644 --- a/docs/ru/operations/settings/settings.md +++ b/docs/ru/operations/settings/settings.md @@ -408,11 +408,11 @@ INSERT INTO table_with_enum_column_for_tsv_insert FORMAT TSV 102 2; - `'best_effort'` — включает расширенный парсинг. - ClickHouse может парсить базовый формат `YYYY-MM-DD HH:MM:SS` и все форматы [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601). Например, `'2018-06-08T01:02:03.000Z'`. +ClickHouse может парсить базовый формат `YYYY-MM-DD HH:MM:SS` и все форматы [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601). Например, `'2018-06-08T01:02:03.000Z'`. - `'basic'` — используется базовый парсер. - ClickHouse может парсить только базовый формат `YYYY-MM-DD HH:MM:SS` или `YYYY-MM-DD`. Например, `'2019-08-20 10:18:56'` или `2019-08-20`. +ClickHouse может парсить только базовый формат `YYYY-MM-DD HH:MM:SS` или `YYYY-MM-DD`. Например, `'2019-08-20 10:18:56'` или `2019-08-20`. Значение по умолчанию: `'basic'`. diff --git a/docs/ru/operations/system-tables/errors.md b/docs/ru/operations/system-tables/errors.md index 43c5e0852f0..321920e8535 100644 --- a/docs/ru/operations/system-tables/errors.md +++ b/docs/ru/operations/system-tables/errors.md @@ -6,7 +6,7 @@ - `name` ([String](../../sql-reference/data-types/string.md)) — название ошибки (`errorCodeToName`). - `code` ([Int32](../../sql-reference/data-types/int-uint.md)) — номер кода ошибки. -- `value` ([UInt64](../../sql-reference/data-types/int-uint.md)) - сколько раз возникала эта ошибка. +- `value` ([UInt64](../../sql-reference/data-types/int-uint.md)) — число, возникающих ошибок. **Пример** diff --git a/docs/ru/sql-reference/functions/hash-functions.md b/docs/ru/sql-reference/functions/hash-functions.md index d9c02ef9883..5dfdff91c31 100644 --- a/docs/ru/sql-reference/functions/hash-functions.md +++ b/docs/ru/sql-reference/functions/hash-functions.md @@ -157,7 +157,7 @@ SELECT groupBitXor(cityHash64(*)) FROM table ## farmHash64 {#farmhash64} -Создает 64-битное значение [FarmHash](https://github.com/google/farmhash) или значение. `farmFingerprint64` предпочтительнее для стабильных и перемещаемых значений. +Создает 64-битное значение [FarmHash](https://github.com/google/farmhash) или значение Fingerprint. `farmFingerprint64` предпочтительнее для стабильных и перемещаемых значений. ``` sql farmFingerprint64(par1, ...) From c2f1885b123ad4833c422ec21de32847661ceb40 Mon Sep 17 00:00:00 2001 From: damozhaeva <68770561+damozhaeva@users.noreply.github.com> Date: Fri, 11 Dec 2020 11:17:40 +0300 Subject: [PATCH 018/294] Update docs/ru/operations/system-tables/errors.md Co-authored-by: Anna <42538400+adevyatova@users.noreply.github.com> --- docs/ru/operations/system-tables/errors.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ru/operations/system-tables/errors.md b/docs/ru/operations/system-tables/errors.md index 321920e8535..3a824c8c834 100644 --- a/docs/ru/operations/system-tables/errors.md +++ b/docs/ru/operations/system-tables/errors.md @@ -6,7 +6,7 @@ - `name` ([String](../../sql-reference/data-types/string.md)) — название ошибки (`errorCodeToName`). - `code` ([Int32](../../sql-reference/data-types/int-uint.md)) — номер кода ошибки. -- `value` ([UInt64](../../sql-reference/data-types/int-uint.md)) — число, возникающих ошибок. +- `value` ([UInt64](../../sql-reference/data-types/int-uint.md)) — количество ошибок. **Пример** From 8b2feaeca24b15415a707daba6494b06cb780292 Mon Sep 17 00:00:00 2001 From: spongedc Date: Mon, 14 Dec 2020 22:37:25 +0800 Subject: [PATCH 019/294] Support SHOW CREATE VIEW Syntax --- src/Common/ErrorCodes.cpp | 1 + src/Databases/IDatabase.h | 1 + src/Interpreters/InterpreterFactory.cpp | 4 +++ .../InterpreterShowCreateQuery.cpp | 9 ++++++- src/Parsers/ASTCreateQuery.h | 2 ++ src/Parsers/ParserTablePropertiesQuery.cpp | 20 +++++++++----- src/Parsers/ParserTablePropertiesQuery.h | 2 +- src/Parsers/TablePropertiesQueriesASTs.h | 9 +++++++ .../01602_show_create_view.reference | 3 +++ .../0_stateless/01602_show_create_view.sql | 26 +++++++++++++++++++ 10 files changed, 69 insertions(+), 8 deletions(-) create mode 100644 tests/queries/0_stateless/01602_show_create_view.reference create mode 100644 tests/queries/0_stateless/01602_show_create_view.sql diff --git a/src/Common/ErrorCodes.cpp b/src/Common/ErrorCodes.cpp index 1e381808d16..328d0a364ff 100644 --- a/src/Common/ErrorCodes.cpp +++ b/src/Common/ErrorCodes.cpp @@ -529,6 +529,7 @@ M(560, ZSTD_ENCODER_FAILED) \ M(561, ZSTD_DECODER_FAILED) \ M(562, TLD_LIST_NOT_FOUND) \ + M(563, NOT_VIEW) \ \ M(999, KEEPER_EXCEPTION) \ M(1000, POCO_EXCEPTION) \ diff --git a/src/Databases/IDatabase.h b/src/Databases/IDatabase.h index fadec5fe7a9..fb2a9652d4b 100644 --- a/src/Databases/IDatabase.h +++ b/src/Databases/IDatabase.h @@ -30,6 +30,7 @@ namespace ErrorCodes extern const int NOT_IMPLEMENTED; extern const int CANNOT_GET_CREATE_TABLE_QUERY; extern const int CANNOT_GET_CREATE_DICTIONARY_QUERY; + extern const int NOT_VIEW; } class IDatabaseTablesIterator diff --git a/src/Interpreters/InterpreterFactory.cpp b/src/Interpreters/InterpreterFactory.cpp index 30992905f5d..156c3c1ba51 100644 --- a/src/Interpreters/InterpreterFactory.cpp +++ b/src/Interpreters/InterpreterFactory.cpp @@ -160,6 +160,10 @@ std::unique_ptr InterpreterFactory::get(ASTPtr & query, Context & { return std::make_unique(query, context); } + else if (query->as()) + { + return std::make_unique(query, context); + } else if (query->as()) { return std::make_unique(query, context); diff --git a/src/Interpreters/InterpreterShowCreateQuery.cpp b/src/Interpreters/InterpreterShowCreateQuery.cpp index 907523ce94b..bcf6cc09473 100644 --- a/src/Interpreters/InterpreterShowCreateQuery.cpp +++ b/src/Interpreters/InterpreterShowCreateQuery.cpp @@ -43,12 +43,19 @@ BlockInputStreamPtr InterpreterShowCreateQuery::executeImpl() { ASTPtr create_query; ASTQueryWithTableAndOutput * show_query; - if ((show_query = query_ptr->as())) + if ((show_query = query_ptr->as()) || + (show_query = query_ptr->as())) { auto resolve_table_type = show_query->temporary ? Context::ResolveExternal : Context::ResolveOrdinary; auto table_id = context.resolveStorageID(*show_query, resolve_table_type); context.checkAccess(AccessType::SHOW_COLUMNS, table_id); create_query = DatabaseCatalog::instance().getDatabase(table_id.database_name)->getCreateTableQuery(table_id.table_name, context); + if (query_ptr->as()) + { + auto & ast_create_query = create_query->as(); + if (!ast_create_query.isView()) + throw Exception("'" + ast_create_query.database + "." + ast_create_query.table +"' is not VIEW", ErrorCodes::NOT_VIEW); + } } else if ((show_query = query_ptr->as())) { diff --git a/src/Parsers/ASTCreateQuery.h b/src/Parsers/ASTCreateQuery.h index 7b2deb99698..67ad9a89392 100644 --- a/src/Parsers/ASTCreateQuery.h +++ b/src/Parsers/ASTCreateQuery.h @@ -91,6 +91,8 @@ public: return removeOnCluster(clone(), new_database); } + bool isView() { return is_view || is_materialized_view || is_live_view; } + protected: void formatQueryImpl(const FormatSettings & settings, FormatState & state, FormatStateStacked frame) const override; }; diff --git a/src/Parsers/ParserTablePropertiesQuery.cpp b/src/Parsers/ParserTablePropertiesQuery.cpp index 2ee85a3330d..fcb7c8ef7a4 100644 --- a/src/Parsers/ParserTablePropertiesQuery.cpp +++ b/src/Parsers/ParserTablePropertiesQuery.cpp @@ -21,6 +21,7 @@ bool ParserTablePropertiesQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & ParserKeyword s_create("CREATE"); ParserKeyword s_database("DATABASE"); ParserKeyword s_table("TABLE"); + ParserKeyword s_view("VIEW"); ParserKeyword s_dictionary("DICTIONARY"); ParserToken s_dot(TokenType::Dot); ParserIdentifier name_p; @@ -30,6 +31,7 @@ bool ParserTablePropertiesQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & std::shared_ptr query; bool parse_only_database_name = false; + bool parse_show_create_view = false; bool temporary = false; if (s_exists.ignore(pos, expected)) @@ -56,6 +58,11 @@ bool ParserTablePropertiesQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & } else if (s_dictionary.checkWithoutMoving(pos, expected)) query = std::make_shared(); + else if (s_view.ignore(pos, expected)) + { + query = std::make_shared(); + parse_show_create_view = true; + } else query = std::make_shared(); } @@ -71,15 +78,16 @@ bool ParserTablePropertiesQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & } else { - if (temporary || s_temporary.ignore(pos, expected)) - query->temporary = true; - - if (!s_table.ignore(pos, expected)) - s_dictionary.ignore(pos, expected); + if (!parse_show_create_view) + { + if (temporary || s_temporary.ignore(pos, expected)) + query->temporary = true; + if (!s_table.ignore(pos, expected)) + s_dictionary.ignore(pos, expected); + } if (!name_p.parse(pos, table, expected)) return false; - if (s_dot.ignore(pos, expected)) { database = table; diff --git a/src/Parsers/ParserTablePropertiesQuery.h b/src/Parsers/ParserTablePropertiesQuery.h index 5de4c45db88..8d2c26d34ab 100644 --- a/src/Parsers/ParserTablePropertiesQuery.h +++ b/src/Parsers/ParserTablePropertiesQuery.h @@ -7,7 +7,7 @@ namespace DB { -/** Query (EXISTS | SHOW CREATE) [TABLE|DICTIONARY] [db.]name [FORMAT format] +/** Query (EXISTS | SHOW CREATE) [DATABASE|TABLE|DICTIONARY] [db.]name [FORMAT format] */ class ParserTablePropertiesQuery : public IParserBase { diff --git a/src/Parsers/TablePropertiesQueriesASTs.h b/src/Parsers/TablePropertiesQueriesASTs.h index 6a8e3b2ce83..cbb9b2a382c 100644 --- a/src/Parsers/TablePropertiesQueriesASTs.h +++ b/src/Parsers/TablePropertiesQueriesASTs.h @@ -29,6 +29,14 @@ struct ASTShowCreateTableQueryIDAndQueryNames static constexpr auto QueryTemporary = "SHOW CREATE TEMPORARY TABLE"; }; +struct ASTShowCreateViewQueryIDAndQueryNames +{ + static constexpr auto ID = "ShowCreateViewQuery"; + static constexpr auto Query = "SHOW CREATE VIEW"; + /// No temporary view are supported, just for parsing + static constexpr auto QueryTemporary = ""; +}; + struct ASTShowCreateDatabaseQueryIDAndQueryNames { static constexpr auto ID = "ShowCreateDatabaseQuery"; @@ -54,6 +62,7 @@ struct ASTDescribeQueryExistsQueryIDAndQueryNames using ASTExistsTableQuery = ASTQueryWithTableAndOutputImpl; using ASTExistsDictionaryQuery = ASTQueryWithTableAndOutputImpl; using ASTShowCreateTableQuery = ASTQueryWithTableAndOutputImpl; +using ASTShowCreateViewQuery = ASTQueryWithTableAndOutputImpl; using ASTShowCreateDictionaryQuery = ASTQueryWithTableAndOutputImpl; class ASTShowCreateDatabaseQuery : public ASTQueryWithTableAndOutputImpl diff --git a/tests/queries/0_stateless/01602_show_create_view.reference b/tests/queries/0_stateless/01602_show_create_view.reference new file mode 100644 index 00000000000..2c2ba13bef3 --- /dev/null +++ b/tests/queries/0_stateless/01602_show_create_view.reference @@ -0,0 +1,3 @@ +CREATE VIEW test_1602.v\n(\n `EventDate` DateTime,\n `CounterID` UInt32,\n `UserID` UInt32\n) AS\nSELECT *\nFROM test_1602.tbl +CREATE MATERIALIZED VIEW test_1602.vv\n(\n `EventDate` DateTime,\n `CounterID` UInt32,\n `UserID` UInt32\n)\nENGINE = MergeTree\nPARTITION BY toYYYYMM(EventDate)\nORDER BY (CounterID, EventDate, intHash32(UserID))\nSETTINGS index_granularity = 8192 AS\nSELECT *\nFROM test_1602.tbl +CREATE LIVE VIEW test_1602.vvv\n(\n `EventDate` DateTime,\n `CounterID` UInt32,\n `UserID` UInt32\n) AS\nSELECT *\nFROM test_1602.tbl diff --git a/tests/queries/0_stateless/01602_show_create_view.sql b/tests/queries/0_stateless/01602_show_create_view.sql new file mode 100644 index 00000000000..0a4ecd4ceec --- /dev/null +++ b/tests/queries/0_stateless/01602_show_create_view.sql @@ -0,0 +1,26 @@ +DROP DATABASE IF EXISTS test_1602; + +CREATE DATABASE test_1602; + +CREATE TABLE test_1602.tbl (`EventDate` DateTime, `CounterID` UInt32, `UserID` UInt32) ENGINE = MergeTree() PARTITION BY toYYYYMM(EventDate) ORDER BY (CounterID, EventDate, intHash32(UserID)) SETTINGS index_granularity = 8192; + +CREATE VIEW test_1602.v AS SELECT * FROM test_1602.tbl; + +CREATE MATERIALIZED VIEW test_1602.vv (`EventDate` DateTime, `CounterID` UInt32, `UserID` UInt32) ENGINE = MergeTree() PARTITION BY toYYYYMM(EventDate) ORDER BY (CounterID, EventDate, intHash32(UserID)) SETTINGS index_granularity = 8192 AS SELECT * FROM test_1602.tbl; + + +SET allow_experimental_live_view=1; + +CREATE LIVE VIEW test_1602.vvv AS SELECT * FROM test_1602.tbl; + +SHOW CREATE VIEW test_1602.v; + +SHOW CREATE VIEW test_1602.vv; + +SHOW CREATE VIEW test_1602.vvv; + +SHOW CREATE VIEW test_1602.not_exist_view; -- { serverError 390 } + +SHOW CREATE VIEW test_1602.tbl; -- { serverError 563 } + +DROP DATABASE IF EXISTS test_1602; From 90cc37ae561f90085601f3bf437a543d85f155e3 Mon Sep 17 00:00:00 2001 From: Dmitriy Date: Mon, 14 Dec 2020 23:23:29 +0300 Subject: [PATCH 020/294] Document execute_merges_on_single_replica_time_threshold setting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Задокументировал настройку execute_merges_on_single_replica_time_threshold. --- .../mergetree-family/replication.md | 2 ++ docs/en/operations/settings/settings.md | 16 +++++++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/docs/en/engines/table-engines/mergetree-family/replication.md b/docs/en/engines/table-engines/mergetree-family/replication.md index 625869a3cb8..4758ff321e8 100644 --- a/docs/en/engines/table-engines/mergetree-family/replication.md +++ b/docs/en/engines/table-engines/mergetree-family/replication.md @@ -113,6 +113,8 @@ You can have any number of replicas of the same data. Yandex.Metrica uses double The system monitors data synchronicity on replicas and is able to recover after a failure. Failover is automatic (for small differences in data) or semi-automatic (when data differs too much, which may indicate a configuration error). +You can use a special logic for performing merges (for more information, see the documentation for [execute_merges_on_single_replica_time_threshold](../../../operations/settings/settings.md#execute-merges-on-single-replica-time-threshold) setting). + ## Creating Replicated Tables {#creating-replicated-tables} The `Replicated` prefix is added to the table engine name. For example:`ReplicatedMergeTree`. diff --git a/docs/en/operations/settings/settings.md b/docs/en/operations/settings/settings.md index c5e44d4c464..3b2c9a67999 100644 --- a/docs/en/operations/settings/settings.md +++ b/docs/en/operations/settings/settings.md @@ -2410,7 +2410,6 @@ Result: {"number":"1"} {"number":"2"} ``` - ======= ## allow_nullable_key {#allow-nullable-key} @@ -2423,4 +2422,19 @@ Possible values: Default value: `0`. +## execute_merges_on_single_replica_time_threshold {#execute-merges-on-single-replica-time-threshold} + +Enables special logic to perform merges on replicas. + +Possible values: + +- Positive integer (in seconds). +- 0 — Special merge logic is not executed. + +Default value: `0`. + +**Special logic to perform merges** + +Selects one replica to perform the merge on. Sets the time threshold from the start of the merge. Other replicas wait for the merge to finish, then download the result. If the time threshold passes and the selected replica does not perform merges, then the merge is performed manually. + [Original article](https://clickhouse.tech/docs/en/operations/settings/settings/) From d9a1b05910ebd73cabd2711cc43aabdca71483cf Mon Sep 17 00:00:00 2001 From: Dmitriy Date: Mon, 14 Dec 2020 23:37:03 +0300 Subject: [PATCH 021/294] Update settings.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Убрал лишние символы в заголовке настройки allow_nullable_key. --- docs/en/operations/settings/settings.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/operations/settings/settings.md b/docs/en/operations/settings/settings.md index 3b2c9a67999..c60cd2c2c52 100644 --- a/docs/en/operations/settings/settings.md +++ b/docs/en/operations/settings/settings.md @@ -2410,7 +2410,7 @@ Result: {"number":"1"} {"number":"2"} ``` -======= + ## allow_nullable_key {#allow-nullable-key} Allows using of the [Nullable](../../sql-reference/data-types/nullable.md#data_type-nullable)-typed values in a sorting and a primary key for [MergeTree](../../engines/table-engines/mergetree-family/mergetree.md#table_engines-mergetree) tables. From dc27e0a4fd152c1d1b24cf32af2e080d7fa7cd96 Mon Sep 17 00:00:00 2001 From: spongedc Date: Tue, 15 Dec 2020 20:29:26 +0800 Subject: [PATCH 022/294] Fix error code --- src/Common/ErrorCodes.cpp | 1 - src/Databases/IDatabase.h | 1 - src/Interpreters/InterpreterShowCreateQuery.cpp | 2 +- tests/queries/0_stateless/01602_show_create_view.sql | 5 ++--- 4 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/Common/ErrorCodes.cpp b/src/Common/ErrorCodes.cpp index 328d0a364ff..1e381808d16 100644 --- a/src/Common/ErrorCodes.cpp +++ b/src/Common/ErrorCodes.cpp @@ -529,7 +529,6 @@ M(560, ZSTD_ENCODER_FAILED) \ M(561, ZSTD_DECODER_FAILED) \ M(562, TLD_LIST_NOT_FOUND) \ - M(563, NOT_VIEW) \ \ M(999, KEEPER_EXCEPTION) \ M(1000, POCO_EXCEPTION) \ diff --git a/src/Databases/IDatabase.h b/src/Databases/IDatabase.h index fb2a9652d4b..fadec5fe7a9 100644 --- a/src/Databases/IDatabase.h +++ b/src/Databases/IDatabase.h @@ -30,7 +30,6 @@ namespace ErrorCodes extern const int NOT_IMPLEMENTED; extern const int CANNOT_GET_CREATE_TABLE_QUERY; extern const int CANNOT_GET_CREATE_DICTIONARY_QUERY; - extern const int NOT_VIEW; } class IDatabaseTablesIterator diff --git a/src/Interpreters/InterpreterShowCreateQuery.cpp b/src/Interpreters/InterpreterShowCreateQuery.cpp index bcf6cc09473..869573f0d66 100644 --- a/src/Interpreters/InterpreterShowCreateQuery.cpp +++ b/src/Interpreters/InterpreterShowCreateQuery.cpp @@ -54,7 +54,7 @@ BlockInputStreamPtr InterpreterShowCreateQuery::executeImpl() { auto & ast_create_query = create_query->as(); if (!ast_create_query.isView()) - throw Exception("'" + ast_create_query.database + "." + ast_create_query.table +"' is not VIEW", ErrorCodes::NOT_VIEW); + throw Exception("'" + ast_create_query.database + "." + ast_create_query.table +"' is not VIEW", ErrorCodes::BAD_ARGUMENTS); } } else if ((show_query = query_ptr->as())) diff --git a/tests/queries/0_stateless/01602_show_create_view.sql b/tests/queries/0_stateless/01602_show_create_view.sql index 0a4ecd4ceec..8e049d2d4cd 100644 --- a/tests/queries/0_stateless/01602_show_create_view.sql +++ b/tests/queries/0_stateless/01602_show_create_view.sql @@ -4,8 +4,7 @@ CREATE DATABASE test_1602; CREATE TABLE test_1602.tbl (`EventDate` DateTime, `CounterID` UInt32, `UserID` UInt32) ENGINE = MergeTree() PARTITION BY toYYYYMM(EventDate) ORDER BY (CounterID, EventDate, intHash32(UserID)) SETTINGS index_granularity = 8192; -CREATE VIEW test_1602.v AS SELECT * FROM test_1602.tbl; - +CREATE VIEW test_1602.v AS SELECT * FROM test_1602.tbl; CREATE MATERIALIZED VIEW test_1602.vv (`EventDate` DateTime, `CounterID` UInt32, `UserID` UInt32) ENGINE = MergeTree() PARTITION BY toYYYYMM(EventDate) ORDER BY (CounterID, EventDate, intHash32(UserID)) SETTINGS index_granularity = 8192 AS SELECT * FROM test_1602.tbl; @@ -21,6 +20,6 @@ SHOW CREATE VIEW test_1602.vvv; SHOW CREATE VIEW test_1602.not_exist_view; -- { serverError 390 } -SHOW CREATE VIEW test_1602.tbl; -- { serverError 563 } +SHOW CREATE VIEW test_1602.tbl; -- { serverError 36 } DROP DATABASE IF EXISTS test_1602; From 0338b6d0ed5ed72837dd86b7a76505b52e5e342d Mon Sep 17 00:00:00 2001 From: ana-uvarova Date: Tue, 15 Dec 2020 17:54:45 +0300 Subject: [PATCH 023/294] Transferred system.metric_log to another page. --- .../settings.md | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/docs/en/operations/server-configuration-parameters/settings.md b/docs/en/operations/server-configuration-parameters/settings.md index 533fcea5500..3028e12c20f 100644 --- a/docs/en/operations/server-configuration-parameters/settings.md +++ b/docs/en/operations/server-configuration-parameters/settings.md @@ -758,6 +758,66 @@ If the table doesn’t exist, ClickHouse will create it. If the structure of the ``` +## system.metric_log {#system_tables-metric_log} + +Contains history of metrics values from tables [`system.metrics`](../../operations/system-tables/metrics.md) and [`system.events`](../../operations/system-tables/events.md#system_tables-events), periodically flushed to disk. + +To turn on metrics history collection on `system.metric_log`, create `/etc/clickhouse-server/config.d/metric_log.xml` with following content: + +``` xml + + + system + metric_log
+ 7500 + 1000 +
+
+``` + +Columns: +- `event_date` ([Date](../../sql-reference/data-types/date.md)) — Event date. +- `event_time` ([DateTime](../../sql-reference/data-types/datetime.md)) — Event time. +- `event_time_microseconds` ([DateTime64](../../sql-reference/data-types/datetime64.md)) — Event time with microseconds resolution. + +**Example** + +``` sql +SELECT * FROM system.metric_log LIMIT 1 FORMAT Vertical; +``` + +``` text +Row 1: +────── +event_date: 2020-09-05 +event_time: 2020-09-05 16:22:33 +event_time_microseconds: 2020-09-05 16:22:33.196807 +milliseconds: 196 +ProfileEvent_Query: 0 +ProfileEvent_SelectQuery: 0 +ProfileEvent_InsertQuery: 0 +ProfileEvent_FailedQuery: 0 +ProfileEvent_FailedSelectQuery: 0 +... +... +CurrentMetric_Revision: 54439 +CurrentMetric_VersionInteger: 20009001 +CurrentMetric_RWLockWaitingReaders: 0 +CurrentMetric_RWLockWaitingWriters: 0 +CurrentMetric_RWLockActiveReaders: 0 +CurrentMetric_RWLockActiveWriters: 0 +CurrentMetric_GlobalThread: 74 +CurrentMetric_GlobalThreadActive: 26 +CurrentMetric_LocalThread: 0 +CurrentMetric_LocalThreadActive: 0 +CurrentMetric_DistributedFilesToInsert: 0 +``` + +**See also** + +- [system.asynchronous_metrics](../../operations/system-tables/asynchronous_metrics.md) — Contains periodically calculated metrics. +- [Monitoring](../../operations/monitoring.md) — Base concepts of ClickHouse monitoring. + ## text_log {#server_configuration_parameters-text_log} Settings for the [text_log](../../operations/system-tables/text_log.md#system_tables-text_log) system table for logging text messages. From b81a932408d5c23313a0682c5bb1ef2d4f8e7769 Mon Sep 17 00:00:00 2001 From: ana-uvarova Date: Tue, 15 Dec 2020 20:49:00 +0300 Subject: [PATCH 024/294] redone --- .../settings.md | 42 +------------------ .../en/operations/system-tables/metric_log.md | 14 +------ 2 files changed, 2 insertions(+), 54 deletions(-) diff --git a/docs/en/operations/server-configuration-parameters/settings.md b/docs/en/operations/server-configuration-parameters/settings.md index 3028e12c20f..318b80a23db 100644 --- a/docs/en/operations/server-configuration-parameters/settings.md +++ b/docs/en/operations/server-configuration-parameters/settings.md @@ -758,9 +758,7 @@ If the table doesn’t exist, ClickHouse will create it. If the structure of the ``` -## system.metric_log {#system_tables-metric_log} - -Contains history of metrics values from tables [`system.metrics`](../../operations/system-tables/metrics.md) and [`system.events`](../../operations/system-tables/events.md#system_tables-events), periodically flushed to disk. +## metric_log {#metric_log} To turn on metrics history collection on `system.metric_log`, create `/etc/clickhouse-server/config.d/metric_log.xml` with following content: @@ -775,44 +773,6 @@ To turn on metrics history collection on `system.metric_log`, create `/etc/click ``` -Columns: -- `event_date` ([Date](../../sql-reference/data-types/date.md)) — Event date. -- `event_time` ([DateTime](../../sql-reference/data-types/datetime.md)) — Event time. -- `event_time_microseconds` ([DateTime64](../../sql-reference/data-types/datetime64.md)) — Event time with microseconds resolution. - -**Example** - -``` sql -SELECT * FROM system.metric_log LIMIT 1 FORMAT Vertical; -``` - -``` text -Row 1: -────── -event_date: 2020-09-05 -event_time: 2020-09-05 16:22:33 -event_time_microseconds: 2020-09-05 16:22:33.196807 -milliseconds: 196 -ProfileEvent_Query: 0 -ProfileEvent_SelectQuery: 0 -ProfileEvent_InsertQuery: 0 -ProfileEvent_FailedQuery: 0 -ProfileEvent_FailedSelectQuery: 0 -... -... -CurrentMetric_Revision: 54439 -CurrentMetric_VersionInteger: 20009001 -CurrentMetric_RWLockWaitingReaders: 0 -CurrentMetric_RWLockWaitingWriters: 0 -CurrentMetric_RWLockActiveReaders: 0 -CurrentMetric_RWLockActiveWriters: 0 -CurrentMetric_GlobalThread: 74 -CurrentMetric_GlobalThreadActive: 26 -CurrentMetric_LocalThread: 0 -CurrentMetric_LocalThreadActive: 0 -CurrentMetric_DistributedFilesToInsert: 0 -``` - **See also** - [system.asynchronous_metrics](../../operations/system-tables/asynchronous_metrics.md) — Contains periodically calculated metrics. diff --git a/docs/en/operations/system-tables/metric_log.md b/docs/en/operations/system-tables/metric_log.md index c6aefc5034f..8cef5dc0931 100644 --- a/docs/en/operations/system-tables/metric_log.md +++ b/docs/en/operations/system-tables/metric_log.md @@ -2,19 +2,6 @@ Contains history of metrics values from tables `system.metrics` and `system.events`, periodically flushed to disk. -To turn on metrics history collection on `system.metric_log`, create `/etc/clickhouse-server/config.d/metric_log.xml` with following content: - -``` xml - - - system - metric_log
- 7500 - 1000 -
-
-``` - Columns: - `event_date` ([Date](../../sql-reference/data-types/date.md)) — Event date. - `event_time` ([DateTime](../../sql-reference/data-types/datetime.md)) — Event time. @@ -55,6 +42,7 @@ CurrentMetric_DistributedFilesToInsert: 0 **See also** +- [metric_log setting](../../operations/server-configuration-parameters/settings.md#metric_log) — What you should add. - [system.asynchronous_metrics](../../operations/system-tables/asynchronous_metrics.md) — Contains periodically calculated metrics. - [system.events](../../operations/system-tables/events.md#system_tables-events) — Contains a number of events that occurred. - [system.metrics](../../operations/system-tables/metrics.md) — Contains instantly calculated metrics. From 4ab2022d24b4979c14e8a647baa6a7dfcb3df091 Mon Sep 17 00:00:00 2001 From: ana-uvarova Date: Tue, 15 Dec 2020 20:57:26 +0300 Subject: [PATCH 025/294] deleted links --- .../operations/server-configuration-parameters/settings.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/docs/en/operations/server-configuration-parameters/settings.md b/docs/en/operations/server-configuration-parameters/settings.md index 318b80a23db..2586295ac0f 100644 --- a/docs/en/operations/server-configuration-parameters/settings.md +++ b/docs/en/operations/server-configuration-parameters/settings.md @@ -773,11 +773,6 @@ To turn on metrics history collection on `system.metric_log`, create `/etc/click ``` -**See also** - -- [system.asynchronous_metrics](../../operations/system-tables/asynchronous_metrics.md) — Contains periodically calculated metrics. -- [Monitoring](../../operations/monitoring.md) — Base concepts of ClickHouse monitoring. - ## text_log {#server_configuration_parameters-text_log} Settings for the [text_log](../../operations/system-tables/text_log.md#system_tables-text_log) system table for logging text messages. From e54c90c9e24c37f3f83e2ffd27cb5571fec35cd3 Mon Sep 17 00:00:00 2001 From: ana-uvarova Date: Tue, 15 Dec 2020 21:03:06 +0300 Subject: [PATCH 026/294] right place --- .../settings.md | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/docs/en/operations/server-configuration-parameters/settings.md b/docs/en/operations/server-configuration-parameters/settings.md index 2586295ac0f..1f9302f8be3 100644 --- a/docs/en/operations/server-configuration-parameters/settings.md +++ b/docs/en/operations/server-configuration-parameters/settings.md @@ -567,6 +567,21 @@ For more information, see the MergeTreeSettings.h header file. ``` +## metric_log {#metric_log} + +To turn on metrics history collection on `system.metric_log`, create `/etc/clickhouse-server/config.d/metric_log.xml` with following content: + +``` xml + + + system + metric_log
+ 7500 + 1000 +
+
+``` + ## replicated_merge_tree {#server_configuration_parameters-replicated_merge_tree} Fine tuning for tables in the [ReplicatedMergeTree](../../engines/table-engines/mergetree-family/mergetree.md). @@ -758,21 +773,6 @@ If the table doesn’t exist, ClickHouse will create it. If the structure of the ``` -## metric_log {#metric_log} - -To turn on metrics history collection on `system.metric_log`, create `/etc/clickhouse-server/config.d/metric_log.xml` with following content: - -``` xml - - - system - metric_log
- 7500 - 1000 -
-
-``` - ## text_log {#server_configuration_parameters-text_log} Settings for the [text_log](../../operations/system-tables/text_log.md#system_tables-text_log) system table for logging text messages. From bf096097cbdeb80555ad03d7592d24e690ea711b Mon Sep 17 00:00:00 2001 From: alexey-milovidov Date: Tue, 15 Dec 2020 21:56:06 +0300 Subject: [PATCH 027/294] Update InterpreterShowCreateQuery.cpp --- src/Interpreters/InterpreterShowCreateQuery.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Interpreters/InterpreterShowCreateQuery.cpp b/src/Interpreters/InterpreterShowCreateQuery.cpp index 869573f0d66..e4f30c30cfb 100644 --- a/src/Interpreters/InterpreterShowCreateQuery.cpp +++ b/src/Interpreters/InterpreterShowCreateQuery.cpp @@ -54,7 +54,7 @@ BlockInputStreamPtr InterpreterShowCreateQuery::executeImpl() { auto & ast_create_query = create_query->as(); if (!ast_create_query.isView()) - throw Exception("'" + ast_create_query.database + "." + ast_create_query.table +"' is not VIEW", ErrorCodes::BAD_ARGUMENTS); + throw Exception(backQuote(ast_create_query.database) + "." + backQuote(ast_create_query.table) + " is not a VIEW", ErrorCodes::BAD_ARGUMENTS); } } else if ((show_query = query_ptr->as())) From 81b0fa298992d42fe4df6a934ae6e90f21eacf51 Mon Sep 17 00:00:00 2001 From: spongedc Date: Wed, 16 Dec 2020 11:19:38 +0800 Subject: [PATCH 028/294] 1. rename is_view to is_ordinary_view 2. add more tests --- src/Interpreters/InterpreterCreateQuery.cpp | 6 +++--- src/Parsers/ASTCreateQuery.h | 2 +- src/Parsers/ParserCreateQuery.cpp | 6 +++--- src/Storages/StorageFactory.cpp | 2 +- tests/queries/0_stateless/01602_show_create_view.sql | 2 ++ 5 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/Interpreters/InterpreterCreateQuery.cpp b/src/Interpreters/InterpreterCreateQuery.cpp index d57a14a61dc..8f3d5086b6b 100644 --- a/src/Interpreters/InterpreterCreateQuery.cpp +++ b/src/Interpreters/InterpreterCreateQuery.cpp @@ -593,7 +593,7 @@ void InterpreterCreateQuery::setEngine(ASTCreateQuery & create) const if (create.as_table_function) return; - if (create.storage || create.is_view || create.is_materialized_view || create.is_live_view || create.is_dictionary) + if (create.storage || create.is_dictionary || create.isView()) { if (create.temporary && create.storage && create.storage->engine && create.storage->engine->name != "Memory") throw Exception( @@ -748,7 +748,7 @@ BlockIO InterpreterCreateQuery::createTable(ASTCreateQuery & create) if (create.to_table_id && create.to_table_id.database_name.empty()) create.to_table_id.database_name = current_database; - if (create.select && (create.is_view || create.is_materialized_view || create.is_live_view)) + if (create.select && create.isView()) { AddDefaultDatabaseVisitor visitor(current_database); visitor.visit(*create.select); @@ -884,7 +884,7 @@ BlockIO InterpreterCreateQuery::fillTableIfNeeded(const ASTCreateQuery & create) { /// If the query is a CREATE SELECT, insert the data into the table. if (create.select && !create.attach - && !create.is_view && !create.is_live_view && (!create.is_materialized_view || create.is_populate)) + && !create.is_ordinary_view && !create.is_live_view && (!create.is_materialized_view || create.is_populate)) { auto insert = std::make_shared(); insert->table_id = {create.database, create.table, create.uuid}; diff --git a/src/Parsers/ASTCreateQuery.h b/src/Parsers/ASTCreateQuery.h index 67ad9a89392..ee178b74460 100644 --- a/src/Parsers/ASTCreateQuery.h +++ b/src/Parsers/ASTCreateQuery.h @@ -57,7 +57,7 @@ class ASTCreateQuery : public ASTQueryWithTableAndOutput, public ASTQueryWithOnC public: bool attach{false}; /// Query ATTACH TABLE, not CREATE TABLE. bool if_not_exists{false}; - bool is_view{false}; + bool is_ordinary_view{false}; bool is_materialized_view{false}; bool is_live_view{false}; bool is_populate{false}; diff --git a/src/Parsers/ParserCreateQuery.cpp b/src/Parsers/ParserCreateQuery.cpp index b08646bcf9d..262ddb09398 100644 --- a/src/Parsers/ParserCreateQuery.cpp +++ b/src/Parsers/ParserCreateQuery.cpp @@ -735,7 +735,7 @@ bool ParserCreateViewQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expec String cluster_str; bool attach = false; bool if_not_exists = false; - bool is_view = false; + bool is_ordinary_view = false; bool is_materialized_view = false; bool is_populate = false; bool replace_view = false; @@ -759,7 +759,7 @@ bool ParserCreateViewQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expec is_materialized_view = true; } else - is_view = true; + is_ordinary_view = true; if (!s_view.ignore(pos, expected)) return false; @@ -816,7 +816,7 @@ bool ParserCreateViewQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expec query->attach = attach; query->if_not_exists = if_not_exists; - query->is_view = is_view; + query->is_ordinary_view = is_ordinary_view; query->is_materialized_view = is_materialized_view; query->is_populate = is_populate; query->replace_view = replace_view; diff --git a/src/Storages/StorageFactory.cpp b/src/Storages/StorageFactory.cpp index eda9f36010f..93982c2f3a3 100644 --- a/src/Storages/StorageFactory.cpp +++ b/src/Storages/StorageFactory.cpp @@ -53,7 +53,7 @@ StoragePtr StorageFactory::get( bool has_engine_args = false; - if (query.is_view) + if (query.is_ordinary_view) { if (query.storage) throw Exception("Specifying ENGINE is not allowed for a View", ErrorCodes::INCORRECT_QUERY); diff --git a/tests/queries/0_stateless/01602_show_create_view.sql b/tests/queries/0_stateless/01602_show_create_view.sql index 8e049d2d4cd..855ccf58562 100644 --- a/tests/queries/0_stateless/01602_show_create_view.sql +++ b/tests/queries/0_stateless/01602_show_create_view.sql @@ -22,4 +22,6 @@ SHOW CREATE VIEW test_1602.not_exist_view; -- { serverError 390 } SHOW CREATE VIEW test_1602.tbl; -- { serverError 36 } +SHOW CREATE TEMPORARY VIEW; -- { serverError 60 } + DROP DATABASE IF EXISTS test_1602; From a0083e23d68a2047323b469e7cc146c25d6fc389 Mon Sep 17 00:00:00 2001 From: spongedc Date: Wed, 16 Dec 2020 12:07:50 +0800 Subject: [PATCH 029/294] Fix compile error --- src/Databases/DatabaseOnDisk.cpp | 2 +- src/Interpreters/InterpreterCreateQuery.cpp | 4 ++-- src/Parsers/ASTCreateQuery.cpp | 2 +- src/Parsers/ASTCreateQuery.h | 2 +- src/Parsers/New/AST/CreateViewQuery.cpp | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Databases/DatabaseOnDisk.cpp b/src/Databases/DatabaseOnDisk.cpp index 1e6b4019c4b..7fb91e1d074 100644 --- a/src/Databases/DatabaseOnDisk.cpp +++ b/src/Databases/DatabaseOnDisk.cpp @@ -111,7 +111,7 @@ String getObjectDefinitionFromCreateQuery(const ASTPtr & query) create->replace_view = false; /// For views it is necessary to save the SELECT query itself, for the rest - on the contrary - if (!create->is_view && !create->is_materialized_view && !create->is_live_view) + if (!create->isView()) create->select = nullptr; create->format = nullptr; diff --git a/src/Interpreters/InterpreterCreateQuery.cpp b/src/Interpreters/InterpreterCreateQuery.cpp index 8f3d5086b6b..0f158eb757d 100644 --- a/src/Interpreters/InterpreterCreateQuery.cpp +++ b/src/Interpreters/InterpreterCreateQuery.cpp @@ -624,7 +624,7 @@ void InterpreterCreateQuery::setEngine(ASTCreateQuery & create) const const String qualified_name = backQuoteIfNeed(as_database_name) + "." + backQuoteIfNeed(as_table_name); - if (as_create.is_view) + if (as_create.is_ordinary_view) throw Exception( "Cannot CREATE a table AS " + qualified_name + ", it is a View", ErrorCodes::INCORRECT_QUERY); @@ -1030,7 +1030,7 @@ AccessRightsElements InterpreterCreateQuery::getRequiredAccess() const { required_access.emplace_back(AccessType::CREATE_DICTIONARY, create.database, create.table); } - else if (create.is_view || create.is_materialized_view || create.is_live_view) + else if (create.isView()) { if (create.temporary) required_access.emplace_back(AccessType::CREATE_TEMPORARY_TABLE); diff --git a/src/Parsers/ASTCreateQuery.cpp b/src/Parsers/ASTCreateQuery.cpp index 03db54c6957..4cb512fee20 100644 --- a/src/Parsers/ASTCreateQuery.cpp +++ b/src/Parsers/ASTCreateQuery.cpp @@ -231,7 +231,7 @@ void ASTCreateQuery::formatQueryImpl(const FormatSettings & settings, FormatStat if (!is_dictionary) { std::string what = "TABLE"; - if (is_view) + if (is_ordinary_view) what = "VIEW"; if (is_materialized_view) what = "MATERIALIZED VIEW"; diff --git a/src/Parsers/ASTCreateQuery.h b/src/Parsers/ASTCreateQuery.h index ee178b74460..4dc895a5774 100644 --- a/src/Parsers/ASTCreateQuery.h +++ b/src/Parsers/ASTCreateQuery.h @@ -91,7 +91,7 @@ public: return removeOnCluster(clone(), new_database); } - bool isView() { return is_view || is_materialized_view || is_live_view; } + bool isView() const { return is_ordinary_view || is_materialized_view || is_live_view; } protected: void formatQueryImpl(const FormatSettings & settings, FormatState & state, FormatStateStacked frame) const override; diff --git a/src/Parsers/New/AST/CreateViewQuery.cpp b/src/Parsers/New/AST/CreateViewQuery.cpp index df68687eb13..e558a141ca9 100644 --- a/src/Parsers/New/AST/CreateViewQuery.cpp +++ b/src/Parsers/New/AST/CreateViewQuery.cpp @@ -35,7 +35,7 @@ ASTPtr CreateViewQuery::convertToOld() const query->attach = attach; query->replace_view = replace; query->if_not_exists = if_not_exists; - query->is_view = true; + query->is_ordinary_view = true; query->cluster = cluster_name; if (has(SCHEMA)) query->set(query->columns_list, get(SCHEMA)->convertToOld()); From c906b1ca769679af8bf8f0ed1db8fd80877fa416 Mon Sep 17 00:00:00 2001 From: myrrc Date: Wed, 16 Dec 2020 15:21:23 +0300 Subject: [PATCH 030/294] added the initial solution --- src/Functions/FunctionBinaryArithmetic.h | 29 ++++++++++++++++++++---- src/Functions/multiply.cpp | 2 +- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/Functions/FunctionBinaryArithmetic.h b/src/Functions/FunctionBinaryArithmetic.h index 8c684d3578f..ea6169194e4 100644 --- a/src/Functions/FunctionBinaryArithmetic.h +++ b/src/Functions/FunctionBinaryArithmetic.h @@ -3,6 +3,8 @@ // Include this first, because `#define _asan_poison_address` from // llvm/Support/Compiler.h conflicts with its forward declaration in // sanitizer/asan_interface.h +#include +#include #include #include @@ -467,6 +469,11 @@ public: Case && IsDataTypeDecimal, RightDataType>, Case && IsIntegralOrExtended, LeftDataType>, Case && IsIntegralOrExtended, RightDataType>, + + /// e.g Decimal * Float64 = Float64 + Case && IsFloatingPoint, RightDataType>, + Case && IsFloatingPoint, LeftDataType>, + /// Decimal Real is not supported (traditional DBs convert Decimal Real to Real) Case && !IsIntegralOrExtendedOrDecimal, InvalidType>, Case && !IsIntegralOrExtendedOrDecimal, InvalidType>, @@ -790,10 +797,12 @@ public: } DataTypePtr type_res; - bool valid = castBothTypes(arguments[0].get(), arguments[1].get(), [&](const auto & left, const auto & right) + + const bool valid = castBothTypes(arguments[0].get(), arguments[1].get(), [&](const auto & left, const auto & right) { using LeftDataType = std::decay_t; using RightDataType = std::decay_t; + if constexpr (std::is_same_v || std::is_same_v) { if constexpr (!Op::allow_fixed_string) @@ -810,6 +819,7 @@ public: else { using ResultDataType = typename BinaryOperationTraits::ResultDataType; + if constexpr (!std::is_same_v) { if constexpr (IsDataTypeDecimal && IsDataTypeDecimal) @@ -817,6 +827,10 @@ public: ResultDataType result_type = decimalResultType(left, right); type_res = std::make_shared(result_type.getPrecision(), result_type.getScale()); } + else if constexpr ((IsDataTypeDecimal && IsFloatingPoint) || + (IsDataTypeDecimal && IsFloatingPoint)) + type_res = std::make_shared, + LeftDataType, RightDataType>>(); else if constexpr (IsDataTypeDecimal) type_res = std::make_shared(left.getPrecision(), left.getScale()); else if constexpr (IsDataTypeDecimal) @@ -841,10 +855,15 @@ public: } return false; }); - if (!valid) - throw Exception("Illegal types " + arguments[0]->getName() + " and " + arguments[1]->getName() + " of arguments of function " + String(name), - ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); - return type_res; + + if (valid) + return type_res; + + throw Exception( + "Illegal types " + arguments[0]->getName() + + " and " + arguments[1]->getName() + + " of arguments of function " + String(name), + ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT); } ColumnPtr executeFixedString(const ColumnsWithTypeAndName & arguments) const diff --git a/src/Functions/multiply.cpp b/src/Functions/multiply.cpp index 7552af7dbf1..18c1927b4a5 100644 --- a/src/Functions/multiply.cpp +++ b/src/Functions/multiply.cpp @@ -25,7 +25,7 @@ struct MultiplyImpl return static_cast(a) * b; } - /// Apply operation and check overflow. It's used for Deciamal operations. @returns true if overflowed, false otherwise. + /// Apply operation and check overflow. It's used for Decimal operations. @returns true if overflowed, false otherwise. template static inline bool apply(A a, B b, Result & c) { From d7145a5ee15fa81b64c3204a75e66d40ed70267b Mon Sep 17 00:00:00 2001 From: myrrc Date: Wed, 16 Dec 2020 18:01:18 +0300 Subject: [PATCH 031/294] working --- src/Functions/FunctionBinaryArithmetic.h | 26 +++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/src/Functions/FunctionBinaryArithmetic.h b/src/Functions/FunctionBinaryArithmetic.h index ea6169194e4..76ae6301843 100644 --- a/src/Functions/FunctionBinaryArithmetic.h +++ b/src/Functions/FunctionBinaryArithmetic.h @@ -974,7 +974,15 @@ public: using OpImpl = DecimalBinaryOperation; using OpImplCheck = DecimalBinaryOperation; - ResultDataType type = decimalResultType(left, right); + ResultDataType type; + + if constexpr (IsFloatingPoint) + { + if constexpr (IsFloatingPoint) + type = ... + + } else + type = decimalResultType(left, right); static constexpr const bool dec_a = IsDecimalNumber; static constexpr const bool dec_b = IsDecimalNumber; @@ -987,8 +995,20 @@ public: /// non-vector result if (col_left_const && col_right_const) { - NativeResultType const_a = col_left_const->template getValue(); - NativeResultType const_b = col_right_const->template getValue(); + NativeResultType const_a; + NativeResultType const_b; + + if constexpr (IsFloatingPoint && dec_a) + const_a = DecimalUtils::convertTo( + col_left_const->template getValue(), scale_a); + else + const_a = col_left_const->template getValue(); + + if constexpr (IsFloatingPoint && dec_b) + const_b = DecimalUtils::convertTo( + col_right_const->template getValue(), scale_b); + else + const_b = col_right_const->template getValue(); auto res = check_decimal_overflow ? OpImplCheck::template constantConstant(const_a, const_b, scale_a, scale_b) : From f143a37693027395c873eab0e53085569508c5e1 Mon Sep 17 00:00:00 2001 From: myrrc Date: Wed, 16 Dec 2020 18:11:26 +0300 Subject: [PATCH 032/294] simplified case to multiply only --- src/Functions/FunctionBinaryArithmetic.h | 25 +++++++++++------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/src/Functions/FunctionBinaryArithmetic.h b/src/Functions/FunctionBinaryArithmetic.h index 76ae6301843..73fd40db454 100644 --- a/src/Functions/FunctionBinaryArithmetic.h +++ b/src/Functions/FunctionBinaryArithmetic.h @@ -470,30 +470,36 @@ public: Case && IsIntegralOrExtended, LeftDataType>, Case && IsIntegralOrExtended, RightDataType>, - /// e.g Decimal * Float64 = Float64 - Case && IsFloatingPoint, RightDataType>, - Case && IsFloatingPoint, LeftDataType>, - /// Decimal Real is not supported (traditional DBs convert Decimal Real to Real) Case && !IsIntegralOrExtendedOrDecimal, InvalidType>, Case && !IsIntegralOrExtendedOrDecimal, InvalidType>, + /// number number -> see corresponding impl Case && !IsDateOrDateTime, DataTypeFromFieldType>, + + /// e.g Decimal * Float64 = Float64 + Case::multiply, Switch< + Case && IsFloatingPoint, RightDataType>, + Case && IsFloatingPoint, LeftDataType>>>, + /// Date + Integral -> Date /// Integral + Date -> Date Case::plus, Switch< Case, LeftDataType>, Case, RightDataType>>>, + /// Date - Date -> Int32 /// Date - Integral -> Date Case::minus, Switch< Case, DataTypeInt32>, Case && IsIntegral, LeftDataType>>>, + /// least(Date, Date) -> Date /// greatest(Date, Date) -> Date Case && (IsOperation::least || IsOperation::greatest), LeftDataType>, + /// Date % Int32 -> Int32 /// Date % Float -> Float64 Case::modulo, Switch< @@ -501,7 +507,6 @@ public: Case && IsFloatingPoint, DataTypeFloat64>>>>; }; - template