work in progress

This commit is contained in:
Sergei Trifonov 2022-09-02 04:12:05 +02:00
parent 899bf99d44
commit 7fec55eea4
14 changed files with 121 additions and 108 deletions

View File

@ -640,6 +640,12 @@
executed by any user. You can change this behaviour by setting this to true.
If it's set to true then this query requires "GRANT SELECT ON information_schema.<table>" just like as for ordinary tables. -->
<select_from_information_schema_requires_grant>false</select_from_information_schema_requires_grant>
<!-- By default, for backward compatibility a settings profile constraint for a specific setting inherit every not set field from
previous profile. You can change this behaviour by setting this to true.
If it's set to true then if settings profile has a constraint for a specific setting, then this constraint completely cancels all
actions of previous constraint (defined in other profiles) for the same specific setting, including fields that are not set by new constraint. -->
<settings_constraints_replace_previous>false</settings_constraints_replace_previous>
</access_control_improvements>
<!-- Default profile of settings. -->

View File

@ -171,6 +171,7 @@ void AccessControl::setUpFromMainConfig(const Poco::Util::AbstractConfiguration
setOnClusterQueriesRequireClusterGrant(config_.getBool("access_control_improvements.on_cluster_queries_require_cluster_grant", false));
setSelectFromSystemDatabaseRequiresGrant(config_.getBool("access_control_improvements.select_from_system_db_requires_grant", false));
setSelectFromInformationSchemaRequiresGrant(config_.getBool("access_control_improvements.select_from_information_schema_requires_grant", false));
setSettingsConstraintsReplacePrevious(config_.getBool("access_control_improvements.settings_constraints_replace_previous", false));
addStoragesFromMainConfig(config_, config_path_, get_zookeeper_function_);
}

View File

@ -158,6 +158,9 @@ public:
void setSelectFromInformationSchemaRequiresGrant(bool enable) { select_from_information_schema_requires_grant = enable; }
bool doesSelectFromInformationSchemaRequireGrant() const { return select_from_information_schema_requires_grant; }
void setSettingsConstraintsReplacePrevious(bool enable) { settings_constraints_replace_previous = enable; }
bool doesSettingsConstraintsReplacePrevious() const { return settings_constraints_replace_previous; }
std::shared_ptr<const ContextAccess> getContextAccess(
const UUID & user_id,
const std::vector<UUID> & current_roles,
@ -223,6 +226,7 @@ private:
std::atomic_bool on_cluster_queries_require_cluster_grant = false;
std::atomic_bool select_from_system_db_requires_grant = false;
std::atomic_bool select_from_information_schema_requires_grant = false;
std::atomic_bool settings_constraints_replace_previous = false;
};
}

View File

@ -36,46 +36,43 @@ void SettingsConstraints::clear()
}
void SettingsConstraints::constrainMinValue(const String & setting_name, const Field & min_value)
void SettingsConstraints::setMinValue(const String & setting_name, const Field & min_value)
{
constraints[setting_name].min_value = Settings::castValueUtil(setting_name, min_value);
}
void SettingsConstraints::constrainMaxValue(const String & setting_name, const Field & max_value)
void SettingsConstraints::setMaxValue(const String & setting_name, const Field & max_value)
{
constraints[setting_name].max_value = Settings::castValueUtil(setting_name, max_value);
}
void SettingsConstraints::constrainReadOnly(const String & setting_name, bool read_only)
void SettingsConstraints::setIsConst(const String & setting_name, bool is_const)
{
constraints[setting_name].read_only = read_only;
constraints[setting_name].is_const = is_const;
}
void SettingsConstraints::allowMinValue(const String & setting_name, const Field & min_value)
void SettingsConstraints::setChangableInReadonly(const String & setting_name, bool changeable_in_readonly)
{
allowances[setting_name].min_value = Settings::castValueUtil(setting_name, min_value);
constraints[setting_name].changeable_in_readonly = changeable_in_readonly;
}
void SettingsConstraints::allowMaxValue(const String & setting_name, const Field & max_value)
{
allowances[setting_name].max_value = Settings::castValueUtil(setting_name, max_value);
}
void SettingsConstraints::allowReadOnly(const String & setting_name, bool read_only)
{
allowances[setting_name].read_only = read_only;
}
void SettingsConstraints::get(const Settings & current_settings, std::string_view setting_name, Field & min_value, Field & max_value, bool & read_only) const
void SettingsConstraints::get(const Settings & current_settings, std::string_view setting_name, Field & min_value, Field & max_value, bool & is_const) const
{
auto range = getRange(current_settings, setting_name);
min_value = range.min_value;
max_value = range.max_value;
read_only = range.read_only;
is_const = range.is_const;
}
void SettingsConstraints::merge(const SettingsConstraints & other)
{
if (access_control.doesSettingsConstraintsReplacePrevious())
{
for (const auto & [other_name, other_constraint] : other.constraints)
constraints[other_name] = other_constraint;
}
else
{
for (const auto & [other_name, other_constraint] : other.constraints)
{
auto & constraint = constraints[other_name];
@ -83,18 +80,9 @@ void SettingsConstraints::merge(const SettingsConstraints & other)
constraint.min_value = other_constraint.min_value;
if (!other_constraint.max_value.isNull())
constraint.max_value = other_constraint.max_value;
if (other_constraint.read_only)
constraint.read_only = true;
if (other_constraint.is_const)
constraint.is_const = true; // NOTE: In this mode <readonly/> flag cannot be overriden to be false
}
for (const auto & [other_name, other_allowance] : other.allowances)
{
auto & allowance = allowances[other_name];
if (!other_allowance.min_value.isNull())
allowance.min_value = other_allowance.min_value;
if (!other_allowance.max_value.isNull())
allowance.max_value = other_allowance.max_value;
if (other_allowance.read_only)
allowance.read_only = true;
}
}
@ -229,7 +217,7 @@ bool SettingsConstraints::Range::check(SettingChange & change, const Field & new
return false;
}
if (read_only)
if (is_const)
{
if (reaction == THROW_ON_VIOLATION)
throw Exception("Setting " + setting_name + " should not be changed", ErrorCodes::SETTING_CONSTRAINT_VIOLATION);
@ -279,36 +267,34 @@ SettingsConstraints::Range SettingsConstraints::getRange(const Settings & curren
/** The `readonly` value is understood as follows:
* 0 - no read-only restrictions.
* 1 - only read requests, as well as changing explicitly allowed settings.
* 1 - only read requests, as well as changing settings with `changable_in_readonly` flag.
* 2 - only read requests, as well as changing settings, except for the `readonly` setting.
*/
if (current_settings.readonly > 1 && setting_name == "readonly")
return Range::forbidden("Cannot modify 'readonly' setting in readonly mode", ErrorCodes::READONLY);
auto it = constraints.find(setting_name);
if (current_settings.readonly == 1)
{
auto it = allowances.find(setting_name);
if (it == allowances.end())
if (it == constraints.end() || !it->second.changeable_in_readonly)
return Range::forbidden("Cannot modify '" + String(setting_name) + "' setting in readonly mode", ErrorCodes::READONLY);
return it->second;
}
else // For both readonly=0 and readonly=2
{
auto it = constraints.find(setting_name);
if (it == constraints.end())
return Range::allowed();
return it->second;
}
return it->second;
}
bool SettingsConstraints::Range::operator==(const Range & other) const
{
return read_only == other.read_only && min_value == other.min_value && max_value == other.max_value;
return is_const == other.is_const && changeable_in_readonly == other.changeable_in_readonly && min_value == other.min_value && max_value == other.max_value;
}
bool operator ==(const SettingsConstraints & left, const SettingsConstraints & right)
{
return left.constraints == right.constraints && left.allowances == right.allowances;
return left.constraints == right.constraints;
}
}

View File

@ -35,15 +35,12 @@ class AccessControl;
* <max>20000000000</max>
* </max_memory_usage>
* <force_index_by_date>
* <readonly/>
* <const/>
* </force_index_by_date>
* <max_threads>
* <changable_in_readonly/>
* </max_threads>
* </constraints>
* <allow>
* <max_memory_usage>
* <min>200000</min>
* <max>10000000000</max>
* <max_memory_usage>
* </allow>
* </user_profile>
* </profiles>
*
@ -51,7 +48,7 @@ class AccessControl;
* If a setting cannot be change due to the read-only mode this class throws an exception.
* The value of `readonly` is understood as follows:
* 0 - not read-only mode, no additional checks.
* 1 - only read queries, as well as changing explicitly allowed settings.
* 1 - only read queries, as well as changing settings with <changable_in_readonly/> flag.
* 2 - only read queries and you can change the settings, except for the `readonly` setting.
*
*/
@ -68,14 +65,12 @@ public:
void clear();
bool empty() const { return constraints.empty(); }
void constrainMinValue(const String & setting_name, const Field & min_value);
void constrainMaxValue(const String & setting_name, const Field & max_value);
void constrainReadOnly(const String & setting_name, bool read_only);
void allowMinValue(const String & setting_name, const Field & min_value);
void allowMaxValue(const String & setting_name, const Field & max_value);
void allowReadOnly(const String & setting_name, bool read_only);
void setMinValue(const String & setting_name, const Field & min_value);
void setMaxValue(const String & setting_name, const Field & max_value);
void setIsConst(const String & setting_name, bool is_const);
void setChangableInReadonly(const String & setting_name, bool is_const);
void get(const Settings & current_settings, std::string_view setting_name, Field & min_value, Field & max_value, bool & read_only) const;
void get(const Settings & current_settings, std::string_view setting_name, Field & min_value, Field & max_value, bool & is_const) const;
void merge(const SettingsConstraints & other);
@ -99,7 +94,8 @@ private:
struct Range
{
bool read_only = false;
bool is_const = false;
bool changeable_in_readonly = false;
Field min_value;
Field max_value;
@ -119,7 +115,7 @@ private:
static Range forbidden(const String & explain, int code)
{
return Range{.read_only = true, .explain = explain, .code = code};
return Range{.is_const = true, .explain = explain, .code = code};
}
};
@ -143,7 +139,6 @@ private:
// Special container for heterogeneous lookups: to avoid `String` construction during `find(std::string_view)`
using RangeMap = std::unordered_map<String, Range, StringHash, std::equal_to<>>;
RangeMap constraints;
RangeMap allowances;
const AccessControl * access_control = nullptr;
};

