diff --git a/examples/http-server/go.mod b/examples/http-server/go.mod index 480fd7b57..f26b56651 100644 --- a/examples/http-server/go.mod +++ b/examples/http-server/go.mod @@ -11,8 +11,8 @@ require ( github.com/tidwall/gjson v1.18.0 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.1 // indirect - golang.org/x/net v0.34.0 // indirect - golang.org/x/sync v0.10.0 // indirect + golang.org/x/net v0.35.0 // indirect + golang.org/x/sync v0.11.0 // indirect golang.org/x/tools v0.22.0 // indirect rsc.io/binaryregexp v0.2.0 // indirect ) diff --git a/examples/http-server/go.sum b/examples/http-server/go.sum index 05299a64f..8b924af90 100644 --- a/examples/http-server/go.sum +++ b/examples/http-server/go.sum @@ -19,12 +19,12 @@ github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= -golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= -golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= -golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= +golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= +golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= +golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= rsc.io/binaryregexp v0.2.0 h1:HfqmD5MEmC0zvwBuF187nq9mdnXjXsSivRiXN7SmRkE= diff --git a/go.sum b/go.sum index 82e88f4d9..89d5a14aa 100644 --- a/go.sum +++ b/go.sum @@ -14,8 +14,6 @@ github.com/jcchavezs/mergefs v0.1.0 h1:7oteO7Ocl/fnfFMkoVLJxTveCjrsd//UB0j89xmnp github.com/jcchavezs/mergefs v0.1.0/go.mod h1:eRLTrsA+vFwQZ48hj8p8gki/5v9C2bFtHH5Mnn4bcGk= github.com/magefile/mage v1.15.1-0.20241126214340-bdc92f694516 h1:aAO0L0ulox6m/CLRYvJff+jWXYYCKGpEm3os7dM/Z+M= github.com/magefile/mage v1.15.1-0.20241126214340-bdc92f694516/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= -github.com/mccutchen/go-httpbin/v2 v2.15.0 h1:3b2s8LMRR2aFd+8U+1Bx2kdgHNQ5ZQkQOiW8e52Jj9A= -github.com/mccutchen/go-httpbin/v2 v2.15.0/go.mod h1:GBy5I7XwZ4ZLhT3hcq39I4ikwN9x4QUt6EAxNiR8Jus= github.com/mccutchen/go-httpbin/v2 v2.16.0 h1:dzoFvE4fWPIzWZ7ZRznLDYK9FpqXyx5NE3uzCngeTHM= github.com/mccutchen/go-httpbin/v2 v2.16.0/go.mod h1:GBy5I7XwZ4ZLhT3hcq39I4ikwN9x4QUt6EAxNiR8Jus= github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM= @@ -55,8 +53,6 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= -golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= -golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -65,8 +61,6 @@ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -79,8 +73,6 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= -golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= diff --git a/internal/corazarules/rule_match.go b/internal/corazarules/rule_match.go index 5b6700ed4..781332854 100644 --- a/internal/corazarules/rule_match.go +++ b/internal/corazarules/rule_match.go @@ -95,6 +95,8 @@ type MatchedRule struct { DisruptiveAction_ DisruptiveAction // Is meant to be logged Log_ bool + // Is meant to be logged to audit + Audit_ bool // Server IP address ServerIPAddress_ string // Client IP address @@ -133,6 +135,10 @@ func (mr *MatchedRule) Log() bool { return mr.Log_ } +func (mr *MatchedRule) Audit() bool { + return mr.Audit_ +} + func (mr *MatchedRule) ServerIPAddress() string { return mr.ServerIPAddress_ } diff --git a/internal/corazawaf/transaction.go b/internal/corazawaf/transaction.go index 8264c7e67..1a565810d 100644 --- a/internal/corazawaf/transaction.go +++ b/internal/corazawaf/transaction.go @@ -509,9 +509,7 @@ func (tx *Transaction) MatchRule(r *Rule, mds []types.MatchData) { // tx.MatchedRules = append(tx.MatchedRules, mr) // If the rule is set to audit, we log the transaction to the audit log - if r.Audit { - tx.audit = true - } + tx.audit = r.Audit // set highest_severity hs := tx.variables.highestSeverity @@ -527,6 +525,7 @@ func (tx *Transaction) MatchRule(r *Rule, mds []types.MatchData) { ClientIPAddress_: tx.variables.remoteAddr.Get(), Rule_: &r.RuleMetadata, Log_: r.Log, + Audit_: tx.audit, MatchedDatas_: mds, Context_: tx.context, } @@ -559,7 +558,6 @@ func (tx *Transaction) MatchRule(r *Rule, mds []types.MatchData) { if tx.WAF.ErrorLogCb != nil && r.Log { tx.WAF.ErrorLogCb(mr) } - } // GetStopWatch is used to debug phase durations @@ -1494,13 +1492,12 @@ func (tx *Transaction) AuditLog() *auditlog.Log { Stopwatch_: tx.GetStopWatch(), Rulesets_: tx.WAF.ComponentNames, } - case types.AuditLogPartRulesMatched: for _, mr := range tx.matchedRules { // Log action is required to log a matched rule on both error log and audit log // An assertion has to be done to check if the MatchedRule implements the Log() function before calling Log() // It is performed to avoid breaking the Coraza v3.* API adding a Log() method to the MatchedRule interface mrWithlog, ok := mr.(*corazarules.MatchedRule) - if ok && mrWithlog.Log() { + if ok && mrWithlog.Audit() { r := mr.Rule() for _, matchData := range mr.MatchedDatas() { al.Messages_ = append(al.Messages_, auditlog.Message{ @@ -1524,6 +1521,8 @@ func (tx *Transaction) AuditLog() *auditlog.Log { } } } + case types.AuditLogPartRulesMatched: + // implement matched rules } } diff --git a/internal/corazawaf/transaction_test.go b/internal/corazawaf/transaction_test.go index 979aa608b..3f7cc5908 100644 --- a/internal/corazawaf/transaction_test.go +++ b/internal/corazawaf/transaction_test.go @@ -727,6 +727,7 @@ func TestAuditLogFields(t *testing.T) { rule := NewRule() rule.ID_ = 131 rule.Log = true + rule.Audit = true tx.MatchRule(rule, []types.MatchData{ &corazarules.MatchData{ Variable_: variables.UniqueID, diff --git a/testing/auditlog_test.go b/testing/auditlog_test.go index fdbfb2c5c..99075ec09 100644 --- a/testing/auditlog_test.go +++ b/testing/auditlog_test.go @@ -194,7 +194,7 @@ func TestAuditLogOnNoLog(t *testing.T) { SecAuditLogParts ABCHIJKZ SecAuditLogRelevantStatus ".*" # auditlog tells that the transaction will have to log matches meant to be logged (not the ones with nolog) - SecRule ARGS "@unconditionalMatch" "id:1,phase:1,nolog,auditlog,msg:'nolog message'" + SecRule ARGS "@unconditionalMatch" "id:1,phase:1,nolog,msg:'nolog message'" `); err != nil { t.Fatal(err) } @@ -226,6 +226,50 @@ func TestAuditLogOnNoLog(t *testing.T) { } } +func TestAuditLogOnNoLogAuditLog(t *testing.T) { + waf := corazawaf.NewWAF() + parser := seclang.NewParser(waf) + if err := parser.FromString(` + SecRuleEngine DetectionOnly + SecAuditEngine On + SecAuditLogFormat json + SecAuditLogType serial + SecAuditLogParts ABCHIJKZ + SecAuditLogRelevantStatus ".*" + # auditlog tells that the transaction will have to log matches meant to be logged (not the ones with nolog) + SecRule ARGS "@unconditionalMatch" "id:1,phase:1,nolog,auditlog,msg:'unconditional match'" + `); err != nil { + t.Fatal(err) + } + // generate a random tmp file + file, err := os.Create(filepath.Join(t.TempDir(), "tmp.log")) + if err != nil { + t.Fatal(err) + } + defer os.Remove(file.Name()) + if err := parser.FromString(fmt.Sprintf("SecAuditLog %s", file.Name())); err != nil { + t.Fatal(err) + } + tx := waf.NewTransaction() + tx.AddGetRequestArgument("test", "test") + tx.ProcessRequestHeaders() + // now we read file + if _, err := file.Seek(0, 0); err != nil { + t.Error(err) + } + tx.ProcessLogging() + var al auditlog.Log + if err := json.NewDecoder(file).Decode(&al); err != nil { + t.Error(err) + } + if len(al.Messages()) != 1 { + t.Fatalf("Expected 1 message, got %d", len(al.Messages())) + } + if al.Messages()[0].Message() != "unconditional match" { + t.Errorf("Expected message %q, got %q", "unconditional match", al.Messages()[0].Message()) + } +} + func TestAuditLogRequestMethodURIProtocol(t *testing.T) { waf := corazawaf.NewWAF() parser := seclang.NewParser(waf) @@ -333,3 +377,88 @@ func TestAuditLogRequestBody(t *testing.T) { t.Fatalf("Expected %s uri, got %s", params, req.Body()) } } + +func TestAuditLogHFlag(t *testing.T) { + waf := corazawaf.NewWAF() + parser := seclang.NewParser(waf) + if err := parser.FromString(` + SecRuleEngine DetectionOnly + SecAuditEngine On + SecAuditLogFormat json + SecAuditLogType serial + SecAuditLogParts ABCHZ + SecAuditLogRelevantStatus ".*" + # auditlog should contain messages section on H flag included + SecRule ARGS "@unconditionalMatch" "id:1,phase:1,log,auditlog,msg:'unconditional match'" + `); err != nil { + t.Fatal(err) + } + // generate a random tmp file + file, err := os.Create(filepath.Join(t.TempDir(), "tmp.log")) + if err != nil { + t.Fatal(err) + } + defer os.Remove(file.Name()) + if err := parser.FromString(fmt.Sprintf("SecAuditLog %s", file.Name())); err != nil { + t.Fatal(err) + } + tx := waf.NewTransaction() + tx.AddGetRequestArgument("test", "test") + tx.ProcessRequestHeaders() + // now we read file + if _, err := file.Seek(0, 0); err != nil { + t.Error(err) + } + tx.ProcessLogging() + var al auditlog.Log + if err := json.NewDecoder(file).Decode(&al); err != nil { + t.Error(err) + } + if len(al.Messages()) != 1 { + t.Fatalf("Expected 1 message, got %d", len(al.Messages())) + } + if al.Messages()[0].Message() != "unconditional match" { + t.Errorf("Expected message %q, got %q", "unconditional match", al.Messages()[0].Message()) + } +} + +func TestAuditLogNoHFlag(t *testing.T) { + waf := corazawaf.NewWAF() + parser := seclang.NewParser(waf) + if err := parser.FromString(` + SecRuleEngine DetectionOnly + SecAuditEngine On + SecAuditLogFormat json + SecAuditLogType serial + SecAuditLogParts ABCKZ + SecAuditLogRelevantStatus ".*" + # auditlog should not contain messages section without H flag included, but should contain Rule below + SecRule ARGS "@unconditionalMatch" "id:1,phase:1,log,auditlog,msg:'nolog message'" + `); err != nil { + t.Fatal(err) + } + // generate a random tmp file + file, err := os.Create(filepath.Join(t.TempDir(), "tmp.log")) + if err != nil { + t.Fatal(err) + } + defer os.Remove(file.Name()) + if err := parser.FromString(fmt.Sprintf("SecAuditLog %s", file.Name())); err != nil { + t.Fatal(err) + } + tx := waf.NewTransaction() + tx.AddGetRequestArgument("test", "test") + tx.ProcessRequestHeaders() + // now we read file + if _, err := file.Seek(0, 0); err != nil { + t.Error(err) + } + tx.ProcessLogging() + var al auditlog.Log + if err := json.NewDecoder(file).Decode(&al); err != nil { + t.Error(err) + } + if len(al.Messages()) > 0 { + t.Fatalf("expected no messages got %d", len(al.Messages())) + } +} diff --git a/testing/coreruleset/go.sum b/testing/coreruleset/go.sum index 618a93897..342ecd1dc 100644 --- a/testing/coreruleset/go.sum +++ b/testing/coreruleset/go.sum @@ -4,8 +4,6 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60= github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= -github.com/bmatcuk/doublestar/v4 v4.8.0 h1:DSXtrypQddoug1459viM9X9D3dp1Z7993fw36I2kNcQ= -github.com/bmatcuk/doublestar/v4 v4.8.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/bmatcuk/doublestar/v4 v4.8.1 h1:54Bopc5c2cAvhLRAzqOGCYHYyhcDHsFF4wWIR5wKP38= github.com/bmatcuk/doublestar/v4 v4.8.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/corazawaf/coraza-coreruleset v0.0.0-20240226094324-415b1017abdc h1:OlJhrgI3I+FLUCTI3JJW8MoqyM78WbqJjecqMnqG+wc= @@ -101,12 +99,6 @@ github.com/valllabh/ocsf-schema-golang v1.0.3 h1:eR8k/3jP/OOqB8LRCtdJ4U+vlgd/gk5 github.com/valllabh/ocsf-schema-golang v1.0.3/go.mod h1:sZ3as9xqm1SSK5feFWIR2CuGeGRhsM7TR1MbpBctzPk= github.com/yargevad/filepathx v1.0.0 h1:SYcT+N3tYGi+NvazubCNlvgIPbzAk7i7y2dwg3I5FYc= github.com/yargevad/filepathx v1.0.0/go.mod h1:BprfX/gpYNJHJfc35GjRRpVcwWXS89gGulUIU5tK3tA= -golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= -golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= -golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= -golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= -golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= -golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=