Merge branch '2.3' into develop

This commit is contained in:
Johan Wikman
2019-03-21 09:26:06 +02:00
18 changed files with 583 additions and 152 deletions

View File

@ -219,18 +219,19 @@ GWBUF* gwbuf_clone(GWBUF* buf)
return rval;
}
GWBUF* gwbuf_deep_clone(const GWBUF* buf)
static GWBUF* gwbuf_deep_clone_portion(const GWBUF* buf, size_t length)
{
mxb_assert(buf->owner == RoutingWorker::get_current_id());
GWBUF* rval = NULL;
if (buf)
{
size_t buflen = gwbuf_length(buf);
rval = gwbuf_alloc(buflen);
rval = gwbuf_alloc(length);
if (rval && gwbuf_copy_data(buf, 0, buflen, GWBUF_DATA(rval)) == buflen)
if (rval && gwbuf_copy_data(buf, 0, length, GWBUF_DATA(rval)) == length)
{
// The copying of the type is done to retain the type characteristic of the buffer without
// having a link the orginal data or parsing info.
rval->gwbuf_type = buf->gwbuf_type;
}
else
@ -243,7 +244,12 @@ GWBUF* gwbuf_deep_clone(const GWBUF* buf)
return rval;
}
static GWBUF* gwbuf_clone_portion(GWBUF* buf,
GWBUF* gwbuf_deep_clone(const GWBUF* buf)
{
return gwbuf_deep_clone_portion(buf, gwbuf_length(buf));
}
static GWBUF *gwbuf_clone_portion(GWBUF* buf,
size_t start_offset,
size_t length)
{
@ -310,7 +316,7 @@ GWBUF* gwbuf_split(GWBUF** buf, size_t length)
if (length > 0)
{
mxb_assert(GWBUF_LENGTH(buffer) > length);
GWBUF* partial = gwbuf_clone_portion(buffer, 0, length);
GWBUF* partial = gwbuf_deep_clone_portion(buffer, length);
/** If the head points to the original head of the buffer chain
* and we are splitting a contiguous buffer, we only need to return

View File

@ -450,6 +450,7 @@ const char* config_pre_parse_global_params[] =
CN_MAXLOG,
CN_LOG_AUGMENTATION,
CN_LOG_TO_SHM,
CN_SUBSTITUTE_VARIABLES,
NULL
};

View File

@ -148,8 +148,6 @@ uint32_t get_prepare_type(GWBUF* buffer)
}
}
mxb_assert((type & (QUERY_TYPE_PREPARE_STMT | QUERY_TYPE_PREPARE_NAMED_STMT)) == 0);
return type;
}

View File

@ -229,7 +229,7 @@ int BinlogFilterSession::clientReply(GWBUF* pPacket)
// they are replaced by a RAND_EVENT event packet
if (m_skip)
{
replaceEvent(&pPacket);
replaceEvent(&pPacket, hdr);
}
break;
@ -323,7 +323,7 @@ bool BinlogFilterSession::checkEvent(GWBUF* buffer,
* Some events skipped.
* Set next pos to 0 instead of real one and new CRC32
*/
fixEvent(event + MYSQL_HEADER_LEN + 1, hdr.event_size);
fixEvent(event + MYSQL_HEADER_LEN + 1, hdr.event_size, hdr);
}
break;
@ -444,12 +444,12 @@ static void event_set_crc32(uint8_t* event, uint32_t event_size)
* @param event Pointer to event data
* @event_size The event size
*/
void BinlogFilterSession::fixEvent(uint8_t* event, uint32_t event_size)
void BinlogFilterSession::fixEvent(uint8_t* event, uint32_t event_size, const REP_HEADER& hdr)
{
// Set next pos to 0.
// The next_pos offset is the 13th byte in replication event header 19 bytes
// + 4 (time) + 1 (type) + 4 (server_id) + 4 (event_size)
gw_mysql_set_byte4(event + 4 + 1 + 4 + 4, 0);
gw_mysql_set_byte4(event + 4 + 1 + 4 + 4, hdr.next_pos);
// Set CRC32 in the new event
if (m_crc)
@ -466,7 +466,7 @@ void BinlogFilterSession::fixEvent(uint8_t* event, uint32_t event_size)
*
* @param pPacket The GWBUF with event data
*/
void BinlogFilterSession::replaceEvent(GWBUF** ppPacket)
void BinlogFilterSession::replaceEvent(GWBUF** ppPacket, const REP_HEADER& hdr)
{
uint32_t buf_len = gwbuf_length(*ppPacket);
@ -596,7 +596,7 @@ void BinlogFilterSession::replaceEvent(GWBUF** ppPacket)
}
// Fix Event Next pos = 0 and set new CRC32
fixEvent(ptr + MYSQL_HEADER_LEN + 1, new_event_size);
fixEvent(ptr + MYSQL_HEADER_LEN + 1, new_event_size, hdr);
}
/**

View File

@ -85,13 +85,13 @@ private:
void filterError(GWBUF* pPacket);
// Fix event: set next pos to 0 and set new CRC32
void fixEvent(uint8_t* data, uint32_t event_size);
void fixEvent(uint8_t* data, uint32_t event_size, const REP_HEADER& hdr);
// Whether to skip current event
bool checkEvent(GWBUF* data, const REP_HEADER& hdr);
// Filter the replication event
void replaceEvent(GWBUF** data);
void replaceEvent(GWBUF** data, const REP_HEADER& hdr);
// Handle event size
void handlePackets(uint32_t len, const REP_HEADER& hdr);

View File

@ -1479,71 +1479,81 @@ int DbfwSession::routeQuery(GWBUF* buffer)
if (qc_query_is_type(type, QUERY_TYPE_PREPARE_NAMED_STMT))
{
analyzed_queue = qc_get_preparable_stmt(buffer);
mxb_assert(analyzed_queue);
// 'analyzed_queue' will be NULL if the statement is prepared from
// a variable like in : "prepare ps from @a".
}
SUser suser = find_user_data(this_thread->users(m_instance), user(), remote());
bool query_ok = false;
if (command_is_mandatory(buffer))
if (!analyzed_queue)
{
query_ok = true;
set_error("Firewall rejects statements prepared from a variable.");
}
else if (suser)
else
{
char* rname = NULL;
bool match = suser->match(m_instance, this, analyzed_queue, &rname);
SUser suser = find_user_data(this_thread->users(m_instance), user(), remote());
switch (m_instance->get_action())
if (command_is_mandatory(buffer))
{
case FW_ACTION_ALLOW:
query_ok = match;
break;
case FW_ACTION_BLOCK:
query_ok = !match;
break;
case FW_ACTION_IGNORE:
query_ok = true;
break;
default:
MXS_ERROR("Unknown dbfwfilter action: %d", m_instance->get_action());
mxb_assert(false);
break;
}
if (m_instance->get_log_bitmask() != FW_LOG_NONE)
else if (suser)
{
if (match && m_instance->get_log_bitmask() & FW_LOG_MATCH)
{
MXS_NOTICE("[%s] Rule '%s' for '%s' matched by %s@%s: %s",
m_session->service->name(),
rname,
suser->name(),
user().c_str(),
remote().c_str(),
get_sql(buffer).c_str());
}
else if (!match && m_instance->get_log_bitmask() & FW_LOG_NO_MATCH)
{
MXS_NOTICE("[%s] Query for '%s' by %s@%s was not matched: %s",
m_session->service->name(),
suser->name(),
user().c_str(),
remote().c_str(),
get_sql(buffer).c_str());
}
}
char* rname = NULL;
bool match = suser->match(m_instance, this, analyzed_queue, &rname);
MXS_FREE(rname);
}
/** If the instance is in whitelist mode, only users that have a rule
* defined for them are allowed */
else if (m_instance->get_action() != FW_ACTION_ALLOW)
{
query_ok = true;
switch (m_instance->get_action())
{
case FW_ACTION_ALLOW:
query_ok = match;
break;
case FW_ACTION_BLOCK:
query_ok = !match;
break;
case FW_ACTION_IGNORE:
query_ok = true;
break;
default:
MXS_ERROR("Unknown dbfwfilter action: %d", m_instance->get_action());
mxb_assert(false);
break;
}
if (m_instance->get_log_bitmask() != FW_LOG_NONE)
{
if (match && m_instance->get_log_bitmask() & FW_LOG_MATCH)
{
MXS_NOTICE("[%s] Rule '%s' for '%s' matched by %s@%s: %s",
m_session->service->name,
rname,
suser->name(),
user().c_str(),
remote().c_str(),
get_sql(buffer).c_str());
}
else if (!match && m_instance->get_log_bitmask() & FW_LOG_NO_MATCH)
{
MXS_NOTICE("[%s] Query for '%s' by %s@%s was not matched: %s",
m_session->service->name,
suser->name(),
user().c_str(),
remote().c_str(),
get_sql(buffer).c_str());
}
}
MXS_FREE(rname);
}
/** If the instance is in whitelist mode, only users that have a rule
* defined for them are allowed */
else if (m_instance->get_action() != FW_ACTION_ALLOW)
{
query_ok = true;
}
}
if (query_ok)

View File

@ -90,13 +90,10 @@ extern "C" MXS_MODULE* MXS_CREATE_MODULE()
"V1.0.0",
RCAP_TYPE_CONTIGUOUS_INPUT | RCAP_TYPE_CONTIGUOUS_OUTPUT,
&MaskingFilter::s_object,
NULL, /* Process init.
* */
NULL, /* Process finish.
* */
NULL, /* Thread init. */
NULL, /* Thread finish.
* */
NULL, /* Process init. */
NULL, /* Process finish. */
NULL, /* Thread init. */
NULL, /* Thread finish. */
{
{
Config::rules_name,
@ -124,6 +121,12 @@ extern "C" MXS_MODULE* MXS_CREATE_MODULE()
Config::prevent_function_usage_default,
MXS_MODULE_OPT_NONE,
},
{
Config::check_user_variables_name,
MXS_MODULE_PARAM_BOOL,
Config::check_user_variables_default,
MXS_MODULE_OPT_NONE,
},
{MXS_END_MODULE_PARAMS}
}
};

View File

@ -27,6 +27,7 @@ const char config_value_never[] = "never";
const char config_value_always[] = "always";
const char config_name_prevent_function_usage[] = "prevent_function_usage";
const char config_check_user_variables[] = "check_user_variables";
const char config_value_true[] = "true";
}
@ -84,6 +85,14 @@ const char* MaskingFilterConfig::prevent_function_usage_name = config_name_preve
// static
const char* MaskingFilterConfig::prevent_function_usage_default = config_value_true;
/*
* PARAM check_user_variables
*/
const char* MaskingFilterConfig::check_user_variables_name = config_check_user_variables;
// static
const char* MaskingFilterConfig::check_user_variables_default = config_value_true;
/*
* MaskingFilterConfig
*/
@ -115,3 +124,9 @@ bool MaskingFilterConfig::get_prevent_function_usage(const MXS_CONFIG_PARAMETER*
{
return pParams->get_bool(prevent_function_usage_name);
}
// static
bool MaskingFilterConfig::get_check_user_variables(const MXS_CONFIG_PARAMETER* pParams)
{
return config_get_bool(pParams, check_user_variables_name);
}

View File

@ -45,12 +45,16 @@ public:
static const char* prevent_function_usage_name;
static const char* prevent_function_usage_default;
static const char* check_user_variables_name;
static const char* check_user_variables_default;
MaskingFilterConfig(const char* zName, const MXS_CONFIG_PARAMETER* pParams)
: m_name(zName)
, m_large_payload(get_large_payload(pParams))
, m_rules(get_rules(pParams))
, m_warn_type_mismatch(get_warn_type_mismatch(pParams))
, m_prevent_function_usage(get_prevent_function_usage(pParams))
, m_check_user_variables(get_check_user_variables(pParams))
{
}
~MaskingFilterConfig()
@ -82,6 +86,11 @@ public:
return m_prevent_function_usage;
}
bool check_user_variables() const
{
return m_check_user_variables;
}
void set_large_payload(large_payload_t l)
{
m_large_payload = l;
@ -101,10 +110,21 @@ public:
m_prevent_function_usage = b;
}
void set_check_user_variables(bool b)
{
m_check_user_variables = b;
}
bool is_parsing_needed() const
{
return prevent_function_usage() || check_user_variables();
}
static large_payload_t get_large_payload(const MXS_CONFIG_PARAMETER* pParams);
static std::string get_rules(const MXS_CONFIG_PARAMETER* pParams);
static warn_type_mismatch_t get_warn_type_mismatch(const MXS_CONFIG_PARAMETER* pParams);
static bool get_prevent_function_usage(const MXS_CONFIG_PARAMETER* pParams);
static bool get_check_user_variables(const MXS_CONFIG_PARAMETER* pParams);
private:
std::string m_name;
@ -112,4 +132,5 @@ private:
std::string m_rules;
warn_type_mismatch_t m_warn_type_mismatch;
bool m_prevent_function_usage;
bool m_check_user_variables;
};

