MXS-1302: capture option in use and pcre2_match matches all the patterns

pcre2_match matches all the patterns
This commit is contained in:
MassimilianoPinto 2017-07-14 09:22:22 +02:00
parent 2410c23e66
commit 85d7d67ab8
3 changed files with 551 additions and 182 deletions

View File

@ -22,6 +22,7 @@
#include <maxscale/mysql_utils.h>
#include <maxscale/pcre2.hh>
#include <maxscale/utils.hh>
#include <maxscale/json_api.h>
using std::auto_ptr;
using std::string;
@ -32,6 +33,8 @@ using maxscale::Closer;
namespace
{
static const char MASKING_DEFAULT_FILL[] = "X";
static const char KEY_APPLIES_TO[] = "applies_to";
static const char KEY_COLUMN[] = "column";
static const char KEY_DATABASE[] = "database";
@ -43,6 +46,7 @@ static const char KEY_TABLE[] = "table";
static const char KEY_VALUE[] = "value";
static const char KEY_WITH[] = "with";
static const char KEY_OBFUSCATE[] = "obfuscate";
static const char KEY_CAPTURE[] = "capture";
/**
* @class AccountVerbatim
@ -363,7 +367,11 @@ bool create_rules_from_array(json_t* pRules, vector<shared_ptr<MaskingRules::Rul
}
else
{
sRule = MaskingRules::ReplaceRule::create_from(pRule);
json_t* pCapture = json_object_get(pReplace, KEY_CAPTURE);
// Capture takes the precedence
sRule = pCapture ?
MaskingRules::CaptureRule::create_from(pRule) :
MaskingRules::ReplaceRule::create_from(pRule);
}
if (sRule.get())
@ -395,7 +403,8 @@ bool create_rules_from_array(json_t* pRules, vector<shared_ptr<MaskingRules::Rul
*
* @return True, if all rules could be created.
*/
bool create_rules_from_root(json_t* pRoot, vector<shared_ptr<MaskingRules::Rule> >& rules)
bool create_rules_from_root(json_t* pRoot,
vector<shared_ptr<MaskingRules::Rule> >& rules)
{
bool parsed = false;
json_t* pRules = json_object_get(pRoot, KEY_RULES);
@ -408,7 +417,9 @@ bool create_rules_from_root(json_t* pRoot, vector<shared_ptr<MaskingRules::Rule>
}
else
{
MXS_ERROR("The masking rules object contains a `%s` key, but it is not an array.", KEY_RULES);
MXS_ERROR("The masking rules object contains a `%s` key, "
"but it is not an array.",
KEY_RULES);
}
}
@ -473,7 +484,7 @@ MaskingRules::CaptureRule::CaptureRule(const std::string& column,
const std::string& database,
const std::vector<SAccount>& applies_to,
const std::vector<SAccount>& exempted,
const std::string& regexp,
pcre2_code* regexp,
const std::string& fill)
: MaskingRules::Rule::Rule(column, table, database, applies_to, exempted)
, m_regexp(regexp)
@ -495,6 +506,7 @@ MaskingRules::ObfuscateRule::~ObfuscateRule()
MaskingRules::CaptureRule::~CaptureRule()
{
pcre2_code_free(m_regexp);
}
/** Check the Json array for user rules
@ -529,127 +541,369 @@ static bool validate_user_rules(json_t* pApplies_to, json_t* pExempted)
return true;
}
static json_t* rule_get_object(json_t* pRule,
const char *rule_type)
{
json_t *pObj = NULL;
// Check 'rule_type' object
if (!pRule || !(pObj = json_object_get(pRule, rule_type)))
{
MXS_ERROR("A masking rule does not contain the '%s' key.",
rule_type);
return NULL;
}
if (!json_is_object(pObj))
{
MXS_ERROR("A masking rule contains a '%s' key, "
"but the value is not a valid Json object.",
rule_type);
return NULL;
}
return pObj;
}
/**
* Checks database, table and column values
*
* @param pColumn The database column
* @param pTable The database table
* @param pDatabase The database name
*
* @return true on success, false otherwise
*/
static bool rule_check_database_options(json_t* pColumn,
json_t* pTable,
json_t* pDatabase)
{
// Only column is mandatory; both table and database are optional.
if ((pColumn && json_is_string(pColumn)) &&
(!pTable || json_is_string(pTable)) &&
(!pDatabase || json_is_string(pDatabase)))
{
return true;
}
else
{
if (!pColumn || !json_is_string(pColumn))
{
MXS_ERROR("The '%s' object of a masking rule does not have "
"the mandatory '%s' key or it's not a valid Json string.",
KEY_REPLACE,
KEY_COLUMN);
}
else
{
MXS_ERROR("In the '%s' object of a masking rule, the keys "
"'%s' and/or '%s' re not valid Json strings.",
KEY_REPLACE,
KEY_TABLE,
KEY_DATABASE);
}
return false;
}
}
/**
* Returns a Json objet with the fill value
*
* @param pDoc The Json input object
*
* @return A Json object or NULL
*/
static json_t* rule_get_fill(json_t* pDoc)
{
json_t* pFill = json_object_get(pDoc, KEY_FILL);
if (!pFill)
{
// Allowed. Use default value for fill and add it to pWith.
pFill = json_string(MASKING_DEFAULT_FILL);
if (pFill)
{
json_object_set_new(pDoc, KEY_FILL, pFill);
}
else
{
MXS_ERROR("json_string() error, cannot produce"
" a valid '%s' object for rule '%s'.",
KEY_FILL,
KEY_REPLACE);
}
}
return pFill;
}
/**
* Perform rule checks for all Rule classes
*
* @param pRule The Json rule
* @param applies_to Account instances corresponding to the
* accounts listed in 'applies_to' in the json file.
* @param exempted Account instances corresponding to the
* accounts listed in 'exempted' in the json file.
*
* @return True on success, false on errors.
*/
static bool rule_run_common_checks(json_t* pRule,
vector<shared_ptr<MaskingRules::Rule::Account> >* applies_to,
vector<shared_ptr<MaskingRules::Rule::Account> >* exempted)
{
json_t* pApplies_to = json_object_get(pRule, KEY_APPLIES_TO);
json_t* pExempted = json_object_get(pRule, KEY_EXEMPTED);
// Check for pApplies_to and pExempted
if (!validate_user_rules(pApplies_to, pExempted))
{
return false;
}
// Set the account rules
if (pApplies_to && pExempted &&
(!get_accounts(KEY_APPLIES_TO, pApplies_to, *applies_to) ||
!get_accounts(KEY_EXEMPTED, pExempted, *exempted)))
{
return false;
}
return true;
}
/**
* Returns rule values from a Json rule object
*
* @param pRule The Json rule
* @param column The column value from the json file.
* @param table The table value from the json file.
* @param database The database value from the json file.
*
* @return True on success, false on errors.
*/
static bool rule_get_common_values(json_t* pRule,
std::string* column,
std::string* table,
std::string* database)
{
// Get database, table && column
json_t* pDatabase = json_object_get(pRule, KEY_DATABASE);
json_t* pTable = json_object_get(pRule, KEY_TABLE);
json_t* pColumn = json_object_get(pRule, KEY_COLUMN);
// Check column/table/dataase
if (!rule_check_database_options(pColumn,
pTable,
pDatabase))
{
return false;
}
// Column exists
column->assign(json_string_value(pColumn));
// Check optional table and dbname
if (pTable)
{
table->assign(json_string_value(pTable));
}
if (pDatabase)
{
database->assign(json_string_value(pDatabase));
}
return true;
}
/**
* Check Json object, run common checks and return rule values
*
* @param pRule The Json rule object
* @param applies_to Account instances corresponding to the
* accounts listed in 'applies_to' in the json file.
* @param exempted Account instances corresponding to the
* accounts listed in 'exempted' in the json file.
* @param column The column value from the json file.
* @param table The table value from the json file.
* @param database The database value from the json file.
* @param rule_type The rule_type (obfuscate or replace)
*
* @return True on success, false on errors
*/
bool rule_get_values(json_t* pRule,
vector<shared_ptr<MaskingRules::Rule::Account> >* applies_to,
vector<shared_ptr<MaskingRules::Rule::Account> >* exempted,
std::string* column,
std::string* table,
std::string* database,
const char *rule_type)
{
json_t *pKeyObj;
// Get Key object based on 'rule_type' param
if ((pKeyObj = rule_get_object(pRule,
rule_type)) &&
// Run checks on user access
rule_run_common_checks(pRule,
applies_to,
exempted) &&
// Extract values from the rule
rule_get_common_values(pKeyObj,
column,
table,
database))
{
return true;
}
return false;
}
/**
* Returns 'capture' regexp & 'fill' value from a 'replace' rule
*
* @param pRule The Json rule doc
* @param pCapture The string buffer for 'capture'value
* @param pFill The string buffer for 'fill' value
*
* @return True on success, false on errors
*/
bool rule_get_capture_fill(json_t* pRule,
std::string *pCapture,
std::string* pFill)
{
// Get the 'with' key from the rule
json_t* pWith = json_object_get(pRule, KEY_WITH);
if (!pWith || !json_is_object(pWith))
{
MXS_ERROR("A masking '%s' rule doesn't have a valid '%s' key",
KEY_REPLACE,
KEY_WITH);
return false;
}
// Get the 'replace' rule object
json_t* pKeyObj;
if (!(pKeyObj = rule_get_object(pRule, KEY_REPLACE)))
{
return false;
}
// Get fill from 'with' object
json_t* pTheFill = rule_get_fill(pWith);
// Get 'capture' from 'replace' ojbect
json_t* pTheCapture = json_object_get(pKeyObj, KEY_CAPTURE);
// Check values
if ((!pTheFill || !json_is_string(pTheFill)) ||
((!pTheCapture || !json_is_string(pTheCapture))))
{
MXS_ERROR("A masking '%s' rule has '%s' and/or '%s' "
"invalid Json strings.",
KEY_REPLACE,
KEY_CAPTURE,
KEY_FILL);
return false;
}
else
{
// Update the string buffers
pFill->assign(json_string_value(pTheFill));
pCapture->assign(json_string_value(pTheCapture));
return true;
}
}
/**
* Returns 'value' & 'fill' from a 'replace' rule
*
* @param pRule The Json rule doc
* @param pValue The string buffer for 'value'
* @param pFill The string buffer for 'fill'
*
* @return True on success, false on errors
*/
bool rule_get_value_fill(json_t* pRule,
std::string *pValue,
std::string* pFill)
{
// Get the 'with' key from the rule
json_t* pWith = json_object_get(pRule, KEY_WITH);
if (!pWith || !json_is_object(pWith))
{
MXS_ERROR("A masking '%s' rule doesn't have a valid '%s' key.",
KEY_REPLACE,
KEY_WITH);
return false;
}
// Get fill from 'with' object
json_t* pTheFill = rule_get_fill(pWith);
// Get value from 'with' object
json_t* pTheValue = json_object_get(pWith, KEY_VALUE);
// Check values
if ((!pTheFill || !json_is_string(pTheFill)) ||
(!pTheValue || !json_is_string(pTheValue)))
{
MXS_ERROR("A masking '%s' rule has '%s' and/or '%s' "
"invalid Json strings.",
KEY_REPLACE,
KEY_VALUE,
KEY_FILL);
return false;
}
else
{
// Update the string buffers
pFill->assign(json_string_value(pTheFill));
pValue->assign(json_string_value(pTheValue));
return true;
}
}
//static
auto_ptr<MaskingRules::Rule> MaskingRules::ReplaceRule::create_from(json_t* pRule)
{
ss_dassert(json_is_object(pRule));
auto_ptr<MaskingRules::Rule> sRule;
json_t* pReplace = json_object_get(pRule, KEY_REPLACE);
json_t* pWith = json_object_get(pRule, KEY_WITH);
json_t* pApplies_to = json_object_get(pRule, KEY_APPLIES_TO);
json_t* pExempted = json_object_get(pRule, KEY_EXEMPTED);
// Check replace && with
if (pReplace && pWith)
{
const char *err = NULL;
if (!json_is_object(pReplace))
{
err = KEY_REPLACE;
}
if (!json_is_object(pWith))
{
err = KEY_WITH;
}
if (err)
{
MXS_ERROR("A masking rule contains a '%s' key, "
"but the value is not an object.",
err);
return sRule;
}
}
else
{
MXS_ERROR("A masking rule does not contain a '%s' and/or a '%s' key.",
KEY_REPLACE,
KEY_WITH);
return sRule;
}
// Check for pApplies_to and pExempted
if (!validate_user_rules(pApplies_to, pExempted))
{
return sRule;
}
json_t *pReplace;
std::string column, table, database, value, fill;
vector<shared_ptr<MaskingRules::Rule::Account> > applies_to;
vector<shared_ptr<MaskingRules::Rule::Account> > exempted;
auto_ptr<MaskingRules::Rule> sRule;
// Set the account rules
if (pApplies_to && pExempted &&
(!get_accounts(KEY_APPLIES_TO, pApplies_to, applies_to) ||
!get_accounts(KEY_EXEMPTED, pExempted, exempted)))
// Check rule, extract base values
if (rule_get_values(pRule,
&applies_to,
&exempted,
&column,
&table,
&database,
KEY_REPLACE) &&
rule_get_value_fill(pRule, &value, &fill)) // get value/fill
{
return sRule;
}
// Get database, table && column
json_t* pDatabase = json_object_get(pReplace, KEY_DATABASE);
json_t* pTable = json_object_get(pReplace, KEY_TABLE);
json_t* pColumn = json_object_get(pReplace, KEY_COLUMN);
// A column is mandatory; both table and database are optional.
if ((pColumn && json_is_string(pColumn)) &&
(!pTable || json_is_string(pTable)) &&
(!pDatabase || json_is_string(pDatabase)))
{
json_t* pValue = json_object_get(pWith, KEY_VALUE);
json_t* pFill = json_object_get(pWith, KEY_FILL);
if (!pFill)
if (!value.empty() && !fill.empty())
{
// Allowed. Use default value for fill and add it to pWith.
pFill = json_string("X");
if (pFill)
{
json_object_set_new(pWith, KEY_FILL, pFill);
}
else
{
MXS_ERROR("json_string() error, cannot produce a valid rule.");
}
// Apply value/fill: instantiate the ReplaceRule class
sRule = auto_ptr<MaskingRules::ReplaceRule>(new MaskingRules::ReplaceRule(column,
table,
database,
applies_to,
exempted,
value,
fill));
}
if (pFill)
else
{
if ((!pValue || (json_is_string(pValue) && json_string_length(pValue))) &&
(json_is_string(pFill) && json_string_length(pFill)))
{
string column(json_string_value(pColumn));
string table(pTable ? json_string_value(pTable) : "");
string database(pDatabase ? json_string_value(pDatabase) : "");
string value(pValue ? json_string_value(pValue) : "");
string fill(pFill ? json_string_value(pFill) : "");
sRule = auto_ptr<MaskingRules::ReplaceRule>(new MaskingRules::ReplaceRule(column,
table,
database,
applies_to,
exempted,
value,
fill));
}
else
{
MXS_ERROR("One of the keys '%s' or '%s' of masking rule object '%s' "
"has a non-string value or the string is empty.",
KEY_VALUE, KEY_FILL, KEY_WITH);
}
MXS_ERROR("Key '%s' or '%s' of masking '%s' rule object '%s' "
"has a non-string value or empty value.",
KEY_VALUE,
KEY_FILL,
KEY_REPLACE,
KEY_WITH);
}
}
else
{
MXS_ERROR("The '%s' object of a masking rule does not have a '%s' key, or "
"the values of that key and/or possible '%s' and '%s' keys are "
"not strings.",
KEY_REPLACE,
KEY_COLUMN,
KEY_TABLE,
KEY_DATABASE);
}
return sRule;
}
@ -659,71 +913,105 @@ auto_ptr<MaskingRules::Rule> MaskingRules::ObfuscateRule::create_from(json_t* pR
{
ss_dassert(json_is_object(pRule));
auto_ptr<MaskingRules::Rule> sRule;
// Get obfuscate
json_t* pObfuscate = json_object_get(pRule, KEY_OBFUSCATE);
// Get applies_to
json_t* pApplies_to = json_object_get(pRule, KEY_APPLIES_TO);
// Get applies_to
json_t* pExempted = json_object_get(pRule, KEY_EXEMPTED);
// Check the pObfuscate object
if (pObfuscate && !json_is_object(pObfuscate))
{
MXS_ERROR("A masking rule contains a '%s' key, "
"but the value is not an object.",
KEY_OBFUSCATE);
return sRule;
}
// Check for pApplies_to and pExempted
if (!validate_user_rules(pApplies_to, pExempted))
{
return sRule;
}
std::string column, table, database;
vector<shared_ptr<MaskingRules::Rule::Account> > applies_to;
vector<shared_ptr<MaskingRules::Rule::Account> > exempted;
auto_ptr<MaskingRules::Rule> sRule;
// Set the account rules
if (pApplies_to && pExempted &&
(!get_accounts(KEY_APPLIES_TO, pApplies_to, applies_to) ||
!get_accounts(KEY_EXEMPTED, pExempted, exempted)))
// Check rule, extract base values
if (rule_get_values(pRule,
&applies_to,
&exempted,
&column,
&table,
&database,
KEY_OBFUSCATE))
{
return sRule;
}
// Get database, table and column from obfuscate object
json_t* pDatabase = json_object_get(pObfuscate, KEY_DATABASE);
json_t* pTable = json_object_get(pObfuscate, KEY_TABLE);
json_t* pColumn = json_object_get(pObfuscate, KEY_COLUMN);
// A column is mandatory; both table and database are optional.
if ((pColumn && json_is_string(pColumn)) &&
(!pTable || json_is_string(pTable)) &&
(!pDatabase || json_is_string(pDatabase)))
{
// Instantiate the ObfuscateRule class
string column(json_string_value(pColumn));
string table(pTable ? json_string_value(pTable) : "");
string database(pDatabase ? json_string_value(pDatabase) : "");
sRule = auto_ptr<MaskingRules::Rule>(new MaskingRules::ObfuscateRule(column,
table,
database,
applies_to,
exempted));
}
else
return sRule;
}
/**
* Compiles a pcre2 pattern match
*
* @param match_string The pattern match to compile
*
* @return A valid pcre2_code code or NULL on errors.
*/
static pcre2_code* rule_compile_pcre2_match(const char* match_string)
{
int errcode;
PCRE2_SIZE erroffset;
// Compile regexp
pcre2_code* pCode = pcre2_compile((PCRE2_SPTR)match_string,
PCRE2_ZERO_TERMINATED,
0,
&errcode,
&erroffset,
NULL);
if (!pCode)
{
MXS_ERROR("The '%s' object of a masking rule does not have a '%s' key, or "
"the values of that key and/or possible '%s' and '%s' keys are "
"not strings.",
KEY_OBFUSCATE,
KEY_COLUMN,
KEY_TABLE,
KEY_DATABASE);
PCRE2_UCHAR errbuf[512];
pcre2_get_error_message(errcode, errbuf, sizeof(errbuf));
MXS_ERROR("Regex compilation failed at %d for regex '%s': %s",
(int)erroffset, match_string, errbuf);
return NULL;
}
return pCode;
}
//static
auto_ptr<MaskingRules::Rule> MaskingRules::CaptureRule::create_from(json_t* pRule)
{
ss_dassert(json_is_object(pRule));
std::string column, table, database, value, fill, capture;
vector<shared_ptr<MaskingRules::Rule::Account> > applies_to;
vector<shared_ptr<MaskingRules::Rule::Account> > exempted;
auto_ptr<MaskingRules::Rule> sRule;
// Check rule, extract base values
// Note: the capture rule has same rule_type of "replace"
if (rule_get_values(pRule,
&applies_to,
&exempted,
&column,
&table,
&database,
KEY_REPLACE) &&
rule_get_capture_fill(pRule, // get capture/fill
&capture,
&fill))
{
if (!capture.empty() && !fill.empty())
{
// Compile the regexp capture
pcre2_code* pCode = rule_compile_pcre2_match(capture.c_str());
if (pCode)
{
Closer<pcre2_code*> code(pCode);
// Instantiate the CaptureRule class
sRule = auto_ptr<MaskingRules::CaptureRule>(new MaskingRules::CaptureRule(column,
table,
database,
applies_to,
exempted,
pCode,
fill));
// Ownership of pCode has been moved to the CaptureRule object.
code.release();
}
}
}
return sRule;
@ -834,8 +1122,91 @@ static inline char maxscale_basic_obfuscation(const char c)
return c;
}
/**
* Fills a buffer with a fill string
*
* @param f_first The iterator pointing to first fill byt
* @param f_last The iterator pointing to last fill byte
* @param o_first The iterator pointing to first buffer byte
* @param o_last The iterator pointing to last buffer byte
*/
template<class FillIter, class OutIter>
inline void fill_buffer(FillIter f_first,
FillIter f_last,
OutIter o_first,
OutIter o_last)
{
FillIter pFill = f_first;
while (o_first != o_last)
{
*o_first++ = *pFill++;
if (pFill == f_last)
{
pFill = f_first;
}
}
}
void MaskingRules::CaptureRule::rewrite(LEncString& s) const
{
int rv = 0;
uint32_t n_matches = 0;
PCRE2_SIZE* ovector = NULL;
// Create the match data object from m_regexp class member
pcre2_match_data* pData = pcre2_match_data_create_from_pattern(m_regexp, NULL);
// Set initial offset to the input beginning
PCRE2_SIZE startoffset = 0;
// Get input string size
size_t total_len = s.length();
if (pData)
{
// Get the fill size
size_t fill_len = m_fill.length();
Closer<pcre2_match_data*> data(pData);
// Match all the compiled pattern
while ((startoffset < total_len) &&
(rv = pcre2_match(m_regexp,
(PCRE2_SPTR)s.to_string().c_str(),
PCRE2_ZERO_TERMINATED,
startoffset,
0,
pData,
NULL)) >= 0)
{
// Get offset array value pairs of substrings: $0=0,1 ; $1=2,3
PCRE2_SIZE* ovector = pcre2_get_ovector_pointer(pData);
// Get Full Match substring size: $0 is [0] and [1]
size_t substring_len = ovector[1] - ovector[0];
// Go to Full Match substring offset: 0
LEncString::iterator i = s.begin() + ovector[0];
// Avoid infinite loop in pcre2_match for a zero-length match
if (ovector[1] == ovector[0])
{
break;
}
// Copy the fill string into substring
fill_buffer(m_fill.begin(), m_fill.end(), i, i + substring_len);
// Set offset to the end of Full Match substring or break
startoffset = ovector[1];
}
// Log errors, exclding NO_MATCH or PARTIAL
if (rv < 0 && (rv != PCRE2_ERROR_NOMATCH || PCRE2_ERROR_PARTIAL))
{
MXS_PCRE2_PRINT_ERROR(rv);
}
}
else
{
MXS_ERROR("Allocation of matching data for PCRE2 failed."
" This is most likely caused by a lack of memory");
}
}
void MaskingRules::ObfuscateRule::rewrite(LEncString& s) const
@ -869,18 +1240,8 @@ void MaskingRules::ReplaceRule::rewrite(LEncString& s) const
LEncString::iterator i = s.begin();
size_t len = m_fill.length();
while (total_len)
{
if (total_len < len)
{
len = total_len;
}
std::copy(m_fill.data(), m_fill.data() + len, i);
i += len;
total_len -= len;
}
// Copy the fill string
fill_buffer(m_fill.begin(), m_fill.end(), s.begin(), s.end());
}
else
{

View File

@ -256,7 +256,7 @@ public:
* accounts listed in 'applies_to' in the json file.
* @param exempted Account instances corresponding to the
* accounts listed in 'exempted' in the json file.
* @param regexp The capture regexp from the json file.
* @param regexp The compiled capture regexp from the json file.
* @param fill The fill value from the json file.
*/
CaptureRule(const std::string& column,
@ -264,14 +264,14 @@ public:
const std::string& database,
const std::vector<SAccount>& applies_to,
const std::vector<SAccount>& exempted,
const std::string& regexp,
pcre2_code* regexp,
const std::string& fill);
~CaptureRule();
const std::string& capture() const
const pcre2_code& capture() const
{
return m_regexp;
return *m_regexp;
}
const std::string& fill() const
@ -297,7 +297,7 @@ public:
void rewrite(LEncString& s) const;
private:
std::string m_regexp;
pcre2_code* m_regexp;
std::string m_fill;
private:

View File

@ -140,6 +140,14 @@ public:
return rv;
}
iterator operator + (ptrdiff_t n)
{
ss_dassert(m_pS);
iterator rv = m_pS;
rv += n;
return rv;
}
iterator& operator += (ptrdiff_t n)
{
ss_dassert(m_pS);