MXS-2457 Treat string args as fields
The masking filter will now consider all string arguments to
functions to be fields. This in order to prevent bypassing of
the masking with
> set @@sql_mode='ANSI_QUOTES';
> select concat("ssn") from masking;
This may lead to false positives, but no can do.
This commit is contained in:
@ -96,6 +96,26 @@ Please see the configuration parameter
|
|||||||
[require_fully_parsed](#require_fully_parsed)
|
[require_fully_parsed](#require_fully_parsed)
|
||||||
for how to change the default behaviour.
|
for how to change the default behaviour.
|
||||||
|
|
||||||
|
From MaxScale 2.3.7 onwards, the masking filter will treat any strings
|
||||||
|
passed to functions as if they were fields. The reason is that as the
|
||||||
|
MaxScale query classifier is not aware of whether `ANSI_QUOTES` is
|
||||||
|
enabled or not, it is possible to bypass the masking by turning that
|
||||||
|
option on.
|
||||||
|
```
|
||||||
|
mysql> set @@sql_mode = 'ANSI_QUOTES';
|
||||||
|
mysql> select concat("ssn") from managers;
|
||||||
|
```
|
||||||
|
Before this change, the content of the field `ssn` would have been
|
||||||
|
returned in clear text even if the column should have been masked.
|
||||||
|
|
||||||
|
Note that this change will mean that there may be false positives
|
||||||
|
if `ANSI_QUOTES` is not enabled and a string argument happens to
|
||||||
|
be the same as the name of a field to be masked.
|
||||||
|
|
||||||
|
Please see the configuration parameter
|
||||||
|
[treat_string_arg_as_field(#treat_string_arg_as_field)
|
||||||
|
for how to change the default behaviour.
|
||||||
|
|
||||||
## Limitations
|
## Limitations
|
||||||
|
|
||||||
The masking filter can _only_ be used for masking columns of the following
|
The masking filter can _only_ be used for masking columns of the following
|
||||||
@ -215,6 +235,17 @@ Note that if this parameter is set to false, then `prevent_function_usage`,
|
|||||||
less effective, as it with a statement that can not be fully parsed may be
|
less effective, as it with a statement that can not be fully parsed may be
|
||||||
possible to bypass the protection that they are intended to provide.
|
possible to bypass the protection that they are intended to provide.
|
||||||
|
|
||||||
|
#### `treat_string_arg_as_field`
|
||||||
|
|
||||||
|
This optional parameter specifies how the masking filter should treat
|
||||||
|
strings used as arguments to functions. If true, they will be handled
|
||||||
|
as fields, which will cause fields to be masked even if `ANSI_QUOTES` has
|
||||||
|
been enabled and `"` is used instead of backtick.
|
||||||
|
```
|
||||||
|
treat_string_arg_as_field=false
|
||||||
|
```
|
||||||
|
The default value is `true`.
|
||||||
|
|
||||||
#### `check_user_variables`
|
#### `check_user_variables`
|
||||||
|
|
||||||
This optional parameter specifies how the masking filter should
|
This optional parameter specifies how the masking filter should
|
||||||
|
|||||||
@ -145,6 +145,12 @@ extern "C" MXS_MODULE* MXS_CREATE_MODULE()
|
|||||||
Config::require_fully_parsed_default,
|
Config::require_fully_parsed_default,
|
||||||
MXS_MODULE_OPT_NONE,
|
MXS_MODULE_OPT_NONE,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Config::treat_string_arg_as_field_name,
|
||||||
|
MXS_MODULE_PARAM_BOOL,
|
||||||
|
Config::treat_string_arg_as_field_default,
|
||||||
|
MXS_MODULE_OPT_NONE
|
||||||
|
},
|
||||||
{MXS_END_MODULE_PARAMS}
|
{MXS_END_MODULE_PARAMS}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -17,14 +17,15 @@
|
|||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
|
|
||||||
const char config_name_check_subqueries[] = "check_subqueries";
|
const char config_name_check_subqueries[] = "check_subqueries";
|
||||||
const char config_name_check_unions[] = "check_unions";
|
const char config_name_check_unions[] = "check_unions";
|
||||||
const char config_name_check_user_variables[] = "check_user_variables";
|
const char config_name_check_user_variables[] = "check_user_variables";
|
||||||
const char config_name_large_payload[] = "large_payload";
|
const char config_name_large_payload[] = "large_payload";
|
||||||
const char config_name_prevent_function_usage[] = "prevent_function_usage";
|
const char config_name_prevent_function_usage[] = "prevent_function_usage";
|
||||||
const char config_name_require_fully_parsed[] = "require_fully_parsed";
|
const char config_name_require_fully_parsed[] = "require_fully_parsed";
|
||||||
const char config_name_rules[] = "rules";
|
const char config_name_rules[] = "rules";
|
||||||
const char config_name_warn_type_mismatch[] = "warn_type_mismatch";
|
const char config_name_warn_type_mismatch[] = "warn_type_mismatch";
|
||||||
|
const char config_name_treat_string_arg_as_field[] = "treat_string_arg_as_field";
|
||||||
|
|
||||||
|
|
||||||
const char config_value_abort[] = "abort";
|
const char config_value_abort[] = "abort";
|
||||||
@ -105,7 +106,11 @@ const char* MaskingFilterConfig::check_subqueries_default = config_value_true;
|
|||||||
const char* MaskingFilterConfig::require_fully_parsed_name = config_name_require_fully_parsed;
|
const char* MaskingFilterConfig::require_fully_parsed_name = config_name_require_fully_parsed;
|
||||||
const char* MaskingFilterConfig::require_fully_parsed_default = config_name_require_fully_parsed;
|
const char* MaskingFilterConfig::require_fully_parsed_default = config_name_require_fully_parsed;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* PARAM treat_string_arg_as_field
|
||||||
|
*/
|
||||||
|
const char* MaskingFilterConfig::treat_string_arg_as_field_name = config_name_treat_string_arg_as_field;
|
||||||
|
const char* MaskingFilterConfig::treat_string_arg_as_field_default = config_value_true;
|
||||||
/*
|
/*
|
||||||
* MaskingFilterConfig
|
* MaskingFilterConfig
|
||||||
*/
|
*/
|
||||||
@ -161,3 +166,9 @@ bool MaskingFilterConfig::get_require_fully_parsed(const MXS_CONFIG_PARAMETER* p
|
|||||||
{
|
{
|
||||||
return config_get_bool(pParams, require_fully_parsed_name);
|
return config_get_bool(pParams, require_fully_parsed_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// static
|
||||||
|
bool MaskingFilterConfig::get_treat_string_arg_as_field(const MXS_CONFIG_PARAMETER* pParams)
|
||||||
|
{
|
||||||
|
return config_get_bool(pParams, treat_string_arg_as_field_name);
|
||||||
|
}
|
||||||
|
|||||||
@ -57,6 +57,9 @@ public:
|
|||||||
static const char* require_fully_parsed_name;
|
static const char* require_fully_parsed_name;
|
||||||
static const char* require_fully_parsed_default;
|
static const char* require_fully_parsed_default;
|
||||||
|
|
||||||
|
static const char* treat_string_arg_as_field_name;
|
||||||
|
static const char* treat_string_arg_as_field_default;
|
||||||
|
|
||||||
MaskingFilterConfig(const char* zName, const MXS_CONFIG_PARAMETER* pParams)
|
MaskingFilterConfig(const char* zName, const MXS_CONFIG_PARAMETER* pParams)
|
||||||
: m_name(zName)
|
: m_name(zName)
|
||||||
, m_large_payload(get_large_payload(pParams))
|
, m_large_payload(get_large_payload(pParams))
|
||||||
@ -67,6 +70,7 @@ public:
|
|||||||
, m_check_unions(get_check_unions(pParams))
|
, m_check_unions(get_check_unions(pParams))
|
||||||
, m_check_subqueries(get_check_subqueries(pParams))
|
, m_check_subqueries(get_check_subqueries(pParams))
|
||||||
, m_require_fully_parsed(get_require_fully_parsed(pParams))
|
, m_require_fully_parsed(get_require_fully_parsed(pParams))
|
||||||
|
, m_treat_string_arg_as_field(get_treat_string_arg_as_field(pParams))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -119,6 +123,11 @@ public:
|
|||||||
return m_require_fully_parsed;
|
return m_require_fully_parsed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool treat_string_arg_as_field() const
|
||||||
|
{
|
||||||
|
return m_treat_string_arg_as_field;
|
||||||
|
}
|
||||||
|
|
||||||
void set_large_payload(large_payload_t l)
|
void set_large_payload(large_payload_t l)
|
||||||
{
|
{
|
||||||
m_large_payload = l;
|
m_large_payload = l;
|
||||||
@ -158,6 +167,11 @@ public:
|
|||||||
m_require_fully_parsed = b;
|
m_require_fully_parsed = b;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void set_treat_string_arg_as_field(bool b)
|
||||||
|
{
|
||||||
|
m_treat_string_arg_as_field = b;
|
||||||
|
}
|
||||||
|
|
||||||
bool is_parsing_needed() const
|
bool is_parsing_needed() const
|
||||||
{
|
{
|
||||||
return prevent_function_usage() || check_user_variables() || check_unions() || check_subqueries();
|
return prevent_function_usage() || check_user_variables() || check_unions() || check_subqueries();
|
||||||
@ -171,6 +185,7 @@ public:
|
|||||||
static bool get_check_unions(const MXS_CONFIG_PARAMETER* pParams);
|
static bool get_check_unions(const MXS_CONFIG_PARAMETER* pParams);
|
||||||
static bool get_check_subqueries(const MXS_CONFIG_PARAMETER* pParams);
|
static bool get_check_subqueries(const MXS_CONFIG_PARAMETER* pParams);
|
||||||
static bool get_require_fully_parsed(const MXS_CONFIG_PARAMETER* pParams);
|
static bool get_require_fully_parsed(const MXS_CONFIG_PARAMETER* pParams);
|
||||||
|
static bool get_treat_string_arg_as_field(const MXS_CONFIG_PARAMETER* pParams);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::string m_name;
|
std::string m_name;
|
||||||
@ -182,4 +197,5 @@ private:
|
|||||||
bool m_check_unions;
|
bool m_check_unions;
|
||||||
bool m_check_subqueries;
|
bool m_check_subqueries;
|
||||||
bool m_require_fully_parsed;
|
bool m_require_fully_parsed;
|
||||||
|
bool m_treat_string_arg_as_field;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -48,6 +48,46 @@ GWBUF* create_parse_error_response()
|
|||||||
return create_error_response(zMessage);
|
return create_error_response(zMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class EnableOption
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
EnableOption(const EnableOption&) = delete;
|
||||||
|
EnableOption& operator=(const EnableOption&) = delete;
|
||||||
|
|
||||||
|
EnableOption(uint32_t option)
|
||||||
|
: m_option(option)
|
||||||
|
, m_options(0)
|
||||||
|
, m_disable(false)
|
||||||
|
{
|
||||||
|
if (m_option)
|
||||||
|
{
|
||||||
|
m_options = qc_get_options();
|
||||||
|
|
||||||
|
if (!(m_options & m_option))
|
||||||
|
{
|
||||||
|
uint32_t options = (m_options | m_option);
|
||||||
|
MXB_AT_DEBUG(bool rv = )qc_set_options(options);
|
||||||
|
mxb_assert(rv);
|
||||||
|
m_disable = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
~EnableOption()
|
||||||
|
{
|
||||||
|
if (m_disable)
|
||||||
|
{
|
||||||
|
MXB_AT_DEBUG(bool rv = )qc_set_options(m_options);
|
||||||
|
mxb_assert(rv);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint32_t m_option;
|
||||||
|
uint32_t m_options;
|
||||||
|
bool m_disable;
|
||||||
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MaskingFilterSession::MaskingFilterSession(MXS_SESSION* pSession, const MaskingFilter* pFilter)
|
MaskingFilterSession::MaskingFilterSession(MXS_SESSION* pSession, const MaskingFilter* pFilter)
|
||||||
@ -127,6 +167,9 @@ bool MaskingFilterSession::check_textual_query(GWBUF* pPacket)
|
|||||||
{
|
{
|
||||||
bool rv = false;
|
bool rv = false;
|
||||||
|
|
||||||
|
uint32_t option = m_filter.config().treat_string_arg_as_field() ? QC_OPTION_STRING_ARG_AS_FIELD : 0;
|
||||||
|
EnableOption enable(option);
|
||||||
|
|
||||||
if (qc_parse(pPacket, QC_COLLECT_FIELDS | QC_COLLECT_FUNCTIONS) == QC_QUERY_PARSED
|
if (qc_parse(pPacket, QC_COLLECT_FIELDS | QC_COLLECT_FUNCTIONS) == QC_QUERY_PARSED
|
||||||
|| !m_filter.config().require_fully_parsed())
|
|| !m_filter.config().require_fully_parsed())
|
||||||
{
|
{
|
||||||
@ -166,6 +209,9 @@ bool MaskingFilterSession::check_binary_query(GWBUF* pPacket)
|
|||||||
{
|
{
|
||||||
bool rv = false;
|
bool rv = false;
|
||||||
|
|
||||||
|
uint32_t option = m_filter.config().treat_string_arg_as_field() ? QC_OPTION_STRING_ARG_AS_FIELD : 0;
|
||||||
|
EnableOption enable(option);
|
||||||
|
|
||||||
if (qc_parse(pPacket, QC_COLLECT_FIELDS | QC_COLLECT_FUNCTIONS) == QC_QUERY_PARSED
|
if (qc_parse(pPacket, QC_COLLECT_FIELDS | QC_COLLECT_FUNCTIONS) == QC_QUERY_PARSED
|
||||||
|| !m_filter.config().require_fully_parsed())
|
|| !m_filter.config().require_fully_parsed())
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user