mirror of
https://github.com/ClickHouse/ClickHouse.git
synced 2024-11-29 19:12:03 +00:00
Merge pull request #60144 from ClickHouse/per-user-s3-settings
Support specifying users for S3 settings
This commit is contained in:
commit
34e8e673e9
@ -127,7 +127,7 @@ BackupReaderS3::BackupReaderS3(
|
|||||||
: BackupReaderDefault(read_settings_, write_settings_, getLogger("BackupReaderS3"))
|
: BackupReaderDefault(read_settings_, write_settings_, getLogger("BackupReaderS3"))
|
||||||
, s3_uri(s3_uri_)
|
, s3_uri(s3_uri_)
|
||||||
, data_source_description{DataSourceType::ObjectStorage, ObjectStorageType::S3, MetadataStorageType::None, s3_uri.endpoint, false, false}
|
, data_source_description{DataSourceType::ObjectStorage, ObjectStorageType::S3, MetadataStorageType::None, s3_uri.endpoint, false, false}
|
||||||
, s3_settings(context_->getStorageS3Settings().getSettings(s3_uri.uri.toString()))
|
, s3_settings(context_->getStorageS3Settings().getSettings(s3_uri.uri.toString(), context_->getUserName()))
|
||||||
{
|
{
|
||||||
auto & request_settings = s3_settings.request_settings;
|
auto & request_settings = s3_settings.request_settings;
|
||||||
request_settings.updateFromSettings(context_->getSettingsRef());
|
request_settings.updateFromSettings(context_->getSettingsRef());
|
||||||
@ -217,7 +217,7 @@ BackupWriterS3::BackupWriterS3(
|
|||||||
: BackupWriterDefault(read_settings_, write_settings_, getLogger("BackupWriterS3"))
|
: BackupWriterDefault(read_settings_, write_settings_, getLogger("BackupWriterS3"))
|
||||||
, s3_uri(s3_uri_)
|
, s3_uri(s3_uri_)
|
||||||
, data_source_description{DataSourceType::ObjectStorage, ObjectStorageType::S3, MetadataStorageType::None, s3_uri.endpoint, false, false}
|
, data_source_description{DataSourceType::ObjectStorage, ObjectStorageType::S3, MetadataStorageType::None, s3_uri.endpoint, false, false}
|
||||||
, s3_settings(context_->getStorageS3Settings().getSettings(s3_uri.uri.toString()))
|
, s3_settings(context_->getStorageS3Settings().getSettings(s3_uri.uri.toString(), context_->getUserName()))
|
||||||
{
|
{
|
||||||
auto & request_settings = s3_settings.request_settings;
|
auto & request_settings = s3_settings.request_settings;
|
||||||
request_settings.updateFromSettings(context_->getSettingsRef());
|
request_settings.updateFromSettings(context_->getSettingsRef());
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
#include <IO/S3Common.h>
|
#include <IO/S3Common.h>
|
||||||
|
|
||||||
#include <Common/Exception.h>
|
#include <Common/Exception.h>
|
||||||
|
#include <Common/StringUtils/StringUtils.h>
|
||||||
#include <Poco/Util/AbstractConfiguration.h>
|
#include <Poco/Util/AbstractConfiguration.h>
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
#if USE_AWS_S3
|
#if USE_AWS_S3
|
||||||
@ -124,6 +126,15 @@ AuthSettings AuthSettings::loadFromConfig(const std::string & config_elem, const
|
|||||||
HTTPHeaderEntries headers = getHTTPHeaders(config_elem, config);
|
HTTPHeaderEntries headers = getHTTPHeaders(config_elem, config);
|
||||||
ServerSideEncryptionKMSConfig sse_kms_config = getSSEKMSConfig(config_elem, config);
|
ServerSideEncryptionKMSConfig sse_kms_config = getSSEKMSConfig(config_elem, config);
|
||||||
|
|
||||||
|
std::unordered_set<std::string> users;
|
||||||
|
Poco::Util::AbstractConfiguration::Keys keys;
|
||||||
|
config.keys(config_elem, keys);
|
||||||
|
for (const auto & key : keys)
|
||||||
|
{
|
||||||
|
if (startsWith(key, "user"))
|
||||||
|
users.insert(config.getString(config_elem + "." + key));
|
||||||
|
}
|
||||||
|
|
||||||
return AuthSettings
|
return AuthSettings
|
||||||
{
|
{
|
||||||
std::move(access_key_id), std::move(secret_access_key), std::move(session_token),
|
std::move(access_key_id), std::move(secret_access_key), std::move(session_token),
|
||||||
@ -134,10 +145,16 @@ AuthSettings AuthSettings::loadFromConfig(const std::string & config_elem, const
|
|||||||
use_environment_credentials,
|
use_environment_credentials,
|
||||||
use_insecure_imds_request,
|
use_insecure_imds_request,
|
||||||
expiration_window_seconds,
|
expiration_window_seconds,
|
||||||
no_sign_request
|
no_sign_request,
|
||||||
|
std::move(users)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool AuthSettings::canBeUsedByUser(const String & user) const
|
||||||
|
{
|
||||||
|
return users.empty() || users.contains(user);
|
||||||
|
}
|
||||||
|
|
||||||
bool AuthSettings::hasUpdates(const AuthSettings & other) const
|
bool AuthSettings::hasUpdates(const AuthSettings & other) const
|
||||||
{
|
{
|
||||||
AuthSettings copy = *this;
|
AuthSettings copy = *this;
|
||||||
@ -173,6 +190,8 @@ void AuthSettings::updateFrom(const AuthSettings & from)
|
|||||||
|
|
||||||
if (from.no_sign_request.has_value())
|
if (from.no_sign_request.has_value())
|
||||||
no_sign_request = from.no_sign_request;
|
no_sign_request = from.no_sign_request;
|
||||||
|
|
||||||
|
users.insert(from.users.begin(), from.users.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
@ -92,9 +93,13 @@ struct AuthSettings
|
|||||||
std::optional<uint64_t> expiration_window_seconds;
|
std::optional<uint64_t> expiration_window_seconds;
|
||||||
std::optional<bool> no_sign_request;
|
std::optional<bool> no_sign_request;
|
||||||
|
|
||||||
|
std::unordered_set<std::string> users;
|
||||||
|
|
||||||
bool hasUpdates(const AuthSettings & other) const;
|
bool hasUpdates(const AuthSettings & other) const;
|
||||||
void updateFrom(const AuthSettings & from);
|
void updateFrom(const AuthSettings & from);
|
||||||
|
|
||||||
|
bool canBeUsedByUser(const String & user) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool operator==(const AuthSettings & other) const = default;
|
bool operator==(const AuthSettings & other) const = default;
|
||||||
};
|
};
|
||||||
|
@ -1385,7 +1385,7 @@ const StorageS3::Configuration & StorageS3::getConfiguration()
|
|||||||
|
|
||||||
bool StorageS3::Configuration::update(const ContextPtr & context)
|
bool StorageS3::Configuration::update(const ContextPtr & context)
|
||||||
{
|
{
|
||||||
auto s3_settings = context->getStorageS3Settings().getSettings(url.uri.toString());
|
auto s3_settings = context->getStorageS3Settings().getSettings(url.uri.toString(), context->getUserName());
|
||||||
request_settings = s3_settings.request_settings;
|
request_settings = s3_settings.request_settings;
|
||||||
request_settings.updateFromSettings(context->getSettings());
|
request_settings.updateFromSettings(context->getSettings());
|
||||||
|
|
||||||
|
@ -293,7 +293,7 @@ void StorageS3Settings::loadFromConfig(const String & config_elem, const Poco::U
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
S3Settings StorageS3Settings::getSettings(const String & endpoint) const
|
S3Settings StorageS3Settings::getSettings(const String & endpoint, const String & user) const
|
||||||
{
|
{
|
||||||
std::lock_guard lock(mutex);
|
std::lock_guard lock(mutex);
|
||||||
auto next_prefix_setting = s3_settings.upper_bound(endpoint);
|
auto next_prefix_setting = s3_settings.upper_bound(endpoint);
|
||||||
@ -302,7 +302,8 @@ S3Settings StorageS3Settings::getSettings(const String & endpoint) const
|
|||||||
for (auto possible_prefix_setting = next_prefix_setting; possible_prefix_setting != s3_settings.begin();)
|
for (auto possible_prefix_setting = next_prefix_setting; possible_prefix_setting != s3_settings.begin();)
|
||||||
{
|
{
|
||||||
std::advance(possible_prefix_setting, -1);
|
std::advance(possible_prefix_setting, -1);
|
||||||
if (boost::algorithm::starts_with(endpoint, possible_prefix_setting->first))
|
const auto & [endpoint_prefix, settings] = *possible_prefix_setting;
|
||||||
|
if (boost::algorithm::starts_with(endpoint, endpoint_prefix) && settings.auth_settings.canBeUsedByUser(user))
|
||||||
return possible_prefix_setting->second;
|
return possible_prefix_setting->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,7 +112,7 @@ class StorageS3Settings
|
|||||||
public:
|
public:
|
||||||
void loadFromConfig(const String & config_elem, const Poco::Util::AbstractConfiguration & config, const Settings & settings);
|
void loadFromConfig(const String & config_elem, const Poco::Util::AbstractConfiguration & config, const Settings & settings);
|
||||||
|
|
||||||
S3Settings getSettings(const String & endpoint) const;
|
S3Settings getSettings(const String & endpoint, const String & user) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
mutable std::mutex mutex;
|
mutable std::mutex mutex;
|
||||||
|
@ -10,6 +10,13 @@
|
|||||||
<upload_part_size_multiply_parts_count_threshold>3</upload_part_size_multiply_parts_count_threshold>
|
<upload_part_size_multiply_parts_count_threshold>3</upload_part_size_multiply_parts_count_threshold>
|
||||||
<upload_part_size_multiply_factor>2</upload_part_size_multiply_factor>
|
<upload_part_size_multiply_factor>2</upload_part_size_multiply_factor>
|
||||||
</multipart>
|
</multipart>
|
||||||
|
<limited>
|
||||||
|
<endpoint>http://minio1:9001/root/data/backups/limited/</endpoint>
|
||||||
|
<access_key_id>minio</access_key_id>
|
||||||
|
<secret_access_key>minio123</secret_access_key>
|
||||||
|
<user>superuser1</user>
|
||||||
|
<user>superuser2</user>
|
||||||
|
</limited>
|
||||||
</s3>
|
</s3>
|
||||||
<backup_threads>1</backup_threads>
|
<backup_threads>1</backup_threads>
|
||||||
<restore_threads>1</restore_threads>
|
<restore_threads>1</restore_threads>
|
||||||
|
@ -452,3 +452,57 @@ def test_backup_to_zip():
|
|||||||
backup_name = new_backup_name()
|
backup_name = new_backup_name()
|
||||||
backup_destination = f"S3('http://minio1:9001/root/data/backups/{backup_name}.zip', 'minio', 'minio123')"
|
backup_destination = f"S3('http://minio1:9001/root/data/backups/{backup_name}.zip', 'minio', 'minio123')"
|
||||||
check_backup_and_restore(storage_policy, backup_destination)
|
check_backup_and_restore(storage_policy, backup_destination)
|
||||||
|
|
||||||
|
|
||||||
|
def test_user_specific_auth(start_cluster):
|
||||||
|
def create_user(user):
|
||||||
|
node.query(f"CREATE USER {user}")
|
||||||
|
node.query(f"GRANT CURRENT GRANTS ON *.* TO {user}")
|
||||||
|
|
||||||
|
create_user("superuser1")
|
||||||
|
create_user("superuser2")
|
||||||
|
create_user("regularuser")
|
||||||
|
|
||||||
|
node.query("CREATE TABLE specific_auth (col UInt64) ENGINE=Memory")
|
||||||
|
|
||||||
|
assert "Access Denied" in node.query_and_get_error(
|
||||||
|
"BACKUP TABLE specific_auth TO S3('http://minio1:9001/root/data/backups/limited/backup1.zip')"
|
||||||
|
)
|
||||||
|
assert "Access Denied" in node.query_and_get_error(
|
||||||
|
"BACKUP TABLE specific_auth TO S3('http://minio1:9001/root/data/backups/limited/backup1.zip')",
|
||||||
|
user="regularuser",
|
||||||
|
)
|
||||||
|
|
||||||
|
node.query(
|
||||||
|
"BACKUP TABLE specific_auth TO S3('http://minio1:9001/root/data/backups/limited/backup1.zip')",
|
||||||
|
user="superuser1",
|
||||||
|
)
|
||||||
|
node.query(
|
||||||
|
"RESTORE TABLE specific_auth FROM S3('http://minio1:9001/root/data/backups/limited/backup1.zip')",
|
||||||
|
user="superuser1",
|
||||||
|
)
|
||||||
|
|
||||||
|
node.query(
|
||||||
|
"BACKUP TABLE specific_auth TO S3('http://minio1:9001/root/data/backups/limited/backup2.zip')",
|
||||||
|
user="superuser2",
|
||||||
|
)
|
||||||
|
node.query(
|
||||||
|
"RESTORE TABLE specific_auth FROM S3('http://minio1:9001/root/data/backups/limited/backup2.zip')",
|
||||||
|
user="superuser2",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert "Access Denied" in node.query_and_get_error(
|
||||||
|
"RESTORE TABLE specific_auth FROM S3('http://minio1:9001/root/data/backups/limited/backup1.zip')",
|
||||||
|
user="regularuser",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert "HTTP response code: 403" in node.query_and_get_error(
|
||||||
|
"SELECT * FROM s3('http://minio1:9001/root/data/backups/limited/backup1.zip', 'RawBLOB')",
|
||||||
|
user="regularuser",
|
||||||
|
)
|
||||||
|
node.query(
|
||||||
|
"SELECT * FROM s3('http://minio1:9001/root/data/backups/limited/backup1.zip', 'RawBLOB')",
|
||||||
|
user="superuser1",
|
||||||
|
)
|
||||||
|
|
||||||
|
node.query("DROP TABLE IF EXISTS test.specific_auth")
|
||||||
|
Loading…
Reference in New Issue
Block a user