Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions docker/test/integration/runner/compose/docker_compose_ldap.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
version: '2.3'

services:
openldap:
image: osixia/openldap:1.4.0
command: "--copy-service --loglevel debug"
environment:
LDAP_ORGANIZATION: "company"
LDAP_DOMAIN: "company.com"
LDAP_ADMIN_PASSWORD: "admin"
LDAP_TLS: "false"
volumes:
- ${LDAP_DIR}/../configs/ldap:/container/service/slapd/assets/config/bootstrap/ldif/custom
expose:
- "389"
- "636"
healthcheck:
test: ldapsearch -x -H ldap://localhost:$${LDAP_PORT:-389} -b "dc=company,dc=com" -D "cn=admin,dc=company,dc=com" -w admin
interval: 10s
timeout: 10s
retries: 3
start_period: 300s
security_opt:
- label:disable
2 changes: 1 addition & 1 deletion programs/client/Client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ std::vector<String> Client::loadWarningMessages()
"" /* query_id */,
QueryProcessingStage::Complete,
&global_context->getSettingsRef(),
&global_context->getClientInfo(), false, {});
&global_context->getClientInfo(), false, {}, {});
while (true)
{
Packet packet = connection->receivePacket();
Expand Down
2 changes: 2 additions & 0 deletions src/Access/AccessControl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -716,6 +716,7 @@ int AccessControl::getBcryptWorkfactor() const
std::shared_ptr<const ContextAccess> AccessControl::getContextAccess(
const UUID & user_id,
const std::vector<UUID> & current_roles,
const std::vector<UUID> & external_roles,
bool use_default_roles,
const Settings & settings,
const String & current_database,
Expand All @@ -724,6 +725,7 @@ std::shared_ptr<const ContextAccess> AccessControl::getContextAccess(
ContextAccessParams params;
params.user_id = user_id;
params.current_roles.insert(current_roles.begin(), current_roles.end());
params.external_roles.insert(external_roles.begin(), external_roles.end());
params.use_default_roles = use_default_roles;
params.current_database = current_database;
params.readonly = settings.readonly;
Expand Down
1 change: 1 addition & 0 deletions src/Access/AccessControl.h
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ class AccessControl : public MultipleAccessStorage
std::shared_ptr<const ContextAccess> getContextAccess(
const UUID & user_id,
const std::vector<UUID> & current_roles,
const std::vector<UUID> & external_roles,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems it's more generic to always use current roles instead of external roles. I mean we could kind of pack current_roles and external_roles here together. When we connect to another server and send initial_user, we can send it along with initial_user's current roles. And then on another server TCPHandler will get those roles and use them instead of user's default roles in Context::setUser(). And LDAPAccessStorage on another server will also use the initial user's current roles to add them to User::granted_roles. What do you think?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And that would also allow another server to understand changes in current roles. For example,

SET CURRENT ROLE role1, role2;
SELECT * FROM remote(another server);

so another server would understand that current roles are role1 and role2 even if the initial user has more granted roles.

bool use_default_roles,
const Settings & settings,
const String & current_database,
Expand Down
2 changes: 1 addition & 1 deletion src/Access/ContextAccess.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,7 @@ void ContextAccess::setUser(const UserPtr & user_) const
current_roles = user->granted_roles.findGranted(params.current_roles);
current_roles_with_admin_option = user->granted_roles.findGrantedWithAdminOption(params.current_roles);
}
current_roles.insert(current_roles.end(), params.external_roles.begin(), params.external_roles.end());
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't feel right to add roles to current_roles without adding them to user->default_roles. I think we can just put external roles instead of current roles, and modify User in LDAPAccessStorage to contain the default roles we want.


subscription_for_roles_changes.reset();
enabled_roles = access_control->getEnabledRoles(current_roles, current_roles_with_admin_option);
Expand Down Expand Up @@ -424,7 +425,6 @@ std::shared_ptr<const ContextAccess> ContextAccess::getFullAccess()
return res;
}


SettingsChanges ContextAccess::getDefaultSettings() const
{
std::lock_guard lock{mutex};
Expand Down
3 changes: 2 additions & 1 deletion src/Access/ContextAccess.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ struct ContextAccessParams
{
std::optional<UUID> user_id;
boost::container::flat_set<UUID> current_roles;
boost::container::flat_set<UUID> external_roles;
bool use_default_roles = false;
UInt64 readonly = 0;
bool allow_ddl = false;
Expand All @@ -50,7 +51,7 @@ struct ContextAccessParams
auto toTuple() const
{
return std::tie(
user_id, current_roles, use_default_roles, readonly, allow_ddl, allow_introspection,
user_id, current_roles, external_roles, use_default_roles, readonly, allow_ddl, allow_introspection,
current_database, interface, http_method, address, forwarded_address, quota_key);
}

Expand Down
5 changes: 4 additions & 1 deletion src/Access/LDAPAccessStorage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ namespace ErrorCodes
extern const int BAD_ARGUMENTS;
}


LDAPAccessStorage::LDAPAccessStorage(const String & storage_name_, AccessControl & access_control_, const Poco::Util::AbstractConfiguration & config, const String & prefix)
: IAccessStorage(storage_name_), access_control(access_control_), memory_storage(storage_name_, access_control.getChangesNotifier(), false)
{
Expand Down Expand Up @@ -320,6 +319,10 @@ std::set<String> LDAPAccessStorage::mapExternalRolesNoLock(const LDAPClient::Sea
{
std::set<String> role_names;

// If this node can't access LDAP server (or has not privileges to fetch roles) and gets empty list of external roles
if (external_roles.size() == 0)
return role_names;

if (external_roles.size() != role_search_params.size())
throw Exception(ErrorCodes::BAD_ARGUMENTS, "Unable to map external roles");
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if it gets a list of external roles from the initiator, but the configuration file is not the same here so role_search_params is not the same here as it was on the initiator and we fail here? I mean maybe this check and also the check for role name's prefix is useful here in general but not for external roles from the initiator?


Expand Down
2 changes: 2 additions & 0 deletions src/Client/ClientBase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -942,6 +942,7 @@ void ClientBase::processOrdinaryQuery(const String & query_to_execute, ASTPtr pa
&global_context->getSettingsRef(),
&global_context->getClientInfo(),
true,
{},
[&](const Progress & progress) { onProgress(progress); });

if (send_external_tables)
Expand Down Expand Up @@ -1351,6 +1352,7 @@ void ClientBase::processInsertQuery(const String & query_to_execute, ASTPtr pars
&global_context->getSettingsRef(),
&global_context->getClientInfo(),
true,
{},
[&](const Progress & progress) { onProgress(progress); });

if (send_external_tables)
Expand Down
17 changes: 16 additions & 1 deletion src/Client/Connection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include <Formats/NativeWriter.h>
#include <Client/Connection.h>
#include <Client/ConnectionParameters.h>
#include "Common/logger_useful.h"
#include <Common/ClickHouseRevision.h>
#include <Common/Exception.h>
#include <Common/NetException.h>
Expand All @@ -22,8 +23,8 @@
#include <Common/StringUtils/StringUtils.h>
#include <Common/OpenSSLHelpers.h>
#include <Common/randomSeed.h>
#include <Common/logger_useful.h>
#include <Core/Block.h>
#include <Core/ProtocolDefines.h>
#include <Interpreters/ClientInfo.h>
#include <Interpreters/OpenTelemetrySpanLog.h>
#include <Compression/CompressionFactory.h>
Expand Down Expand Up @@ -551,6 +552,7 @@ void Connection::sendQuery(
const Settings * settings,
const ClientInfo * client_info,
bool with_pending_data,
const std::vector<String> & external_roles,
std::function<void(const Progress &)>)
{
OpenTelemetry::SpanHolder span("Connection::sendQuery()", OpenTelemetry::CLIENT);
Expand Down Expand Up @@ -618,6 +620,18 @@ void Connection::sendQuery(
else
writeStringBinary("" /* empty string is a marker of the end of settings */, *out);

String external_roles_str;
if (server_revision >= DBMS_MIN_PROTOCOL_VERSION_WITH_INTERSERVER_EXTERNALLY_GRANTED_ROLES)
{
WriteBufferFromString buffer(external_roles_str);
writeVectorBinary(external_roles, buffer);
buffer.finalize();

LOG_DEBUG(log_wrapper.get(), "Sending external_roles with query: [{}] ({})", fmt::join(external_roles, ", "), external_roles.size());

writeStringBinary(external_roles_str, *out);
}

/// Interserver secret
if (server_revision >= DBMS_MIN_REVISION_WITH_INTERSERVER_SECRET)
{
Expand All @@ -638,6 +652,7 @@ void Connection::sendQuery(
data += query;
data += query_id;
data += client_info->initial_user;
data += external_roles_str;
/// TODO: add source/target host/ip-address

std::string hash = encodeSHA256(data);
Expand Down
1 change: 1 addition & 0 deletions src/Client/Connection.h
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ class Connection : public IServerConnection
const Settings * settings/* = nullptr */,
const ClientInfo * client_info/* = nullptr */,
bool with_pending_data/* = false */,
const std::vector<String> & external_roles,
std::function<void(const Progress &)> process_progress_callback) override;

void sendCancel() override;
Expand Down
7 changes: 4 additions & 3 deletions src/Client/HedgedConnections.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,8 @@ void HedgedConnections::sendQuery(
const String & query_id,
UInt64 stage,
ClientInfo & client_info,
bool with_pending_data)
bool with_pending_data,
const std::vector<String> & external_roles)
{
std::lock_guard lock(cancel_mutex);

Expand All @@ -163,7 +164,7 @@ void HedgedConnections::sendQuery(
hedged_connections_factory.skipReplicasWithTwoLevelAggregationIncompatibility();
}

auto send_query = [this, timeouts, query, query_id, stage, client_info, with_pending_data](ReplicaState & replica)
auto send_query = [this, timeouts, query, query_id, stage, client_info, with_pending_data, &external_roles](ReplicaState & replica)
{
Settings modified_settings = settings;

Expand All @@ -182,7 +183,7 @@ void HedgedConnections::sendQuery(
modified_settings.parallel_replica_offset = fd_to_replica_location[replica.packet_receiver->getFileDescriptor()].offset;
}

replica.connection->sendQuery(timeouts, query, /* query_parameters */ {}, query_id, stage, &modified_settings, &client_info, with_pending_data, {});
replica.connection->sendQuery(timeouts, query, /* query_parameters */ {}, query_id, stage, &modified_settings, &client_info, with_pending_data, external_roles, {});
replica.change_replica_timeout.setRelative(timeouts.receive_data_timeout);
replica.packet_receiver->setTimeout(hedged_connections_factory.getConnectionTimeouts().receive_timeout);
};
Expand Down
3 changes: 2 additions & 1 deletion src/Client/HedgedConnections.h
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,8 @@ class HedgedConnections : public IConnections
const String & query_id,
UInt64 stage,
ClientInfo & client_info,
bool with_pending_data) override;
bool with_pending_data,
const std::vector<String> & external_roles) override;

void sendReadTaskResponse(const String &) override
{
Expand Down
3 changes: 2 additions & 1 deletion src/Client/IConnections.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ class IConnections : boost::noncopyable
const String & query_id,
UInt64 stage,
ClientInfo & client_info,
bool with_pending_data) = 0;
bool with_pending_data,
const std::vector<String> & external_roles) = 0;

virtual void sendReadTaskResponse(const String &) = 0;
virtual void sendMergeTreeReadTaskResponse(const ParallelReadResponse & response) = 0;
Expand Down
1 change: 1 addition & 0 deletions src/Client/IServerConnection.h
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ class IServerConnection : boost::noncopyable
const Settings * settings,
const ClientInfo * client_info,
bool with_pending_data,
const std::vector<String>& external_roles,
std::function<void(const Progress &)> process_progress_callback) = 0;

virtual void sendCancel() = 0;
Expand Down
1 change: 1 addition & 0 deletions src/Client/LocalConnection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ void LocalConnection::sendQuery(
const Settings *,
const ClientInfo * client_info,
bool,
const std::vector<String> & /*external_roles*/,
std::function<void(const Progress &)> process_progress_callback)
{
/// Suggestion comes without client_info.
Expand Down
1 change: 1 addition & 0 deletions src/Client/LocalConnection.h
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ class LocalConnection : public IServerConnection, WithContext
const Settings * settings/* = nullptr */,
const ClientInfo * client_info/* = nullptr */,
bool with_pending_data/* = false */,
const std::vector<String> & external_roles,
std::function<void(const Progress &)> process_progress_callback) override;

void sendCancel() override;
Expand Down
7 changes: 4 additions & 3 deletions src/Client/MultiplexedConnections.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,8 @@ void MultiplexedConnections::sendQuery(
const String & query_id,
UInt64 stage,
ClientInfo & client_info,
bool with_pending_data)
bool with_pending_data,
const std::vector<String> & external_roles)
{
std::lock_guard lock(cancel_mutex);

Expand Down Expand Up @@ -157,14 +158,14 @@ void MultiplexedConnections::sendQuery(
modified_settings.parallel_replica_offset = i;

replica_states[i].connection->sendQuery(
timeouts, query, /* query_parameters */ {}, query_id, stage, &modified_settings, &client_info, with_pending_data, {});
timeouts, query, /* query_parameters */ {}, query_id, stage, &modified_settings, &client_info, with_pending_data, external_roles, {});
}
}
else
{
/// Use single replica.
replica_states[0].connection->sendQuery(
timeouts, query, /* query_parameters */ {}, query_id, stage, &modified_settings, &client_info, with_pending_data, {});
timeouts, query, /* query_parameters */ {}, query_id, stage, &modified_settings, &client_info, with_pending_data, external_roles, {});
}

sent_query = true;
Expand Down
3 changes: 2 additions & 1 deletion src/Client/MultiplexedConnections.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ class MultiplexedConnections final : public IConnections
const String & query_id,
UInt64 stage,
ClientInfo & client_info,
bool with_pending_data) override;
bool with_pending_data,
const std::vector<String> & external_roles) override;

