mirror of
https://github.com/ClickHouse/ClickHouse.git
synced 2024-11-29 02:52:13 +00:00
Better solution
This commit is contained in:
parent
6825d85daa
commit
3fd10e0011
@ -16,6 +16,8 @@
|
|||||||
|
|
||||||
#include <base/find_symbols.h>
|
#include <base/find_symbols.h>
|
||||||
|
|
||||||
|
#include <Access/AccessControl.h>
|
||||||
|
|
||||||
#include "config_version.h"
|
#include "config_version.h"
|
||||||
#include <Common/Exception.h>
|
#include <Common/Exception.h>
|
||||||
#include <Common/formatReadable.h>
|
#include <Common/formatReadable.h>
|
||||||
@ -180,6 +182,64 @@ std::vector<String> 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<const ColumnString &>(*packet.block.getByPosition(0).column);
|
||||||
|
const ColumnString & column_message = typeid_cast<const ColumnString &>(*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<String>(), column_message[i].get<String>()});
|
||||||
|
}
|
||||||
|
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)
|
void Client::initialize(Poco::Util::Application & self)
|
||||||
{
|
{
|
||||||
Poco::Util::Application::initialize(self);
|
Poco::Util::Application::initialize(self);
|
||||||
@ -258,6 +318,16 @@ try
|
|||||||
if (is_interactive && !config().has("no-warnings"))
|
if (is_interactive && !config().has("no-warnings"))
|
||||||
showWarnings();
|
showWarnings();
|
||||||
|
|
||||||
|
/// Load password complexity rules
|
||||||
|
try
|
||||||
|
{
|
||||||
|
loadPasswordComplexityRules();
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
/// Ignore exception
|
||||||
|
}
|
||||||
|
|
||||||
if (is_interactive && !delayed_interactive)
|
if (is_interactive && !delayed_interactive)
|
||||||
{
|
{
|
||||||
runInteractive();
|
runInteractive();
|
||||||
|
@ -48,5 +48,6 @@ private:
|
|||||||
void printChangedSettings() const;
|
void printChangedSettings() const;
|
||||||
void showWarnings();
|
void showWarnings();
|
||||||
std::vector<String> loadWarningMessages();
|
std::vector<String> loadWarningMessages();
|
||||||
|
void loadPasswordComplexityRules();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -467,28 +467,28 @@
|
|||||||
<allow_implicit_no_password>1</allow_implicit_no_password>
|
<allow_implicit_no_password>1</allow_implicit_no_password>
|
||||||
|
|
||||||
<!-- Complexity requirements for plaintext passwords. -->
|
<!-- Complexity requirements for plaintext passwords. -->
|
||||||
<password_complexity>
|
<!-- <password_complexity>
|
||||||
<rule>
|
<rule>
|
||||||
<match>.{12}</match>
|
<pattern>.{12}</pattern>
|
||||||
<message>be at least 12 characters long</message>
|
<message>be at least 12 characters long</message>
|
||||||
</rule>
|
</rule>
|
||||||
<rule>
|
<rule>
|
||||||
<match>\p{N}</match>
|
<pattern>\p{N}</pattern>
|
||||||
<message>contain at least 1 numeric character</message>
|
<message>contain at least 1 numeric character</message>
|
||||||
</rule>
|
</rule>
|
||||||
<rule>
|
<rule>
|
||||||
<match>\p{Ll}</match>
|
<pattern>\p{Ll}</pattern>
|
||||||
<message>contain at least 1 lowercase character</message>
|
<message>contain at least 1 lowercase character</message>
|
||||||
</rule>
|
</rule>
|
||||||
<rule>
|
<rule>
|
||||||
<match>\p{Lu}</match>
|
<pattern>\p{Lu}</pattern>
|
||||||
<message>contain at least 1 uppercase character</message>
|
<message>contain at least 1 uppercase character</message>
|
||||||
</rule>
|
</rule>
|
||||||
<rule>
|
<rule>
|
||||||
<match>[^\p{L}\p{N}]</match>
|
<pattern>[^\p{L}\p{N}]</pattern>
|
||||||
<message>contain at least 1 special character</message>
|
<message>contain at least 1 special character</message>
|
||||||
</rule>
|
</rule>
|
||||||
</password_complexity>
|
</password_complexity> -->
|
||||||
|
|
||||||
<!-- Policy from the <storage_configuration> for the temporary files.
|
<!-- Policy from the <storage_configuration> for the temporary files.
|
||||||
If not set <tmp_path> is used, otherwise <tmp_path> is ignored.
|
If not set <tmp_path> is used, otherwise <tmp_path> is ignored.
|
||||||
|
@ -145,7 +145,7 @@ private:
|
|||||||
class AccessControl::PasswordComplexityRules
|
class AccessControl::PasswordComplexityRules
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
void setPasswordComplexityRules(const Poco::Util::AbstractConfiguration & config_)
|
void setPasswordComplexityRulesFromConfig(const Poco::Util::AbstractConfiguration & config_)
|
||||||
{
|
{
|
||||||
std::lock_guard lock{mutex};
|
std::lock_guard lock{mutex};
|
||||||
|
|
||||||
@ -160,37 +160,49 @@ public:
|
|||||||
{
|
{
|
||||||
if (key == "rule" || key.starts_with("rule["))
|
if (key == "rule" || key.starts_with("rule["))
|
||||||
{
|
{
|
||||||
String match(config_.getString("password_complexity." + key + ".match"));
|
String pattern(config_.getString("password_complexity." + key + ".pattern"));
|
||||||
String message(config_.getString("password_complexity." + key + ".message"));
|
String message(config_.getString("password_complexity." + key + ".message"));
|
||||||
|
|
||||||
auto matcher = std::make_unique<RE2>(match, RE2::Quiet);
|
auto matcher = std::make_unique<RE2>(pattern, RE2::Quiet);
|
||||||
if (!matcher->ok())
|
if (!matcher->ok())
|
||||||
throw Exception(ErrorCodes::CANNOT_COMPILE_REGEXP,
|
throw Exception(ErrorCodes::CANNOT_COMPILE_REGEXP,
|
||||||
"Password complexity pattern {} cannot be compiled: {}",
|
"Password complexity pattern {} cannot be compiled: {}",
|
||||||
match, matcher->error());
|
pattern, matcher->error());
|
||||||
|
|
||||||
rules.push_back({std::move(matcher), std::move(message)});
|
rules.push_back({std::move(matcher), std::move(pattern), std::move(message)});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void addPasswordComplexityRule(std::pair<String, String> rule_)
|
||||||
|
{
|
||||||
|
auto matcher = std::make_unique<RE2>(rule_.first, RE2::Quiet);
|
||||||
|
if (!matcher->ok())
|
||||||
|
throw Exception(ErrorCodes::CANNOT_COMPILE_REGEXP,
|
||||||
|
"Password complexity pattern {} cannot be compiled: {}",
|
||||||
|
rule_.first, matcher->error());
|
||||||
|
|
||||||
|
std::lock_guard lock{mutex};
|
||||||
|
rules.push_back({std::move(matcher), std::move(rule_.first), std::move(rule_.second)});
|
||||||
|
}
|
||||||
|
|
||||||
void checkPasswordComplexityRules(const String & password_) const
|
void checkPasswordComplexityRules(const String & password_) const
|
||||||
{
|
{
|
||||||
String exception_text;
|
String exception_text;
|
||||||
bool failed = false;
|
bool failed = false;
|
||||||
|
|
||||||
std::lock_guard lock{mutex};
|
std::lock_guard lock{mutex};
|
||||||
for (const auto & [matcher, message] : rules)
|
for (const auto & rule : rules)
|
||||||
{
|
{
|
||||||
if (!RE2::PartialMatch(password_, *matcher))
|
if (!RE2::PartialMatch(password_, *rule.matcher))
|
||||||
{
|
{
|
||||||
failed = true;
|
failed = true;
|
||||||
|
|
||||||
if (!exception_text.empty())
|
if (!exception_text.empty())
|
||||||
exception_text += ", ";
|
exception_text += ", ";
|
||||||
|
|
||||||
exception_text += message;
|
exception_text += rule.exception_message;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -198,8 +210,30 @@ public:
|
|||||||
throw Exception(ErrorCodes::BAD_ARGUMENTS, "Invalid password. The password should: {}", exception_text);
|
throw Exception(ErrorCodes::BAD_ARGUMENTS, "Invalid password. The password should: {}", exception_text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<std::pair<String, String>> getPasswordComplexityRules()
|
||||||
|
{
|
||||||
|
std::vector<std::pair<String, String>> result;
|
||||||
|
|
||||||
|
std::lock_guard lock{mutex};
|
||||||
|
result.reserve(rules.size());
|
||||||
|
|
||||||
|
for (const auto & rule : rules)
|
||||||
|
result.push_back({rule.original_pattern, rule.exception_message});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::vector<std::pair<std::unique_ptr<RE2>, String>> rules TSA_GUARDED_BY(mutex);
|
struct Rule
|
||||||
|
{
|
||||||
|
std::unique_ptr<RE2> matcher;
|
||||||
|
String original_pattern;
|
||||||
|
String exception_message;
|
||||||
|
};
|
||||||
|
|
||||||
|
using Rules = std::vector<Rule>;
|
||||||
|
|
||||||
|
Rules rules TSA_GUARDED_BY(mutex);
|
||||||
mutable std::mutex mutex;
|
mutable std::mutex mutex;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -231,7 +265,7 @@ void AccessControl::setUpFromMainConfig(const Poco::Util::AbstractConfiguration
|
|||||||
setImplicitNoPasswordAllowed(config_.getBool("allow_implicit_no_password", true));
|
setImplicitNoPasswordAllowed(config_.getBool("allow_implicit_no_password", true));
|
||||||
setNoPasswordAllowed(config_.getBool("allow_no_password", true));
|
setNoPasswordAllowed(config_.getBool("allow_no_password", true));
|
||||||
setPlaintextPasswordAllowed(config_.getBool("allow_plaintext_password", true));
|
setPlaintextPasswordAllowed(config_.getBool("allow_plaintext_password", true));
|
||||||
setPasswordComplexityRules(config_);
|
setPasswordComplexityRulesFromConfig(config_);
|
||||||
|
|
||||||
/// Optional improvements in access control system.
|
/// Optional improvements in access control system.
|
||||||
/// The default values are false because we need to be compatible with earlier access configurations
|
/// The default values are false because we need to be compatible with earlier access configurations
|
||||||
@ -597,9 +631,14 @@ bool AccessControl::isPlaintextPasswordAllowed() const
|
|||||||
return allow_plaintext_password;
|
return allow_plaintext_password;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AccessControl::setPasswordComplexityRules(const Poco::Util::AbstractConfiguration & config_)
|
void AccessControl::setPasswordComplexityRulesFromConfig(const Poco::Util::AbstractConfiguration & config_)
|
||||||
{
|
{
|
||||||
password_rules->setPasswordComplexityRules(config_);
|
password_rules->setPasswordComplexityRulesFromConfig(config_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AccessControl::addPasswordComplexityRule(std::pair<String, String> rule_)
|
||||||
|
{
|
||||||
|
password_rules->addPasswordComplexityRule(std::move(rule_));
|
||||||
}
|
}
|
||||||
|
|
||||||
void AccessControl::checkPasswordComplexityRules(const String & password_) const
|
void AccessControl::checkPasswordComplexityRules(const String & password_) const
|
||||||
@ -607,6 +646,11 @@ void AccessControl::checkPasswordComplexityRules(const String & password_) const
|
|||||||
password_rules->checkPasswordComplexityRules(password_);
|
password_rules->checkPasswordComplexityRules(password_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<std::pair<String, String>> AccessControl::getPasswordComplexityRules() const
|
||||||
|
{
|
||||||
|
return password_rules->getPasswordComplexityRules();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
std::shared_ptr<const ContextAccess> AccessControl::getContextAccess(
|
std::shared_ptr<const ContextAccess> AccessControl::getContextAccess(
|
||||||
const UUID & user_id,
|
const UUID & user_id,
|
||||||
|
@ -148,8 +148,11 @@ public:
|
|||||||
bool isPlaintextPasswordAllowed() const;
|
bool isPlaintextPasswordAllowed() const;
|
||||||
|
|
||||||
/// Check complexity requirements for plaintext passwords
|
/// Check complexity requirements for plaintext passwords
|
||||||
void setPasswordComplexityRules(const Poco::Util::AbstractConfiguration & config_);
|
|
||||||
|
void setPasswordComplexityRulesFromConfig(const Poco::Util::AbstractConfiguration & config_);
|
||||||
|
void addPasswordComplexityRule(std::pair<String, String> rule_);
|
||||||
void checkPasswordComplexityRules(const String & password_) const;
|
void checkPasswordComplexityRules(const String & password_) const;
|
||||||
|
std::vector<std::pair<String, String>> getPasswordComplexityRules() const;
|
||||||
|
|
||||||
/// Enables logic that users without permissive row policies can still read rows using a SELECT query.
|
/// Enables logic that users without permissive row policies can still read rows using a SELECT query.
|
||||||
/// For example, if there two users A, B and a row policy is defined only for A, then
|
/// For example, if there two users A, B and a row policy is defined only for A, then
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
#include <Core/Block.h>
|
#include <Core/Block.h>
|
||||||
#include <Core/Protocol.h>
|
#include <Core/Protocol.h>
|
||||||
#include <Formats/FormatFactory.h>
|
#include <Formats/FormatFactory.h>
|
||||||
|
#include <Access/AccessControl.h>
|
||||||
|
|
||||||
#include "config_version.h"
|
#include "config_version.h"
|
||||||
|
|
||||||
@ -43,6 +44,7 @@
|
|||||||
#include <Parsers/ASTInsertQuery.h>
|
#include <Parsers/ASTInsertQuery.h>
|
||||||
#include <Parsers/ASTCreateQuery.h>
|
#include <Parsers/ASTCreateQuery.h>
|
||||||
#include <Parsers/ASTCreateFunctionQuery.h>
|
#include <Parsers/ASTCreateFunctionQuery.h>
|
||||||
|
#include <Parsers/Access/ASTCreateUserQuery.h>
|
||||||
#include <Parsers/ASTDropQuery.h>
|
#include <Parsers/ASTDropQuery.h>
|
||||||
#include <Parsers/ASTSetQuery.h>
|
#include <Parsers/ASTSetQuery.h>
|
||||||
#include <Parsers/ASTUseQuery.h>
|
#include <Parsers/ASTUseQuery.h>
|
||||||
@ -1563,6 +1565,15 @@ void ClientBase::processParsedSingleQuery(const String & full_query, const Strin
|
|||||||
updateLoggerLevel(logs_level_field->safeGet<String>());
|
updateLoggerLevel(logs_level_field->safeGet<String>());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (const auto * create_user_query = parsed_query->as<ASTCreateUserQuery>())
|
||||||
|
{
|
||||||
|
if (!create_user_query->attach && create_user_query->temporary_password_for_checks)
|
||||||
|
{
|
||||||
|
global_context->getAccessControl().checkPasswordComplexityRules(create_user_query->temporary_password_for_checks.value());
|
||||||
|
create_user_query->temporary_password_for_checks.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
processed_rows = 0;
|
processed_rows = 0;
|
||||||
written_first_block = false;
|
written_first_block = false;
|
||||||
progress_indication.resetProgress();
|
progress_indication.resetProgress();
|
||||||
|
@ -108,8 +108,11 @@ BlockIO InterpreterCreateUserQuery::execute()
|
|||||||
throw Exception(ErrorCodes::BAD_ARGUMENTS,
|
throw Exception(ErrorCodes::BAD_ARGUMENTS,
|
||||||
"Authentication type NO_PASSWORD must be explicitly specified, check the setting allow_implicit_no_password in the server configuration");
|
"Authentication type NO_PASSWORD must be explicitly specified, check the setting allow_implicit_no_password in the server configuration");
|
||||||
|
|
||||||
if (query.auth_data && query.auth_data->getType() == AuthenticationType::PLAINTEXT_PASSWORD)
|
if (!query.attach && query.temporary_password_for_checks)
|
||||||
access_control.checkPasswordComplexityRules(query.auth_data->getPassword());
|
{
|
||||||
|
access_control.checkPasswordComplexityRules(query.temporary_password_for_checks.value());
|
||||||
|
query.temporary_password_for_checks.reset();
|
||||||
|
}
|
||||||
|
|
||||||
std::optional<RolesOrUsersSet> default_roles_from_query;
|
std::optional<RolesOrUsersSet> default_roles_from_query;
|
||||||
if (query.default_roles)
|
if (query.default_roles)
|
||||||
|
@ -47,6 +47,8 @@ public:
|
|||||||
std::optional<AuthenticationData> auth_data;
|
std::optional<AuthenticationData> auth_data;
|
||||||
bool show_password = true; /// formatImpl() shows a password or hash by default
|
bool show_password = true; /// formatImpl() shows a password or hash by default
|
||||||
|
|
||||||
|
mutable std::optional<String> temporary_password_for_checks;
|
||||||
|
|
||||||
std::optional<AllowedClientHosts> hosts;
|
std::optional<AllowedClientHosts> hosts;
|
||||||
std::optional<AllowedClientHosts> add_hosts;
|
std::optional<AllowedClientHosts> add_hosts;
|
||||||
std::optional<AllowedClientHosts> remove_hosts;
|
std::optional<AllowedClientHosts> remove_hosts;
|
||||||
|
@ -51,7 +51,7 @@ namespace
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool parseAuthenticationData(IParserBase::Pos & pos, Expected & expected, AuthenticationData & auth_data)
|
bool parseAuthenticationData(IParserBase::Pos & pos, Expected & expected, AuthenticationData & auth_data, std::optional<String> & temporary_password_for_checks)
|
||||||
{
|
{
|
||||||
return IParserBase::wrapParseImpl(pos, [&]
|
return IParserBase::wrapParseImpl(pos, [&]
|
||||||
{
|
{
|
||||||
@ -165,6 +165,10 @@ namespace
|
|||||||
common_names.insert(ast_child->as<const ASTLiteral &>().value.safeGet<String>());
|
common_names.insert(ast_child->as<const ASTLiteral &>().value.safeGet<String>());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Save password separately for future complexity rules check
|
||||||
|
if (expect_password)
|
||||||
|
temporary_password_for_checks = value;
|
||||||
|
|
||||||
auth_data = AuthenticationData{*type};
|
auth_data = AuthenticationData{*type};
|
||||||
if (auth_data.getType() == AuthenticationType::SHA256_PASSWORD)
|
if (auth_data.getType() == AuthenticationType::SHA256_PASSWORD)
|
||||||
{
|
{
|
||||||
@ -438,6 +442,7 @@ bool ParserCreateUserQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expec
|
|||||||
|
|
||||||
std::optional<String> new_name;
|
std::optional<String> new_name;
|
||||||
std::optional<AuthenticationData> auth_data;
|
std::optional<AuthenticationData> auth_data;
|
||||||
|
std::optional<String> temporary_password_for_checks;
|
||||||
std::optional<AllowedClientHosts> hosts;
|
std::optional<AllowedClientHosts> hosts;
|
||||||
std::optional<AllowedClientHosts> add_hosts;
|
std::optional<AllowedClientHosts> add_hosts;
|
||||||
std::optional<AllowedClientHosts> remove_hosts;
|
std::optional<AllowedClientHosts> remove_hosts;
|
||||||
@ -452,9 +457,11 @@ bool ParserCreateUserQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expec
|
|||||||
if (!auth_data)
|
if (!auth_data)
|
||||||
{
|
{
|
||||||
AuthenticationData new_auth_data;
|
AuthenticationData new_auth_data;
|
||||||
if (parseAuthenticationData(pos, expected, new_auth_data))
|
std::optional<String> new_temporary_password_for_checks;
|
||||||
|
if (parseAuthenticationData(pos, expected, new_auth_data, new_temporary_password_for_checks))
|
||||||
{
|
{
|
||||||
auth_data = std::move(new_auth_data);
|
auth_data = std::move(new_auth_data);
|
||||||
|
temporary_password_for_checks = std::move(new_temporary_password_for_checks);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -539,6 +546,7 @@ bool ParserCreateUserQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expec
|
|||||||
query->names = std::move(names);
|
query->names = std::move(names);
|
||||||
query->new_name = std::move(new_name);
|
query->new_name = std::move(new_name);
|
||||||
query->auth_data = std::move(auth_data);
|
query->auth_data = std::move(auth_data);
|
||||||
|
query->temporary_password_for_checks = std::move(temporary_password_for_checks);
|
||||||
query->hosts = std::move(hosts);
|
query->hosts = std::move(hosts);
|
||||||
query->add_hosts = std::move(add_hosts);
|
query->add_hosts = std::move(add_hosts);
|
||||||
query->remove_hosts = std::move(remove_hosts);
|
query->remove_hosts = std::move(remove_hosts);
|
||||||
|
26
src/Storages/System/StorageSystemPasswordRules.cpp
Normal file
26
src/Storages/System/StorageSystemPasswordRules.cpp
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
#include <Interpreters/Context.h>
|
||||||
|
#include <Access/AccessControl.h>
|
||||||
|
#include <Storages/System/StorageSystemPasswordRules.h>
|
||||||
|
|
||||||
|
|
||||||
|
namespace DB
|
||||||
|
{
|
||||||
|
|
||||||
|
NamesAndTypesList StorageSystemPasswordRules::getNamesAndTypes()
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
{"pattern", std::make_shared<DataTypeString>()},
|
||||||
|
{"message", std::make_shared<DataTypeString>()},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void StorageSystemPasswordRules::fillData(MutableColumns & res_columns, ContextPtr context, const SelectQueryInfo &) const
|
||||||
|
{
|
||||||
|
for (const auto & [original_pattern, exception_message] : context->getAccessControl().getPasswordComplexityRules())
|
||||||
|
{
|
||||||
|
res_columns[0]->insert(original_pattern);
|
||||||
|
res_columns[1]->insert(exception_message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
26
src/Storages/System/StorageSystemPasswordRules.h
Normal file
26
src/Storages/System/StorageSystemPasswordRules.h
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Storages/System/IStorageSystemOneBlock.h>
|
||||||
|
|
||||||
|
|
||||||
|
namespace DB
|
||||||
|
{
|
||||||
|
|
||||||
|
class Context;
|
||||||
|
|
||||||
|
/** Implements system.password_rules table that contains complexity rules for passwords for users
|
||||||
|
* to be applied in clickhouse-client.
|
||||||
|
*/
|
||||||
|
class StorageSystemPasswordRules final : public IStorageSystemOneBlock<StorageSystemPasswordRules>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
std::string getName() const override { return "SystemPasswordRules"; }
|
||||||
|
|
||||||
|
static NamesAndTypesList getNamesAndTypes();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
using IStorageSystemOneBlock::IStorageSystemOneBlock;
|
||||||
|
|
||||||
|
void fillData(MutableColumns & res_columns, ContextPtr, const SelectQueryInfo &) const override;
|
||||||
|
};
|
||||||
|
}
|
@ -31,6 +31,7 @@
|
|||||||
#include <Storages/System/StorageSystemOne.h>
|
#include <Storages/System/StorageSystemOne.h>
|
||||||
#include <Storages/System/StorageSystemPartMovesBetweenShards.h>
|
#include <Storages/System/StorageSystemPartMovesBetweenShards.h>
|
||||||
#include <Storages/System/StorageSystemParts.h>
|
#include <Storages/System/StorageSystemParts.h>
|
||||||
|
#include <Storages/System/StorageSystemPasswordRules.h>
|
||||||
#include <Storages/System/StorageSystemProjectionParts.h>
|
#include <Storages/System/StorageSystemProjectionParts.h>
|
||||||
#include <Storages/System/StorageSystemPartsColumns.h>
|
#include <Storages/System/StorageSystemPartsColumns.h>
|
||||||
#include <Storages/System/StorageSystemProjectionPartsColumns.h>
|
#include <Storages/System/StorageSystemProjectionPartsColumns.h>
|
||||||
@ -131,6 +132,7 @@ void attachSystemTablesLocal(ContextPtr context, IDatabase & system_database)
|
|||||||
attach<StorageSystemPrivileges>(context, system_database, "privileges");
|
attach<StorageSystemPrivileges>(context, system_database, "privileges");
|
||||||
attach<StorageSystemErrors>(context, system_database, "errors");
|
attach<StorageSystemErrors>(context, system_database, "errors");
|
||||||
attach<StorageSystemWarnings>(context, system_database, "warnings");
|
attach<StorageSystemWarnings>(context, system_database, "warnings");
|
||||||
|
attach<StorageSystemPasswordRules>(context, system_database, "password_rules");
|
||||||
attach<StorageSystemDataSkippingIndices>(context, system_database, "data_skipping_indices");
|
attach<StorageSystemDataSkippingIndices>(context, system_database, "data_skipping_indices");
|
||||||
attach<StorageSystemLicenses>(context, system_database, "licenses");
|
attach<StorageSystemLicenses>(context, system_database, "licenses");
|
||||||
attach<StorageSystemTimeZones>(context, system_database, "time_zones");
|
attach<StorageSystemTimeZones>(context, system_database, "time_zones");
|
||||||
|
@ -5,41 +5,50 @@
|
|||||||
</logger>
|
</logger>
|
||||||
|
|
||||||
<tcp_port>9000</tcp_port>
|
<tcp_port>9000</tcp_port>
|
||||||
|
<path>./</path>
|
||||||
|
<mark_cache_size>0</mark_cache_size>
|
||||||
|
|
||||||
|
<users>
|
||||||
|
<default>
|
||||||
|
<password></password>
|
||||||
|
|
||||||
|
<networks>
|
||||||
|
<ip>::/0</ip>
|
||||||
|
</networks>
|
||||||
|
|
||||||
|
<access_management>1</access_management>
|
||||||
|
</default>
|
||||||
|
</users>
|
||||||
|
|
||||||
<password_complexity>
|
<password_complexity>
|
||||||
<rule>
|
<rule>
|
||||||
<match>.{12}</match>
|
<pattern>.{12}</pattern>
|
||||||
<message>be at least 12 characters long</message>
|
<message>be at least 12 characters long</message>
|
||||||
</rule>
|
</rule>
|
||||||
<rule>
|
<rule>
|
||||||
<match>\p{N}</match>
|
<pattern>\p{N}</pattern>
|
||||||
<message>contain at least 1 numeric character</message>
|
<message>contain at least 1 numeric character</message>
|
||||||
</rule>
|
</rule>
|
||||||
<rule>
|
<rule>
|
||||||
<match>\p{Ll}</match>
|
<pattern>\p{Ll}</pattern>
|
||||||
<message>contain at least 1 lowercase character</message>
|
<message>contain at least 1 lowercase character</message>
|
||||||
</rule>
|
</rule>
|
||||||
<rule>
|
<rule>
|
||||||
<match>\p{Lu}</match>
|
<pattern>\p{Lu}</pattern>
|
||||||
<message>contain at least 1 uppercase character</message>
|
<message>contain at least 1 uppercase character</message>
|
||||||
</rule>
|
</rule>
|
||||||
<rule>
|
<rule>
|
||||||
<match>[^\p{L}\p{N}]</match>
|
<pattern>[^\p{L}\p{N}]</pattern>
|
||||||
<message>contain at least 1 special character</message>
|
<message>contain at least 1 special character</message>
|
||||||
</rule>
|
</rule>
|
||||||
</password_complexity>
|
</password_complexity>
|
||||||
|
|
||||||
<path>.</path>
|
<profiles>
|
||||||
<mark_cache_size>0</mark_cache_size>
|
<default/>
|
||||||
<!-- Sources to read users, roles, access rights, profiles of settings, quotas. -->
|
</profiles>
|
||||||
<user_directories>
|
|
||||||
<users_xml>
|
<quotas>
|
||||||
<!-- Path to configuration file with predefined users. -->
|
<default />
|
||||||
<path>users.xml</path>
|
</quotas>
|
||||||
</users_xml>
|
|
||||||
<local_directory>
|
|
||||||
<!-- Path to folder where users created by SQL commands are stored. -->
|
|
||||||
<path>./</path>
|
|
||||||
</local_directory>
|
|
||||||
</user_directories>
|
|
||||||
</clickhouse>
|
</clickhouse>
|
||||||
|
|
||||||
|
@ -2,4 +2,3 @@ OK
|
|||||||
OK
|
OK
|
||||||
OK
|
OK
|
||||||
OK
|
OK
|
||||||
OK
|
|
||||||
|
@ -1,27 +1,83 @@
|
|||||||
#!/usr/bin/env bash
|
#!/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
|
# 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 <tcp_port>" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
$CLICKHOUSE_CLIENT -q "CREATE USER u_2474 IDENTIFIED WITH plaintext_password BY ''" 2>&1 | grep -qF \
|
$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' ||:
|
"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 \
|
$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' ||:
|
"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 \
|
$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' ||:
|
"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 \
|
$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' ||:
|
"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 -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
|
||||||
|
Loading…
Reference in New Issue
Block a user