Merge branch '2.3' into develop
This commit is contained in:
commit
fb0745e3de
@ -146,6 +146,32 @@ query itself is logged. The log messages are logged at the notice level.
|
||||
Log all queries that do not match a rule. The matched user and the query is
|
||||
logged. The log messages are logged at the notice level.
|
||||
|
||||
#### `treat_string_as_field`
|
||||
This optional parameter specifies how the database firewall should treat
|
||||
strings. If true, they will be handled as fields, which will cause column
|
||||
blocking rules to match even if `ANSI_QUOTES` has been enabled and `"` is
|
||||
used instead of backtick.
|
||||
```
|
||||
treat_string_as_field=false
|
||||
```
|
||||
The default value is `true`.
|
||||
|
||||
Note that this may cause a false positive, if a "true" string contains the
|
||||
name of a column to be blocked.
|
||||
|
||||
#### `treat_string_arg_as_field`
|
||||
This optional parameter specifies how the database firewall should treat
|
||||
strings used as arguments to functions. If true, they will be handled
|
||||
as fields, which will cause function column blocking rules to match even
|
||||
even if `ANSI_QUOTES` has been enabled and `"` is used instead of backtick.
|
||||
```
|
||||
treat_string_arg_as_field=false
|
||||
```
|
||||
The default value is `true`.
|
||||
|
||||
Note that this may cause a false positive, if a "true" string contains the
|
||||
name of a column to be blocked.
|
||||
|
||||
## Rule syntax
|
||||
|
||||
The rules are defined by using the following syntax:
|
||||
|
@ -96,6 +96,26 @@ Please see the configuration parameter
|
||||
[require_fully_parsed](#require_fully_parsed)
|
||||
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
|
||||
|
||||
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
|
||||
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`
|
||||
|
||||
This optional parameter specifies how the masking filter should
|
||||
|
@ -29,6 +29,17 @@ enum qc_init_kind_t
|
||||
QC_INIT_BOTH = 0x03
|
||||
};
|
||||
|
||||
/**
|
||||
* qc_option_t defines options that affect the classification.
|
||||
*/
|
||||
enum qc_option_t
|
||||
{
|
||||
QC_OPTION_STRING_ARG_AS_FIELD = (1 << 0), /*< Report a string argument to a function as a field. */
|
||||
QC_OPTION_STRING_AS_FIELD = (1 << 1), /*< Report strings as fields. */
|
||||
};
|
||||
|
||||
const uint32_t QC_OPTION_MASK = QC_OPTION_STRING_ARG_AS_FIELD | QC_OPTION_STRING_AS_FIELD;
|
||||
|
||||
/**
|
||||
* qc_sql_mode_t specifies what should be assumed of the statements
|
||||
* that will be parsed.
|
||||
@ -452,6 +463,22 @@ struct QUERY_CLASSIFIER
|
||||
*/
|
||||
void (* qc_info_close)(QC_STMT_INFO* info);
|
||||
|
||||
/**
|
||||
* Gets the options of the *calling* thread.
|
||||
*
|
||||
* @return Bit mask of values from qc_option_t.
|
||||
*/
|
||||
uint32_t (* qc_get_options)();
|
||||
|
||||
/**
|
||||
* Sets the options for the *calling* thread.
|
||||
*
|
||||
* @param options Bits from qc_option_t.
|
||||
*
|
||||
* @return QC_RESULT_OK if @c options is valid, otherwise QC_RESULT_ERROR.
|
||||
*/
|
||||
int32_t (* qc_set_options)(uint32_t options);
|
||||
|
||||
/**
|
||||
* Get result from info.
|
||||
*
|
||||
@ -971,6 +998,22 @@ json_t* qc_get_cache_stats_as_json();
|
||||
*/
|
||||
const char* qc_result_to_string(qc_parse_result_t result);
|
||||
|
||||
/**
|
||||
* Gets the options of the *calling* thread.
|
||||
*
|
||||
* @return Bit mask of values from qc_option_t.
|
||||
*/
|
||||
uint32_t qc_get_options();
|
||||
|
||||
/**
|
||||
* Sets the options for the *calling* thread.
|
||||
*
|
||||
* @param options Bits from qc_option_t.
|
||||
*
|
||||
* @return true if the options were valid, false otherwise.
|
||||
*/
|
||||
bool qc_set_options(uint32_t options);
|
||||
|
||||
/**
|
||||
* Public interface to query classifier cache state.
|
||||
*/
|
||||
|
@ -23,6 +23,7 @@ module=masking
|
||||
rules=/home/vagrant/masking_auto_firewall.json
|
||||
warn_type_mismatch=always
|
||||
large_payload=ignore
|
||||
treat_string_arg_as_field=false
|
||||
|
||||
[RWS]
|
||||
type=service
|
||||
|
2
maxscale-system-test/fw/deny19
Normal file
2
maxscale-system-test/fw/deny19
Normal file
@ -0,0 +1,2 @@
|
||||
SELECT CONCAT(x1) FROM t1;
|
||||
SELECT CONCAT("x1") FROM t1;
|
@ -1,3 +1,4 @@
|
||||
update t1 set x1=1 where fl=0;
|
||||
SELECT x1 FROM t1;
|
||||
select t1.x1 as 'something' from t1 as t1 limit 1;
|
||||
select t1.x1 as 'something' from t1 as t1 limit 1;
|
||||
SELECT "x1" FROM t1;
|
||||
|
6
maxscale-system-test/fw/pass19
Normal file
6
maxscale-system-test/fw/pass19
Normal file
@ -0,0 +1,6 @@
|
||||
DROP TABLE IF EXISTS t1;
|
||||
CREATE TABLE t1 (x1 TEXT, x2 TEXT);
|
||||
select sleep(5);
|
||||
SELECT x1 FROM t1;
|
||||
SELECT x2 FROM t1;
|
||||
SELECT CONCAT(x2) FROM t1;
|
2
maxscale-system-test/fw/rules19
Normal file
2
maxscale-system-test/fw/rules19
Normal file
@ -0,0 +1,2 @@
|
||||
rule test19 match function concat columns x1
|
||||
users %@% match any rules test19
|
@ -44,7 +44,7 @@ int main(int argc, char* argv[])
|
||||
Test->maxscales->access_homedir[0]);
|
||||
|
||||
sprintf(rules_dir, "%s/fw/", test_dir);
|
||||
int N = 18;
|
||||
int N = 19;
|
||||
int i;
|
||||
|
||||
for (i = 1; i < N + 1; i++)
|
||||
|
@ -74,8 +74,6 @@ void test_one_ps(TestConnections& test, const char* zQuery, Expect expect)
|
||||
|
||||
void run(TestConnections& test)
|
||||
{
|
||||
init(test);
|
||||
|
||||
MYSQL* pMysql = test.maxscales->conn_rwsplit[0];
|
||||
|
||||
int rv;
|
||||
@ -130,6 +128,38 @@ void run(TestConnections& test)
|
||||
test_one(test, "select * FROM (select * from masking_auto_firewall) tbl", Expect::FAILURE);
|
||||
}
|
||||
|
||||
void run_ansi_quotes(TestConnections& test)
|
||||
{
|
||||
// This SHOULD go through as we have 'treat_string_arg_as_field=false"
|
||||
test_one(test, "select concat(\"a\") from masking_auto_firewall", Expect::SUCCESS);
|
||||
|
||||
Connection c = test.maxscales->rwsplit();
|
||||
c.connect();
|
||||
|
||||
test.expect(c.query("SET @@SQL_MODE = CONCAT(@@SQL_MODE, ',ANSI_QUOTES')"),
|
||||
"Could not turn on 'ANSI_QUOTES'");
|
||||
|
||||
// This SHOULD still go through as we still have 'treat_string_arg_as_field=false"
|
||||
test_one(test, "select concat(\"a\") from masking_auto_firewall", Expect::SUCCESS);
|
||||
|
||||
// Let's turn on 'treat_string_arg_as_field=true'
|
||||
test.maxscales->ssh_node(0,
|
||||
"sed -i -e "
|
||||
"'s/treat_string_arg_as_field=false/treat_string_arg_as_field=true/' "
|
||||
"/etc/maxscale.cnf",
|
||||
true);
|
||||
// and restart MaxScale
|
||||
test.maxscales->restart();
|
||||
|
||||
// This should NOT go through as we have 'treat_string_arg_as_field=true" and ANSI_QUOTES.
|
||||
test_one(test, "select concat(\"a\") from masking_auto_firewall", Expect::FAILURE);
|
||||
|
||||
// Have to reconnect as we restarted MaxScale.
|
||||
c.connect();
|
||||
test.expect(c.query("SET @@SQL_MODE = REPLACE(@@SQL_MODE, 'ANSI_QUOTES', '')"),
|
||||
"Could not turn off 'ANSI_QUOTES'");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
@ -151,7 +181,9 @@ int main(int argc, char* argv[])
|
||||
|
||||
if (test.maxscales->connect_rwsplit() == 0)
|
||||
{
|
||||
init(test);
|
||||
run(test);
|
||||
run_ansi_quotes(test);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -195,6 +195,7 @@ static struct
|
||||
static thread_local struct
|
||||
{
|
||||
qc_sql_mode_t sql_mode;
|
||||
uint32_t options;
|
||||
NAME_MAPPING* function_name_mappings;
|
||||
// The version information is not used; the embedded library parses according
|
||||
// to the version of the embedded library it has been linked with. However, we
|
||||
@ -203,6 +204,7 @@ static thread_local struct
|
||||
} this_thread =
|
||||
{
|
||||
QC_SQL_MODE_DEFAULT,
|
||||
0,
|
||||
function_name_mappings_default,
|
||||
0
|
||||
};
|
||||
@ -2276,7 +2278,6 @@ static void unalias_names(st_select_lex* select,
|
||||
}
|
||||
|
||||
static void add_field_info(parsing_info_t* info,
|
||||
st_select_lex* select,
|
||||
const char* database,
|
||||
const char* table,
|
||||
const char* column,
|
||||
@ -2284,8 +2285,6 @@ static void add_field_info(parsing_info_t* info,
|
||||
{
|
||||
mxb_assert(column);
|
||||
|
||||
unalias_names(select, database, table, &database, &table);
|
||||
|
||||
QC_FIELD_INFO item = {(char*)database, (char*)table, (char*)column};
|
||||
|
||||
size_t i;
|
||||
@ -2361,6 +2360,20 @@ static void add_field_info(parsing_info_t* info,
|
||||
}
|
||||
}
|
||||
|
||||
static void add_field_info(parsing_info_t* info,
|
||||
st_select_lex* select,
|
||||
const char* database,
|
||||
const char* table,
|
||||
const char* column,
|
||||
List<Item>* excludep)
|
||||
{
|
||||
mxb_assert(column);
|
||||
|
||||
unalias_names(select, database, table, &database, &table);
|
||||
|
||||
add_field_info(info, database, table, column, excludep);
|
||||
}
|
||||
|
||||
static void add_function_field_usage(const char* database,
|
||||
const char* table,
|
||||
const char* column,
|
||||
@ -2485,6 +2498,19 @@ static void add_function_field_usage(st_select_lex* select,
|
||||
add_function_field_usage(select, static_cast<Item_field*>(item), fi);
|
||||
break;
|
||||
|
||||
case Item::STRING_ITEM:
|
||||
if (this_thread.options & QC_OPTION_STRING_ARG_AS_FIELD)
|
||||
{
|
||||
String* s = item->val_str();
|
||||
int len = s->length();
|
||||
char tmp[len + 1];
|
||||
memcpy(tmp, s->ptr(), len);
|
||||
tmp[len] = 0;
|
||||
|
||||
add_function_field_usage(nullptr, nullptr, tmp, fi);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
// mxb_assert(!true);
|
||||
;
|
||||
@ -3058,6 +3084,19 @@ static void update_field_infos(parsing_info_t* pi,
|
||||
}
|
||||
break;
|
||||
|
||||
case Item::STRING_ITEM:
|
||||
if (this_thread.options & QC_OPTION_STRING_AS_FIELD)
|
||||
{
|
||||
String* s = item->val_str();
|
||||
int len = s->length();
|
||||
char tmp[len + 1];
|
||||
memcpy(tmp, s->ptr(), len);
|
||||
tmp[len] = 0;
|
||||
|
||||
add_field_info(pi, nullptr, nullptr, tmp, excludep);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@ -3536,6 +3575,27 @@ int32_t qc_mysql_set_sql_mode(qc_sql_mode_t sql_mode)
|
||||
return rv;
|
||||
}
|
||||
|
||||
uint32_t qc_mysql_get_options()
|
||||
{
|
||||
return this_thread.options;
|
||||
}
|
||||
|
||||
int32_t qc_mysql_set_options(uint32_t options)
|
||||
{
|
||||
int32_t rv = QC_RESULT_OK;
|
||||
|
||||
if ((options & ~QC_OPTION_MASK) == 0)
|
||||
{
|
||||
this_thread.options = options;
|
||||
}
|
||||
else
|
||||
{
|
||||
rv = QC_RESULT_ERROR;
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* EXPORTS
|
||||
*/
|
||||
@ -3571,6 +3631,8 @@ extern "C"
|
||||
qc_mysql_set_sql_mode,
|
||||
nullptr, // qc_info_dup not supported.
|
||||
nullptr, // qc_info_close not supported.
|
||||
qc_mysql_get_options,
|
||||
qc_mysql_set_options,
|
||||
nullptr, // qc_get_result_from_info not supported
|
||||
};
|
||||
|
||||
|
@ -155,6 +155,7 @@ static thread_local struct
|
||||
bool initialized; // Whether the thread specific data has been initialized.
|
||||
sqlite3* pDb; // Thread specific database handle.
|
||||
qc_sql_mode_t sql_mode; // What sql_mode is used.
|
||||
uint32_t options; // Options affecting classification.
|
||||
QcSqliteInfo* pInfo; // The information for the current statement being classified.
|
||||
uint64_t version; // Encoded version number
|
||||
uint32_t version_major;
|
||||
@ -842,6 +843,14 @@ public:
|
||||
update_field_infos_from_expr(pAliases, context, pExpr, pExclude);
|
||||
break;
|
||||
|
||||
case TK_STRING: // select "a" ..., for @@sql_mode containing 'ANSI_QUOTES'
|
||||
if (this_thread.options & QC_OPTION_STRING_AS_FIELD)
|
||||
{
|
||||
const char* zColumn = pExpr->u.zToken;
|
||||
update_field_infos_from_column(pAliases, context, zColumn, pExclude);
|
||||
}
|
||||
break;
|
||||
|
||||
case TK_VARIABLE:
|
||||
{
|
||||
if (zToken[0] == '@')
|
||||
@ -1152,6 +1161,13 @@ public:
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (pExpr->op == TK_STRING)
|
||||
{
|
||||
if (this_thread.options & QC_OPTION_STRING_ARG_AS_FIELD)
|
||||
{
|
||||
zColumn = pExpr->u.zToken;
|
||||
}
|
||||
}
|
||||
|
||||
if (zColumn)
|
||||
{
|
||||
@ -1187,6 +1203,17 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void update_field_infos_from_column(QcAliases* pAliases,
|
||||
uint32_t context,
|
||||
const char* zColumn,
|
||||
const ExprList* pExclude)
|
||||
{
|
||||
if (must_check_sequence_related_functions() || must_collect_fields())
|
||||
{
|
||||
update_field_info(pAliases, context, nullptr, nullptr, zColumn, pExclude);
|
||||
}
|
||||
}
|
||||
|
||||
void update_field_infos_from_exprlist(QcAliases* pAliases,
|
||||
uint32_t context,
|
||||
const ExprList* pEList,
|
||||
@ -4577,6 +4604,8 @@ static int32_t qc_sqlite_get_sql_mode(qc_sql_mode_t* sql_mode);
|
||||
static int32_t qc_sqlite_set_sql_mode(qc_sql_mode_t sql_mode);
|
||||
static QC_STMT_INFO* qc_sqlite_info_dup(QC_STMT_INFO* info);
|
||||
static void qc_sqlite_info_close(QC_STMT_INFO* info);
|
||||
static uint32_t qc_sqlite_get_options();
|
||||
static int32_t qc_sqlite_set_options(uint32_t options);
|
||||
static QC_STMT_RESULT qc_sqlite_get_result_from_info(const QC_STMT_INFO* pInfo);
|
||||
|
||||
|
||||
@ -5261,6 +5290,27 @@ void qc_sqlite_info_close(QC_STMT_INFO* info)
|
||||
static_cast<QcSqliteInfo*>(info)->dec_ref();
|
||||
}
|
||||
|
||||
uint32_t qc_sqlite_get_options()
|
||||
{
|
||||
return this_thread.options;
|
||||
}
|
||||
|
||||
int32_t qc_sqlite_set_options(uint32_t options)
|
||||
{
|
||||
int32_t rv = QC_RESULT_OK;
|
||||
|
||||
if ((options & ~QC_OPTION_MASK) == 0)
|
||||
{
|
||||
this_thread.options = options;
|
||||
}
|
||||
else
|
||||
{
|
||||
rv = QC_RESULT_ERROR;
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
QC_STMT_RESULT qc_sqlite_get_result_from_info(const QC_STMT_INFO* pInfo)
|
||||
{
|
||||
return static_cast<const QcSqliteInfo*>(pInfo)->get_result();
|
||||
@ -5301,6 +5351,8 @@ extern "C"
|
||||
qc_sqlite_set_sql_mode,
|
||||
qc_sqlite_info_dup,
|
||||
qc_sqlite_info_close,
|
||||
qc_sqlite_get_options,
|
||||
qc_sqlite_set_options,
|
||||
qc_sqlite_get_result_from_info,
|
||||
};
|
||||
|
||||
|
@ -96,9 +96,11 @@ class QCInfoCache;
|
||||
static thread_local struct
|
||||
{
|
||||
QCInfoCache* pInfo_cache;
|
||||
uint32_t options;
|
||||
} this_thread =
|
||||
{
|
||||
nullptr
|
||||
nullptr,
|
||||
0
|
||||
};
|
||||
|
||||
|
||||
@ -147,7 +149,8 @@ public:
|
||||
{
|
||||
Entry& entry = i->second;
|
||||
|
||||
if (entry.sql_mode == this_unit.qc_sql_mode)
|
||||
if ((entry.sql_mode == this_unit.qc_sql_mode) &&
|
||||
(entry.options == this_thread.options))
|
||||
{
|
||||
mxb_assert(this_unit.classifier);
|
||||
this_unit.classifier->qc_info_dup(entry.pInfo);
|
||||
@ -158,7 +161,7 @@ public:
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the sql_mode has changed, we discard the existing result.
|
||||
// If the sql_mode or options has changed, we discard the existing result.
|
||||
erase(i);
|
||||
|
||||
++m_stats.misses;
|
||||
@ -198,7 +201,7 @@ public:
|
||||
{
|
||||
this_unit.classifier->qc_info_dup(pInfo);
|
||||
|
||||
m_infos.emplace(canonical_stmt, Entry(pInfo, this_unit.qc_sql_mode));
|
||||
m_infos.emplace(canonical_stmt, Entry(pInfo, this_unit.qc_sql_mode, this_thread.options));
|
||||
|
||||
++m_stats.inserts;
|
||||
m_stats.size += size;
|
||||
@ -248,15 +251,17 @@ public:
|
||||
private:
|
||||
struct Entry
|
||||
{
|
||||
Entry(QC_STMT_INFO* pInfo, qc_sql_mode_t sql_mode)
|
||||
Entry(QC_STMT_INFO* pInfo, qc_sql_mode_t sql_mode, uint32_t options)
|
||||
: pInfo(pInfo)
|
||||
, sql_mode(sql_mode)
|
||||
, options(options)
|
||||
, hits(0)
|
||||
{
|
||||
}
|
||||
|
||||
QC_STMT_INFO* pInfo;
|
||||
qc_sql_mode_t sql_mode;
|
||||
uint32_t options;
|
||||
int64_t hits;
|
||||
};
|
||||
|
||||
@ -1319,6 +1324,29 @@ void qc_set_sql_mode(qc_sql_mode_t sql_mode)
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t qc_get_options()
|
||||
{
|
||||
QC_TRACE();
|
||||
mxb_assert(this_unit.classifier);
|
||||
|
||||
return this_unit.classifier->qc_get_options();
|
||||
}
|
||||
|
||||
bool qc_set_options(uint32_t options)
|
||||
{
|
||||
QC_TRACE();
|
||||
mxb_assert(this_unit.classifier);
|
||||
|
||||
int32_t rv = this_unit.classifier->qc_set_options(options);
|
||||
|
||||
if (rv == QC_RESULT_OK)
|
||||
{
|
||||
this_thread.options = options;
|
||||
}
|
||||
|
||||
return rv == QC_RESULT_OK;
|
||||
}
|
||||
|
||||
void qc_get_cache_properties(QC_CACHE_PROPERTIES* properties)
|
||||
{
|
||||
properties->max_size = this_unit.cache_max_size();
|
||||
@ -1330,6 +1358,11 @@ bool qc_set_cache_properties(const QC_CACHE_PROPERTIES* properties)
|
||||
|
||||
if (properties->max_size >= 0)
|
||||
{
|
||||
if (properties->max_size == 0)
|
||||
{
|
||||
MXS_NOTICE("Query classifier cache disabled.");
|
||||
}
|
||||
|
||||
this_unit.set_cache_max_size(properties->max_size);
|
||||
rv = true;
|
||||
}
|
||||
|
@ -128,6 +128,48 @@ private:
|
||||
};
|
||||
|
||||
thread_local DbfwThread* this_thread = NULL;
|
||||
|
||||
// TODO: In 2.4 move to query_classifier.hh.
|
||||
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;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
bool parse_at_times(const char** tok, char** saveptr, Rule* ruledef);
|
||||
@ -584,6 +626,16 @@ MXS_MODULE* MXS_CREATE_MODULE()
|
||||
MXS_MODULE_OPT_ENUM_UNIQUE,
|
||||
action_values
|
||||
},
|
||||
{
|
||||
"treat_string_arg_as_field",
|
||||
MXS_MODULE_PARAM_BOOL,
|
||||
"true"
|
||||
},
|
||||
{
|
||||
"treat_string_as_field",
|
||||
MXS_MODULE_PARAM_BOOL,
|
||||
"true"
|
||||
},
|
||||
{MXS_END_MODULE_PARAMS}
|
||||
}
|
||||
};
|
||||
@ -1199,6 +1251,8 @@ int global_version = 1;
|
||||
Dbfw::Dbfw(MXS_CONFIG_PARAMETER* params)
|
||||
: m_action((enum fw_actions)params->get_enum("action", action_values))
|
||||
, m_log_match(0)
|
||||
, m_treat_string_as_field(params->get_bool("treat_string_as_field"))
|
||||
, m_treat_string_arg_as_field(params->get_bool("treat_string_arg_as_field"))
|
||||
, m_filename(params->get_string("rules"))
|
||||
, m_version(atomic_add(&global_version, 1))
|
||||
{
|
||||
@ -1227,6 +1281,26 @@ Dbfw* Dbfw::create(const char* zName, MXS_CONFIG_PARAMETER* pParams)
|
||||
if (process_rule_file(file, &rules, &users))
|
||||
{
|
||||
rval = new(std::nothrow) Dbfw(pParams);
|
||||
|
||||
if (rval)
|
||||
{
|
||||
if (rval->treat_string_as_field() || rval->treat_string_arg_as_field())
|
||||
{
|
||||
QC_CACHE_PROPERTIES cache_properties;
|
||||
qc_get_cache_properties(&cache_properties);
|
||||
|
||||
if (cache_properties.max_size != 0)
|
||||
{
|
||||
MXS_NOTICE("The parameter 'treat_string_arg_as_field' or(and) "
|
||||
"'treat_string_as_field' is enabled for %s, "
|
||||
"disabling the query classifier cache.",
|
||||
zName);
|
||||
|
||||
cache_properties.max_size = 0;
|
||||
qc_set_cache_properties(&cache_properties);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rval;
|
||||
@ -1468,6 +1542,19 @@ int DbfwSession::routeQuery(GWBUF* buffer)
|
||||
}
|
||||
else
|
||||
{
|
||||
uint32_t option = 0;
|
||||
|
||||
if (m_instance->treat_string_as_field())
|
||||
{
|
||||
option |= QC_OPTION_STRING_AS_FIELD;
|
||||
}
|
||||
|
||||
if (m_instance->treat_string_arg_as_field())
|
||||
{
|
||||
option |= QC_OPTION_STRING_ARG_AS_FIELD;
|
||||
}
|
||||
|
||||
EnableOption enable(option);
|
||||
GWBUF* analyzed_queue = buffer;
|
||||
|
||||
// QUERY_TYPE_PREPARE_STMT need not be handled separately as the
|
||||
|
@ -224,6 +224,26 @@ public:
|
||||
*/
|
||||
fw_actions get_action() const;
|
||||
|
||||
/**
|
||||
* Should strings be treated as fields?
|
||||
*
|
||||
* @return True, if they should, false otherwise.
|
||||
*/
|
||||
bool treat_string_as_field() const
|
||||
{
|
||||
return m_treat_string_as_field;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should string args be treated as fields?
|
||||
*
|
||||
* @return True, if they should, false otherwise.
|
||||
*/
|
||||
bool treat_string_arg_as_field() const
|
||||
{
|
||||
return m_treat_string_arg_as_field;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get logging option bitmask
|
||||
*
|
||||
@ -267,6 +287,8 @@ public:
|
||||
private:
|
||||
fw_actions m_action; /*< Default operation mode, defaults to deny */
|
||||
int m_log_match; /*< Log matching and/or non-matching queries */
|
||||
bool m_treat_string_as_field;
|
||||
bool m_treat_string_arg_as_field;
|
||||
mutable std::mutex m_lock; /*< Instance spinlock */
|
||||
std::string m_filename; /*< Path to the rule file */
|
||||
int m_version; /*< Latest rule file version, incremented on reload */
|
||||
|
@ -145,6 +145,12 @@ extern "C" MXS_MODULE* MXS_CREATE_MODULE()
|
||||
Config::require_fully_parsed_default,
|
||||
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}
|
||||
}
|
||||
};
|
||||
@ -179,6 +185,22 @@ MaskingFilter* MaskingFilter::create(const char* zName, MXS_CONFIG_PARAMETER* pP
|
||||
if (sRules.get())
|
||||
{
|
||||
pFilter = new MaskingFilter(config, sRules);
|
||||
|
||||
if (config.treat_string_arg_as_field())
|
||||
{
|
||||
QC_CACHE_PROPERTIES cache_properties;
|
||||
qc_get_cache_properties(&cache_properties);
|
||||
|
||||
if (cache_properties.max_size != 0)
|
||||
{
|
||||
MXS_NOTICE("The parameter 'treat_string_arg_as_field' is enabled for %s, "
|
||||
"disabling the query classifier cache.",
|
||||
zName);
|
||||
|
||||
cache_properties.max_size = 0;
|
||||
qc_set_cache_properties(&cache_properties);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return pFilter;
|
||||
|
@ -17,14 +17,15 @@
|
||||
namespace
|
||||
{
|
||||
|
||||
const char config_name_check_subqueries[] = "check_subqueries";
|
||||
const char config_name_check_unions[] = "check_unions";
|
||||
const char config_name_check_user_variables[] = "check_user_variables";
|
||||
const char config_name_large_payload[] = "large_payload";
|
||||
const char config_name_prevent_function_usage[] = "prevent_function_usage";
|
||||
const char config_name_require_fully_parsed[] = "require_fully_parsed";
|
||||
const char config_name_rules[] = "rules";
|
||||
const char config_name_warn_type_mismatch[] = "warn_type_mismatch";
|
||||
const char config_name_check_subqueries[] = "check_subqueries";
|
||||
const char config_name_check_unions[] = "check_unions";
|
||||
const char config_name_check_user_variables[] = "check_user_variables";
|
||||
const char config_name_large_payload[] = "large_payload";
|
||||
const char config_name_prevent_function_usage[] = "prevent_function_usage";
|
||||
const char config_name_require_fully_parsed[] = "require_fully_parsed";
|
||||
const char config_name_rules[] = "rules";
|
||||
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";
|
||||
@ -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_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
|
||||
*/
|
||||
@ -161,3 +166,9 @@ bool MaskingFilterConfig::get_require_fully_parsed(const MXS_CONFIG_PARAMETER* p
|
||||
{
|
||||
return pParams->get_bool(require_fully_parsed_name);
|
||||
}
|
||||
|
||||
// static
|
||||
bool MaskingFilterConfig::get_treat_string_arg_as_field(const MXS_CONFIG_PARAMETER* pParams)
|
||||
{
|
||||
return pParams->get_bool(treat_string_arg_as_field_name);
|
||||
}
|
||||
|
@ -57,6 +57,9 @@ public:
|
||||
static const char* require_fully_parsed_name;
|
||||
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)
|
||||
: m_name(zName)
|
||||
, m_large_payload(get_large_payload(pParams))
|
||||
@ -67,6 +70,7 @@ public:
|
||||
, m_check_unions(get_check_unions(pParams))
|
||||
, m_check_subqueries(get_check_subqueries(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;
|
||||
}
|
||||
|
||||
bool treat_string_arg_as_field() const
|
||||
{
|
||||
return m_treat_string_arg_as_field;
|
||||
}
|
||||
|
||||
void set_large_payload(large_payload_t l)
|
||||
{
|
||||
m_large_payload = l;
|
||||
@ -158,6 +167,11 @@ public:
|
||||
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
|
||||
{
|
||||
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_subqueries(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:
|
||||
std::string m_name;
|
||||
@ -182,4 +197,5 @@ private:
|
||||
bool m_check_unions;
|
||||
bool m_check_subqueries;
|
||||
bool m_require_fully_parsed;
|
||||
bool m_treat_string_arg_as_field;
|
||||
};
|
||||
|
@ -48,6 +48,47 @@ GWBUF* create_parse_error_response()
|
||||
return create_error_response(zMessage);
|
||||
}
|
||||
|
||||
// TODO: In 2.4 move to query_classifier.hh.
|
||||
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)
|
||||
@ -127,6 +168,9 @@ bool MaskingFilterSession::check_textual_query(GWBUF* pPacket)
|
||||
{
|
||||
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
|
||||
|| !m_filter.config().require_fully_parsed())
|
||||
{
|
||||
@ -166,6 +210,9 @@ bool MaskingFilterSession::check_binary_query(GWBUF* pPacket)
|
||||
{
|
||||
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
|
||||
|| !m_filter.config().require_fully_parsed())
|
||||
{
|
||||
|
Loading…
x
Reference in New Issue
Block a user