From 755c43f450138fa0a7c4b75d35fa13986e705690 Mon Sep 17 00:00:00 2001
From: ale <ale@incal.net>
Date: Wed, 21 Sep 2022 22:08:39 +0100
Subject: [PATCH] Run mod_security logs through a JSON converter

---
 Dockerfile                                    |  6 ++
 .../conf-available/modsecurity-custom.conf    |  2 +
 modsec_logger.go                              | 67 +++++++++++++++++++
 3 files changed, 75 insertions(+)
 create mode 100644 modsec_logger.go

diff --git a/Dockerfile b/Dockerfile
index 9dd0bad9..1b0bfc0a 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,3 +1,8 @@
+FROM golang:1.19 AS gobuild
+COPY modsec_logger.go /src/modsec_logger.go
+WORKDIR /src
+RUN go build -tags netgo -o modsec_logger modsec_logger.go
+
 FROM composer:2.2.9 as build
 
 ADD . /build
@@ -15,6 +20,7 @@ COPY docker/wp-config.php /opt/noblogs/www/wp-config.php
 COPY docker/wp-cache-config.php /opt/noblogs/www/wp-content/wp-cache-config.php
 COPY docker/conf /tmp/conf
 COPY docker/build.sh /tmp/build.sh
+COPY --from=gobuild /src/modsec_logger /usr/local/bin/modsec_logger
 
 RUN /tmp/build.sh && rm /tmp/build.sh
 
diff --git a/docker/conf/apache2/conf-available/modsecurity-custom.conf b/docker/conf/apache2/conf-available/modsecurity-custom.conf
index 10340f55..07b6877f 100644
--- a/docker/conf/apache2/conf-available/modsecurity-custom.conf
+++ b/docker/conf/apache2/conf-available/modsecurity-custom.conf
@@ -6,5 +6,7 @@
                   SecRuleEngine Off
         </Location>
 
+	ErrorLog "|/usr/local/bin/modsec_logger"
+
 </IfModule>
 </IfModule>
diff --git a/modsec_logger.go b/modsec_logger.go
new file mode 100644
index 00000000..2988f3ad
--- /dev/null
+++ b/modsec_logger.go
@@ -0,0 +1,67 @@
+// Tool to rewrite mod_security2 logs (very difficult to parse
+// although they are in semi-structured format) to JSON.
+//
+package main
+
+import (
+	"bufio"
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"io"
+	"os"
+	"regexp"
+)
+
+var (
+	outerRx = regexp.MustCompile(`\[[^\]]+]`)
+	innerRx = regexp.MustCompile(`\[([^ ]+) \"?(.*)\"\]$`)
+	needle  = []byte("ModSecurity: ")
+)
+
+func parseModSec(w io.Writer, line []byte) bool {
+	if !bytes.Contains(line, needle) {
+		return false
+	}
+
+	fields := make(map[string]interface{})
+	var tags []string
+	for _, inner := range outerRx.FindAll(line, -1) {
+		for _, matches := range innerRx.FindAllSubmatch(inner, -1) {
+			field := string(matches[1])
+			value := string(matches[2])
+			switch field {
+			case "tag":
+				tags = append(tags, value)
+			case "client", "unique_id", "file", "line":
+				// Suppress these tags.
+			default:
+				fields[field] = value
+			}
+		}
+	}
+	if len(fields) == 0 {
+		return false
+	}
+	if len(tags) > 0 {
+		fields["tag"] = tags
+	}
+
+	data, _ := json.Marshal(fields)
+	fmt.Fprintf(w, "@cee:{\"modsec\":%s}\n", data)
+	return true
+}
+
+func main() {
+	outw := bufio.NewWriter(os.Stdout)
+	defer outw.Flush()
+
+	scanner := bufio.NewScanner(os.Stdin)
+	for scanner.Scan() {
+		line := scanner.Bytes()
+		if !parseModSec(outw, line) {
+			outw.Write(line)
+			io.WriteString(outw, "\n")
+		}
+	}
+}
-- 
GitLab