mirror of
https://github.com/openappsec/openappsec.git
synced 2026-01-17 16:00:26 +03:00
Jan 06 2026 dev (#387)
* sync code * update code to support brotli * update code to support brotli * update code to support brotli * sync code * fix findBrotli * sync code * sync code * sync code * sync code --------- Co-authored-by: Ned Wright <nedwright@proton.me> Co-authored-by: Daniel Eisenberg <danielei@checkpoint.com>
This commit is contained in:
@@ -51,12 +51,56 @@ static const boost::regex error_log_regex(
|
||||
", (host: \".+?\")$"
|
||||
);
|
||||
|
||||
static const boost::regex incident_log_regex(
|
||||
"("
|
||||
+ syslog_regex_string + ") "
|
||||
+ "incidentLog\\s+"
|
||||
"host=([^\\s]+)\\s+"
|
||||
"host_port=(\\d+)\\s+"
|
||||
"client_addr=([\\d\\.]+)\\s+"
|
||||
"client_port=(\\d+)\\s+"
|
||||
"time_local=\\[([^\\]]+)\\]\\s+"
|
||||
"request=\"([^\"]+)\"\\s+"
|
||||
"status=(\\d{3})\\s+"
|
||||
"uri=\"([^\"]+)\"\\s+"
|
||||
"request_id=([^\\s]+)\\s+"
|
||||
"upstream_status=([^,\\s]+)(?:,\\s*[^\\s]+)?\\s+"
|
||||
"upstream_response_time=([^,\\s]+)(?:,\\s*[^\\s]+)?\\s+"
|
||||
"body_bytes_sent=(\\d+)\\s+"
|
||||
"referer=\"([^\"]*)\"\\s+"
|
||||
"user_agent=\"([^\"]*)\"\\s+"
|
||||
"pf=([^\\s]*)\\s+"
|
||||
"x_event_id=([^\\s]*)"
|
||||
);
|
||||
|
||||
static const boost::regex generic_crit_log_regex(
|
||||
"("
|
||||
+ syslog_regex_string + ") "
|
||||
+ "(?:\\d{4}/\\d{2}/\\d{2} \\d{2}:\\d{2}:\\d{2} )?" // Optional nginx timestamp
|
||||
+ "\\[crit\\] (.+)$"
|
||||
);
|
||||
|
||||
static const boost::regex generic_emerg_log_regex(
|
||||
"("
|
||||
+ syslog_regex_string + ") "
|
||||
+ "(?:\\d{4}/\\d{2}/\\d{2} \\d{2}:\\d{2}:\\d{2} )?" // Optional nginx timestamp
|
||||
+ "\\[emerg\\] (.+)$"
|
||||
);
|
||||
|
||||
static const boost::regex generic_fallback_log_regex(
|
||||
"("
|
||||
+ syslog_regex_string + ") "
|
||||
+ "(?:\\d{4}/\\d{2}/\\d{2} \\d{2}:\\d{2}:\\d{2} )?" // Optional nginx timestamp
|
||||
+ "\\[(\\w+)\\] (.+)$"
|
||||
);
|
||||
|
||||
static const boost::regex server_regex("(\\d+\\.\\d+\\.\\d+\\.\\d+)|(\\w+\\.\\w+)");
|
||||
static const boost::regex uri_regex("^/");
|
||||
static const boost::regex port_regex("\\d+");
|
||||
static const boost::regex response_code_regex("[0-9]{3}");
|
||||
static const boost::regex http_method_regex("[A-Za-z]+");
|
||||
|
||||
static const string central_nginx_manager = "Central NGINX Manager";
|
||||
class NginxMessageReader::Impl
|
||||
{
|
||||
public:
|
||||
@@ -64,6 +108,15 @@ public:
|
||||
init()
|
||||
{
|
||||
dbgFlow(D_NGINX_MESSAGE_READER);
|
||||
|
||||
if (Singleton::exists<I_Environment>()) {
|
||||
auto name = Singleton::Consume<I_Environment>::by<Report>()->get<string>("Service Name");
|
||||
if (name.ok()) {
|
||||
dbgInfo(D_NGINX_MESSAGE_READER) << "Service name: " << *name;
|
||||
service_name = *name;
|
||||
}
|
||||
}
|
||||
|
||||
I_MainLoop *mainloop = Singleton::Consume<I_MainLoop>::by<NginxMessageReader>();
|
||||
mainloop->addOneTimeRoutine(
|
||||
I_MainLoop::RoutineType::System,
|
||||
@@ -97,6 +150,7 @@ public:
|
||||
"429",
|
||||
"accessControl.rateLimit.returnCode"
|
||||
);
|
||||
|
||||
dbgTrace(D_NGINX_MESSAGE_READER) << "Selected rate-limit status code: " << rate_limit_status_code;
|
||||
}
|
||||
|
||||
@@ -107,6 +161,7 @@ private:
|
||||
RESPONSE_CODE,
|
||||
HOST,
|
||||
SOURCE,
|
||||
SOURCE_PORT,
|
||||
DESTINATION_IP,
|
||||
DESTINATION_PORT,
|
||||
EVENT_MESSAGE,
|
||||
@@ -114,9 +169,21 @@ private:
|
||||
ASSET_NAME,
|
||||
RULE_NAME,
|
||||
RULE_ID,
|
||||
REFERENCE_ID,
|
||||
LOG_TYPE,
|
||||
PROXY_FAULT,
|
||||
X_EVENT_ID,
|
||||
MATCHED_REASON,
|
||||
REMEDIATION,
|
||||
COUNT
|
||||
};
|
||||
|
||||
struct GenericLogInfo {
|
||||
string timestamp;
|
||||
string severity;
|
||||
string message;
|
||||
};
|
||||
|
||||
void
|
||||
initSyslogServerSocket()
|
||||
{
|
||||
@@ -175,10 +242,12 @@ private:
|
||||
bool log_sent;
|
||||
if (isAccessLog(log)) {
|
||||
log_sent = sendAccessLog(log);
|
||||
} else if (isAlertErrorLog(log) || isErrorLog(log)) {
|
||||
} else if (isAlertErrorLog(log) || isErrorLog(log) || isCritErrorLog(log) || isEmergErrorLog(log)) {
|
||||
log_sent = sendErrorLog(log);
|
||||
} else if (isIncidentLog(log)) {
|
||||
log_sent = sendIncidentLog(log);
|
||||
} else {
|
||||
dbgWarning(D_NGINX_MESSAGE_READER) << "Unexpected nginx log format";
|
||||
dbgWarning(D_NGINX_MESSAGE_READER) << "Unexpected nginx log format for message: "<< log;
|
||||
continue;
|
||||
}
|
||||
if (!log_sent) {
|
||||
@@ -222,13 +291,74 @@ private:
|
||||
{
|
||||
dbgFlow(D_NGINX_MESSAGE_READER) << "Error log" << log;
|
||||
Maybe<EnumArray<LogInfo, string>> log_info = parseErrorLog(log);
|
||||
if (!log_info.ok()) {
|
||||
dbgWarning(D_NGINX_MESSAGE_READER)
|
||||
<< "Failed parsing the NGINX logs. Error: "
|
||||
<< log_info.getErr();
|
||||
if (log_info.ok()) {
|
||||
return sendLog(log_info.unpack());
|
||||
}
|
||||
|
||||
if (service_name == central_nginx_manager) {
|
||||
dbgDebug(D_NGINX_MESSAGE_READER) << "Detailed parsing failed, trying generic parsing";
|
||||
Maybe<GenericLogInfo> generic_log = parseGenericErrorLog(log);
|
||||
if (generic_log.ok()) {
|
||||
return sendGenericLog(generic_log.unpack());
|
||||
}
|
||||
}
|
||||
|
||||
dbgWarning(D_NGINX_MESSAGE_READER)
|
||||
<< "Failed parsing the NGINX logs. Error: "
|
||||
<< log_info.getErr()
|
||||
<< service_name;
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool
|
||||
isValidUuid(const string &uuid_str)
|
||||
{
|
||||
if (uuid_str.empty() || uuid_str == "-") {
|
||||
return false;
|
||||
}
|
||||
return sendLog(log_info.unpack());
|
||||
|
||||
if (uuid_str.length() != 36) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (uuid_str[8] != '-' || uuid_str[13] != '-' || uuid_str[18] != '-' || uuid_str[23] != '-') {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < uuid_str.length(); ++i) {
|
||||
if (i == 8 || i == 13 || i == 18 || i == 23) {
|
||||
continue;
|
||||
}
|
||||
char c = uuid_str[i];
|
||||
if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
sendIncidentLog(const string &log)
|
||||
{
|
||||
dbgFlow(D_NGINX_MESSAGE_READER) << "Incident log" << log;
|
||||
Maybe<EnumArray<LogInfo, string>> log_info = parseIncidentLog(log);
|
||||
if (!log_info.ok()) {
|
||||
dbgTrace(D_NGINX_MESSAGE_READER) << log_info.getErr();
|
||||
return false;
|
||||
}
|
||||
auto unpacked_log_info = log_info.unpack();
|
||||
|
||||
if (isValidUuid(unpacked_log_info[LogInfo::X_EVENT_ID])) {
|
||||
dbgTrace(D_NGINX_MESSAGE_READER)
|
||||
<< "Skipping incident log - valid x_event_id present: "
|
||||
<< unpacked_log_info[LogInfo::X_EVENT_ID];
|
||||
return true;
|
||||
}
|
||||
|
||||
unpacked_log_info[LogInfo::LOG_TYPE] = "incidentLog";
|
||||
|
||||
return sendNotification(unpacked_log_info);
|
||||
}
|
||||
|
||||
bool
|
||||
@@ -253,7 +383,111 @@ private:
|
||||
}
|
||||
|
||||
bool
|
||||
sendLog(const EnumArray<LogInfo, string> &log_info)
|
||||
isIncidentLog(const string &log) const
|
||||
{
|
||||
dbgFlow(D_NGINX_MESSAGE_READER) << "Check if log is of type 'incident log'. Log: " << log;
|
||||
return log.find("incidentLog") != string::npos;
|
||||
}
|
||||
|
||||
bool
|
||||
sendNotification(const EnumArray<LogInfo, string> &log_info)
|
||||
{
|
||||
dbgFlow(D_NGINX_MESSAGE_READER);
|
||||
string event_name;
|
||||
|
||||
if (
|
||||
log_info[LogInfo::LOG_TYPE] == "incidentLog"
|
||||
&& (log_info[LogInfo::RESPONSE_CODE][0] == '4' || log_info[LogInfo::RESPONSE_CODE][0] == '5')
|
||||
) {
|
||||
bool is_proxy_fault = (log_info[LogInfo::PROXY_FAULT] == "yes");
|
||||
event_name = getEventName(is_proxy_fault);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
LogGen log(
|
||||
event_name,
|
||||
ReportIS::Level::LOG,
|
||||
ReportIS::Audience::SECURITY,
|
||||
ReportIS::Severity::HIGH,
|
||||
ReportIS::Priority::HIGH,
|
||||
ReportIS::Tags::REVERSE_PROXY
|
||||
);
|
||||
log << LogField("eventConfidence", "High");
|
||||
|
||||
for (LogInfo field : makeRange<LogInfo>()) {
|
||||
Maybe<string> string_field = convertLogFieldToString(field);
|
||||
if (!string_field.ok()) {
|
||||
dbgDebug(D_NGINX_MESSAGE_READER) << "Enum field was not converted: " << string_field.getErr();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (string_field.unpack().empty() || log_info[field].empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (field == LogInfo::ASSET_ID || field == LogInfo::ASSET_NAME) {
|
||||
log.addToOrigin(LogField(string_field.unpack(), log_info[field]));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (field != LogInfo::DESTINATION_PORT && field != LogInfo::SOURCE_PORT) {
|
||||
log << LogField(string_field.unpack(), log_info[field]);
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
log << LogField(string_field.unpack(), stoi(log_info[field]));
|
||||
} catch (const exception &e) {
|
||||
dbgError(D_NGINX_MESSAGE_READER)
|
||||
<< "Unable to convert port to numeric value: "
|
||||
<< e.what();
|
||||
log << LogField(string_field.unpack(), 0);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
isCritErrorLog(const string &log) const
|
||||
{
|
||||
dbgFlow(D_NGINX_MESSAGE_READER) << "Check if log is of type 'crit log'. Log: " << log;
|
||||
return log.find("[crit]") != string::npos;
|
||||
}
|
||||
|
||||
bool
|
||||
isEmergErrorLog(const string &log) const
|
||||
{
|
||||
dbgFlow(D_NGINX_MESSAGE_READER) << "Check if log is of type 'emerg log'. Log: " << log;
|
||||
return log.find("[emerg]") != string::npos;
|
||||
}
|
||||
|
||||
Maybe<string>
|
||||
getCNMEventName(const EnumArray<LogInfo, string> &log_info) const
|
||||
{
|
||||
dbgFlow(D_NGINX_MESSAGE_READER);
|
||||
string event_name;
|
||||
switch (log_info[LogInfo::RESPONSE_CODE][0]) {
|
||||
case '4': {
|
||||
event_name = "NGINX Proxy Error: Invalid request or incorrect NGINX configuration - Request dropped."
|
||||
" Please check the reverse proxy configuration of your relevant assets";
|
||||
break;
|
||||
}
|
||||
case '5': {
|
||||
event_name = "NGINX Proxy Error: Request failed! Please verify your proxy configuration."
|
||||
"If the issue persists please contact open-appsec support";
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
dbgError(D_NGINX_MESSAGE_READER) << "Irrelevant status code";
|
||||
return genError("Irrelevant status code");
|
||||
}
|
||||
}
|
||||
return event_name;
|
||||
}
|
||||
|
||||
Maybe<string>
|
||||
getRPMEventName(const EnumArray<LogInfo, string> &log_info) const
|
||||
{
|
||||
dbgFlow(D_NGINX_MESSAGE_READER);
|
||||
string event_name;
|
||||
@@ -265,27 +499,55 @@ private:
|
||||
}
|
||||
case '5': {
|
||||
event_name = "AppSec Gateway reverse proxy error - Request dropped. "
|
||||
"Please verify the reverse proxy configuration of your relevant assets. "
|
||||
"If the issue persists please contact Check Point Support";
|
||||
"Please verify the reverse proxy configuration of your relevant assets. "
|
||||
"If the issue persists please contact Check Point Support";
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
dbgError(D_NGINX_MESSAGE_READER) << "Irrelevant status code";
|
||||
return false;
|
||||
return genError("Irrelevant status code");
|
||||
}
|
||||
}
|
||||
return event_name;
|
||||
}
|
||||
|
||||
Maybe<string> getEventName(const EnumArray<LogInfo, string> &log_info)
|
||||
{
|
||||
if (service_name != central_nginx_manager) {
|
||||
dbgWarning(D_NGINX_MESSAGE_READER)
|
||||
<< "Unknown service name: "
|
||||
<< service_name
|
||||
<< " Response will be sent as CNM";
|
||||
}
|
||||
|
||||
return getCNMEventName(log_info);
|
||||
}
|
||||
|
||||
bool
|
||||
sendLog(const EnumArray<LogInfo, string> &log_info)
|
||||
{
|
||||
dbgFlow(D_NGINX_MESSAGE_READER);
|
||||
Maybe<string> event_name = getEventName(log_info);
|
||||
|
||||
if (!event_name.ok()) {
|
||||
dbgError(D_NGINX_MESSAGE_READER) << event_name.getErr();
|
||||
return false;
|
||||
}
|
||||
|
||||
dbgTrace(D_NGINX_MESSAGE_READER)
|
||||
<< "Nginx log's event name and response code: "
|
||||
<< event_name
|
||||
<< event_name.unpack()
|
||||
<< ", "
|
||||
<< log_info[LogInfo::RESPONSE_CODE];
|
||||
|
||||
LogGen log(
|
||||
event_name,
|
||||
event_name.unpack(),
|
||||
ReportIS::Audience::SECURITY,
|
||||
ReportIS::Severity::INFO,
|
||||
ReportIS::Severity::HIGH,
|
||||
ReportIS::Priority::LOW,
|
||||
ReportIS::Tags::REVERSE_PROXY
|
||||
service_name == central_nginx_manager ?
|
||||
ReportIS::Tags::CENTRAL_NGINX_MANAGER :
|
||||
ReportIS::Tags::REVERSE_PROXY
|
||||
);
|
||||
log << LogField("eventConfidence", "High");
|
||||
|
||||
@@ -296,6 +558,10 @@ private:
|
||||
return false;
|
||||
}
|
||||
|
||||
if (string_field.unpack().empty() || log_info[field].empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (field != LogInfo::DESTINATION_PORT) {
|
||||
log << LogField(string_field.unpack(), log_info[field]);
|
||||
continue;
|
||||
@@ -313,6 +579,45 @@ private:
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
sendGenericLog(const GenericLogInfo &log_info)
|
||||
{
|
||||
dbgFlow(D_NGINX_MESSAGE_READER) << "Sending generic log";
|
||||
|
||||
string event_name = "NGINX Proxy Error: Request failed! Please verify your proxy configuration."
|
||||
"If the issue persists please contact open-appsec support";
|
||||
|
||||
ReportIS::Severity severity = ReportIS::Severity::MEDIUM;
|
||||
ReportIS::Priority priority = ReportIS::Priority::MEDIUM;
|
||||
if (log_info.severity == "emerg" || log_info.severity == "crit") {
|
||||
severity = ReportIS::Severity::CRITICAL;
|
||||
priority = ReportIS::Priority::URGENT;
|
||||
} else if (log_info.severity == "error" || log_info.severity == "alert") {
|
||||
severity = ReportIS::Severity::HIGH;
|
||||
priority = ReportIS::Priority::HIGH;
|
||||
}
|
||||
|
||||
LogGen log(
|
||||
event_name,
|
||||
ReportIS::Audience::SECURITY,
|
||||
severity,
|
||||
priority,
|
||||
ReportIS::Tags::CENTRAL_NGINX_MANAGER
|
||||
);
|
||||
|
||||
log << LogField("eventConfidence", "High");
|
||||
log << LogField("timestamp", log_info.timestamp);
|
||||
log << LogField("httpResponseBody", formatGenericLogMessage(log_info));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
string
|
||||
formatGenericLogMessage(const GenericLogInfo &log_info)
|
||||
{
|
||||
return "[" + log_info.severity + "] " + log_info.message;
|
||||
}
|
||||
|
||||
bool
|
||||
sendRateLimitLog(const EnumArray<LogInfo, string> &log_info)
|
||||
{
|
||||
@@ -338,7 +643,6 @@ private:
|
||||
string security_action = "Drop";
|
||||
bool is_log_required = false;
|
||||
|
||||
// Prevent events checkbox (in triggers)
|
||||
if (rate_limit_trigger.isPreventLogActive(LogTriggerConf::SecurityType::AccessControl)) {
|
||||
is_log_required = true;
|
||||
}
|
||||
@@ -372,7 +676,11 @@ private:
|
||||
dbgDebug(D_NGINX_MESSAGE_READER) << "Enum field was not converted: " << string_field.getErr();
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if (string_field.unpack().empty() || log_info[field].empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
field == LogInfo::HOST ||
|
||||
field == LogInfo::URI ||
|
||||
@@ -406,6 +714,55 @@ private:
|
||||
return true;
|
||||
}
|
||||
|
||||
static string
|
||||
getStatusCodeMessage(const string &status_code)
|
||||
{
|
||||
static map<string, string> status_messages = {
|
||||
// 4xx Client Error codes
|
||||
{"400", "Bad Request - malformed syntax"},
|
||||
{"401", "Unauthorized - authentication required"},
|
||||
{"403", "Forbidden - access denied"},
|
||||
{"404", "Not Found - resource does not exist"},
|
||||
{"405", "Method Not Allowed - HTTP verb not permitted"},
|
||||
{"408", "Request Timeout - client too slow"},
|
||||
{"411", "Length Required - missing Content-Length"},
|
||||
{"413", "Payload Too Large - body exceeds limit"},
|
||||
{"414", "URI Too Long - request target exceeds limit"},
|
||||
{"416", "Range Not Satisfiable - invalid byte range"},
|
||||
{"429", "Too Many Requests - rate limit exceeded"},
|
||||
{"431", "Header Fields Too Large - headers exceed limit"},
|
||||
{"451", "Unavailable For Legal Reasons"},
|
||||
|
||||
// NGINX specific 4xx codes
|
||||
{"494", "Request Header Too Large - NGINX internal"},
|
||||
{"495", "SSL Certificate Error - invalid client cert"},
|
||||
{"496", "SSL Certificate Required - none provided"},
|
||||
{"497", "Plain HTTP sent to HTTPS port"},
|
||||
|
||||
// 5xx Server Error codes
|
||||
{"500", "Internal Server Error"},
|
||||
{"501", "Not Implemented - feature unsupported"},
|
||||
{"502", "Bad Gateway - upstream connection failed"},
|
||||
{"503", "Service Unavailable - server overloaded"},
|
||||
{"504", "Gateway Timeout - upstream too slow"},
|
||||
{"505", "HTTP Version Not Supported"},
|
||||
{"507", "Insufficient Storage - WebDAV quota exceeded"}
|
||||
};
|
||||
|
||||
auto it = status_messages.find(status_code);
|
||||
return (it != status_messages.end()) ? it->second : "HTTP Error " + status_code;
|
||||
}
|
||||
|
||||
static string
|
||||
getEventName(bool is_proxy_fault)
|
||||
{
|
||||
if (!is_proxy_fault) {
|
||||
return "Upstream Application Error";
|
||||
}
|
||||
|
||||
return "Reverse Proxy Error";
|
||||
}
|
||||
|
||||
Maybe<string>
|
||||
convertLogFieldToString(LogInfo field)
|
||||
{
|
||||
@@ -420,7 +777,9 @@ private:
|
||||
case LogInfo::HOST:
|
||||
return string("httpHostName");
|
||||
case LogInfo::SOURCE:
|
||||
return string("httpSourceId");
|
||||
return string("sourceip");
|
||||
case LogInfo::SOURCE_PORT:
|
||||
return string("sourcePort");
|
||||
case LogInfo::DESTINATION_IP:
|
||||
return string("destinationIp");
|
||||
case LogInfo::DESTINATION_PORT:
|
||||
@@ -435,6 +794,18 @@ private:
|
||||
return string("ruleId");
|
||||
case LogInfo::RULE_NAME:
|
||||
return string("ruleName");
|
||||
case LogInfo::REFERENCE_ID:
|
||||
return string("eventReferenceId");
|
||||
case LogInfo::MATCHED_REASON:
|
||||
return string("matchreason");
|
||||
case LogInfo::REMEDIATION:
|
||||
return string("eventRemediation");
|
||||
case LogInfo::LOG_TYPE:
|
||||
return string("");
|
||||
case LogInfo::PROXY_FAULT:
|
||||
return string("");
|
||||
case LogInfo::X_EVENT_ID:
|
||||
return string("");
|
||||
case LogInfo::COUNT:
|
||||
dbgError(D_NGINX_MESSAGE_READER) << "LogInfo::COUNT is not allowed";
|
||||
return genError("LogInfo::COUNT is not allowed");
|
||||
@@ -512,7 +883,7 @@ private:
|
||||
static_cast<uint16_t>(stoi(log_info[LogInfo::DESTINATION_PORT]))
|
||||
);
|
||||
} catch (const exception &e) {
|
||||
dbgError(D_NGINX_MESSAGE_READER) << "Failed register values for context " << e.what();
|
||||
dbgWarning(D_NGINX_MESSAGE_READER) << "Failed register values for context " << e.what();
|
||||
}
|
||||
ctx.registerValue<string>(HttpTransactionData::host_name_ctx, log_info[LogInfo::HOST]);
|
||||
ctx.registerValue<string>(HttpTransactionData::uri_ctx, log_info[LogInfo::URI]);
|
||||
@@ -525,12 +896,65 @@ private:
|
||||
}
|
||||
|
||||
BasicRuleConfig context = rule_by_ctx.unpack();
|
||||
|
||||
dbgTrace(D_NGINX_MESSAGE_READER)
|
||||
<< "Adding context fields to log info. Asset ID: "
|
||||
<< context.getAssetId()
|
||||
<< ", Asset Name: "
|
||||
<< context.getAssetName()
|
||||
<< ", Rule ID: "
|
||||
<< context.getRuleId()
|
||||
<< ", Rule Name: "
|
||||
<< context.getRuleName();
|
||||
|
||||
log_info[LogInfo::ASSET_ID] = context.getAssetId();
|
||||
log_info[LogInfo::ASSET_NAME] = context.getAssetName();
|
||||
log_info[LogInfo::RULE_ID] = context.getRuleId();
|
||||
log_info[LogInfo::RULE_NAME] = context.getRuleName();
|
||||
}
|
||||
|
||||
Maybe<GenericLogInfo>
|
||||
parseGenericErrorLog(const string &log_line)
|
||||
{
|
||||
dbgFlow(D_NGINX_MESSAGE_READER) << "Parsing generic error log: " << log_line;
|
||||
|
||||
boost::smatch matcher;
|
||||
GenericLogInfo generic_log;
|
||||
|
||||
if (isCritErrorLog(log_line)) {
|
||||
if (NGEN::Regex::regexSearch(__FILE__, __LINE__, log_line, matcher, generic_crit_log_regex)) {
|
||||
const int timestamp_index = 2;
|
||||
const int message_index = 5;
|
||||
generic_log.timestamp = string(matcher[timestamp_index].first, matcher[timestamp_index].second);
|
||||
generic_log.severity = "crit";
|
||||
generic_log.message = string(matcher[message_index].first, matcher[message_index].second);
|
||||
return generic_log;
|
||||
}
|
||||
} else if (isEmergErrorLog(log_line)) {
|
||||
if (NGEN::Regex::regexSearch(__FILE__, __LINE__, log_line, matcher, generic_emerg_log_regex)) {
|
||||
const int timestamp_index = 2;
|
||||
const int message_index = 5;
|
||||
generic_log.timestamp = string(matcher[timestamp_index].first, matcher[timestamp_index].second);
|
||||
generic_log.severity = "emerg";
|
||||
generic_log.message = string(matcher[message_index].first, matcher[message_index].second);
|
||||
return generic_log;
|
||||
}
|
||||
}
|
||||
|
||||
if (NGEN::Regex::regexSearch(__FILE__, __LINE__, log_line, matcher, generic_fallback_log_regex)) {
|
||||
const int timestamp_index = 2;
|
||||
const int severity_index = 5;
|
||||
const int message_index = 6;
|
||||
generic_log.timestamp = string(matcher[timestamp_index].first, matcher[timestamp_index].second);
|
||||
generic_log.severity = string(matcher[severity_index].first, matcher[severity_index].second);
|
||||
generic_log.message = string(matcher[message_index].first, matcher[message_index].second);
|
||||
return generic_log;
|
||||
}
|
||||
|
||||
dbgWarning(D_NGINX_MESSAGE_READER) << "Could not parse log with generic method: " << log_line;
|
||||
return genError("Could not parse log with generic method");
|
||||
}
|
||||
|
||||
Maybe<EnumArray<LogInfo, string>>
|
||||
parseErrorLog(const string &log_line)
|
||||
{
|
||||
@@ -540,17 +964,28 @@ private:
|
||||
|
||||
boost::smatch matcher;
|
||||
vector<string> result;
|
||||
boost::regex selected_regex;
|
||||
|
||||
if (isAlertErrorLog(log_line)) {
|
||||
selected_regex = alert_log_regex;
|
||||
} else if (isErrorLog(log_line)) {
|
||||
selected_regex = error_log_regex;
|
||||
} else {
|
||||
dbgWarning(D_NGINX_MESSAGE_READER) << "No matching log type found for log: " << log_line;
|
||||
return genError("No matching log type found");
|
||||
}
|
||||
|
||||
if (
|
||||
!NGEN::Regex::regexSearch(
|
||||
__FILE__,
|
||||
__LINE__,
|
||||
log_line,
|
||||
matcher,
|
||||
isAlertErrorLog(log_line) ? alert_log_regex : error_log_regex
|
||||
selected_regex
|
||||
)
|
||||
) {
|
||||
dbgWarning(D_NGINX_MESSAGE_READER) << "Unexpected nginx log format";
|
||||
return genError("Unexpected nginx log format");
|
||||
dbgWarning(D_NGINX_MESSAGE_READER) << "Detailed regex parsing failed for log: " << log_line;
|
||||
return genError("Detailed regex parsing failed");
|
||||
}
|
||||
|
||||
const int event_message_index = 6;
|
||||
@@ -591,8 +1026,8 @@ private:
|
||||
addContextFieldsToLogInfo(log_info);
|
||||
|
||||
if (!validateLog(log_info)) {
|
||||
dbgWarning(D_NGINX_MESSAGE_READER) << "Unexpected nginx log format";
|
||||
return genError("Unexpected nginx log format");
|
||||
dbgWarning(D_NGINX_MESSAGE_READER) << "Log validation failed for detailed parsing";
|
||||
return genError("Log validation failed for detailed parsing");
|
||||
}
|
||||
|
||||
return log_info;
|
||||
@@ -642,6 +1077,104 @@ private:
|
||||
return log_info;
|
||||
}
|
||||
|
||||
|
||||
Maybe<EnumArray<LogInfo, string>>
|
||||
parseIncidentLog(const string &log_line)
|
||||
{
|
||||
dbgTrace(D_NGINX_MESSAGE_READER) << "Parsing incident log line: " << log_line;
|
||||
EnumArray<LogInfo, string> log_info(EnumArray<LogInfo, string>::Fill(), string(""));
|
||||
|
||||
boost::smatch matcher;
|
||||
if (!NGEN::Regex::regexSearch(__FILE__, __LINE__, log_line, matcher, incident_log_regex)) {
|
||||
dbgWarning(D_NGINX_MESSAGE_READER) << "Unexpected nginx incident log format";
|
||||
return genError("Unexpected nginx incident log format");
|
||||
}
|
||||
|
||||
const int host_index = 5; // host=server_name
|
||||
const int host_port_index = 6; // host_port=port
|
||||
const int client_addr_index = 7; // client_addr=IP
|
||||
const int client_port_index = 8; // client_port=port
|
||||
const int request_index = 10; // request="METHOD URI HTTP/1.1"
|
||||
const int status_index = 11; // status=CODE
|
||||
const int uri_index = 12; // uri="PATH"
|
||||
const int request_id_index = 13; // request_id=ID
|
||||
const int proxy_fault_index = 19; // pf=yes or empty
|
||||
const int x_event_id_index = 20; // x_event_id=value or empty
|
||||
|
||||
string host = string(matcher[host_index].first, matcher[host_index].second);
|
||||
string host_port = string(matcher[host_port_index].first, matcher[host_port_index].second);
|
||||
string uri = string(matcher[uri_index].first, matcher[uri_index].second);
|
||||
string proxy_fault = string(matcher[proxy_fault_index].first, matcher[proxy_fault_index].second);
|
||||
|
||||
log_info[LogInfo::HOST] = host;
|
||||
log_info[LogInfo::URI] = uri;
|
||||
log_info[LogInfo::DESTINATION_PORT] = host_port;
|
||||
log_info[LogInfo::PROXY_FAULT] = proxy_fault;
|
||||
|
||||
dbgTrace(D_NGINX_MESSAGE_READER)
|
||||
<< "Parsed host: "
|
||||
<< host
|
||||
<< ", host_port: "
|
||||
<< host_port
|
||||
<< ", uri: "
|
||||
<< uri
|
||||
<< ", proxy_fault: "
|
||||
<< proxy_fault;
|
||||
|
||||
addContextFieldsToLogInfo(log_info);
|
||||
if (log_info[LogInfo::ASSET_ID].empty() || log_info[LogInfo::ASSET_NAME].empty()) {
|
||||
return genError("Asset ID or Asset Name is empty");
|
||||
}
|
||||
|
||||
string client_addr = string(matcher[client_addr_index].first, matcher[client_addr_index].second);
|
||||
string client_port = string(matcher[client_port_index].first, matcher[client_port_index].second);
|
||||
string request = string(matcher[request_index].first, matcher[request_index].second);
|
||||
string status = string(matcher[status_index].first, matcher[status_index].second);
|
||||
string request_id = string(matcher[request_id_index].first, matcher[request_id_index].second);
|
||||
string x_event_id = string(matcher[x_event_id_index].first, matcher[x_event_id_index].second);
|
||||
|
||||
dbgTrace(D_NGINX_MESSAGE_READER)
|
||||
<< "Parsed client_addr: "
|
||||
<< client_addr
|
||||
<< ", client_port: "
|
||||
<< client_port
|
||||
<< ", request: "
|
||||
<< request
|
||||
<< ", status: "
|
||||
<< status
|
||||
<< ", request_id: "
|
||||
<< request_id
|
||||
<< ", proxy_fault: "
|
||||
<< proxy_fault
|
||||
<< ", x_event_id: "
|
||||
<< x_event_id;
|
||||
|
||||
vector<string> request_parts;
|
||||
boost::split(request_parts, request, boost::is_any_of(" "), boost::token_compress_on);
|
||||
string http_method = request_parts.size() > 0 ? request_parts[0] : "";
|
||||
|
||||
log_info[LogInfo::REFERENCE_ID] = request_id;
|
||||
log_info[LogInfo::RESPONSE_CODE] = status;
|
||||
log_info[LogInfo::HTTP_METHOD] = http_method;
|
||||
log_info[LogInfo::SOURCE] = client_addr;
|
||||
log_info[LogInfo::SOURCE_PORT] = client_port;
|
||||
log_info[LogInfo::DESTINATION_IP] = host;
|
||||
log_info[LogInfo::X_EVENT_ID] = x_event_id;
|
||||
|
||||
|
||||
if (!validateLog(log_info)) {
|
||||
dbgWarning(D_NGINX_MESSAGE_READER) << "Unexpected nginx incident log format after validation";
|
||||
return genError("Unexpected nginx incident log format after validation");
|
||||
}
|
||||
|
||||
if (proxy_fault == "yes") {
|
||||
log_info[LogInfo::MATCHED_REASON] = getStatusCodeMessage(status);
|
||||
log_info[LogInfo::REMEDIATION] = getIncidentLogRemediation(status);
|
||||
}
|
||||
|
||||
return log_info;
|
||||
}
|
||||
|
||||
static bool
|
||||
validateLog(const EnumArray<LogInfo, string> &log_info)
|
||||
{
|
||||
@@ -708,8 +1241,54 @@ private:
|
||||
return move(raw_log);
|
||||
}
|
||||
|
||||
static string
|
||||
getIncidentLogRemediation(const string &status_code)
|
||||
{
|
||||
static map<string, string> status_remediations = {
|
||||
{"400", "Check request syntax and format; validate client input and HTTP headers"},
|
||||
{"401", "Verify authentication credentials and configuration"},
|
||||
{"403", "If this is a valid WAF block, no action is required; otherwise,"
|
||||
" contact Check Point Support"},
|
||||
{"404", "Validate the requested asset URI"},
|
||||
{"405", "Ensure allowed HTTP methods are configured correctly"},
|
||||
{"408", "Adjust `client_header_timeout` and `client_body_timeout`"
|
||||
" in the assets advanced nginx server block"},
|
||||
{"411", "Require a Content-Length header on requests with a body"},
|
||||
{"413", "Increase `client_max_body_size` in the asset advanced nginx server block"},
|
||||
{"414", "Increase `large_client_header_buffers` (e.g. `large_client_header_buffers 4 32k;`)"
|
||||
" in the asset advanced nginx server block"},
|
||||
{"416", "Validate Range request headers against actual file sizes"},
|
||||
{"429", "Review rate limiting settings and implement proper backoff strategies"},
|
||||
{"431", "Increase `large_client_header_buffers` (e.g. `large_client_header_buffers 4 32k;`)"
|
||||
" in the asset advanced nginx server block"},
|
||||
{"451", "Review content filtering policies and legal compliance requirements"},
|
||||
{"494", "Increase `large_client_header_buffers` (e.g. `large_client_header_buffers 4 32k;`)"
|
||||
" in the asset advanced nginx server block"},
|
||||
{"495", "Confirm client SSL certificates are valid and properly configured"},
|
||||
{"496", "Configure SSL client certificate requirements correctly"},
|
||||
{"497", "Enforce HTTPS for secure endpoints"},
|
||||
{"500", "Contact Check Point Support"},
|
||||
{"501", "Ensure requested features are supported and configured correctly"},
|
||||
{"502", "Verify upstream server connectivity and health; check proxy configuration"},
|
||||
{"503", "Assess server capacity and load balancing; verify upstream availability"},
|
||||
{"504", "Increase `proxy_connect_timeout` and `proxy_read_timeout`"
|
||||
" in the asset advanced nginx configuration"},
|
||||
{"505", "Ensure HTTP version compatibility between client and server"},
|
||||
{"507", "Contact Check Point Support"}
|
||||
};
|
||||
|
||||
auto it = status_remediations.find(status_code);
|
||||
if (it != status_remediations.end()) {
|
||||
return it->second + ". If this suggestion does not resolve the issue, please contact support.";
|
||||
}
|
||||
|
||||
return "Please verify the nginx configuration of your relevant assets. "
|
||||
"Please contact support if the issue persists.";
|
||||
}
|
||||
|
||||
I_Socket::socketFd syslog_server_socket = -1;
|
||||
string rate_limit_status_code = "429";
|
||||
string service_name = "Unnamed Nano Service";
|
||||
};
|
||||
|
||||
NginxMessageReader::NginxMessageReader() : Component("NginxMessageReader"), pimpl(make_unique<Impl>()) {}
|
||||
|
||||
Reference in New Issue
Block a user