]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
firewall: beginning of ruleset analyzer
authorVictor Julien <vjulien@oisf.net>
Sat, 29 Mar 2025 07:21:46 +0000 (08:21 +0100)
committerVictor Julien <victor@inliniac.net>
Mon, 7 Apr 2025 20:04:14 +0000 (22:04 +0200)
Output a `firewall.json` with a per table list of rules.

Also output a sorted list of the threat detection rules.

src/detect-engine-analyzer.c
src/detect-engine-analyzer.h
src/detect-engine-build.c

index b5d12164edf4e4c7badd95ebb12905b80010b64d..35150a57bc1fea30dafef8a5e08c7962c7a9b2fa 100644 (file)
@@ -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;
+}
index 7419d318567e96b59610e7ef8e8ac474e1d36770..294e243b4643700974485e39fa40ff17a53226bb 100644 (file)
@@ -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 */
index 7c7429fdb459a1833b4b6587045fec99ae2556a2..13fc6e1bc2eafcb6b4586af7f21f6c707073d049 100644 (file)
@@ -2190,6 +2190,10 @@ int SigGroupBuild(DetectEngineCtx *de_ctx)
     if (!DetectEngineMultiTenantEnabled()) {
         VarNameStoreActivate();
     }
+
+    if (de_ctx->flags & DE_HAS_FIREWALL) {
+        FirewallAnalyzer(de_ctx);
+    }
     return 0;
 }