void sendReadTaskResponse(const String &) override;
void sendMergeTreeReadTaskResponse(const ParallelReadResponse & response) override;
Expand Down
2 changes: 1 addition & 1 deletion src/Client/Suggest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ void Suggest::load(ContextPtr context, const ConnectionParameters & connection_p
void Suggest::fetch(IServerConnection & connection, const ConnectionTimeouts & timeouts, const std::string & query)
{
connection.sendQuery(
timeouts, query, {} /* query_parameters */, "" /* query_id */, QueryProcessingStage::Complete, nullptr, nullptr, false, {});
timeouts, query, {} /* query_parameters */, "" /* query_id */, QueryProcessingStage::Complete, nullptr, nullptr, false, {} /* external_roles*/, {});

while (true)
{
Expand Down
5 changes: 4 additions & 1 deletion src/Core/ProtocolDefines.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
/// NOTE: DBMS_TCP_PROTOCOL_VERSION has nothing common with VERSION_REVISION,
/// later is just a number for server version (one number instead of commit SHA)
/// for simplicity (sometimes it may be more convenient in some use cases).
#define DBMS_TCP_PROTOCOL_VERSION 54464
#define DBMS_TCP_PROTOCOL_VERSION 54465

#define DBMS_MIN_PROTOCOL_VERSION_WITH_INITIAL_QUERY_START_TIME 54449

Expand All @@ -77,3 +77,6 @@
#define DBMS_MIN_PROTOCOL_VERSION_WITH_TOTAL_BYTES_IN_PROGRESS 54463

#define DBMS_MIN_PROTOCOL_VERSION_WITH_TIMEZONE_UPDATES 54464

// Roles granted to user may be passed along with query
#define DBMS_MIN_PROTOCOL_VERSION_WITH_INTERSERVER_EXTERNALLY_GRANTED_ROLES 54465
15 changes: 8 additions & 7 deletions src/Core/Settings.h
Original file line number Diff line number Diff line change
Expand Up @@ -744,13 +744,6 @@ class IColumn;
M(Bool, throw_if_no_data_to_insert, true, "Enables or disables empty INSERTs, enabled by default", 0) \
M(Bool, compatibility_ignore_auto_increment_in_create_table, false, "Ignore AUTO_INCREMENT keyword in column declaration if true, otherwise return error. It simplifies migration from MySQL", 0) \
M(Bool, multiple_joins_try_to_keep_original_names, false, "Do not add aliases to top level expression list on multiple joins rewrite", 0) \
M(Bool, optimize_sorting_by_input_stream_properties, true, "Optimize sorting by sorting properties of input stream", 0) \
M(UInt64, insert_keeper_max_retries, 20, "Max retries for keeper operations during insert", 0) \
M(UInt64, insert_keeper_retry_initial_backoff_ms, 100, "Initial backoff timeout for keeper operations during insert", 0) \
M(UInt64, insert_keeper_retry_max_backoff_ms, 10000, "Max backoff timeout for keeper operations during insert", 0) \
M(Float, insert_keeper_fault_injection_probability, 0.0f, "Approximate probability of failure for a keeper request during insert. Valid value is in interval [0.0f, 1.0f]", 0) \
M(UInt64, insert_keeper_fault_injection_seed, 0, "0 - random seed, otherwise the setting value", 0) \
M(Bool, force_aggregation_in_order, false, "Force use of aggregation in order on remote nodes during distributed aggregation. PLEASE, NEVER CHANGE THIS SETTING VALUE MANUALLY!", IMPORTANT) \
M(UInt64, http_max_request_param_data_size, 10_MiB, "Limit on size of request data used as a query parameter in predefined HTTP requests.", 0) \
M(Bool, function_json_value_return_type_allow_nullable, false, "Allow function JSON_VALUE to return nullable type.", 0) \
M(Bool, function_json_value_return_type_allow_complex, false, "Allow function JSON_VALUE to return complex type, such as: struct, array, map.", 0) \
Expand All @@ -770,10 +763,18 @@ class IColumn;
M(UInt64, grace_hash_join_initial_buckets, 1, "Initial number of grace hash join buckets", 0) \
M(UInt64, grace_hash_join_max_buckets, 1024, "Limit on the number of grace hash join buckets", 0) \
M(Bool, optimize_distinct_in_order, true, "Enable DISTINCT optimization if some columns in DISTINCT form a prefix of sorting. For example, prefix of sorting key in merge tree or ORDER BY statement", 0) \
M(Bool, optimize_sorting_by_input_stream_properties, true, "Optimize sorting by sorting properties of input stream", 0) \
M(UInt64, insert_keeper_max_retries, 0, "Max retries for keeper operations during insert", 0) \
M(UInt64, insert_keeper_retry_initial_backoff_ms, 100, "Initial backoff timeout for keeper operations during insert", 0) \
M(UInt64, insert_keeper_retry_max_backoff_ms, 10000, "Max backoff timeout for keeper operations during insert", 0) \
M(Float, insert_keeper_fault_injection_probability, 0.0f, "Approximate probability of failure for a keeper request during insert. Valid value is in interval [0.0f, 1.0f]", 0) \
M(UInt64, insert_keeper_fault_injection_seed, 0, "0 - random seed, otherwise the setting value", 0) \
M(Bool, force_aggregation_in_order, false, "Force use of aggregation in order on remote nodes during distributed aggregation. PLEASE, NEVER CHANGE THIS SETTING VALUE MANUALLY!", IMPORTANT) \
M(Bool, allow_experimental_undrop_table_query, false, "Allow to use undrop query to restore dropped table in a limited time", 0) \
M(Bool, keeper_map_strict_mode, false, "Enforce additional checks during operations on KeeperMap. E.g. throw an exception on an insert for already existing key", 0) \
M(UInt64, extract_kvp_max_pairs_per_row, 1000, "Max number pairs that can be produced by extractKeyValuePairs function. Used to safeguard against consuming too much memory.", 0) \
M(Timezone, session_timezone, "", "The default timezone for current session or query. The server default timezone if empty.", 0) \
M(Bool, allow_extenral_roles_in_interserver_queries, true, "Enable pushing user roles from originator to other nodes while performing a query", 0) \
// End of COMMON_SETTINGS
// Please add settings related to formats into the FORMAT_FACTORY_SETTINGS and move obsolete settings to OBSOLETE_SETTINGS.

Expand Down
6 changes: 4 additions & 2 deletions src/Interpreters/Context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1057,18 +1057,19 @@ ConfigurationPtr Context::getUsersConfig()
return shared->users_config;
}

void Context::setUser(const UUID & user_id_)
void Context::setUser(const UUID & user_id_, const std::vector<UUID> & external_roles_)
{
auto lock = getLock();

user_id = user_id_;

access = getAccessControl().getContextAccess(
user_id_, /* current_roles = */ {}, /* use_default_roles = */ true, settings, current_database, client_info);
user_id_, /* current_roles = */ {}, external_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));
external_roles = std::make_shared<std::vector<UUID>>(external_roles_);

auto default_profile_info = access->getDefaultProfileInfo();
settings_constraints_and_current_profiles = default_profile_info->getConstraintsAndProfileIDs();
Expand Down Expand Up @@ -1140,6 +1141,7 @@ void Context::calculateAccessRights()
access = getAccessControl().getContextAccess(
*user_id,
current_roles ? *current_roles : std::vector<UUID>{},
external_roles ? *external_roles : std::vector<UUID>{},
/* use_default_roles = */ false,
settings,
current_database,
Expand Down
Loading