From: Victor Julien Date: Sat, 29 Mar 2025 07:21:46 +0000 (+0100) Subject: firewall: beginning of ruleset analyzer X-Git-Tag: suricata-8.0.0-beta1~19 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=98372b659993f8a3b237a1240c73f35176998acf;p=thirdparty%2Fsuricata.git firewall: beginning of ruleset analyzer Output a `firewall.json` with a per table list of rules. Also output a sorted list of the threat detection rules. --- diff --git a/src/detect-engine-analyzer.c b/src/detect-engine-analyzer.c index b5d12164ed..35150a57bc 100644 --- a/src/detect-engine-analyzer.c +++ b/src/detect-engine-analyzer.c @@ -1959,3 +1959,221 @@ void EngineAnalysisRules(const DetectEngineCtx *de_ctx, fprintf(fp, "\n"); } } + +#include "app-layer-parser.h" + +static void FirewallAddRulesForState(const DetectEngineCtx *de_ctx, const AppProto a, + const uint8_t state, const uint8_t direction, RuleAnalyzer *ctx) +{ + uint32_t accept_rules = 0; + SCJbSetString(ctx->js, "policy", "drop:flow"); + SCJbOpenArray(ctx->js, "rules"); + for (Signature *s = de_ctx->sig_list; s != NULL; s = s->next) { + if ((s->flags & SIG_FLAG_FIREWALL) == 0) + break; + if (s->type != SIG_TYPE_APP_TX) + continue; + if (s->alproto != a) + continue; + + if (direction == STREAM_TOSERVER) { + if (s->flags & SIG_FLAG_TOCLIENT) { + continue; + } + } else { + if (s->flags & SIG_FLAG_TOSERVER) { + continue; + } + } + + if (s->app_progress_hook == state) { + SCJbAppendString(ctx->js, s->sig_str); + accept_rules += ((s->action & ACTION_ACCEPT) != 0); + } + } + SCJbClose(ctx->js); + + if (accept_rules == 0) { + AnalyzerWarning(ctx, (char *)"no accept rules for state, default policy will be applied"); + } +} + +int FirewallAnalyzer(const DetectEngineCtx *de_ctx) +{ + RuleAnalyzer ctx = { NULL, NULL, NULL }; + ctx.js = SCJbNewObject(); + if (ctx.js == NULL) + return -1; + + SCJbOpenObject(ctx.js, "tables"); + SCJbOpenObject(ctx.js, "packet:filter"); + SCJbSetString(ctx.js, "policy", "drop:packet"); + SCJbOpenArray(ctx.js, "rules"); + uint32_t accept_rules = 0; + uint32_t last_sid = 0; + for (Signature *s = de_ctx->sig_list; s != NULL; s = s->next) { + if ((s->flags & SIG_FLAG_FIREWALL) == 0) + break; + if (s->type != SIG_TYPE_PKT) + continue; + /* don't double list <> sigs */ + if (last_sid == s->id) + continue; + last_sid = s->id; + SCJbAppendString(ctx.js, s->sig_str); + accept_rules += ((s->action & ACTION_ACCEPT) != 0); + } + SCJbClose(ctx.js); + if (accept_rules == 0) { + AnalyzerWarning(&ctx, + (char *)"no accept rules for \'packet:filter\', default policy will be applied"); + } + if (ctx.js_warnings) { + SCJbClose(ctx.js_warnings); + SCJbSetObject(ctx.js, "warnings", ctx.js_warnings); + SCJbFree(ctx.js_warnings); + ctx.js_warnings = NULL; + } + SCJbClose(ctx.js); // packet_filter + + for (AppProto a = 0; a < g_alproto_max; a++) { + if (!AppProtoIsValid(a)) + continue; + + // HACK not all protocols have named states yet + const char *hack = AppLayerParserGetStateNameById(IPPROTO_TCP, a, 0, STREAM_TOSERVER); + if (!hack) + continue; + + SCJbOpenObject(ctx.js, AppProtoToString(a)); + const uint8_t complete_state_ts = + (const uint8_t)AppLayerParserGetStateProgressCompletionStatus(a, STREAM_TOSERVER); + for (uint8_t state = 0; state < complete_state_ts; state++) { + const char *name = + AppLayerParserGetStateNameById(IPPROTO_TCP, a, state, STREAM_TOSERVER); + char table_name[128]; + snprintf(table_name, sizeof(table_name), "app:%s:%s", AppProtoToString(a), name); + SCJbOpenObject(ctx.js, table_name); + FirewallAddRulesForState(de_ctx, a, state, STREAM_TOSERVER, &ctx); + if (ctx.js_warnings) { + SCJbClose(ctx.js_warnings); + SCJbSetObject(ctx.js, "warnings", ctx.js_warnings); + SCJbFree(ctx.js_warnings); + ctx.js_warnings = NULL; + } + SCJbClose(ctx.js); + } + const uint8_t complete_state_tc = + (const uint8_t)AppLayerParserGetStateProgressCompletionStatus(a, STREAM_TOCLIENT); + for (uint8_t state = 0; state < complete_state_tc; state++) { + const char *name = + AppLayerParserGetStateNameById(IPPROTO_TCP, a, state, STREAM_TOCLIENT); + char table_name[128]; + snprintf(table_name, sizeof(table_name), "app:%s:%s", AppProtoToString(a), name); + SCJbOpenObject(ctx.js, table_name); + FirewallAddRulesForState(de_ctx, a, state, STREAM_TOCLIENT, &ctx); + if (ctx.js_warnings) { + SCJbClose(ctx.js_warnings); + SCJbSetObject(ctx.js, "warnings", ctx.js_warnings); + SCJbFree(ctx.js_warnings); + ctx.js_warnings = NULL; + } + SCJbClose(ctx.js); + } + SCJbClose(ctx.js); // app layer + } + SCJbOpenObject(ctx.js, "packet:td"); + SCJbSetString(ctx.js, "policy", "accept:hook"); + last_sid = 0; + SCJbOpenArray(ctx.js, "rules"); + for (Signature *s = de_ctx->sig_list; s != NULL; s = s->next) { + if ((s->flags & SIG_FLAG_FIREWALL) != 0) + continue; + if (s->type == SIG_TYPE_APP_TX) + continue; + if (last_sid == s->id) + continue; + last_sid = s->id; + SCJbAppendString(ctx.js, s->sig_str); + } + SCJbClose(ctx.js); // rules + SCJbClose(ctx.js); // packet:td + SCJbOpenObject(ctx.js, "app:td"); + SCJbSetString(ctx.js, "policy", "accept:hook"); + last_sid = 0; + SCJbOpenArray(ctx.js, "rules"); + for (Signature *s = de_ctx->sig_list; s != NULL; s = s->next) { + if ((s->flags & SIG_FLAG_FIREWALL) != 0) + continue; + if (s->type != SIG_TYPE_APP_TX) + continue; + if (last_sid == s->id) + continue; + last_sid = s->id; + SCJbAppendString(ctx.js, s->sig_str); + } + SCJbClose(ctx.js); // rules + SCJbClose(ctx.js); // app:td + SCJbClose(ctx.js); // tables + + SCJbOpenObject(ctx.js, "lists"); + SCJbOpenObject(ctx.js, "firewall"); + last_sid = 0; + SCJbOpenArray(ctx.js, "rules"); + for (Signature *s = de_ctx->sig_list; s != NULL; s = s->next) { + if ((s->flags & SIG_FLAG_FIREWALL) == 0) + continue; + if (last_sid == s->id) + continue; + last_sid = s->id; + SCJbAppendString(ctx.js, s->sig_str); + } + SCJbClose(ctx.js); // rules + SCJbClose(ctx.js); // firewall + + SCJbOpenObject(ctx.js, "td"); + last_sid = 0; + SCJbOpenArray(ctx.js, "rules"); + for (Signature *s = de_ctx->sig_list; s != NULL; s = s->next) { + if ((s->flags & SIG_FLAG_FIREWALL) != 0) + continue; + if (last_sid == s->id) + continue; + last_sid = s->id; + SCJbAppendString(ctx.js, s->sig_str); + } + SCJbClose(ctx.js); // rules + SCJbClose(ctx.js); // td + + SCJbOpenObject(ctx.js, "all"); + last_sid = 0; + SCJbOpenArray(ctx.js, "rules"); + for (Signature *s = de_ctx->sig_list; s != NULL; s = s->next) { + if (last_sid == s->id) + continue; + last_sid = s->id; + SCJbAppendString(ctx.js, s->sig_str); + } + SCJbClose(ctx.js); // rules + SCJbClose(ctx.js); // all + + SCJbClose(ctx.js); // lists + + SCJbClose(ctx.js); // top level object + + const char *filename = "firewall.json"; + const char *log_dir = SCConfigGetLogDirectory(); + char json_path[PATH_MAX] = ""; + snprintf(json_path, sizeof(json_path), "%s/%s", log_dir, filename); + + SCMutexLock(&g_rules_analyzer_write_m); + FILE *fp = fopen(json_path, "w"); + if (fp != NULL) { + fwrite(SCJbPtr(ctx.js), SCJbLen(ctx.js), 1, fp); + fprintf(fp, "\n"); + fclose(fp); + } + SCMutexUnlock(&g_rules_analyzer_write_m); + SCJbFree(ctx.js); + return 0; +} diff --git a/src/detect-engine-analyzer.h b/src/detect-engine-analyzer.h index 7419d31856..294e243b46 100644 --- a/src/detect-engine-analyzer.h +++ b/src/detect-engine-analyzer.h @@ -39,4 +39,6 @@ void EngineAnalysisRulesFailure( void EngineAnalysisRules2(const struct DetectEngineCtx_ *de_ctx, const Signature *s); +int FirewallAnalyzer(const struct DetectEngineCtx_ *de_ctx); + #endif /* SURICATA_DETECT_ENGINE_ANALYZER_H */ diff --git a/src/detect-engine-build.c b/src/detect-engine-build.c index 7c7429fdb4..13fc6e1bc2 100644 --- a/src/detect-engine-build.c +++ b/src/detect-engine-build.c @@ -2190,6 +2190,10 @@ int SigGroupBuild(DetectEngineCtx *de_ctx) if (!DetectEngineMultiTenantEnabled()) { VarNameStoreActivate(); } + + if (de_ctx->flags & DE_HAS_FIREWALL) { + FirewallAnalyzer(de_ctx); + } return 0; }