View File

@ -31,6 +31,25 @@ using std::ostream;
using std::string;
using std::stringstream;
namespace
{
GWBUF* create_error_response(const char* zMessage)
{
return modutil_create_mysql_err_msg(1, 0, 1141, "HY000", zMessage);
}
GWBUF* create_parse_error_response()
{
const char* zMessage =
"The statement could not be fully parsed and will hence be "
"rejected (masking filter).";
return create_error_response(zMessage);
}
}
MaskingFilterSession::MaskingFilterSession(MXS_SESSION* pSession, const MaskingFilter* pFilter)
: maxscale::FilterSession(pSession)
, m_filter(*pFilter)
@ -48,6 +67,96 @@ MaskingFilterSession* MaskingFilterSession::create(MXS_SESSION* pSession, const
return new MaskingFilterSession(pSession, pFilter);
}
bool MaskingFilterSession::check_query(GWBUF* pPacket)
{
const char* zUser = session_get_user(m_pSession);
const char* zHost = session_get_remote(m_pSession);
if (!zUser)
{
zUser = "";
}
if (!zHost)
{
zHost = "";
}
bool rv = true;
if (rv && m_filter.config().prevent_function_usage())
{
if (is_function_used(pPacket, zUser, zHost))
{
rv = false;
}
}
if (rv && m_filter.config().check_user_variables())
{
if (is_variable_defined(pPacket, zUser, zHost))
{
rv = false;
}
}
return rv;
}
bool MaskingFilterSession::check_textual_query(GWBUF* pPacket)
{
bool rv = false;
if (qc_parse(pPacket, QC_COLLECT_FIELDS | QC_COLLECT_FUNCTIONS) == QC_QUERY_PARSED)
{
if (qc_query_is_type(qc_get_type_mask(pPacket), QUERY_TYPE_PREPARE_NAMED_STMT))
{
GWBUF* pP = qc_get_preparable_stmt(pPacket);
if (pP)
{
rv = check_textual_query(pP);
}
else
{
// If pP is NULL, it indicates that we have a "prepare ps from @a". It must
// be rejected as we currently have no means for checking what columns are
// referred to.
const char* zMessage =
"A statement prepared from a variable is rejected (masking filter).";
set_response(create_error_response(zMessage));
}
}
else
{
rv = check_query(pPacket);
}
}
else
{
set_response(create_parse_error_response());
}
return rv;
}
bool MaskingFilterSession::check_binary_query(GWBUF* pPacket)
{
bool rv = false;
if (qc_parse(pPacket, QC_COLLECT_FIELDS | QC_COLLECT_FUNCTIONS) == QC_QUERY_PARSED)
{
rv = check_query(pPacket);
}
else
{
set_response(create_parse_error_response());
}
return rv;
}
int MaskingFilterSession::routeQuery(GWBUF* pPacket)
{
ComRequest request(pPacket);
@ -58,9 +167,16 @@ int MaskingFilterSession::routeQuery(GWBUF* pPacket)
case MXS_COM_QUERY:
m_res.reset(request.command(), m_filter.rules());
if (m_filter.config().prevent_function_usage() && reject_if_function_used(pPacket))
if (m_filter.config().is_parsing_needed())
{
m_state = EXPECTING_NOTHING;
if (check_textual_query(pPacket))
{
m_state = EXPECTING_RESPONSE;
}
else
{
m_state = EXPECTING_NOTHING;
}
}
else
{
@ -68,6 +184,24 @@ int MaskingFilterSession::routeQuery(GWBUF* pPacket)
}
break;
case MXS_COM_STMT_PREPARE:
if (m_filter.config().is_parsing_needed())
{
if (check_binary_query(pPacket))
{
m_state = IGNORING_RESPONSE;
}
else
{
m_state = EXPECTING_NOTHING;
}
}
else
{
m_state = IGNORING_RESPONSE;
}
break;
case MXS_COM_STMT_EXECUTE:
m_res.reset(request.command(), m_filter.rules());
m_state = EXPECTING_RESPONSE;
@ -370,39 +504,26 @@ void MaskingFilterSession::mask_values(ComPacket& response)
}
}
bool MaskingFilterSession::reject_if_function_used(GWBUF* pPacket)
bool MaskingFilterSession::is_function_used(GWBUF* pPacket, const char* zUser, const char* zHost)
{
bool rejected = false;
bool is_used = false;
SMaskingRules sRules = m_filter.rules();
const char* zUser = session_get_user(m_pSession);
const char* zHost = session_get_remote(m_pSession);
if (!zUser)
{
zUser = "";
}
if (!zHost)
{
zHost = "";
}
auto pred1 = [&sRules, zUser, zHost](const QC_FIELD_INFO& field_info) {
const MaskingRules::Rule* pRule = sRules->get_rule_for(field_info, zUser, zHost);
const MaskingRules::Rule* pRule = sRules->get_rule_for(field_info, zUser, zHost);
return pRule ? true : false;
};
return pRule ? true : false;
};
auto pred2 = [&sRules, zUser, zHost, &pred1](const QC_FUNCTION_INFO& function_info) {
const QC_FIELD_INFO* begin = function_info.fields;
const QC_FIELD_INFO* end = begin + function_info.n_fields;
const QC_FIELD_INFO* begin = function_info.fields;
const QC_FIELD_INFO* end = begin + function_info.n_fields;
auto i = std::find_if(begin, end, pred1);
auto i = std::find_if(begin, end, pred1);
return i != end;
};
return i != end;
};
const QC_FUNCTION_INFO* pInfos;
size_t nInfos;
@ -420,11 +541,51 @@ bool MaskingFilterSession::reject_if_function_used(GWBUF* pPacket)
ss << "The function " << i->name << " is used in conjunction with a field "
<< "that should be masked for '" << zUser << "'@'" << zHost << "', access is denied.";
GWBUF* pResponse = modutil_create_mysql_err_msg(1, 0, 1141, "HY000", ss.str().c_str());
set_response(pResponse);
set_response(create_error_response(ss.str().c_str()));
rejected = true;
is_used = true;
}
return rejected;
return is_used;
}
bool MaskingFilterSession::is_variable_defined(GWBUF* pPacket, const char* zUser, const char* zHost)
{
if (!qc_query_is_type(qc_get_type_mask(pPacket), QUERY_TYPE_USERVAR_WRITE))
{
return false;
}
bool is_defined = false;
SMaskingRules sRules = m_filter.rules();
auto pred = [&sRules, zUser, zHost](const QC_FIELD_INFO& field_info) {
const MaskingRules::Rule* pRule = sRules->get_rule_for(field_info, zUser, zHost);
return pRule ? true : false;
};
const QC_FIELD_INFO* pInfos;
size_t nInfos;
qc_get_field_info(pPacket, &pInfos, &nInfos);
const QC_FIELD_INFO* begin = pInfos;
const QC_FIELD_INFO* end = begin + nInfos;
auto i = std::find_if(begin, end, pred);
if (i != end)
{
std::stringstream ss;
ss << "The field " << i->column << " that should be masked for '" << zUser << "'@'" << zHost
<< "' is used when defining a variable, access is denied.";
set_response(create_error_response(ss.str().c_str()));
is_defined = true;
}
return is_defined;
}

