mirror of
https://github.com/rsyslog/rsyslog.git
synced 2025-12-12 19:30:42 +01:00
Merge pull request #6316 from alorbach/cursor/add-regex-support-for-trailing-data-removal-default-1d66
mmsnareparse: add parameter ignoreTrailingPattern.regex
This commit is contained in:
commit
c327c574b2
@ -285,7 +285,8 @@ Parameters
|
||||
"``definition.json``", "string", "``unset``", "Inline JSON descriptor following the same schema as ``definition.file``. Processed after the file-based overrides."
|
||||
"``runtime.config``", "string", "``unset``", "Persistent runtime configuration file. Supports the definition schema plus ``options`` such as ``enable_debug`` and ``enable_fallback``."
|
||||
"``validation.mode`` / ``validation_mode``", "string", "``permissive``", "Selects parser strictness: ``permissive`` ignores issues, ``moderate`` records warnings, ``strict`` aborts when thresholds are exceeded."
|
||||
"``ignoreTrailingPattern``", "string", "``unset``", "Pattern that marks the start of a trailing extra-data section to be ignored during parsing. When set, the parser searches for this pattern in trailing positions (after the last tab-separated token). If found, the message is truncated at that point before parsing, and the truncated extra-data section is stored in the ``!extradata_section`` message property. This is useful for removing non-standard trailing enrichment data that may be added by third-party enrichers. The pattern is a literal string match (not a regex)."
|
||||
"``ignoreTrailingPattern``", "string", "``unset``", "Pattern that marks the start of a trailing extra-data section to be ignored during parsing. When set, the parser searches for this pattern in trailing positions (after the last tab-separated token). If found, the message is truncated at the last tab character (removing the entire last token), and the truncated extra-data section (including any dynamic data like numbers preceding the pattern) is stored in the ``!extradata_section`` message property. This is useful for removing non-standard trailing custom data that may be added by third-party enrichers. The pattern is a literal string match (not a regex). This parameter is mutually exclusive with ``ignoreTrailingPattern.regex``."
|
||||
"``ignoreTrailingPattern.regex``", "string", "``unset``", "POSIX extended regular expression that marks the start of a trailing extra-data section to be ignored during parsing. When set, the parser applies the regex to the trailing token (content after the last tab-separated token). If the regex matches, the message is truncated at the last tab character (removing the entire last token), and the truncated extra-data section is stored in the ``!extradata_section`` message property. This is useful for removing non-standard trailing custom data with dynamic prefixes (e.g., random integers) that may be added by third-party enrichers. Example: ``^[0-9]+\\s+custom_section:`` matches a number followed by whitespace and then the marker. This parameter is mutually exclusive with ``ignoreTrailingPattern``."
|
||||
|
||||
Extracted fields
|
||||
----------------
|
||||
|
||||
@ -29,6 +29,7 @@
|
||||
#include <time.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/in.h>
|
||||
#include <regex.h>
|
||||
|
||||
#include "conf.h"
|
||||
#include "datetime.h"
|
||||
@ -442,6 +443,8 @@ typedef struct _instanceData {
|
||||
sbool emitRawPayload;
|
||||
sbool emitDebugJson;
|
||||
uchar *ignoreTrailingPattern;
|
||||
regex_t ignoreTrailingPattern_preg; /* compiled regex for ignoreTrailingPattern.regex */
|
||||
sbool ignoreTrailingPattern_isRegex; /* flag indicating if regex mode is enabled */
|
||||
validation_context_t validationTemplate;
|
||||
section_descriptor_t *sectionDescriptors;
|
||||
size_t sectionDescriptorCount;
|
||||
@ -494,6 +497,7 @@ struct modConfData_s {
|
||||
char *definitionJson;
|
||||
char *runtimeConfigFile;
|
||||
uchar *ignoreTrailingPattern;
|
||||
uchar *ignoreTrailingPatternRegex;
|
||||
validation_context_t validationTemplate;
|
||||
};
|
||||
static modConfData_t *loadModConf = NULL;
|
||||
@ -5083,69 +5087,128 @@ static char *detect_and_truncate_trailing_extradata(instanceData *pData, char *m
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char *pattern = (const char *)pData->ignoreTrailingPattern;
|
||||
size_t patternLen = strlen(pattern);
|
||||
if (patternLen == 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Find the last tab character to identify the end of the last token */
|
||||
char *lastTab = strrchr(mutableMsg, '\t');
|
||||
if (lastTab == NULL) {
|
||||
/* No tabs found - this is an edge case as SNARE format normally uses tab-separated values.
|
||||
* However, we still attempt to remove trailing enrichment sections from malformed or
|
||||
* Valid Snare messages will always have tabs and be handled in the else block below,
|
||||
* where truncation is safely anchored to the last token.
|
||||
*
|
||||
* This fallback path attempts to remove trailing enrichment sections from malformed or
|
||||
* non-standard messages. Be conservative: only truncate if pattern appears in the
|
||||
* trailing portion (last 20% or last 200 chars, whichever is smaller) to avoid
|
||||
* accidentally removing legitimate message content. */
|
||||
size_t msgLenVal = strlen(mutableMsg);
|
||||
if (msgLenVal < patternLen) {
|
||||
return NULL;
|
||||
}
|
||||
size_t trailingSearchLen = msgLenVal / 5; /* Last 20% */
|
||||
if (trailingSearchLen > 200) {
|
||||
trailingSearchLen = 200;
|
||||
}
|
||||
if (trailingSearchLen < patternLen) {
|
||||
trailingSearchLen = patternLen;
|
||||
}
|
||||
/* Search backwards from end within the trailing portion only */
|
||||
char *searchStart = mutableMsg + msgLenVal - trailingSearchLen;
|
||||
for (char *searchPos = mutableMsg + msgLenVal - patternLen; searchPos >= searchStart; searchPos--) {
|
||||
if (memcmp(searchPos, pattern, patternLen) == 0) {
|
||||
if (pData->ignoreTrailingPattern_isRegex) {
|
||||
/* For regex mode, search within the trailing portion (last 20% or last 200 chars) */
|
||||
size_t trailingSearchLen = msgLenVal / 5; /* Last 20% */
|
||||
if (trailingSearchLen > 200) {
|
||||
trailingSearchLen = 200;
|
||||
}
|
||||
if (trailingSearchLen > msgLenVal) {
|
||||
trailingSearchLen = msgLenVal;
|
||||
}
|
||||
char *searchStart = mutableMsg + msgLenVal - trailingSearchLen;
|
||||
if (searchStart < mutableMsg) {
|
||||
searchStart = mutableMsg;
|
||||
}
|
||||
/* Try regex match on the trailing portion */
|
||||
const int isMatch = !regexec(&pData->ignoreTrailingPattern_preg, searchStart, 0, NULL, 0);
|
||||
if (isMatch) {
|
||||
/* Found pattern in trailing position - save it and truncate before it */
|
||||
extradataSection = strdup(searchPos);
|
||||
extradataSection = strdup(searchStart);
|
||||
if (extradataSection == NULL) {
|
||||
return NULL; /* out of memory */
|
||||
}
|
||||
*searchPos = '\0';
|
||||
*searchStart = '\0';
|
||||
if (msgLen != NULL) {
|
||||
*msgLen = searchPos - mutableMsg;
|
||||
*msgLen = searchStart - mutableMsg;
|
||||
}
|
||||
return extradataSection;
|
||||
}
|
||||
} else {
|
||||
/* Static pattern mode */
|
||||
const char *pattern = (const char *)pData->ignoreTrailingPattern;
|
||||
size_t patternLen = strlen(pattern);
|
||||
if (patternLen == 0) {
|
||||
return NULL;
|
||||
}
|
||||
if (msgLenVal < patternLen) {
|
||||
return NULL;
|
||||
}
|
||||
size_t trailingSearchLen = msgLenVal / 5; /* Last 20% */
|
||||
if (trailingSearchLen > 200) {
|
||||
trailingSearchLen = 200;
|
||||
}
|
||||
if (trailingSearchLen < patternLen) {
|
||||
trailingSearchLen = patternLen;
|
||||
}
|
||||
/* Search backwards from end within the trailing portion only */
|
||||
char *searchStart = mutableMsg + msgLenVal - trailingSearchLen;
|
||||
for (char *searchPos = mutableMsg + msgLenVal - patternLen; searchPos >= searchStart; searchPos--) {
|
||||
if (memcmp(searchPos, pattern, patternLen) == 0) {
|
||||
/* Found pattern in trailing position - save it and truncate before it */
|
||||
extradataSection = strdup(searchPos);
|
||||
if (extradataSection == NULL) {
|
||||
return NULL; /* out of memory */
|
||||
}
|
||||
*searchPos = '\0';
|
||||
if (msgLen != NULL) {
|
||||
*msgLen = searchPos - mutableMsg;
|
||||
}
|
||||
return extradataSection;
|
||||
}
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Pattern must appear after the last tab to be considered trailing */
|
||||
char *searchStart = lastTab + 1;
|
||||
char *patternPos = strstr(searchStart, pattern);
|
||||
|
||||
if (patternPos != NULL) {
|
||||
/* Pattern found in trailing position - truncate at the start of the last token
|
||||
* (after the last tab) to remove the entire enrichment section including any
|
||||
* preceding content in that token (e.g., dynamic numbers before the pattern) */
|
||||
/* Save the extra-data section before truncating */
|
||||
extradataSection = strdup(searchStart);
|
||||
if (extradataSection == NULL) {
|
||||
return NULL;
|
||||
if (pData->ignoreTrailingPattern_isRegex) {
|
||||
/* Regex mode: apply regexec to the trailing token */
|
||||
const int isMatch = !regexec(&pData->ignoreTrailingPattern_preg, searchStart, 0, NULL, 0);
|
||||
if (isMatch) {
|
||||
/* Pattern found in trailing position - truncate at the start of the last token
|
||||
* (after the last tab) to remove the entire enrichment section including any
|
||||
* preceding content in that token (e.g., dynamic numbers before the pattern) */
|
||||
/* Save the extra-data section before truncating */
|
||||
extradataSection = strdup(searchStart);
|
||||
if (extradataSection == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
/* Now truncate at the last tab */
|
||||
*lastTab = '\0';
|
||||
if (msgLen != NULL) {
|
||||
*msgLen = lastTab - mutableMsg;
|
||||
}
|
||||
return extradataSection;
|
||||
}
|
||||
/* Now truncate at the last tab */
|
||||
*lastTab = '\0';
|
||||
if (msgLen != NULL) {
|
||||
*msgLen = lastTab - mutableMsg;
|
||||
} else {
|
||||
/* Static pattern mode: use strstr */
|
||||
const char *pattern = (const char *)pData->ignoreTrailingPattern;
|
||||
/* Defensive check: ensure pattern is not empty to avoid unintended matches */
|
||||
if (strlen(pattern) > 0) {
|
||||
char *patternPos = strstr(searchStart, pattern);
|
||||
|
||||
if (patternPos != NULL) {
|
||||
/* Pattern found in trailing position - truncate at the start of the last token
|
||||
* (after the last tab) to remove the entire enrichment section including any
|
||||
* preceding content in that token (e.g., dynamic numbers before the pattern) */
|
||||
/* Save the extra-data section before truncating */
|
||||
extradataSection = strdup(searchStart);
|
||||
if (extradataSection == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
/* Now truncate at the last tab */
|
||||
*lastTab = '\0';
|
||||
if (msgLen != NULL) {
|
||||
*msgLen = lastTab - mutableMsg;
|
||||
}
|
||||
return extradataSection;
|
||||
}
|
||||
}
|
||||
return extradataSection;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
@ -5257,22 +5320,29 @@ static rsRetVal process_message(instanceData *pData, smsg_t *pMsg, uchar *msgTex
|
||||
|
||||
DEF_OMOD_STATIC_DATA;
|
||||
|
||||
static struct cnfparamdescr modpdescr[] = {{"definition.file", eCmdHdlrString, 0},
|
||||
static struct cnfparamdescr modpdescr[] = {
|
||||
{"definition.file", eCmdHdlrString, 0}, {"definition.json", eCmdHdlrString, 0},
|
||||
{"runtime.config", eCmdHdlrString, 0}, {"validation.mode", eCmdHdlrString, 0},
|
||||
{"ignoreTrailingPattern", eCmdHdlrString, 0}, {"ignoreTrailingPattern.regex", eCmdHdlrString, 0}};
|
||||
static struct cnfparamblk modpblk = {CNFPARAMBLK_VERSION, ARRAY_SIZE(modpdescr), modpdescr};
|
||||
|
||||
static struct cnfparamdescr actpdescr[] = {{"container", eCmdHdlrString, 0},
|
||||
{"rootpath", eCmdHdlrString, 0},
|
||||
{"template", eCmdHdlrGetWord, 0},
|
||||
{"enable.network", eCmdHdlrBinary, 0},
|
||||
{"enable.laps", eCmdHdlrBinary, 0},
|
||||
{"enable.tls", eCmdHdlrBinary, 0},
|
||||
{"enable.wdac", eCmdHdlrBinary, 0},
|
||||
{"emit.rawpayload", eCmdHdlrBinary, 0},
|
||||
{"emit.debugjson", eCmdHdlrBinary, 0},
|
||||
{"debugjson", eCmdHdlrBinary, 0},
|
||||
{"definition.file", eCmdHdlrString, 0},
|
||||
{"definition.json", eCmdHdlrString, 0},
|
||||
{"runtime.config", eCmdHdlrString, 0},
|
||||
{"validation.mode", eCmdHdlrString, 0},
|
||||
{"ignoreTrailingPattern", eCmdHdlrString, 0}};
|
||||
static struct cnfparamblk modpblk = {CNFPARAMBLK_VERSION, ARRAY_SIZE(modpdescr), modpdescr};
|
||||
|
||||
static struct cnfparamdescr actpdescr[] = {
|
||||
{"container", eCmdHdlrString, 0}, {"rootpath", eCmdHdlrString, 0},
|
||||
{"template", eCmdHdlrGetWord, 0}, {"enable.network", eCmdHdlrBinary, 0},
|
||||
{"enable.laps", eCmdHdlrBinary, 0}, {"enable.tls", eCmdHdlrBinary, 0},
|
||||
{"enable.wdac", eCmdHdlrBinary, 0}, {"emit.rawpayload", eCmdHdlrBinary, 0},
|
||||
{"emit.debugjson", eCmdHdlrBinary, 0}, {"debugjson", eCmdHdlrBinary, 0},
|
||||
{"definition.file", eCmdHdlrString, 0}, {"definition.json", eCmdHdlrString, 0},
|
||||
{"runtime.config", eCmdHdlrString, 0}, {"validation.mode", eCmdHdlrString, 0},
|
||||
{"validation_mode", eCmdHdlrString, 0}, {"ignoreTrailingPattern", eCmdHdlrString, 0}};
|
||||
{"validation_mode", eCmdHdlrString, 0},
|
||||
{"ignoreTrailingPattern", eCmdHdlrString, 0},
|
||||
{"ignoreTrailingPattern.regex", eCmdHdlrString, 0}};
|
||||
static struct cnfparamblk actpblk = {CNFPARAMBLK_VERSION, ARRAY_SIZE(actpdescr), actpdescr};
|
||||
|
||||
BEGINbeginCnfLoad
|
||||
@ -5287,6 +5357,8 @@ BEGINbeginCnfLoad
|
||||
pModConf->runtimeConfigFile = NULL;
|
||||
free(pModConf->ignoreTrailingPattern);
|
||||
pModConf->ignoreTrailingPattern = NULL;
|
||||
free(pModConf->ignoreTrailingPatternRegex);
|
||||
pModConf->ignoreTrailingPatternRegex = NULL;
|
||||
init_validation_context(&pModConf->validationTemplate);
|
||||
ENDbeginCnfLoad
|
||||
|
||||
@ -5346,10 +5418,23 @@ BEGINsetModCnf
|
||||
}
|
||||
free(loadModConf->ignoreTrailingPattern);
|
||||
loadModConf->ignoreTrailingPattern = (uchar *)value;
|
||||
} else if (!strcmp(modpblk.descr[i].name, "ignoreTrailingPattern.regex")) {
|
||||
char *value = es_str2cstr(pvals[i].val.d.estr, NULL);
|
||||
if (value == NULL) {
|
||||
ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
|
||||
}
|
||||
free(loadModConf->ignoreTrailingPatternRegex);
|
||||
loadModConf->ignoreTrailingPatternRegex = (uchar *)value;
|
||||
} else {
|
||||
dbgprintf("mmsnareparse: unhandled module parameter '%s'\n", modpblk.descr[i].name);
|
||||
}
|
||||
}
|
||||
/* Check mutual exclusivity */
|
||||
if (loadModConf->ignoreTrailingPattern != NULL && loadModConf->ignoreTrailingPatternRegex != NULL) {
|
||||
LogError(0, RS_RET_PARAM_NOT_PERMITTED,
|
||||
"mmsnareparse: ignoreTrailingPattern and ignoreTrailingPattern.regex are mutually exclusive");
|
||||
ABORT_FINALIZE(RS_RET_PARAM_NOT_PERMITTED);
|
||||
}
|
||||
finalize_it:
|
||||
if (pvals != NULL) cnfparamvalsDestruct(pvals, &modpblk);
|
||||
ENDsetModCnf
|
||||
@ -5378,6 +5463,8 @@ BEGINfreeCnf
|
||||
pModConf->runtimeConfigFile = NULL;
|
||||
free(pModConf->ignoreTrailingPattern);
|
||||
pModConf->ignoreTrailingPattern = NULL;
|
||||
free(pModConf->ignoreTrailingPatternRegex);
|
||||
pModConf->ignoreTrailingPatternRegex = NULL;
|
||||
}
|
||||
ENDfreeCnf
|
||||
|
||||
@ -5397,6 +5484,9 @@ BEGINfreeInstance
|
||||
CODESTARTfreeInstance;
|
||||
free(pData->container);
|
||||
free(pData->ignoreTrailingPattern);
|
||||
if (pData->ignoreTrailingPattern_isRegex) {
|
||||
regfree(&pData->ignoreTrailingPattern_preg);
|
||||
}
|
||||
free_runtime_tables(pData);
|
||||
free_runtime_config(&pData->runtimeConfig);
|
||||
ENDfreeInstance
|
||||
@ -5417,6 +5507,8 @@ static inline void setInstParamDefaults(instanceData *pData) {
|
||||
pData->emitRawPayload = 1;
|
||||
pData->emitDebugJson = 0;
|
||||
pData->ignoreTrailingPattern = NULL;
|
||||
pData->ignoreTrailingPattern_isRegex = 0;
|
||||
memset(&pData->ignoreTrailingPattern_preg, 0, sizeof(regex_t));
|
||||
init_validation_context(&pData->validationTemplate);
|
||||
init_runtime_config(&pData->runtimeConfig);
|
||||
pData->sectionDescriptors = NULL;
|
||||
@ -5436,6 +5528,8 @@ BEGINnewActInst
|
||||
char *definitionJson = NULL;
|
||||
char *runtimeConfigFile = NULL;
|
||||
char *templateName = NULL;
|
||||
sbool hasStaticPattern = 0;
|
||||
sbool hasRegexPattern = 0;
|
||||
CODESTARTnewActInst;
|
||||
if ((pvals = nvlstGetParams(lst, &actpblk, NULL)) == NULL) {
|
||||
LogError(0, RS_RET_MISSING_CNFPARAMS, "mmsnareparse: missing configuration parameters");
|
||||
@ -5455,6 +5549,14 @@ BEGINnewActInst
|
||||
ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
|
||||
}
|
||||
}
|
||||
if (loadModConf->ignoreTrailingPatternRegex != NULL) {
|
||||
free(pData->ignoreTrailingPattern);
|
||||
pData->ignoreTrailingPattern = (uchar *)strdup((char *)loadModConf->ignoreTrailingPatternRegex);
|
||||
if (pData->ignoreTrailingPattern == NULL) {
|
||||
ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
|
||||
}
|
||||
pData->ignoreTrailingPattern_isRegex = 1;
|
||||
}
|
||||
}
|
||||
for (i = 0; i < (int)actpblk.nParams; ++i) {
|
||||
if (!pvals[i].bUsed) continue;
|
||||
@ -5496,12 +5598,51 @@ BEGINnewActInst
|
||||
CHKiRet(set_validation_mode(pData, mode));
|
||||
free(mode);
|
||||
} else if (!strcmp(actpblk.descr[i].name, "ignoreTrailingPattern")) {
|
||||
hasStaticPattern = 1;
|
||||
char *value = es_str2cstr(pvals[i].val.d.estr, NULL);
|
||||
if (value == NULL) {
|
||||
ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
|
||||
}
|
||||
free(pData->ignoreTrailingPattern);
|
||||
pData->ignoreTrailingPattern = (uchar *)value;
|
||||
pData->ignoreTrailingPattern_isRegex = 0;
|
||||
} else if (!strcmp(actpblk.descr[i].name, "ignoreTrailingPattern.regex")) {
|
||||
hasRegexPattern = 1;
|
||||
char *value = es_str2cstr(pvals[i].val.d.estr, NULL);
|
||||
if (value == NULL) {
|
||||
ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
|
||||
}
|
||||
free(pData->ignoreTrailingPattern);
|
||||
pData->ignoreTrailingPattern = (uchar *)value;
|
||||
pData->ignoreTrailingPattern_isRegex = 1;
|
||||
}
|
||||
}
|
||||
/* Check mutual exclusivity - both action parameters cannot be set */
|
||||
if (hasStaticPattern && hasRegexPattern) {
|
||||
LogError(0, RS_RET_PARAM_NOT_PERMITTED,
|
||||
"mmsnareparse: ignoreTrailingPattern and ignoreTrailingPattern.regex are mutually exclusive");
|
||||
ABORT_FINALIZE(RS_RET_PARAM_NOT_PERMITTED);
|
||||
}
|
||||
/* Also check if module config and action config conflict */
|
||||
if (loadModConf != NULL) {
|
||||
if ((loadModConf->ignoreTrailingPattern != NULL && hasRegexPattern) ||
|
||||
(loadModConf->ignoreTrailingPatternRegex != NULL && hasStaticPattern)) {
|
||||
LogError(0, RS_RET_PARAM_NOT_PERMITTED,
|
||||
"mmsnareparse: ignoreTrailingPattern and ignoreTrailingPattern.regex are mutually exclusive");
|
||||
ABORT_FINALIZE(RS_RET_PARAM_NOT_PERMITTED);
|
||||
}
|
||||
}
|
||||
/* Compile regex if regex mode is enabled */
|
||||
if (pData->ignoreTrailingPattern != NULL && pData->ignoreTrailingPattern_isRegex == 1) {
|
||||
const int errcode =
|
||||
regcomp(&pData->ignoreTrailingPattern_preg, (char *)pData->ignoreTrailingPattern, REG_EXTENDED);
|
||||
if (errcode != 0) {
|
||||
char errbuff[512];
|
||||
/* POSIX: Use NULL as regex argument after regcomp failure for portability */
|
||||
regerror(errcode, NULL, errbuff, sizeof(errbuff));
|
||||
LogError(0, RS_RET_ERR, "mmsnareparse: error compiling ignoreTrailingPattern.regex '%s': %s",
|
||||
(char *)pData->ignoreTrailingPattern, errbuff);
|
||||
ABORT_FINALIZE(RS_RET_ERR);
|
||||
}
|
||||
}
|
||||
CODE_STD_STRING_REQUESTnewActInst(1);
|
||||
|
||||
@ -514,7 +514,8 @@ TESTS += \
|
||||
mmsnareparse-kerberos.sh \
|
||||
mmsnareparse-custom.sh \
|
||||
mmsnareparse-realworld-4624-4634-5140.sh \
|
||||
mmsnareparse-trailing-extradata.sh
|
||||
mmsnareparse-trailing-extradata.sh \
|
||||
mmsnareparse-trailing-extradata-regex.sh
|
||||
if HAVE_VALGRIND
|
||||
TESTS += \
|
||||
mmsnareparse-comprehensive-vg.sh
|
||||
@ -2097,6 +2098,7 @@ EXTRA_DIST= \
|
||||
mmsnareparse-syslog.sh \
|
||||
mmsnareparse-value-types.sh \
|
||||
mmsnareparse-trailing-extradata.sh \
|
||||
mmsnareparse-trailing-extradata-regex.sh \
|
||||
mmexternal-InvldProg-vg.sh \
|
||||
nested-call-shutdown.sh \
|
||||
1.rstest 2.rstest 3.rstest err1.rstest \
|
||||
|
||||
101
tests/mmsnareparse-trailing-extradata-regex.sh
Executable file
101
tests/mmsnareparse-trailing-extradata-regex.sh
Executable file
@ -0,0 +1,101 @@
|
||||
#!/bin/bash
|
||||
# Validate mmsnareparse parsing with trailing extra-data section truncation using regex.
|
||||
# This test verifies regex support for dynamic numeric prefixes in trailing custom data.
|
||||
unset RSYSLOG_DYNNAME
|
||||
. ${srcdir:=.}/diag.sh init
|
||||
|
||||
generate_conf
|
||||
add_conf '
|
||||
module(load="../plugins/mmsnareparse/.libs/mmsnareparse")
|
||||
|
||||
template(name="outfmt" type="list") {
|
||||
property(name="$!win!Event!EventID")
|
||||
constant(value=",")
|
||||
property(name="$!win!Event!Channel")
|
||||
constant(value=",")
|
||||
property(name="$!win!EventData!EventType")
|
||||
constant(value=",")
|
||||
property(name="$!win!EventData!TargetObject")
|
||||
constant(value=",")
|
||||
property(name="$!win!EventData!User")
|
||||
constant(value=",")
|
||||
property(name="$!extradata_section")
|
||||
constant(value="\n")
|
||||
}
|
||||
|
||||
action(type="mmsnareparse"
|
||||
definition.file="../plugins/mmsnareparse/sysmon_definitions.json"
|
||||
ignoreTrailingPattern.regex="^[0-9]+[[:space:]]+custom_section:")
|
||||
action(type="omfile" file="'$RSYSLOG_OUT_LOG'" template="outfmt")
|
||||
'
|
||||
|
||||
startup
|
||||
|
||||
# Test case 1: Standard number prefix (3385599)
|
||||
cat <<'MSG' > ${RSYSLOG_DYNNAME}.input1
|
||||
<14>Mar 22 08:47:23 testhost MSWinEventLog 1 Microsoft-Windows-Sysmon/Operational 20977 Mon Mar 22 08:47:23 2025 13 Windows SYSTEM User SetValue testhost Registry value set (rule: RegistryEvent) Registry value set: RuleName: Default RegistryEvent EventType: SetValue UtcTime: 2025-03-22 08:47:23.284 ProcessGuid: {fd4d0da6-d589-6916-eb03-000000000000} ProcessId: 4 Image: System TargetObject: HKLM\System\CurrentControlSet\Services\TestService\ImagePath Details: "C:\Program Files\TestAgent\TestService.exe" User: NT AUTHORITY\SYSTEM 3385599 custom_section: fromhost-ip=192.168.45.217
|
||||
MSG
|
||||
|
||||
# Test case 2: Different number (12345)
|
||||
cat <<'MSG' > ${RSYSLOG_DYNNAME}.input2
|
||||
<14>Mar 22 08:47:24 testhost MSWinEventLog 1 Microsoft-Windows-Sysmon/Operational 20978 Mon Mar 22 08:47:24 2025 13 Windows SYSTEM User SetValue testhost Registry value set (rule: RegistryEvent) Registry value set: RuleName: Default RegistryEvent EventType: SetValue UtcTime: 2025-03-22 08:47:24.284 ProcessGuid: {fd4d0da6-d589-6916-eb03-000000000001} ProcessId: 4 Image: System TargetObject: HKLM\System\CurrentControlSet\Services\TestService\ImagePath Details: "C:\Program Files\TestAgent\TestService.exe" User: NT AUTHORITY\SYSTEM 12345 custom_section: fromhost-ip=192.168.45.218
|
||||
MSG
|
||||
|
||||
# Test case 3: Single digit (9)
|
||||
cat <<'MSG' > ${RSYSLOG_DYNNAME}.input3
|
||||
<14>Mar 22 08:47:25 testhost MSWinEventLog 1 Microsoft-Windows-Sysmon/Operational 20979 Mon Mar 22 08:47:25 2025 13 Windows SYSTEM User SetValue testhost Registry value set (rule: RegistryEvent) Registry value set: RuleName: Default RegistryEvent EventType: SetValue UtcTime: 2025-03-22 08:47:25.284 ProcessGuid: {fd4d0da6-d589-6916-eb03-000000000002} ProcessId: 4 Image: System TargetObject: HKLM\System\CurrentControlSet\Services\TestService\ImagePath Details: "C:\Program Files\TestAgent\TestService.exe" User: NT AUTHORITY\SYSTEM 9 custom_section: fromhost-ip=192.168.45.219
|
||||
MSG
|
||||
|
||||
# Test case 4: Large number (999999999)
|
||||
cat <<'MSG' > ${RSYSLOG_DYNNAME}.input4
|
||||
<14>Mar 22 08:47:26 testhost MSWinEventLog 1 Microsoft-Windows-Sysmon/Operational 20980 Mon Mar 22 08:47:26 2025 13 Windows SYSTEM User SetValue testhost Registry value set (rule: RegistryEvent) Registry value set: RuleName: Default RegistryEvent EventType: SetValue UtcTime: 2025-03-22 08:47:26.284 ProcessGuid: {fd4d0da6-d589-6916-eb03-000000000003} ProcessId: 4 Image: System TargetObject: HKLM\System\CurrentControlSet\Services\TestService\ImagePath Details: "C:\Program Files\TestAgent\TestService.exe" User: NT AUTHORITY\SYSTEM 999999999 custom_section: fromhost-ip=192.168.45.220
|
||||
MSG
|
||||
|
||||
# Test case 5: Multiple spaces (should still match)
|
||||
cat <<'MSG' > ${RSYSLOG_DYNNAME}.input5
|
||||
<14>Mar 22 08:47:27 testhost MSWinEventLog 1 Microsoft-Windows-Sysmon/Operational 20981 Mon Mar 22 08:47:27 2025 13 Windows SYSTEM User SetValue testhost Registry value set (rule: RegistryEvent) Registry value set: RuleName: Default RegistryEvent EventType: SetValue UtcTime: 2025-03-22 08:47:27.284 ProcessGuid: {fd4d0da6-d589-6916-eb03-000000000004} ProcessId: 4 Image: System TargetObject: HKLM\System\CurrentControlSet\Services\TestService\ImagePath Details: "C:\Program Files\TestAgent\TestService.exe" User: NT AUTHORITY\SYSTEM 42 custom_section: fromhost-ip=192.168.45.221
|
||||
MSG
|
||||
|
||||
# Test case 6: Zero-padded number (000123)
|
||||
cat <<'MSG' > ${RSYSLOG_DYNNAME}.input6
|
||||
<14>Mar 22 08:47:28 testhost MSWinEventLog 1 Microsoft-Windows-Sysmon/Operational 20982 Mon Mar 22 08:47:28 2025 13 Windows SYSTEM User SetValue testhost Registry value set (rule: RegistryEvent) Registry value set: RuleName: Default RegistryEvent EventType: SetValue UtcTime: 2025-03-22 08:47:28.284 ProcessGuid: {fd4d0da6-d589-6916-eb03-000000000005} ProcessId: 4 Image: System TargetObject: HKLM\System\CurrentControlSet\Services\TestService\ImagePath Details: "C:\Program Files\TestAgent\TestService.exe" User: NT AUTHORITY\SYSTEM 000123 custom_section: fromhost-ip=192.168.45.222
|
||||
MSG
|
||||
|
||||
injectmsg_file ${RSYSLOG_DYNNAME}.input1
|
||||
injectmsg_file ${RSYSLOG_DYNNAME}.input2
|
||||
injectmsg_file ${RSYSLOG_DYNNAME}.input3
|
||||
injectmsg_file ${RSYSLOG_DYNNAME}.input4
|
||||
injectmsg_file ${RSYSLOG_DYNNAME}.input5
|
||||
injectmsg_file ${RSYSLOG_DYNNAME}.input6
|
||||
|
||||
shutdown_when_empty
|
||||
wait_shutdown
|
||||
|
||||
# Test that Event ID 13 (Registry value set) is parsed correctly for all test cases
|
||||
# The ignoreTrailingPattern.regex should remove the custom sections with dynamic
|
||||
# numeric prefixes from the message before parsing, so they should NOT appear in any
|
||||
# parsed fields. However, the truncated content is stored in the !extradata_section property.
|
||||
# This test verifies:
|
||||
# 1. Parsing works correctly (EventID=13, Channel, EventType=SetValue, TargetObject, User)
|
||||
# 2. The custom sections with various numeric prefixes are removed from parsing
|
||||
# 3. The truncated content (including the numeric prefix) is stored in !extradata_section property
|
||||
|
||||
# Test case 1: Standard number (3385599)
|
||||
content_check '13,Microsoft-Windows-Sysmon/Operational,SetValue,HKLM\System\CurrentControlSet\Services\TestService\ImagePath,NT AUTHORITY\SYSTEM,3385599 custom_section: fromhost-ip=192.168.45.217' $RSYSLOG_OUT_LOG
|
||||
|
||||
# Test case 2: Different number (12345)
|
||||
content_check '13,Microsoft-Windows-Sysmon/Operational,SetValue,HKLM\System\CurrentControlSet\Services\TestService\ImagePath,NT AUTHORITY\SYSTEM,12345 custom_section: fromhost-ip=192.168.45.218' $RSYSLOG_OUT_LOG
|
||||
|
||||
# Test case 3: Single digit (9)
|
||||
content_check '13,Microsoft-Windows-Sysmon/Operational,SetValue,HKLM\System\CurrentControlSet\Services\TestService\ImagePath,NT AUTHORITY\SYSTEM,9 custom_section: fromhost-ip=192.168.45.219' $RSYSLOG_OUT_LOG
|
||||
|
||||
# Test case 4: Large number (999999999)
|
||||
content_check '13,Microsoft-Windows-Sysmon/Operational,SetValue,HKLM\System\CurrentControlSet\Services\TestService\ImagePath,NT AUTHORITY\SYSTEM,999999999 custom_section: fromhost-ip=192.168.45.220' $RSYSLOG_OUT_LOG
|
||||
|
||||
# Test case 5: Multiple spaces (42 custom_section:)
|
||||
content_check '13,Microsoft-Windows-Sysmon/Operational,SetValue,HKLM\System\CurrentControlSet\Services\TestService\ImagePath,NT AUTHORITY\SYSTEM,42 custom_section: fromhost-ip=192.168.45.221' $RSYSLOG_OUT_LOG
|
||||
|
||||
# Test case 6: Zero-padded number (000123)
|
||||
content_check '13,Microsoft-Windows-Sysmon/Operational,SetValue,HKLM\System\CurrentControlSet\Services\TestService\ImagePath,NT AUTHORITY\SYSTEM,000123 custom_section: fromhost-ip=192.168.45.222' $RSYSLOG_OUT_LOG
|
||||
|
||||
exit_test
|
||||
@ -25,13 +25,13 @@ template(name="outfmt" type="list") {
|
||||
|
||||
action(type="mmsnareparse"
|
||||
definition.file="../plugins/mmsnareparse/sysmon_definitions.json"
|
||||
ignoreTrailingPattern="enrichment_section:")
|
||||
ignoreTrailingPattern="custom_section:")
|
||||
action(type="omfile" file="'$RSYSLOG_OUT_LOG'" template="outfmt")
|
||||
'
|
||||
|
||||
startup
|
||||
cat <<'MSG' > ${RSYSLOG_DYNNAME}.input
|
||||
<14>Mar 22 08:47:23 testhost MSWinEventLog 1 Microsoft-Windows-Sysmon/Operational 20977 Mon Mar 22 08:47:23 2025 13 Windows SYSTEM User SetValue testhost Registry value set (rule: RegistryEvent) Registry value set: RuleName: Default RegistryEvent EventType: SetValue UtcTime: 2025-03-22 08:47:23.284 ProcessGuid: {fd4d0da6-d589-6916-eb03-000000000000} ProcessId: 4 Image: System TargetObject: HKLM\System\CurrentControlSet\Services\TestService\ImagePath Details: "C:\Program Files\TestAgent\TestService.exe" User: NT AUTHORITY\SYSTEM 3385599 enrichment_section: fromhost-ip=192.168.45.217
|
||||
<14>Mar 22 08:47:23 testhost MSWinEventLog 1 Microsoft-Windows-Sysmon/Operational 20977 Mon Mar 22 08:47:23 2025 13 Windows SYSTEM User SetValue testhost Registry value set (rule: RegistryEvent) Registry value set: RuleName: Default RegistryEvent EventType: SetValue UtcTime: 2025-03-22 08:47:23.284 ProcessGuid: {fd4d0da6-d589-6916-eb03-000000000000} ProcessId: 4 Image: System TargetObject: HKLM\System\CurrentControlSet\Services\TestService\ImagePath Details: "C:\Program Files\TestAgent\TestService.exe" User: NT AUTHORITY\SYSTEM 3385599 custom_section: fromhost-ip=192.168.45.217
|
||||
MSG
|
||||
injectmsg_file ${RSYSLOG_DYNNAME}.input
|
||||
|
||||
@ -39,17 +39,17 @@ shutdown_when_empty
|
||||
wait_shutdown
|
||||
|
||||
# Test that Event ID 13 (Registry value set) is parsed correctly
|
||||
# The ignoreTrailingPattern should remove "enrichment_section: fromhost-ip=192.168.45.217"
|
||||
# The ignoreTrailingPattern should remove "custom_section: fromhost-ip=192.168.45.217"
|
||||
# from the message before parsing, so it should NOT appear in any parsed fields.
|
||||
# However, the truncated content is stored in the !extradata_section property.
|
||||
# This test verifies:
|
||||
# 1. Parsing works correctly (EventID=13, Channel, EventType=SetValue, TargetObject, User)
|
||||
# 2. The enrichment section is removed from parsing (doesn't affect parsed fields)
|
||||
# 2. The custom section is removed from parsing (doesn't affect parsed fields)
|
||||
# 3. The truncated content is stored in !extradata_section property (tests with-tabs code path)
|
||||
#
|
||||
# NOTE: A critical bug in the no-tabs code path (lines 5111-5121 in mmsnareparse.c) was fixed
|
||||
# where strdup was called AFTER truncation, resulting in an empty string. The fix reverses
|
||||
# the order: copy first, then truncate. This is now consistent with the with-tabs path.
|
||||
content_check '13,Microsoft-Windows-Sysmon/Operational,SetValue,HKLM\System\CurrentControlSet\Services\TestService\ImagePath,NT AUTHORITY\SYSTEM,3385599 enrichment_section: fromhost-ip=192.168.45.217' $RSYSLOG_OUT_LOG
|
||||
content_check '13,Microsoft-Windows-Sysmon/Operational,SetValue,HKLM\System\CurrentControlSet\Services\TestService\ImagePath,NT AUTHORITY\SYSTEM,3385599 custom_section: fromhost-ip=192.168.45.217' $RSYSLOG_OUT_LOG
|
||||
|
||||
exit_test
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user