Merge pull request #37303 from ClickHouse/fix_trash

Try to fix some trash
This commit is contained in:
Vitaly Baranov 2022-06-07 10:17:39 +02:00 committed by GitHub
commit d199478169
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 456 additions and 168 deletions

View File

@ -70,7 +70,7 @@ public:
auto x = cache.get(params);
if (x)
{
if ((*x)->getUser())
if ((*x)->tryGetUser())
return *x;
/// No user, probably the user has been dropped while it was in the cache.
cache.remove(params);

View File

@ -5,6 +5,7 @@
#include <Access/EnabledQuota.h>
#include <Access/QuotaUsage.h>
#include <Access/User.h>
#include <Access/Role.h>
#include <Access/EnabledRolesInfo.h>
#include <Access/EnabledSettings.h>
#include <Access/SettingsProfilesInfo.h>
@ -29,6 +30,7 @@ namespace ErrorCodes
extern const int QUERY_IS_PROHIBITED;
extern const int FUNCTION_NOT_ALLOWED;
extern const int UNKNOWN_USER;
extern const int LOGICAL_ERROR;
}
@ -187,6 +189,7 @@ void ContextAccess::setUser(const UserPtr & user_) const
if (!user)
{
/// User has been dropped.
user_was_dropped = true;
subscription_for_user_change = {};
subscription_for_roles_changes = {};
access = nullptr;
@ -261,6 +264,20 @@ void ContextAccess::calculateAccessRights() const
UserPtr ContextAccess::getUser() const
{
auto res = tryGetUser();
if (likely(res))
return res;
if (user_was_dropped)
throw Exception(ErrorCodes::UNKNOWN_USER, "User has been dropped");
throw Exception(ErrorCodes::LOGICAL_ERROR, "No user in current context, it's a bug");
}
UserPtr ContextAccess::tryGetUser() const
{
std::lock_guard lock{mutex};
return user;
@ -400,7 +417,7 @@ bool ContextAccess::checkAccessImplHelper(AccessFlags flags, const Args &... arg
if (!flags || is_full_access)
return access_granted();
if (!getUser())
if (!tryGetUser())
return access_denied("User has been dropped", ErrorCodes::UNKNOWN_USER);
/// Access to temporary tables is controlled in an unusual way, not like normal tables.
@ -592,7 +609,7 @@ bool ContextAccess::checkAdminOptionImplHelper(const Container & role_ids, const
throw Exception(getUserName() + ": " + msg, error_code);
};
if (!getUser())
if (!tryGetUser())
{
show_error("User has been dropped", ErrorCodes::UNKNOWN_USER);
return false;
@ -682,4 +699,34 @@ void ContextAccess::checkAdminOption(const std::vector<UUID> & role_ids) const {
void ContextAccess::checkAdminOption(const std::vector<UUID> & role_ids, const Strings & names_of_roles) const { checkAdminOptionImpl<true>(role_ids, names_of_roles); }
void ContextAccess::checkAdminOption(const std::vector<UUID> & role_ids, const std::unordered_map<UUID, String> & names_of_roles) const { checkAdminOptionImpl<true>(role_ids, names_of_roles); }
void ContextAccess::checkGranteeIsAllowed(const UUID & grantee_id, const IAccessEntity & grantee) const
{
if (is_full_access)
return;
auto current_user = getUser();
if (!current_user->grantees.match(grantee_id))
throw Exception(grantee.formatTypeWithName() + " is not allowed as grantee", ErrorCodes::ACCESS_DENIED);
}
void ContextAccess::checkGranteesAreAllowed(const std::vector<UUID> & grantee_ids) const
{
if (is_full_access)
return;
auto current_user = getUser();
if (current_user->grantees == RolesOrUsersSet::AllTag{})
return;
for (const auto & id : grantee_ids)
{
auto entity = access_control->tryRead(id);
if (auto role_entity = typeid_cast<RolePtr>(entity))
checkGranteeIsAllowed(id, *role_entity);
else if (auto user_entity = typeid_cast<UserPtr>(entity))
checkGranteeIsAllowed(id, *user_entity);
}
}
}

View File

@ -28,6 +28,7 @@ struct SettingsProfilesInfo;
class SettingsChanges;
class AccessControl;
class IAST;
struct IAccessEntity;
using ASTPtr = std::shared_ptr<IAST>;
@ -68,8 +69,10 @@ public:
using Params = ContextAccessParams;
const Params & getParams() const { return params; }
/// Returns the current user. The function can return nullptr.
/// Returns the current user. Throws if user is nullptr.
UserPtr getUser() const;
/// Same as above, but can return nullptr.
UserPtr tryGetUser() const;
String getUserName() const;
std::optional<UUID> getUserID() const { return getParams().user_id; }
@ -151,6 +154,11 @@ public:
bool hasAdminOption(const std::vector<UUID> & role_ids, const Strings & names_of_roles) const;
bool hasAdminOption(const std::vector<UUID> & role_ids, const std::unordered_map<UUID, String> & names_of_roles) const;
/// Checks if a grantee is allowed for the current user, throws an exception if not.
void checkGranteeIsAllowed(const UUID & grantee_id, const IAccessEntity & grantee) const;
/// Checks if grantees are allowed for the current user, throws an exception if not.
void checkGranteesAreAllowed(const std::vector<UUID> & grantee_ids) const;
/// Makes an instance of ContextAccess which provides full access to everything
/// without any limitations. This is used for the global context.
static std::shared_ptr<const ContextAccess> getFullAccess();
@ -213,6 +221,7 @@ private:
mutable Poco::Logger * trace_log = nullptr;
mutable UserPtr user;
mutable String user_name;
mutable bool user_was_dropped = false;
mutable scope_guard subscription_for_user_change;
mutable std::shared_ptr<const EnabledRoles> enabled_roles;
mutable scope_guard subscription_for_roles_changes;

View File

@ -1,8 +1,13 @@
#include <Access/User.h>
#include <Core/Protocol.h>
namespace DB
{
namespace ErrorCodes
{
extern const int BAD_ARGUMENTS;
}
bool User::equal(const IAccessEntity & other) const
{
@ -14,4 +19,16 @@ bool User::equal(const IAccessEntity & other) const
&& (settings == other_user.settings) && (grantees == other_user.grantees) && (default_database == other_user.default_database);
}
void User::setName(const String & name_)
{
/// Unfortunately, there is not way to distinguish USER_INTERSERVER_MARKER from actual username in native protocol,
/// so we have to ensure that no such user will appear.
/// Also it was possible to create a user with empty name for some reason.
if (name_.empty())
throw Exception(ErrorCodes::BAD_ARGUMENTS, "User name is empty");
if (name_ == USER_INTERSERVER_MARKER)
throw Exception(ErrorCodes::BAD_ARGUMENTS, "User name '{}' is reserved", USER_INTERSERVER_MARKER);
name = name_;
}
}

View File

@ -28,6 +28,7 @@ struct User : public IAccessEntity
std::shared_ptr<IAccessEntity> clone() const override { return cloneImpl<User>(); }
static constexpr const auto TYPE = AccessEntityType::USER;
AccessEntityType getType() const override { return TYPE; }
void setName(const String & name_) override;
};
using UserPtr = std::shared_ptr<const User>;

View File

@ -397,8 +397,6 @@ void Connection::sendClusterNameAndSalt()
bool Connection::ping()
{
// LOG_TRACE(log_wrapper.get(), "Ping");
try
{
TimeoutSetter timeout_setter(*socket, sync_request_timeout, true);
@ -840,7 +838,6 @@ std::optional<UInt64> Connection::checkPacket(size_t timeout_microseconds)
if (hasReadPendingData() || poll(timeout_microseconds))
{
// LOG_TRACE(log_wrapper.get(), "Receiving packet type");
UInt64 packet_type;
readVarUInt(packet_type, *in);

View File

@ -49,8 +49,7 @@ namespace
else
{
static_assert(kind == Kind::DEFAULT_PROFILES);
if (auto user = context->getUser())
profile_ids = user->settings.toProfileIDs();
profile_ids = context->getUser()->settings.toProfileIDs();
}
profile_names = manager.tryReadNames(profile_ids);

View File

@ -49,8 +49,8 @@ namespace
{
static_assert(kind == Kind::DEFAULT_ROLES);
const auto & manager = context->getAccessControl();
if (auto user = context->getUser())
role_names = manager.tryReadNames(user->granted_roles.findGranted(user->default_roles));
auto user = context->getUser();
role_names = manager.tryReadNames(user->granted_roles.findGranted(user->default_roles));
}
/// We sort the names because the result of the function should not depend on the order of UUIDs.

View File

@ -17,7 +17,6 @@ namespace DB
namespace ErrorCodes
{
extern const int BAD_ARGUMENTS;
}
namespace
{
@ -33,8 +32,8 @@ namespace
{
if (override_name)
user.setName(override_name->toString());
else if (!query.new_name.empty())
user.setName(query.new_name);
else if (query.new_name)
user.setName(*query.new_name);
else if (query.names->size() == 1)
user.setName(query.names->front()->toString());

View File

@ -17,7 +17,6 @@ namespace DB
{
namespace ErrorCodes
{
extern const int ACCESS_DENIED;
extern const int LOGICAL_ERROR;
}
@ -113,31 +112,6 @@ namespace
}
}
/// Checks if a grantee is allowed for the current user, throws an exception if not.
void checkGranteeIsAllowed(const ContextAccess & current_user_access, const UUID & grantee_id, const IAccessEntity & grantee)
{
auto current_user = current_user_access.getUser();
if (current_user && !current_user->grantees.match(grantee_id))
throw Exception(grantee.formatTypeWithName() + " is not allowed as grantee", ErrorCodes::ACCESS_DENIED);
}
/// Checks if grantees are allowed for the current user, throws an exception if not.
void checkGranteesAreAllowed(const AccessControl & access_control, const ContextAccess & current_user_access, const std::vector<UUID> & grantee_ids)
{
auto current_user = current_user_access.getUser();
if (!current_user || (current_user->grantees == RolesOrUsersSet::AllTag{}))
return;
for (const auto & id : grantee_ids)
{
auto entity = access_control.tryRead(id);
if (auto role = typeid_cast<RolePtr>(entity))
checkGranteeIsAllowed(current_user_access, id, *role);
else if (auto user = typeid_cast<UserPtr>(entity))
checkGranteeIsAllowed(current_user_access, id, *user);
}
}
/// Checks if the current user has enough access rights granted with grant option to grant or revoke specified access rights.
void checkGrantOption(
const AccessControl & access_control,
@ -172,13 +146,13 @@ namespace
if (auto role = typeid_cast<RolePtr>(entity))
{
if (need_check_grantees_are_allowed)
checkGranteeIsAllowed(current_user_access, id, *role);
current_user_access.checkGranteeIsAllowed(id, *role);
all_granted_access.makeUnion(role->access);
}
else if (auto user = typeid_cast<UserPtr>(entity))
{
if (need_check_grantees_are_allowed)
checkGranteeIsAllowed(current_user_access, id, *user);
current_user_access.checkGranteeIsAllowed(id, *user);
all_granted_access.makeUnion(user->access);
}
}
@ -245,13 +219,13 @@ namespace
if (auto role = typeid_cast<RolePtr>(entity))
{
if (need_check_grantees_are_allowed)
checkGranteeIsAllowed(current_user_access, id, *role);
current_user_access.checkGranteeIsAllowed(id, *role);
all_granted_roles.makeUnion(role->granted_roles);
}
else if (auto user = typeid_cast<UserPtr>(entity))
{
if (need_check_grantees_are_allowed)
checkGranteeIsAllowed(current_user_access, id, *user);
current_user_access.checkGranteeIsAllowed(id, *user);
all_granted_roles.makeUnion(user->granted_roles);
}
}
@ -401,7 +375,7 @@ BlockIO InterpreterGrantQuery::execute()
{
auto required_access = getRequiredAccessForExecutingOnCluster(elements_to_grant, elements_to_revoke);
checkAdminOptionForExecutingOnCluster(*current_user_access, roles_to_grant, roles_to_revoke);
checkGranteesAreAllowed(access_control, *current_user_access, grantees);
current_user_access->checkGranteesAreAllowed(grantees);
DDLQueryOnClusterParams params;
params.access_to_check = std::move(required_access);
return executeDDLQueryOnCluster(query_ptr, getContext(), params);
@ -418,7 +392,7 @@ BlockIO InterpreterGrantQuery::execute()
checkAdminOption(access_control, *current_user_access, grantees, need_check_grantees_are_allowed, roles_to_grant, roles_to_revoke, query.admin_option);
if (need_check_grantees_are_allowed)
checkGranteesAreAllowed(access_control, *current_user_access, grantees);
current_user_access->checkGranteesAreAllowed(grantees);
/// Update roles and users listed in `grantees`.
auto update_func = [&](const AccessEntityPtr & entity) -> AccessEntityPtr

View File

@ -296,8 +296,7 @@ std::vector<AccessEntityPtr> InterpreterShowCreateAccessEntityQuery::getEntities
}
else if (show_query.current_user)
{
if (auto user = getContext()->getUser())
entities.push_back(user);
entities.push_back(getContext()->getUser());
}
else if (show_query.current_quota)
{

View File

@ -160,7 +160,7 @@ std::vector<AccessEntityPtr> InterpreterShowGrantsQuery::getEntities() const
bool is_current_user = (id == access->getUserID());
bool is_enabled_or_granted_role = entity->isTypeOf<Role>()
&& ((current_user && current_user->granted_roles.isGranted(id)) || roles_info->enabled_roles.contains(id));
&& (current_user->granted_roles.isGranted(id) || roles_info->enabled_roles.contains(id));
if ((is_current_user /* Any user can see his own grants */)
|| (is_enabled_or_granted_role /* and grants from the granted roles */)

View File

@ -702,7 +702,7 @@ void Context::setUserDefinedPath(const String & path)
shared->user_defined_path = path;
}
void Context::addWarningMessage(const String & msg)
void Context::addWarningMessage(const String & msg) const
{
auto lock = getLock();
shared->addWarningMessage(msg);
@ -767,6 +767,7 @@ void Context::setUser(const UUID & user_id_)
user_id_, /* current_roles = */ {}, /* use_default_roles = */ true, settings, current_database, client_info);
auto user = access->getUser();
current_roles = std::make_shared<std::vector<UUID>>(user->granted_roles.findGranted(user->default_roles));
auto default_profile_info = access->getDefaultProfileInfo();

View File

@ -424,7 +424,7 @@ public:
void setUserScriptsPath(const String & path);
void setUserDefinedPath(const String & path);
void addWarningMessage(const String & msg);
void addWarningMessage(const String & msg) const;
VolumePtr setTemporaryStorage(const String & path, const String & policy_name = "");

View File

@ -10,6 +10,7 @@
#include <Common/setThreadName.h>
#include <Interpreters/Context.h>
#include <Interpreters/SessionLog.h>
#include <Interpreters/Cluster.h>
#include <magic_enum.hpp>
@ -246,7 +247,6 @@ void Session::shutdownNamedSessions()
Session::Session(const ContextPtr & global_context_, ClientInfo::Interface interface_, bool is_secure)
: auth_id(UUIDHelpers::generateV4()),
global_context(global_context_),
interface(interface_),
log(&Poco::Logger::get(String{magic_enum::enum_name(interface_)} + "-Session"))
{
prepared_client_info.emplace();
@ -256,10 +256,9 @@ Session::Session(const ContextPtr & global_context_, ClientInfo::Interface inter
Session::~Session()
{
LOG_DEBUG(log, "{} Destroying {} of user {}",
LOG_DEBUG(log, "{} Destroying {}",
toString(auth_id),
(named_session ? "named session '" + named_session->key.second + "'" : "unnamed session"),
(user_id ? toString(*user_id) : "<EMPTY>")
(named_session ? "named session '" + named_session->key.second + "'" : "unnamed session")
);
/// Early release a NamedSessionData.
@ -268,8 +267,8 @@ Session::~Session()
if (notified_session_log_about_login)
{
if (auto session_log = getSessionLog(); session_log && user)
session_log->addLogOut(auth_id, user->getName(), getClientInfo());
if (auto session_log = getSessionLog())
session_log->addLogOut(auth_id, user, getClientInfo());
}
}
@ -314,13 +313,11 @@ void Session::authenticate(const Credentials & credentials_, const Poco::Net::So
{
user_id = global_context->getAccessControl().authenticate(credentials_, address.host());
LOG_DEBUG(log, "{} Authenticated with global context as user {}",
toString(auth_id), user_id ? toString(*user_id) : "<EMPTY>");
toString(auth_id), toString(*user_id));
}
catch (const Exception & e)
{
LOG_DEBUG(log, "{} Authentication failed with error: {}", toString(auth_id), e.what());
if (auto session_log = getSessionLog())
session_log->addLoginFailure(auth_id, *prepared_client_info, credentials_.getUserName(), e);
onAuthenticationFailure(credentials_.getUserName(), address, e);
throw;
}
@ -328,8 +325,21 @@ void Session::authenticate(const Credentials & credentials_, const Poco::Net::So
prepared_client_info->current_address = address;
}
void Session::onAuthenticationFailure(const std::optional<String> & user_name, const Poco::Net::SocketAddress & address_, const Exception & e)
{
LOG_DEBUG(log, "{} Authentication failed with error: {}", toString(auth_id), e.what());
if (auto session_log = getSessionLog())
{
/// Add source address to the log
auto info_for_log = *prepared_client_info;
info_for_log.current_address = address_;
session_log->addLoginFailure(auth_id, info_for_log, user_name, e);
}
}
ClientInfo & Session::getClientInfo()
{
/// FIXME it may produce different info for LoginSuccess and the corresponding Logout entries in the session log
return session_context ? session_context->getClientInfo() : *prepared_client_info;
}
@ -344,9 +354,11 @@ ContextMutablePtr Session::makeSessionContext()
throw Exception("Session context already exists", ErrorCodes::LOGICAL_ERROR);
if (query_context_created)
throw Exception("Session context must be created before any query context", ErrorCodes::LOGICAL_ERROR);
if (!user_id)
throw Exception("Session context must be created after authentication", ErrorCodes::LOGICAL_ERROR);
LOG_DEBUG(log, "{} Creating session context with user_id: {}",
toString(auth_id), user_id ? toString(*user_id) : "<EMPTY>");
toString(auth_id), toString(*user_id));
/// Make a new session context.
ContextMutablePtr new_session_context;
new_session_context = Context::createCopy(global_context);
@ -374,9 +386,11 @@ ContextMutablePtr Session::makeSessionContext(const String & session_name_, std:
throw Exception("Session context already exists", ErrorCodes::LOGICAL_ERROR);
if (query_context_created)
throw Exception("Session context must be created before any query context", ErrorCodes::LOGICAL_ERROR);
if (!user_id)
throw Exception("Session context must be created after authentication", ErrorCodes::LOGICAL_ERROR);
LOG_DEBUG(log, "{} Creating named session context with name: {}, user_id: {}",
toString(auth_id), session_name_, user_id ? toString(*user_id) : "<EMPTY>");
toString(auth_id), session_name_, toString(*user_id));
/// Make a new session context OR
/// if the `session_id` and `user_id` were used before then just get a previously created session context.
@ -396,7 +410,7 @@ ContextMutablePtr Session::makeSessionContext(const String & session_name_, std:
prepared_client_info.reset();
/// Set user information for the new context: current profiles, roles, access rights.
if (user_id && !new_session_context->getUser())
if (user_id && !new_session_context->getAccess()->tryGetUser())
new_session_context->setUser(*user_id);
/// Session context is ready.
@ -420,11 +434,6 @@ ContextMutablePtr Session::makeQueryContext(ClientInfo && query_client_info) con
std::shared_ptr<SessionLog> Session::getSessionLog() const
{
/// For the LOCAL interface we don't send events to the session log
/// because the LOCAL interface is internal, it does nothing with networking.
if (interface == ClientInfo::Interface::LOCAL)
return nullptr;
// take it from global context, since it outlives the Session and always available.
// please note that server may have session_log disabled, hence this may return nullptr.
return global_context->getSessionLog();
@ -432,6 +441,9 @@ std::shared_ptr<SessionLog> Session::getSessionLog() const
ContextMutablePtr Session::makeQueryContextImpl(const ClientInfo * client_info_to_copy, ClientInfo * client_info_to_move) const
{
if (!user_id && getClientInfo().interface != ClientInfo::Interface::TCP_INTERSERVER)
throw Exception("Session context must be created after authentication", ErrorCodes::LOGICAL_ERROR);
/// We can create a query context either from a session context or from a global context.
bool from_session_context = static_cast<bool>(session_context);
@ -439,11 +451,14 @@ ContextMutablePtr Session::makeQueryContextImpl(const ClientInfo * client_info_t
ContextMutablePtr query_context = Context::createCopy(from_session_context ? session_context : global_context);
query_context->makeQueryContext();
LOG_DEBUG(log, "{} Creating query context from {} context, user_id: {}, parent context user: {}",
toString(auth_id),
from_session_context ? "session" : "global",
user_id ? toString(*user_id) : "<EMPTY>",
query_context->getUser() ? query_context->getUser()->getName() : "<NOT SET>");
if (auto query_context_user = query_context->getAccess()->tryGetUser())
{
LOG_DEBUG(log, "{} Creating query context from {} context, user_id: {}, parent context user: {}",
toString(auth_id),
from_session_context ? "session" : "global",
toString(*user_id),
query_context_user->getName());
}
/// Copy the specified client info to the new query context.
auto & res_client_info = query_context->getClientInfo();
@ -473,21 +488,23 @@ ContextMutablePtr Session::makeQueryContextImpl(const ClientInfo * client_info_t
query_context->enableRowPoliciesOfInitialUser();
/// Set user information for the new context: current profiles, roles, access rights.
if (user_id && !query_context->getUser())
if (user_id && !query_context->getAccess()->tryGetUser())
query_context->setUser(*user_id);
/// Query context is ready.
query_context_created = true;
user = query_context->getUser();
if (user_id)
user = query_context->getUser();
if (!notified_session_log_about_login)
{
if (auto session_log = getSessionLog(); user && user_id && session_log)
if (auto session_log = getSessionLog())
{
session_log->addLoginSuccess(
auth_id,
named_session ? std::optional<std::string>(named_session->key.second) : std::nullopt,
*query_context);
*query_context,
user);
notified_session_log_about_login = true;
}

View File

@ -51,6 +51,9 @@ public:
void authenticate(const String & user_name, const String & password, const Poco::Net::SocketAddress & address);
void authenticate(const Credentials & credentials_, const Poco::Net::SocketAddress & address_);
/// Writes a row about login failure into session log (if enabled)
void onAuthenticationFailure(const std::optional<String> & user_name, const Poco::Net::SocketAddress & address_, const Exception & e);
/// Returns a reference to session ClientInfo.
ClientInfo & getClientInfo();
const ClientInfo & getClientInfo() const;
@ -79,7 +82,6 @@ private:
mutable bool notified_session_log_about_login = false;
const UUID auth_id;
const ContextPtr global_context;
const ClientInfo::Interface interface;
/// ClientInfo that will be copied to a session context when it's created.
std::optional<ClientInfo> prepared_client_info;

View File

@ -4,6 +4,7 @@
#include <Access/User.h>
#include <Access/EnabledRolesInfo.h>
#include <Core/Settings.h>
#include <Core/Protocol.h>
#include <DataTypes/DataTypeArray.h>
#include <DataTypes/DataTypeDateTime64.h>
#include <DataTypes/DataTypeDate.h>
@ -94,7 +95,7 @@ NamesAndTypesList SessionLogElement::getNamesAndTypes()
AUTH_TYPE_NAME_AND_VALUE(AuthType::SHA256_PASSWORD),
AUTH_TYPE_NAME_AND_VALUE(AuthType::DOUBLE_SHA1_PASSWORD),
AUTH_TYPE_NAME_AND_VALUE(AuthType::LDAP),
AUTH_TYPE_NAME_AND_VALUE(AuthType::KERBEROS)
AUTH_TYPE_NAME_AND_VALUE(AuthType::KERBEROS),
});
#undef AUTH_TYPE_NAME_AND_VALUE
static_assert(static_cast<int>(AuthenticationType::MAX) == 7);
@ -132,8 +133,8 @@ NamesAndTypesList SessionLogElement::getNamesAndTypes()
{"event_time", std::make_shared<DataTypeDateTime>()},
{"event_time_microseconds", std::make_shared<DataTypeDateTime64>(6)},
{"user", std::make_shared<DataTypeString>()},
{"auth_type", std::move(identified_with_column)},
{"user", std::make_shared<DataTypeNullable>(std::make_shared<DataTypeString>())},
{"auth_type", std::make_shared<DataTypeNullable>(std::move(identified_with_column))},
{"profiles", std::make_shared<DataTypeArray>(lc_string_datatype)},
{"roles", std::make_shared<DataTypeArray>(lc_string_datatype)},
@ -168,8 +169,9 @@ void SessionLogElement::appendToBlock(MutableColumns & columns) const
columns[i++]->insert(event_time);
columns[i++]->insert(event_time_microseconds);
columns[i++]->insert(user);
columns[i++]->insert(user_identified_with);
assert((user && user_identified_with) || client_info.interface == ClientInfo::Interface::TCP_INTERSERVER);
columns[i++]->insert(user ? Field(*user) : Field());
columns[i++]->insert(user_identified_with ? Field(*user_identified_with) : Field());
fillColumnArray(profiles, *columns[i++]);
fillColumnArray(roles, *columns[i++]);
@ -207,7 +209,7 @@ void SessionLogElement::appendToBlock(MutableColumns & columns) const
columns[i++]->insertData(auth_failure_reason.data(), auth_failure_reason.length());
}
void SessionLog::addLoginSuccess(const UUID & auth_id, std::optional<String> session_id, const Context & login_context)
void SessionLog::addLoginSuccess(const UUID & auth_id, std::optional<String> session_id, const Context & login_context, const UserPtr & login_user)
{
const auto access = login_context.getAccess();
const auto & settings = login_context.getSettingsRef();
@ -216,12 +218,12 @@ void SessionLog::addLoginSuccess(const UUID & auth_id, std::optional<String> ses
DB::SessionLogElement log_entry(auth_id, SESSION_LOGIN_SUCCESS);
log_entry.client_info = client_info;
if (login_user)
{
const auto user = access->getUser();
log_entry.user = user->getName();
log_entry.user_identified_with = user->auth_data.getType();
log_entry.external_auth_server = user->auth_data.getLDAPServerName();
log_entry.user = login_user->getName();
log_entry.user_identified_with = login_user->auth_data.getType();
}
log_entry.external_auth_server = login_user ? login_user->auth_data.getLDAPServerName() : "";
if (session_id)
log_entry.session_id = *session_id;
@ -241,7 +243,7 @@ void SessionLog::addLoginSuccess(const UUID & auth_id, std::optional<String> ses
void SessionLog::addLoginFailure(
const UUID & auth_id,
const ClientInfo & info,
const String & user,
const std::optional<String> & user,
const Exception & reason)
{
SessionLogElement log_entry(auth_id, SESSION_LOGIN_FAILURE);
@ -254,10 +256,15 @@ void SessionLog::addLoginFailure(
add(log_entry);
}
void SessionLog::addLogOut(const UUID & auth_id, const String & user, const ClientInfo & client_info)
void SessionLog::addLogOut(const UUID & auth_id, const UserPtr & login_user, const ClientInfo & client_info)
{
auto log_entry = SessionLogElement(auth_id, SESSION_LOGOUT);
log_entry.user = user;
if (login_user)
{
log_entry.user = login_user->getName();
log_entry.user_identified_with = login_user->auth_data.getType();
}
log_entry.external_auth_server = login_user ? login_user->auth_data.getLDAPServerName() : "";
log_entry.client_info = client_info;
add(log_entry);

View File

@ -18,6 +18,8 @@ enum SessionLogElementType : int8_t
};
class ContextAccess;
struct User;
using UserPtr = std::shared_ptr<const User>;
/** A struct which will be inserted as row into session_log table.
*
@ -44,8 +46,8 @@ struct SessionLogElement
time_t event_time{};
Decimal64 event_time_microseconds{};
String user;
AuthenticationType user_identified_with = AuthenticationType::NO_PASSWORD;
std::optional<String> user;
std::optional<AuthenticationType> user_identified_with;
String external_auth_server;
Strings roles;
Strings profiles;
@ -70,9 +72,9 @@ class SessionLog : public SystemLog<SessionLogElement>
using SystemLog<SessionLogElement>::SystemLog;
public:
void addLoginSuccess(const UUID & auth_id, std::optional<String> session_id, const Context & login_context);
void addLoginFailure(const UUID & auth_id, const ClientInfo & info, const String & user, const Exception & reason);
void addLogOut(const UUID & auth_id, const String & user, const ClientInfo & client_info);
void addLoginSuccess(const UUID & auth_id, std::optional<String> session_id, const Context & login_context, const UserPtr & login_user);
void addLoginFailure(const UUID & auth_id, const ClientInfo & info, const std::optional<String> & user, const Exception & reason);
void addLogOut(const UUID & auth_id, const UserPtr & login_user, const ClientInfo & client_info);
};
}

View File

@ -232,7 +232,10 @@ SystemLogs::SystemLogs(ContextPtr global_context, const Poco::Util::AbstractConf
if (zookeeper_log)
logs.emplace_back(zookeeper_log.get());
if (session_log)
{
logs.emplace_back(session_log.get());
global_context->addWarningMessage("Table system.session_log is enabled. It's unreliable and may contain garbage. Do not use it for any kind of security monitoring.");
}
if (transactions_info_log)
logs.emplace_back(transactions_info_log.get());
if (processors_profile_log)

View File

@ -290,8 +290,8 @@ void ASTCreateUserQuery::formatImpl(const FormatSettings & format, FormatState &
formatOnCluster(format);
if (!new_name.empty())
formatRenameTo(new_name, format);
if (new_name)
formatRenameTo(*new_name, format);
if (auth_data)
formatAuthenticationData(*auth_data, show_password, format);

View File

@ -42,7 +42,7 @@ public:
bool or_replace = false;
std::shared_ptr<ASTUserNamesWithHost> names;
String new_name;
std::optional<String> new_name;
std::optional<AuthenticationData> auth_data;
bool show_password = true; /// formatImpl() will show the password or hash.

View File

@ -34,14 +34,19 @@ namespace ErrorCodes
namespace
{
bool parseRenameTo(IParserBase::Pos & pos, Expected & expected, String & new_name)
bool parseRenameTo(IParserBase::Pos & pos, Expected & expected, std::optional<String> & new_name)
{
return IParserBase::wrapParseImpl(pos, [&]
{
if (!ParserKeyword{"RENAME TO"}.ignore(pos, expected))
return false;
return parseUserName(pos, expected, new_name);
String maybe_new_name;
if (!parseUserName(pos, expected, maybe_new_name))
return false;
new_name.emplace(std::move(maybe_new_name));
return true;
});
}
@ -431,7 +436,7 @@ bool ParserCreateUserQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expec
auto names = typeid_cast<std::shared_ptr<ASTUserNamesWithHost>>(names_ast);
auto names_ref = names->names;
String new_name;
std::optional<String> new_name;
std::optional<AuthenticationData> auth_data;
std::optional<AllowedClientHosts> hosts;
std::optional<AllowedClientHosts> add_hosts;
@ -487,7 +492,8 @@ bool ParserCreateUserQuery::parseImpl(Pos & pos, ASTPtr & node, Expected & expec
if (alter)
{
if (new_name.empty() && (names->size() == 1) && parseRenameTo(pos, expected, new_name))
String maybe_new_name;
if (!new_name && (names->size() == 1) && parseRenameTo(pos, expected, new_name))
continue;
if (parseHosts(pos, expected, "ADD", new_hosts))

View File

@ -8,6 +8,7 @@
namespace DB
{
namespace
{
bool parseUserNameWithHost(IParserBase::Pos & pos, Expected & expected, std::shared_ptr<ASTUserNameWithHost> & ast)
@ -18,8 +19,6 @@ namespace
if (!parseIdentifierOrStringLiteral(pos, expected, base_name))
return false;
boost::algorithm::trim(base_name);
String host_pattern;
if (ParserToken{TokenType::At}.ignore(pos, expected))
{

View File

@ -77,8 +77,8 @@ namespace ErrorCodes
extern const int POCO_EXCEPTION;
extern const int SOCKET_TIMEOUT;
extern const int UNEXPECTED_PACKET_FROM_CLIENT;
extern const int SUPPORT_IS_DISABLED;
extern const int UNKNOWN_PROTOCOL;
extern const int AUTHENTICATION_FAILED;
}
TCPHandler::TCPHandler(IServer & server_, TCPServer & tcp_server_, const Poco::Net::StreamSocket & socket_, bool parse_proxy_protocol_, std::string server_display_name_)
@ -110,7 +110,6 @@ void TCPHandler::runImpl()
setThreadName("TCPHandler");
ThreadStatus thread_status;
session = std::make_unique<Session>(server.context(), ClientInfo::Interface::TCP, socket().secure());
extractConnectionSettingsFromContext(server.context());
socket().setReceiveTimeout(receive_timeout);
@ -514,6 +513,12 @@ void TCPHandler::runImpl()
/// It is important to destroy query context here. We do not want it to live arbitrarily longer than the query.
query_context.reset();
if (is_interserver_mode)
{
/// We don't really have session in interserver mode, new one is created for each query. It's better to reset it now.
session.reset();
}
if (network_error)
break;
}
@ -741,7 +746,7 @@ void TCPHandler::processTablesStatusRequest()
TablesStatusRequest request;
request.read(*in, client_tcp_protocol_version);
ContextPtr context_to_resolve_table_names = session->sessionContext() ? session->sessionContext() : server.context();
ContextPtr context_to_resolve_table_names = (session && session->sessionContext()) ? session->sessionContext() : server.context();
TablesStatusResponse response;
for (const QualifiedTableName & table_name: request.tables)
@ -941,7 +946,7 @@ bool TCPHandler::receiveProxyHeader()
}
LOG_TRACE(log, "Forwarded client address from PROXY header: {}", forwarded_address);
session->getClientInfo().forwarded_for = forwarded_address;
forwarded_for = std::move(forwarded_address);
return true;
}
@ -968,6 +973,29 @@ std::string formatHTTPErrorResponseWhenUserIsConnectedToWrongPort(const Poco::Ut
}
std::unique_ptr<Session> TCPHandler::makeSession()
{
auto interface = is_interserver_mode ? ClientInfo::Interface::TCP_INTERSERVER : ClientInfo::Interface::TCP;
auto res = std::make_unique<Session>(server.context(), interface, socket().secure());
auto & client_info = res->getClientInfo();
client_info.forwarded_for = forwarded_for;
client_info.client_name = client_name;
client_info.client_version_major = client_version_major;
client_info.client_version_minor = client_version_minor;
client_info.client_version_patch = client_version_patch;
client_info.client_tcp_protocol_version = client_tcp_protocol_version;
client_info.connection_client_version_major = client_version_major;
client_info.connection_client_version_minor = client_version_minor;
client_info.connection_client_version_patch = client_version_patch;
client_info.connection_tcp_protocol_version = client_tcp_protocol_version;
client_info.interface = interface;
return res;
}
void TCPHandler::receiveHello()
{
@ -1011,26 +1039,14 @@ void TCPHandler::receiveHello()
(!user.empty() ? ", user: " + user : "")
);
auto & client_info = session->getClientInfo();
client_info.client_name = client_name;
client_info.client_version_major = client_version_major;
client_info.client_version_minor = client_version_minor;
client_info.client_version_patch = client_version_patch;
client_info.client_tcp_protocol_version = client_tcp_protocol_version;
client_info.connection_client_version_major = client_version_major;
client_info.connection_client_version_minor = client_version_minor;
client_info.connection_client_version_patch = client_version_patch;
client_info.connection_tcp_protocol_version = client_tcp_protocol_version;
is_interserver_mode = (user == USER_INTERSERVER_MARKER);
is_interserver_mode = (user == USER_INTERSERVER_MARKER) && password.empty();
if (is_interserver_mode)
{
client_info.interface = ClientInfo::Interface::TCP_INTERSERVER;
receiveClusterNameAndSalt();
return;
}
session = makeSession();
session->authenticate(user, password, socket().peerAddress());
}
@ -1210,25 +1226,6 @@ void TCPHandler::receiveClusterNameAndSalt()
{
readStringBinary(cluster, *in);
readStringBinary(salt, *in, 32);
try
{
if (salt.empty())
throw NetException("Empty salt is not allowed", ErrorCodes::UNEXPECTED_PACKET_FROM_CLIENT);
cluster_secret = server.context()->getCluster(cluster)->getSecret();
}
catch (const Exception & e)
{
try
{
/// We try to send error information to the client.
sendException(e, send_exception_with_stack_trace);
}
catch (...) {}
throw;
}
}
void TCPHandler::receiveQuery()
@ -1239,21 +1236,13 @@ void TCPHandler::receiveQuery()
state.is_empty = false;
readStringBinary(state.query_id, *in);
/// In interserer mode,
/// In interserver mode,
/// initial_user can be empty in case of Distributed INSERT via Buffer/Kafka,
/// (i.e. when the INSERT is done with the global context without user),
/// so it is better to reset session to avoid using old user.
if (is_interserver_mode)
{
ClientInfo original_session_client_info = session->getClientInfo();
/// Cleanup fields that should not be reused from previous query.
original_session_client_info.current_user.clear();
original_session_client_info.current_query_id.clear();
original_session_client_info.current_address = {};
session = std::make_unique<Session>(server.context(), ClientInfo::Interface::TCP_INTERSERVER);
session->getClientInfo() = original_session_client_info;
session = makeSession();
}
/// Read client info.
@ -1285,24 +1274,38 @@ void TCPHandler::receiveQuery()
readStringBinary(state.query, *in);
/// TODO Unify interserver authentication (and make sure that it's secure enough)
if (is_interserver_mode)
{
client_info.interface = ClientInfo::Interface::TCP_INTERSERVER;
#if USE_SSL
String cluster_secret = server.context()->getCluster(cluster)->getSecret();
if (salt.empty() || cluster_secret.empty())
{
auto exception = Exception(ErrorCodes::AUTHENTICATION_FAILED, "Interserver authentication failed");
session->onAuthenticationFailure(/* user_name */ std::nullopt, socket().peerAddress(), exception);
throw exception; /// NOLINT
}
std::string data(salt);
data += cluster_secret;
data += state.query;
data += state.query_id;
data += client_info.initial_user;
if (received_hash.size() != 32)
throw NetException("Unexpected hash received from client", ErrorCodes::UNEXPECTED_PACKET_FROM_CLIENT);
std::string calculated_hash = encodeSHA256(data);
assert(calculated_hash.size() == 32);
/// TODO maybe also check that peer address actually belongs to the cluster?
if (calculated_hash != received_hash)
throw NetException("Hash mismatch", ErrorCodes::UNEXPECTED_PACKET_FROM_CLIENT);
/// TODO: change error code?
{
auto exception = Exception(ErrorCodes::AUTHENTICATION_FAILED, "Interserver authentication failed");
session->onAuthenticationFailure(/* user_name */ std::nullopt, socket().peerAddress(), exception);
throw exception; /// NOLINT
}
/// NOTE Usually we get some fields of client_info (including initial_address and initial_user) from user input,
/// so we should not rely on that. However, in this particular case we got client_info from other clickhouse-server, so it's ok.
if (client_info.initial_user.empty())
{
LOG_DEBUG(log, "User (no user, interserver mode)");
@ -1313,9 +1316,11 @@ void TCPHandler::receiveQuery()
session->authenticate(AlwaysAllowCredentials{client_info.initial_user}, client_info.initial_address);
}
#else
throw Exception(
auto exception = Exception(
"Inter-server secret support is disabled, because ClickHouse was built without SSL library",
ErrorCodes::SUPPORT_IS_DISABLED);
ErrorCodes::AUTHENTICATION_FAILED);
session->onAuthenticationFailure(/* user_name */ std::nullopt, socket().peerAddress(), exception);
throw exception; /// NOLINT
#endif
}

View File

@ -147,6 +147,8 @@ private:
bool parse_proxy_protocol = false;
Poco::Logger * log;
String forwarded_for;
String client_name;
UInt64 client_version_major = 0;
UInt64 client_version_minor = 0;
@ -182,7 +184,6 @@ private:
bool is_interserver_mode = false;
String salt;
String cluster;
String cluster_secret;
std::mutex task_callback_mutex;
std::mutex fatal_error_mutex;
@ -204,6 +205,8 @@ private:
void extractConnectionSettingsFromContext(const ContextPtr & context);
std::unique_ptr<Session> makeSession();
bool receiveProxyHeader();
void receiveHello();
bool receivePacket();

View File

@ -26,8 +26,6 @@ void StorageSystemCurrentRoles::fillData(MutableColumns & res_columns, ContextPt
{
auto roles_info = context->getRolesInfo();
auto user = context->getUser();
if (!roles_info || !user)
return;
size_t column_index = 0;
auto & column_role_name = assert_cast<ColumnString &>(*res_columns[column_index++]);

View File

@ -27,8 +27,6 @@ void StorageSystemEnabledRoles::fillData(MutableColumns & res_columns, ContextPt
{
auto roles_info = context->getRolesInfo();
auto user = context->getUser();
if (!roles_info || !user)
return;
size_t column_index = 0;
auto & column_role_name = assert_cast<ColumnString &>(*res_columns[column_index++]);

View File

@ -42,5 +42,20 @@
</replica>
</shard>
</test_cluster_two_replicas_different_databases>
<test_cluster_interserver_secret>
<secret>123457</secret>
<shard>
<replica>
<host>127.0.0.1</host>
<port>9000</port>
</replica>
</shard>
<shard>
<replica>
<host>127.0.0.2</host>
<port>9000</port>
</replica>
</shard>
</test_cluster_interserver_secret>
</remote_servers>
</clickhouse>

View File

@ -0,0 +1,7 @@
<clickhouse>
<session_log>
<database>system</database>
<table>session_log</table>
<flush_interval_milliseconds>100000</flush_interval_milliseconds>
</session_log>
</clickhouse>

View File

@ -45,6 +45,7 @@ ln -sf $SRC_PATH/config.d/logger_test.xml $DEST_SERVER_PATH/config.d/
ln -sf $SRC_PATH/config.d/named_collection.xml $DEST_SERVER_PATH/config.d/
ln -sf $SRC_PATH/config.d/ssl_certs.xml $DEST_SERVER_PATH/config.d/
ln -sf $SRC_PATH/config.d/filesystem_cache_log.xml $DEST_SERVER_PATH/config.d/
ln -sf $SRC_PATH/config.d/session_log.xml $DEST_SERVER_PATH/config.d/
ln -sf $SRC_PATH/users.d/log_queries.xml $DEST_SERVER_PATH/users.d/
ln -sf $SRC_PATH/users.d/readonly.xml $DEST_SERVER_PATH/users.d/

View File

@ -291,20 +291,26 @@ def test_secure_insert_buffer_async():
def test_secure_disagree():
with pytest.raises(QueryRuntimeException, match=".*Hash mismatch.*"):
with pytest.raises(
QueryRuntimeException, match=".*Interserver authentication failed.*"
):
n1.query("SELECT * FROM dist_secure_disagree")
def test_secure_disagree_insert():
n1.query("TRUNCATE TABLE data")
n1.query("INSERT INTO dist_secure_disagree SELECT * FROM numbers(2)")
with pytest.raises(QueryRuntimeException, match=".*Hash mismatch.*"):
with pytest.raises(
QueryRuntimeException, match=".*Interserver authentication failed.*"
):
n1.query(
"SYSTEM FLUSH DISTRIBUTED ON CLUSTER secure_disagree dist_secure_disagree"
)
# check the the connection will be re-established
# IOW that we will not get "Unknown BlockInfo field"
with pytest.raises(QueryRuntimeException, match=".*Hash mismatch.*"):
with pytest.raises(
QueryRuntimeException, match=".*Interserver authentication failed.*"
):
assert int(n1.query("SELECT count() FROM dist_secure_disagree")) == 0

View File

@ -0,0 +1,17 @@
0
1
0
0
LoginFailure NO_PASSWORD 1 1 TCP
LoginFailure NO_PASSWORD 1 1 HTTP
LoginFailure INTERSERVER SECRET NO_PASSWORD 1 1 HTTP
LoginFailure default NO_PASSWORD 1 1 TCP
LoginFailure default NO_PASSWORD 1 1 HTTP
LoginFailure nonexistsnt_user_1119 NO_PASSWORD 1 1 TCP
LoginFailure nonexistsnt_user_1119 NO_PASSWORD 1 1 HTTP
LoginSuccess default PLAINTEXT_PASSWORD 1 1 TCP
LoginSuccess default PLAINTEXT_PASSWORD 1 1 HTTP
LoginSuccess default PLAINTEXT_PASSWORD 1 1 TCP_Interserver
Logout default PLAINTEXT_PASSWORD 1 1 TCP
Logout default PLAINTEXT_PASSWORD 1 1 HTTP
Logout default PLAINTEXT_PASSWORD 1 1 TCP_Interserver

View File

@ -0,0 +1,24 @@
-- Tags: no-fasttest
select * from remote('127.0.0.2', system, one, 'default', '');
select * from remote('127.0.0.2', system, one, 'default', 'wrong password'); -- { serverError AUTHENTICATION_FAILED }
select * from remote('127.0.0.2', system, one, 'nonexistsnt_user_1119', ''); -- { serverError AUTHENTICATION_FAILED }
set receive_timeout=1;
select * from remote('127.0.0.2', system, one, ' INTERSERVER SECRET ', ''); -- { serverError NO_REMOTE_SHARD_AVAILABLE }
set receive_timeout=300;
select * from remote('127.0.0.2', system, one, ' ', ''); -- { serverError AUTHENTICATION_FAILED }
select * from url('http://127.0.0.1:8123/?query=select+1&user=default', LineAsString, 's String');
select * from url('http://127.0.0.1:8123/?query=select+1&user=default&password=wrong', LineAsString, 's String'); -- { serverError RECEIVED_ERROR_FROM_REMOTE_IO_SERVER }
select * from url('http://127.0.0.1:8123/?query=select+1&user=nonexistsnt_user_1119', LineAsString, 's String'); -- { serverError RECEIVED_ERROR_FROM_REMOTE_IO_SERVER }
select * from url('http://127.0.0.1:8123/?query=select+1&user=+INTERSERVER+SECRET+', LineAsString, 's String'); -- { serverError RECEIVED_ERROR_FROM_REMOTE_IO_SERVER }
select * from url('http://127.0.0.1:8123/?query=select+1&user=+++', LineAsString, 's String'); -- { serverError RECEIVED_ERROR_FROM_REMOTE_IO_SERVER }
select * from cluster('test_cluster_interserver_secret', system, one);
system flush logs;
select distinct type, user, auth_type, toString(client_address)!='::ffff:0.0.0.0' as a, client_port!=0 as b, interface from system.session_log
where user in ('default', 'nonexistsnt_user_1119', ' ', ' INTERSERVER SECRET ')
and interface in ('HTTP', 'TCP', 'TCP_Interserver')
and (user != 'default' or (a=1 and b=1)) -- FIXME: we should not write uninitialized address and port (but we do sometimes)
and event_time >= now() - interval 5 minute order by type, user, interface;

View File

@ -0,0 +1,7 @@
7 .
9 spaces .
8 spaces .
10 spaces .
21 Вася Пупкин .
10 无名氏 .
14 🙈 🙉 🙊 .

View File

@ -0,0 +1,33 @@
-- Tags: no-parallel
drop user if exists " ";
drop user if exists ' spaces';
drop user if exists 'spaces ';
drop user if exists " spaces ";
drop user if exists "test 01119";
drop user if exists "Вася Пупкин";
drop user if exists "无名氏 ";
drop user if exists "🙈 🙉 🙊";
create user " ";
create user ' spaces';
create user 'spaces ';
create user ` INTERSERVER SECRET `; -- { serverError BAD_ARGUMENTS }
create user ''; -- { serverError BAD_ARGUMENTS }
create user 'test 01119';
alter user `test 01119` rename to " spaces ";
alter user " spaces " rename to ''; -- { serverError BAD_ARGUMENTS }
alter user " spaces " rename to " INTERSERVER SECRET "; -- { serverError BAD_ARGUMENTS }
create user "Вася Пупкин";
create user "无名氏 ";
create user "🙈 🙉 🙊";
select length(name), name, '.' from system.users where position(name, ' ')!=0 order by name;
drop user " ";
drop user ' spaces';
drop user 'spaces ';
drop user " spaces ";
drop user "Вася Пупкин";
drop user "无名氏 ";
drop user "🙈 🙉 🙊";

View File

@ -0,0 +1,55 @@
#!/usr/bin/env bash
# Tags: race, no-fasttest, no-parallel, no-backward-compatibility-check
# Test tries to reproduce a race between threads:
# - deletes user
# - creates user
# - uses it as session user
# - apply role to the user
#
# https://github.com/ClickHouse/ClickHouse/issues/35714
set -e
CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
# shellcheck source=../shell_config.sh
. "$CURDIR"/../shell_config.sh
$CLICKHOUSE_CLIENT -nm -q "
DROP ROLE IF EXISTS test_role_02242;
CREATE ROLE test_role_02242;
"
function delete_user()
{
$CLICKHOUSE_CLIENT -q "DROP USER IF EXISTS test_user_02242" ||:
}
function create_and_login_user()
{
$CLICKHOUSE_CLIENT -q "CREATE USER IF NOT EXISTS test_user_02242" ||:
$CLICKHOUSE_CLIENT -u "test_user_02242" -q "SELECT COUNT(*) FROM system.session_log WHERE user == 'test_user_02242'" > /dev/null ||:
}
function set_role()
{
$CLICKHOUSE_CLIENT -q "SET ROLE test_role_02242 TO test_user_02242" ||:
}
export -f delete_user
export -f create_and_login_user
export -f set_role
TIMEOUT=10
for (( i = 0 ; i < 100; ++i ))
do
clickhouse_client_loop_timeout $TIMEOUT create_and_login_user 2> /dev/null &
clickhouse_client_loop_timeout $TIMEOUT delete_user 2> /dev/null &
clickhouse_client_loop_timeout $TIMEOUT set_role 2> /dev/null &
done
wait
$CLICKHOUSE_CLIENT -q "DROP ROLE IF EXISTS test_role_02242"
$CLICKHOUSE_CLIENT -q "DROP USER IF EXISTS test_user_02242"

View File

@ -0,0 +1 @@
ACCESS_DENIED

View File

@ -0,0 +1,39 @@
#!/usr/bin/env bash
# Tags: race, no-fasttest, no-parallel, no-backward-compatibility-check
set -e
CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
# shellcheck source=../shell_config.sh
. "$CURDIR"/../shell_config.sh
$CLICKHOUSE_CLIENT -nm -q "
DROP ROLE IF EXISTS test_role_02244;
CREATE ROLE test_role_02244;
DROP USER IF EXISTS kek_02243;
CREATE USER IF NOT EXISTS kek_02243;
REVOKE ALL ON *.* FROM kek_02243;
CREATE TABLE test (n int) engine=Memory;
INSERT INTO test VALUES (1);
"
function create_drop_grant()
{
$CLICKHOUSE_CLIENT -q "CREATE USER IF NOT EXISTS test_user_02243 GRANTEES NONE" ||:
$CLICKHOUSE_CLIENT -q "GRANT ALL ON *.* TO test_user_02243 WITH GRANT OPTION" ||:
$CLICKHOUSE_CLIENT -q "DROP USER IF EXISTS test_user_02243" &
$CLICKHOUSE_CLIENT --user test_user_02243 -q "GRANT ALL ON *.* TO kek_02243" &
wait
}
export -f create_drop_grant
TIMEOUT=10
clickhouse_client_loop_timeout $TIMEOUT create_drop_grant 2> /dev/null &
wait
$CLICKHOUSE_CLIENT --user kek_02243 -q "SELECT * FROM test" 2>&1| grep -Fa "Exception: " | grep -Eo ACCESS_DENIED | uniq
$CLICKHOUSE_CLIENT -q "DROP ROLE IF EXISTS test_role_02243"
$CLICKHOUSE_CLIENT -q "DROP USER IF EXISTS test_user_02243"
$CLICKHOUSE_CLIENT -q "DROP USER IF EXISTS kek_02243"