View File

@ -53,6 +53,10 @@ private:
SUPPRESSING_RESPONSE
};
bool check_query(GWBUF* pPacket);
bool check_textual_query(GWBUF* pPacket);
bool check_binary_query(GWBUF* pPacket);
void handle_response(GWBUF* pPacket);
void handle_field(GWBUF* pPacket);
void handle_row(GWBUF* pPacket);
@ -61,7 +65,8 @@ private:
void mask_values(ComPacket& response);
bool reject_if_function_used(GWBUF* pPacket);
bool is_function_used(GWBUF* pPacket, const char* zUser, const char* zHost);
bool is_variable_defined(GWBUF* pPacket, const char* zUser, const char* zHost);
private:
typedef std::shared_ptr<MaskingRules> SMaskingRules;

View File

@ -191,6 +191,14 @@ void RWBackend::process_reply(GWBUF* buffer)
// TODO: Don't clone the buffer
GWBUF* tmp = gwbuf_clone(buffer);
tmp = gwbuf_consume(tmp, mxs_mysql_get_packet_len(tmp));
// Consume repeating OK packets
while (mxs_mysql_more_results_after_ok(buffer) && have_next_packet(tmp))
{
tmp = gwbuf_consume(tmp, mxs_mysql_get_packet_len(tmp));
mxb_assert(tmp);
}
process_reply(tmp);
gwbuf_free(tmp);
return;