diff --git a/server/modules/filter/masking/maskingfilter.cc b/server/modules/filter/masking/maskingfilter.cc index 233c8ae33..9170e62bd 100644 --- a/server/modules/filter/masking/maskingfilter.cc +++ b/server/modules/filter/masking/maskingfilter.cc @@ -86,7 +86,7 @@ extern "C" MXS_MODULE* MXS_CREATE_MODULE() MXS_FILTER_VERSION, "A masking filter that is capable of masking/obfuscating returned column values.", "V1.0.0", - RCAP_TYPE_STMT_INPUT | RCAP_TYPE_CONTIGUOUS_OUTPUT, + RCAP_TYPE_CONTIGUOUS_INPUT | RCAP_TYPE_CONTIGUOUS_OUTPUT, &MaskingFilter::s_object, NULL, /* Process init. */ NULL, /* Process finish. */ diff --git a/server/modules/filter/masking/maskingfiltersession.cc b/server/modules/filter/masking/maskingfiltersession.cc index e545e4aa9..52d5006fc 100644 --- a/server/modules/filter/masking/maskingfiltersession.cc +++ b/server/modules/filter/masking/maskingfiltersession.cc @@ -18,6 +18,7 @@ #include #include +#include #include #include #include @@ -55,6 +56,68 @@ int MaskingFilterSession::routeQuery(GWBUF* pPacket) switch (request.command()) { case MXS_COM_QUERY: + { + m_res.reset(request.command(), m_filter.rules()); + + 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); + + 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; + + auto i = std::find_if(begin, end, pred1); + + return i != end; + }; + + const QC_FUNCTION_INFO* pInfos; + size_t nInfos; + + qc_get_function_info(pPacket, &pInfos, &nInfos); + + const QC_FUNCTION_INFO* begin = pInfos; + const QC_FUNCTION_INFO* end = begin + nInfos; + + auto i = std::find_if(begin, end, pred2); + + if (i == end) + { + m_state = EXPECTING_RESPONSE; + } + else + { + std::stringstream ss; + 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); + m_state = EXPECTING_NOTHING; + } + } + break; + case MXS_COM_STMT_EXECUTE: m_res.reset(request.command(), m_filter.rules()); m_state = EXPECTING_RESPONSE; @@ -64,7 +127,14 @@ int MaskingFilterSession::routeQuery(GWBUF* pPacket) m_state = IGNORING_RESPONSE; } - return FilterSession::routeQuery(pPacket); + int rv = 1; + + if (m_state != EXPECTING_NOTHING) + { + rv = FilterSession::routeQuery(pPacket); + } + + return rv; } int MaskingFilterSession::clientReply(GWBUF* pPacket) diff --git a/server/modules/filter/masking/maskingrules.cc b/server/modules/filter/masking/maskingrules.cc index f4e16b81a..c1c10e8df 100644 --- a/server/modules/filter/masking/maskingrules.cc +++ b/server/modules/filter/masking/maskingrules.cc @@ -1044,6 +1044,65 @@ string MaskingRules::Rule::match() const return s; } +bool MaskingRules::Rule::matches(const ComQueryResponse::ColumnDef& column_def, + const char* zUser, + const char* zHost) const +{ + const LEncString& table = column_def.org_table(); + const LEncString& database = column_def.schema(); + + // If the resultset does not contain table and database names, as will + // be the case in e.g. "SELECT * FROM table UNION SELECT * FROM table", + // we consider it a match if a table or database have been provided. + // Otherwise it would be easy to bypass a table/database rule. + + bool match = + (m_column == column_def.org_name()) && + (m_table.empty() || table.empty() || (m_table == table)) && + (m_database.empty() || database.empty() || (m_database == database)); + + if (match) + { + // If the column matched, then we need to check whether the rule applies + // to the user and host. + + match = matches_account(zUser, zHost); + } + + return match; +} + +bool MaskingRules::Rule::matches(const QC_FIELD_INFO& field, + const char* zUser, + const char* zHost) const +{ + const char* zColumn = field.column; + const char* zTable = field.table; + const char* zDatabase = field.database; + + ss_dassert(zColumn); + + // If the resultset does not contain table and database names, as will + // be the case in e.g. "SELECT * FROM table UNION SELECT * FROM table", + // we consider it a match if a table or database have been provided. + // Otherwise it would be easy to bypass a table/database rule. + + bool match = + (m_column == zColumn) && + (m_table.empty() || !zTable || (m_table == zTable)) && + (m_database.empty() || !zDatabase || (m_database == zDatabase)); + + if (match) + { + // If the column matched, then we need to check whether the rule applies + // to the user and host. + + match = matches_account(zUser, zHost); + } + + return match; +} + namespace { @@ -1067,52 +1126,32 @@ private: } -bool MaskingRules::Rule::matches(const ComQueryResponse::ColumnDef& column_def, - const char* zUser, - const char* zHost) const +bool MaskingRules::Rule::matches_account(const char* zUser, + const char* zHost) const { - const LEncString& table = column_def.org_table(); - const LEncString& database = column_def.schema(); + bool match = true; - // If the resultset does not contain table and database names, as will - // be the case in e.g. "SELECT * FROM table UNION SELECT * FROM table", - // we consider it a match if a table or database have been provided. - // Otherwise it would be easy to bypass a table/database rule. + AccountMatcher matcher(zUser, zHost); - bool match = - (m_column == column_def.org_name()) && - (m_table.empty() || table.empty() || (m_table == table)) && - (m_database.empty() || database.empty() || (m_database == database)); - - if (match) + if (m_applies_to.size() != 0) { - // If the column matched, then we need to check whether the rule applies - // to the user and host. + vector::const_iterator i = std::find_if(m_applies_to.begin(), + m_applies_to.end(), + matcher); - AccountMatcher matcher(zUser, zHost); + match = (i != m_applies_to.end()); + } - if (m_applies_to.size() != 0) - { - match = false; + if (match && (m_exempted.size() != 0)) + { + // If it is still a match, we need to check whether the user/host is + // exempted. - vector::const_iterator i = std::find_if(m_applies_to.begin(), - m_applies_to.end(), - matcher); + vector::const_iterator i = std::find_if(m_exempted.begin(), + m_exempted.end(), + matcher); - match = (i != m_applies_to.end()); - } - - if (match && (m_exempted.size() != 0)) - { - // If it is still a match, we need to check whether the user/host is - // exempted. - - vector::const_iterator i = std::find_if(m_exempted.begin(), - m_exempted.end(), - matcher); - - match = (i == m_exempted.end()); - } + match = (i == m_exempted.end()); } return match; @@ -1352,13 +1391,14 @@ std::auto_ptr MaskingRules::create_from(json_t* pRoot) namespace { +template class RuleMatcher : std::unary_function { public: - RuleMatcher(const ComQueryResponse::ColumnDef& column_def, + RuleMatcher(const T& field, const char* zUser, const char* zHost) - : m_column_def(column_def) + : m_field(field) , m_zUser(zUser) , m_zHost(zHost) { @@ -1366,11 +1406,11 @@ public: bool operator()(const MaskingRules::SRule& sRule) { - return sRule->matches(m_column_def, m_zUser, m_zHost); + return sRule->matches(m_field, m_zUser, m_zHost); } private: - const ComQueryResponse::ColumnDef& m_column_def; + const T& m_field; const char* m_zUser; const char* m_zHost; }; @@ -1383,7 +1423,7 @@ const MaskingRules::Rule* MaskingRules::get_rule_for(const ComQueryResponse::Col { const Rule* pRule = NULL; - RuleMatcher matcher(column_def, zUser, zHost); + RuleMatcher matcher(column_def, zUser, zHost); vector::const_iterator i = std::find_if(m_rules.begin(), m_rules.end(), matcher); if (i != m_rules.end()) @@ -1395,3 +1435,23 @@ const MaskingRules::Rule* MaskingRules::get_rule_for(const ComQueryResponse::Col return pRule; } + +const MaskingRules::Rule* MaskingRules::get_rule_for(const QC_FIELD_INFO& field_info, + const char* zUser, + const char* zHost) const +{ + const Rule* pRule = NULL; + + RuleMatcher matcher(field_info, zUser, zHost); + vector::const_iterator i = std::find_if(m_rules.begin(), m_rules.end(), matcher); + + if (i != m_rules.end()) + { + const SRule& sRule = *i; + + pRule = sRule.get(); + } + + return pRule; +} + diff --git a/server/modules/filter/masking/maskingrules.hh b/server/modules/filter/masking/maskingrules.hh index 2249bb03c..f4a4efc47 100644 --- a/server/modules/filter/masking/maskingrules.hh +++ b/server/modules/filter/masking/maskingrules.hh @@ -123,6 +123,19 @@ public: const char* zUser, const char* zHost) const; + /** + * Establish whether a rule matches a field and user/host. + * + * @param field What field. + * @param zUser The current user. + * @param zHost The current host. + * + * @return True, if the rule matches. + */ + bool matches(const QC_FIELD_INFO& field, + const char* zUser, + const char* zHost) const; + /** * Mask the column content with value or fill. * @@ -134,6 +147,9 @@ public: Rule(const Rule&); Rule& operator = (const Rule&); + bool matches_account(const char* zUser, + const char* zHost) const; + private: std::string m_column; std::string m_table; @@ -361,6 +377,23 @@ public: const char* zUser, const char* zHost) const; + /** + * Return the rule object that matches a QC_FIELD_INFO and user/host. + * + * @param field A field. + * @param zUser The current user. + * @param zHost The current host. + * + * @return A rule object that matches the field indo and user/host + * or NULL if no such rule object exists. + * + * @attention The returned object remains value only as long as the + * @c MaskingRules object remains valid. + */ + const Rule* get_rule_for(const QC_FIELD_INFO& field_info, + const char* zUser, + const char* zHost) const; + typedef std::tr1::shared_ptr SRule; private: