diff --git a/Dockerfile b/Dockerfile index 9dd0bad9c328b2ae2aa6c5c00455c70eee4fee3e..1b0bfc0a5e6b33129522b36e40425bf7fafee099 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 10340f5598b16b57d19302aa5104042a299f950e..07b6877f7acaa77ee5c4660dd450e9769c1f0e44 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 0000000000000000000000000000000000000000..29e3f2bd064564868b4c33fbc7fcc8e813ea30ab --- /dev/null +++ b/modsec_logger.go @@ -0,0 +1,66 @@ +// 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 := os.Stdout + + scanner := bufio.NewScanner(os.Stdin) + for scanner.Scan() { + line := scanner.Bytes() + if !parseModSec(outw, line) { + outw.Write(line) + io.WriteString(outw, "\n") + } + } +}