mirror of
https://github.com/ClickHouse/ClickHouse.git
synced 2024-11-10 01:25:21 +00:00
Merge pull request #37303 from ClickHouse/fix_trash
Try to fix some trash
This commit is contained in:
commit
d199478169
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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_;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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>;
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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.
|
||||
|
@ -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());
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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 */)
|
||||
|
@ -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();
|
||||
|
@ -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 = "");
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -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.
|
||||
|
@ -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))
|
||||
|
@ -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))
|
||||
{
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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++]);
|
||||
|
@ -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++]);
|
||||
|
@ -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>
|
||||
|
7
tests/config/config.d/session_log.xml
Normal file
7
tests/config/config.d/session_log.xml
Normal 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>
|
@ -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/
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
17
tests/queries/0_stateless/01119_session_log.reference
Normal file
17
tests/queries/0_stateless/01119_session_log.reference
Normal 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
|
24
tests/queries/0_stateless/01119_session_log.sql
Normal file
24
tests/queries/0_stateless/01119_session_log.sql
Normal 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;
|
@ -0,0 +1,7 @@
|
||||
7 .
|
||||
9 spaces .
|
||||
8 spaces .
|
||||
10 spaces .
|
||||
21 Вася Пупкин .
|
||||
10 无名氏 .
|
||||
14 🙈 🙉 🙊 .
|
33
tests/queries/0_stateless/01119_wierd_user_names.sql
Normal file
33
tests/queries/0_stateless/01119_wierd_user_names.sql
Normal 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 "🙈 🙉 🙊";
|
55
tests/queries/0_stateless/02242_delete_user_race.sh
Executable file
55
tests/queries/0_stateless/02242_delete_user_race.sh
Executable 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"
|
@ -0,0 +1 @@
|
||||
ACCESS_DENIED
|
39
tests/queries/0_stateless/02243_drop_user_grant_race.sh
Executable file
39
tests/queries/0_stateless/02243_drop_user_grant_race.sh
Executable 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"
|
Loading…
Reference in New Issue
Block a user