View File

@ -56,7 +56,23 @@ void SettingsProfileElement::init(const ASTSettingsProfileElement & ast, const A
value = ast.value;
min_value = ast.min_value;
max_value = ast.max_value;
readonly = ast.readonly;
changeable_in_readonly = false;
switch (ast.type)
{
case ASTSettingsProfileElement::ConstraintType::NONE:
is_const.reset();
break;
case ASTSettingsProfileElement::ConstraintType::WRITABLE:
is_const.emplace(false);
break;
case ASTSettingsProfileElement::ConstraintType::CONST:
is_const.emplace(true);
break;
case ASTSettingsProfileElement::ConstraintType::CHANGEABLE_IN_READONLY:
is_const.reset();
changeable_in_readonly = true;
break;
}
if (!value.isNull())
value = Settings::castValueUtil(setting_name, value);
@ -208,25 +224,14 @@ SettingsConstraints SettingsProfileElements::toSettingsConstraints(const AccessC
{
if (!elem.setting_name.empty() && (elem.setting_name != ALLOW_BACKUP_SETTING_NAME))
{
switch (elem.kind)
{
case SettingsProfileElement::RangeKind::Constrain:
if (!elem.min_value.isNull())
res.constrainMinValue(elem.setting_name, elem.min_value);
res.setMinValue(elem.setting_name, elem.min_value);
if (!elem.max_value.isNull())
res.constrainMaxValue(elem.setting_name, elem.max_value);
if (elem.readonly)
res.constrainReadOnly(elem.setting_name, *elem.readonly);
break;
case SettingsProfileElement::RangeKind::Allow:
if (!elem.min_value.isNull())
res.allowMinValue(elem.setting_name, elem.min_value);
if (!elem.max_value.isNull())
res.allowMaxValue(elem.setting_name, elem.max_value);
if (elem.readonly)
res.allowReadOnly(elem.setting_name, *elem.readonly);
break;
}
res.setMaxValue(elem.setting_name, elem.max_value);
if (elem.is_const)
res.setIsConst(elem.setting_name, *elem.is_const);
if (elem.changeable_in_readonly)
res.setChangableInReadonly(elem.setting_name, true);
}
}
return res;

View File

@ -21,20 +21,14 @@ struct SettingsProfileElement
{
std::optional<UUID> parent_profile;
enum class RangeKind
{
Constrain = 0,
Allow
};
String setting_name;
Field value;
RangeKind kind = RangeKind::Constrain;
Field min_value;
Field max_value;
std::optional<bool> readonly;
std::optional<bool> is_const;
bool changeable_in_readonly;
auto toTuple() const { return std::tie(parent_profile, setting_name, value, kind, min_value, max_value, readonly); }
auto toTuple() const { return std::tie(parent_profile, setting_name, value, min_value, max_value, is_const, changeable_in_readonly); }
friend bool operator==(const SettingsProfileElement & lhs, const SettingsProfileElement & rhs) { return lhs.toTuple() == rhs.toTuple(); }
friend bool operator!=(const SettingsProfileElement & lhs, const SettingsProfileElement & rhs) { return !(lhs == rhs); }
friend bool operator <(const SettingsProfileElement & lhs, const SettingsProfileElement & rhs) { return lhs.toTuple() < rhs.toTuple(); }

View File

@ -52,10 +52,22 @@ void ASTSettingsProfileElement::formatImpl(const FormatSettings & settings, Form
<< applyVisitor(FieldVisitorToString{}, max_value);
}
if (readonly)
switch (type)
{
settings.ostr << (settings.hilite ? IAST::hilite_keyword : "") << (*readonly ? " READONLY" : " WRITABLE")
case ConstraintType::NONE:
break;
case ConstraintType::WRITABLE:
settings.ostr << (settings.hilite ? IAST::hilite_keyword : "") << " WRITABLE"
<< (settings.hilite ? IAST::hilite_none : "");
break;
case ConstraintType::CONST:
settings.ostr << (settings.hilite ? IAST::hilite_keyword : "") << " READONLY"
<< (settings.hilite ? IAST::hilite_none : "");
break;
case ConstraintType::CHANGEABLE_IN_READONLY:
settings.ostr << (settings.hilite ? IAST::hilite_keyword : "") << " CHANGEABLE_IN_READONLY"
<< (settings.hilite ? IAST::hilite_none : "");
break;
}
}

View File

@ -7,7 +7,7 @@
namespace DB
{
/** Represents a settings profile's element like the following
* {variable [= value] [MIN [=] min_value] [MAX [=] max_value] [READONLY|WRITABLE]} | PROFILE 'profile_name'
* {variable [= value] [MIN [=] min_value] [MAX [=] max_value] [CONST|READONLY|WRITABLE|CHANGEABLE_IN_READONLY]} | PROFILE 'profile_name'
*/
class ASTSettingsProfileElement : public IAST
{
@ -17,7 +17,13 @@ public:
Field value;
Field min_value;
Field max_value;
std::optional<bool> readonly;
enum class ConstraintType {
NONE, // default
WRITABLE,
CONST, // equals READONLY
CHANGEABLE_IN_READONLY,
};
ConstraintType type;
bool id_mode = false; /// If true then `parent_profile` keeps UUID, not a name.
bool use_inherit_keyword = false; /// If true then this element is a part of ASTCreateSettingsProfileQuery.
@ -30,7 +36,7 @@ public:
/** Represents settings profile's elements like the following
* {{variable [= value] [MIN [=] min_value] [MAX [=] max_value] [READONLY|WRITABLE]} | PROFILE 'profile_name'} [,...]
* {{variable [= value] [MIN [=] min_value] [MAX [=] max_value] [CONST|READONLY|WRITABLE|CHANGEABLE_IN_READONLY]} | PROFILE 'profile_name'} [,...]
*/
class ASTSettingsProfileElements : public IAST
{

View File

@ -40,8 +40,9 @@ void StorageSystemSettings::fillData(MutableColumns & res_columns, ContextPtr co
res_columns[3]->insert(setting.getDescription());
Field min, max;
bool read_only = false;
constraints.get(settings, setting_name, min, max, read_only);
bool is_const = false;
bool changeable_in_readonly = false;
constraints.get(settings, setting_name, min, max, is_const, changeable_in_readonly);
/// These two columns can accept strings only.
if (!min.isNull())

View File

@ -29,7 +29,7 @@ NamesAndTypesList StorageSystemSettingsProfileElements::getNamesAndTypes()
{"min", std::make_shared<DataTypeNullable>(std::make_shared<DataTypeString>())},
{"max", std::make_shared<DataTypeNullable>(std::make_shared<DataTypeString>())},
{"readonly", std::make_shared<DataTypeNullable>(std::make_shared<DataTypeUInt8>())},
{"allowance", std::make_shared<DataTypeNullable>(std::make_shared<DataTypeUInt8>())},
{"changeable_in_readonly", std::make_shared<DataTypeNullable>(std::make_shared<DataTypeUInt8>())},
{"inherit_profile", std::make_shared<DataTypeNullable>(std::make_shared<DataTypeString>())},
};
return names_and_types;
@ -65,8 +65,8 @@ void StorageSystemSettingsProfileElements::fillData(MutableColumns & res_columns
auto & column_max_null_map = assert_cast<ColumnNullable &>(*res_columns[i++]).getNullMapData();
auto & column_readonly = assert_cast<ColumnUInt8 &>(assert_cast<ColumnNullable &>(*res_columns[i]).getNestedColumn()).getData();
auto & column_readonly_null_map = assert_cast<ColumnNullable &>(*res_columns[i++]).getNullMapData();
auto & column_allowance = assert_cast<ColumnUInt8 &>(assert_cast<ColumnNullable &>(*res_columns[i]).getNestedColumn()).getData();
auto & column_allowance_null_map = assert_cast<ColumnNullable &>(*res_columns[i++]).getNullMapData();
auto & column_changeable_in_readonly = assert_cast<ColumnUInt8 &>(assert_cast<ColumnNullable &>(*res_columns[i]).getNestedColumn()).getData();
auto & column_changeable_in_readonly_null_map = assert_cast<ColumnNullable &>(*res_columns[i++]).getNullMapData();
auto & column_inherit_profile = assert_cast<ColumnString &>(assert_cast<ColumnNullable &>(*res_columns[i]).getNestedColumn());
auto & column_inherit_profile_null_map = assert_cast<ColumnNullable &>(*res_columns[i++]).getNullMapData();
@ -104,23 +104,23 @@ void StorageSystemSettingsProfileElements::fillData(MutableColumns & res_columns
}
bool inserted_readonly = false;
if (element.readonly && !element.setting_name.empty())
if (element.is_const && !element.setting_name.empty())
{
column_readonly.push_back(*element.readonly);
column_readonly_null_map.push_back(false);
inserted_readonly = true;
}
bool inserted_allowance = false;
if (element.kind == SettingsProfileElement::RangeKind::Allow && !element.setting_name.empty())
bool inserted_changeable_in_readonly = false;
if (element.changeable_in_readonly && !element.setting_name.empty())
{
column_allowance.push_back(true);
column_allowance_null_map.push_back(false);
inserted_allowance = true;
column_changeable_in_readonly.push_back(true);
column_changeable_in_readonly_null_map.push_back(false);
inserted_changeable_in_readonly = true;
}
bool inserted_setting_name = false;
if (inserted_value || inserted_min || inserted_max || inserted_readonly || inserted_allowance)
if (inserted_value || inserted_min || inserted_max || inserted_readonly || inserted_changeable_in_readonly)
{
const auto & setting_name = element.setting_name;
column_setting_name.insertData(setting_name.data(), setting_name.size());

View File

@ -4,5 +4,6 @@
<on_cluster_queries_require_cluster_grant>true</on_cluster_queries_require_cluster_grant>
<select_from_system_db_requires_grant>true</select_from_system_db_requires_grant>
<select_from_information_schema_requires_grant>true</select_from_information_schema_requires_grant>
<settings_constraints_replace_previous>true</settings_constraints_replace_previous>
</access_control_improvements>
</clickhouse>

View File

@ -24,5 +24,6 @@
<on_cluster_queries_require_cluster_grant>true</on_cluster_queries_require_cluster_grant>
<select_from_system_db_requires_grant>true</select_from_system_db_requires_grant>
<select_from_information_schema_requires_grant>true</select_from_information_schema_requires_grant>
<settings_constraints_replace_previous>true</settings_constraints_replace_previous>
</access_control_improvements>
</clickhouse>

View File

@ -3,5 +3,6 @@
<users_without_row_policies_can_read_rows remove="remove"/>
<select_from_system_db_requires_grant remove="remove"/>
<select_from_information_schema_requires_grant remove="remove"/>
<settings_constraints_replace_previous remove="remove"/>
</access_control_improvements>
</clickhouse>