3 Commits
Author SHA1 Message Date
shadowdaoandClaude Opus 4.7 489290ed33 coraza: ship rules-catalog.json generated from bundled CRS at build time
Build and push coraza-spoa / Build-and-Push (push) Successful in 44s
HAProxy Manager Build and Push / Build-and-Push (push) Successful in 52s
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 06:57:42 -07:00
shadowdao b2adcdbed9 coraza: reserve rule-ID range 990000000-990999999 for WHP-generated rules 2026-05-14 06:53:37 -07:00
shadowdao 1f1bc1837e coraza: add second Include for runtime-managed local-overrides.conf 2026-05-14 06:51:24 -07:00
8 changed files with 127 additions and 0 deletions
+13
View File
@@ -27,6 +27,16 @@ RUN git clone --depth 1 --branch "${CORAZA_SPOA_VERSION}" \
&& go mod download \
&& CGO_ENABLED=0 go build -trimpath -ldflags='-s -w' -o /out/coraza-spoa .
# Catalog extractor: walks the bundled CRS at build time and emits
# rules-catalog.json so WHP's UI can render rule metadata without parsing
# .conf files at runtime. Uses the SAME coraza-coreruleset version pin as
# the coraza-spoa binary above (drift between the two would mislabel rules).
FROM repo.anhonesthost.net/cloud-hosting-platform/golang:1.25 AS catalog
WORKDIR /src
COPY catalog-extractor/ .
RUN go build -trimpath -o /out/catalog-extractor . \
&& /out/catalog-extractor > /out/rules-catalog.json
# Distroless runtime: no shell, no package manager, no /tmp by default —
# smallest attack surface for an exposed service. Audit log directory is
# bind-mounted; coraza-spoa writes to it via direct file I/O (no shell needed).
@@ -39,6 +49,9 @@ LABEL org.opencontainers.image.title="coraza-spoa-whp" \
COPY --from=build /out/coraza-spoa /coraza-spoa
COPY config.yaml /etc/coraza-spoa/config.yaml
COPY overrides.conf /etc/coraza/overrides.conf
COPY local-overrides.conf /etc/coraza/local-overrides.conf
COPY host-exceptions/ /etc/coraza/host-exceptions/
COPY --from=catalog /out/rules-catalog.json /etc/coraza/rules-catalog.json
# Audit log directory — bind-mount /var/log/coraza:/var/log/coraza from host
# so logs persist across container restarts and AI Monitor can tail them.
+7
View File
@@ -0,0 +1,7 @@
module catalog-extractor
go 1.25
require github.com/corazawaf/coraza-coreruleset/v4 v4.25.0
require github.com/magefile/mage v1.17.0 // indirect
+12
View File
@@ -0,0 +1,12 @@
github.com/corazawaf/coraza-coreruleset/v4 v4.25.0 h1:tqFO1lfVpTiyWtlN618OXpZMfw+nnN0Q4///W5W+/HM=
github.com/corazawaf/coraza-coreruleset/v4 v4.25.0/go.mod h1:nRuGXITxOPvsLF2VxaTB7pYok8QB8BitX3ZenXcUryY=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/magefile/mage v1.17.0 h1:dS4tkq997Ism03akafC8509iqDjeE7TNTexI25Y7sXM=
github.com/magefile/mage v1.17.0/go.mod h1:Yj51kqllmsgFpvvSzgrZPK9WtluG3kUhFaBUVLo4feA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+80
View File
@@ -0,0 +1,80 @@
package main
import (
"encoding/json"
"fmt"
"io/fs"
"os"
"regexp"
"strconv"
"strings"
crs "github.com/corazawaf/coraza-coreruleset/v4"
)
type Rule struct {
ID int `json:"id"`
Msg string `json:"msg"`
Severity string `json:"severity"`
Tags []string `json:"tags"`
File string `json:"file"`
}
var (
idRe = regexp.MustCompile(`(?i)\bid:'?(\d+)'?`)
msgRe = regexp.MustCompile(`(?i)\bmsg:'([^']+)'`)
severityRe = regexp.MustCompile(`(?i)\bseverity:'?([A-Z]+)'?`)
tagRe = regexp.MustCompile(`(?i)\btag:'([^']+)'`)
)
func main() {
out := []Rule{}
err := fs.WalkDir(crs.FS, ".", func(path string, d fs.DirEntry, err error) error {
if err != nil || d.IsDir() {
return err
}
if !strings.HasSuffix(path, ".conf") {
return nil
}
b, err := fs.ReadFile(crs.FS, path)
if err != nil {
return err
}
// Coalesce backslash-continuation lines so id/msg/etc on the same
// logical rule are visible to the per-line scanner.
text := regexp.MustCompile(`\\\s*\n\s*`).ReplaceAllString(string(b), " ")
for _, line := range strings.Split(text, "\n") {
line = strings.TrimSpace(line)
if !strings.HasPrefix(line, "SecRule") && !strings.HasPrefix(line, "SecAction") {
continue
}
m := idRe.FindStringSubmatch(line)
if m == nil {
continue
}
id, _ := strconv.Atoi(m[1])
r := Rule{ID: id, File: path}
if mm := msgRe.FindStringSubmatch(line); mm != nil {
r.Msg = mm[1]
}
if mm := severityRe.FindStringSubmatch(line); mm != nil {
r.Severity = strings.ToLower(mm[1])
}
for _, mm := range tagRe.FindAllStringSubmatch(line, -1) {
r.Tags = append(r.Tags, mm[1])
}
out = append(out, r)
}
return nil
})
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
if err := enc.Encode(out); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
+3
View File
@@ -34,6 +34,9 @@ applications:
# to see exactly what blocks vs what's detect-only.
Include /etc/coraza/overrides.conf
# Runtime-managed overrides written by WHP UI. Empty by default.
Include /etc/coraza/local-overrides.conf
# Global mode: log all alerts, block only what overrides.conf
# explicitly promotes via ctl:ruleEngine=On.
SecRuleEngine DetectionOnly
+3
View File
@@ -0,0 +1,3 @@
# AUTOGENERATED by WHP — do not hand-edit.
# Source of truth: whp.security_db coraza_rule_overrides table.
# Empty file = no runtime overrides; baked-in overrides.conf governs.
+9
View File
@@ -94,3 +94,12 @@ SecRuleUpdateActionById 930130 "ctl:ruleEngine=On"
# (`session_start` literal appearing in billing form data)
# 950xxx-953xxx — Data leakage / backup-file disclosure (mixed FP)
# ---------------------------------------------------------------------------
# ---------------------------------------------------------------------------
# RESERVED RULE-ID RANGE: 990000000 – 990999999
# WHP's coraza_rule_manager generates per-host-exception rules in this range
# (rule ID = 990000000 + target_rule_id). Do NOT add new rules in this range
# from any other source. When bumping the coraza-spoa pin, check the CRS
# changelog for new rules with 9-digit IDs (rare but possible) and re-namespace
# if collision risk emerges.
# ---------------------------------------------------------------------------