From 3fd10e0011b65d7594dd3d691211c5b4f03e16dd Mon Sep 17 00:00:00 2001 From: Nikolay Degterinsky Date: Tue, 22 Nov 2022 02:41:23 +0000 Subject: [PATCH] Better solution --- programs/client/Client.cpp | 70 +++++++++++++++ programs/client/Client.h | 1 + programs/server/config.xml | 14 +-- src/Access/AccessControl.cpp | 68 +++++++++++--- src/Access/AccessControl.h | 5 +- src/Client/ClientBase.cpp | 11 +++ .../Access/InterpreterCreateUserQuery.cpp | 7 +- src/Parsers/Access/ASTCreateUserQuery.h | 2 + src/Parsers/Access/ParserCreateUserQuery.cpp | 12 ++- .../System/StorageSystemPasswordRules.cpp | 26 ++++++ .../System/StorageSystemPasswordRules.h | 26 ++++++ src/Storages/System/attachSystemTables.cpp | 2 + ...02474_password_complexity_rules.config.xml | 45 ++++++---- .../02474_password_complexity_rules.reference | 1 - .../02474_password_complexity_rules.sh | 88 +++++++++++++++---- 15 files changed, 319 insertions(+), 59 deletions(-) create mode 100644 src/Storages/System/StorageSystemPasswordRules.cpp create mode 100644 src/Storages/System/StorageSystemPasswordRules.h diff --git a/programs/client/Client.cpp b/programs/client/Client.cpp index 6e289b57845..8a81a72be3c 100644 --- a/programs/client/Client.cpp +++ b/programs/client/Client.cpp @@ -16,6 +16,8 @@ #include +#include + #include "config_version.h" #include #include @@ -180,6 +182,64 @@ std::vector Client::loadWarningMessages() } +/// Make query to get all server password complexity rules +void Client::loadPasswordComplexityRules() +{ + /// Older server versions cannot execute the query loading password complexity rules. + constexpr UInt64 min_server_revision_to_load_warnings = DBMS_MIN_PROTOCOL_VERSION_WITH_VIEW_IF_PERMITTED; + + if (server_revision < min_server_revision_to_load_warnings) + return; + + connection->sendQuery(connection_parameters.timeouts, + "SELECT * FROM viewIfPermitted(SELECT pattern, message FROM system.password_rules ELSE null('pattern String, message String'))", + {} /* query_parameters */, + "" /* query_id */, + QueryProcessingStage::Complete, + &global_context->getSettingsRef(), + &global_context->getClientInfo(), false, {}); + while (true) + { + Packet packet = connection->receivePacket(); + switch (packet.type) + { + case Protocol::Server::Data: + if (packet.block) + { + const ColumnString & column_pattern = typeid_cast(*packet.block.getByPosition(0).column); + const ColumnString & column_message = typeid_cast(*packet.block.getByPosition(1).column); + + auto & access_control = global_context->getAccessControl(); + size_t rows = packet.block.rows(); + for (size_t i = 0; i < rows; ++i) + access_control.addPasswordComplexityRule({column_pattern[i].get(), column_message[i].get()}); + } + continue; + + case Protocol::Server::Progress: + case Protocol::Server::ProfileInfo: + case Protocol::Server::Totals: + case Protocol::Server::Extremes: + case Protocol::Server::Log: + continue; + + case Protocol::Server::Exception: + packet.exception->rethrow(); + return; + + case Protocol::Server::EndOfStream: + return; + + case Protocol::Server::ProfileEvents: + continue; + + default: + throw Exception(ErrorCodes::UNKNOWN_PACKET_FROM_SERVER, "Unknown packet {} from server {}", + packet.type, connection->getDescription()); + } + } +} + void Client::initialize(Poco::Util::Application & self) { Poco::Util::Application::initialize(self); @@ -258,6 +318,16 @@ try if (is_interactive && !config().has("no-warnings")) showWarnings(); + /// Load password complexity rules + try + { + loadPasswordComplexityRules(); + } + catch (...) + { + /// Ignore exception + } + if (is_interactive && !delayed_interactive) { runInteractive(); diff --git a/programs/client/Client.h b/programs/client/Client.h index 63f28ca96a2..0f4e0b2dd13 100644 --- a/programs/client/Client.h +++ b/programs/client/Client.h @@ -48,5 +48,6 @@ private: void printChangedSettings() const; void showWarnings(); std::vector loadWarningMessages(); + void loadPasswordComplexityRules(); }; } diff --git a/programs/server/config.xml b/programs/server/config.xml index 3d7904c8ed8..3e7e55d9071 100644 --- a/programs/server/config.xml +++ b/programs/server/config.xml @@ -467,28 +467,28 @@ 1 - + - - - - users.xml - - - - ./ - - + + + + + + + + diff --git a/tests/queries/0_stateless/02474_password_complexity_rules.reference b/tests/queries/0_stateless/02474_password_complexity_rules.reference index 21da4d2be9e..b462a5a7baa 100644 --- a/tests/queries/0_stateless/02474_password_complexity_rules.reference +++ b/tests/queries/0_stateless/02474_password_complexity_rules.reference @@ -2,4 +2,3 @@ OK OK OK OK -OK diff --git a/tests/queries/0_stateless/02474_password_complexity_rules.sh b/tests/queries/0_stateless/02474_password_complexity_rules.sh index ab33665bdba..6ef0c42d803 100755 --- a/tests/queries/0_stateless/02474_password_complexity_rules.sh +++ b/tests/queries/0_stateless/02474_password_complexity_rules.sh @@ -1,27 +1,83 @@ #!/usr/bin/env bash -# Tags: no-parallel +# Tags: long, no-parallel -CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) # shellcheck source=../shell_config.sh -. "$CURDIR"/../shell_config.sh +. "$CUR_DIR"/../shell_config.sh -$CLICKHOUSE_CLIENT -q "DROP USER IF EXISTS u_2474" +server_opts=( + "--config-file=$CUR_DIR/$(basename "${BASH_SOURCE[0]}" .sh).config.xml" + "--" + # to avoid multiple listen sockets (complexity for port discovering) + "--listen_host=127.1" + # we will discover the real port later. + "--tcp_port=0" + "--shutdown_wait_unfinished=0" +) +CLICKHOUSE_WATCHDOG_ENABLE=0 $CLICKHOUSE_SERVER_BINARY "${server_opts[@]}" >& clickhouse-server.log & +server_pid=$! + +trap cleanup EXIT +function cleanup() +{ + kill -9 $server_pid + kill -9 $client_pid + + echo "Test failed. Server log:" + cat clickhouse-server.log + rm -f clickhouse-server.log + + exit 1 +} + +server_port= +i=0 retries=300 +# wait until server will start to listen (max 30 seconds) +while [[ -z $server_port ]] && [[ $i -lt $retries ]]; do + server_port=$(lsof -n -a -P -i tcp -s tcp:LISTEN -p $server_pid 2>/dev/null | awk -F'[ :]' '/LISTEN/ { print $(NF-1) }') + ((++i)) + sleep 0.1 +done +if [[ -z $server_port ]]; then + echo "Cannot wait for LISTEN socket" >&2 + exit 1 +fi + +# wait for the server to start accepting tcp connections (max 30 seconds) +i=0 retries=300 +while ! $CLICKHOUSE_CLIENT_BINARY --host 127.1 --port "$server_port" --format Null -q 'select 1' 2>/dev/null && [[ $i -lt $retries ]]; do + sleep 0.1 +done +if ! $CLICKHOUSE_CLIENT_BINARY --host 127.1 --port "$server_port" --format Null -q 'select 1'; then + echo "Cannot wait until server will start accepting connections on " >&2 + exit 1 +fi -$CLICKHOUSE_CLIENT -q "CREATE USER u_2474 IDENTIFIED WITH plaintext_password BY ''" 2>&1 | grep -qF \ - "DB::Exception: Invalid password. The password should: be at least 12 characters long, contain at least 1 numeric character, contain at least 1 lowercase character, contain at least 1 uppercase character, contain at least 1 special character." && echo 'OK' || echo 'FAIL' ||: +$CLICKHOUSE_CLIENT_BINARY --host 127.1 --port "$server_port" -q "CREATE USER u_2474 IDENTIFIED WITH plaintext_password BY ''" 2>&1 | grep -qF \ + "DB::Exception: Invalid password. The password should: be at least 12 characters long, contain at least 1 numeric character, contain at least 1 lowercase character, contain at least 1 uppercase character, contain at least 1 special character" && echo 'OK' || echo 'FAIL' ||: -$CLICKHOUSE_CLIENT -q "CREATE USER u_2474 IDENTIFIED WITH plaintext_password BY '000000000000'" 2>&1 | grep -qF \ - "DB::Exception: Invalid password. The password should: contain at least 1 lowercase character, contain at least 1 uppercase character, contain at least 1 special character." && echo 'OK' || echo 'FAIL' ||: +$CLICKHOUSE_CLIENT_BINARY --host 127.1 --port "$server_port" -q "CREATE USER u_2474 IDENTIFIED WITH plaintext_password BY '000000000000'" 2>&1 | grep -qF \ + "DB::Exception: Invalid password. The password should: contain at least 1 lowercase character, contain at least 1 uppercase character, contain at least 1 special character" && echo 'OK' || echo 'FAIL' ||: -$CLICKHOUSE_CLIENT -q "CREATE USER u_2474 IDENTIFIED WITH plaintext_password BY 'a00000000000'" 2>&1 | grep -qF \ - "DB::Exception: Invalid password. The password should: contain at least 1 uppercase character, contain at least 1 special character." && echo 'OK' || echo 'FAIL' ||: +$CLICKHOUSE_CLIENT_BINARY --host 127.1 --port "$server_port" -q "CREATE USER u_2474 IDENTIFIED WITH plaintext_password BY 'a00000000000'" 2>&1 | grep -qF \ + "DB::Exception: Invalid password. The password should: contain at least 1 uppercase character, contain at least 1 special character" && echo 'OK' || echo 'FAIL' ||: -$CLICKHOUSE_CLIENT -q "CREATE USER u_2474 IDENTIFIED WITH plaintext_password BY 'aA0000000000'" 2>&1 | grep -qF \ - "DB::Exception: Invalid password. The password should: contain at least 1 special character." && echo 'OK' || echo 'FAIL' ||: - -$CLICKHOUSE_CLIENT -q "CREATE USER u_2474 IDENTIFIED WITH plaintext_password BY 'aA!000000000'" 2>&1 | grep -qF \ - "DB::Exception:" && echo 'FAIL' || echo 'OK' ||: +$CLICKHOUSE_CLIENT_BINARY --host 127.1 --port "$server_port" -q "CREATE USER u_2474 IDENTIFIED WITH plaintext_password BY 'aA0000000000'" 2>&1 | grep -qF \ + "DB::Exception: Invalid password. The password should: contain at least 1 special character" && echo 'OK' || echo 'FAIL' ||: -$CLICKHOUSE_CLIENT -q "DROP USER u_2474" +# send TERM and save the error code to ensure that it is 0 (EXIT_SUCCESS) +kill $server_pid +wait $server_pid +return_code=$? + +wait $client_pid + +trap '' EXIT +if [ $return_code != 0 ]; then + cat clickhouse-server.log +fi +rm -f clickhouse-server.log + +exit $return_code