diff --git a/.github/workflows/tinygo.yml b/.github/workflows/tinygo.yml index 07d6a5f29..f2e51ccec 100644 --- a/.github/workflows/tinygo.yml +++ b/.github/workflows/tinygo.yml @@ -9,27 +9,33 @@ on: - "**/*.md" - "LICENSE" pull_request: + branches: + - v3/dev + - v3/main paths-ignore: - "**/*.md" - "LICENSE" + jobs: test: strategy: matrix: - go-version: [1.18.x, 1.19.x] + # TODO(anuraaga): Add 1.19.x after TinyGo adds support for it. + go-version: [1.18.x] os: [ubuntu-latest] runs-on: ${{ matrix.os }} + container: + image: tinygo/tinygo:0.24.0 steps: - - name: Install Go - uses: actions/setup-go@v2 - with: - go-version: ${{ matrix.go-version }} - - name: Checkout code - uses: actions/checkout@v2 - with: - lfs: true - fetch-depth: 0 #for better blame info - - name: Download vendored dependencies - run: go mod vendor - - name: Tests and coverage - run: go test -v --tags=tinygo ./... \ No newline at end of file + - name: Checkout code + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Install Go + uses: actions/setup-go@v3 + with: + go-version: ${{ matrix.go-version }} + + - name: Tests + run: tinygo test ./... diff --git a/collection/collection_map_test.go b/collection/collection_map_test.go index 8a4c6fdea..8ad68eb17 100644 --- a/collection/collection_map_test.go +++ b/collection/collection_map_test.go @@ -4,7 +4,7 @@ // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, diff --git a/collection/collection_proxy_test.go b/collection/collection_proxy_test.go index 0f22c6ed1..656424109 100644 --- a/collection/collection_proxy_test.go +++ b/collection/collection_proxy_test.go @@ -4,7 +4,7 @@ // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, diff --git a/collection/collection_size_proxy_test.go b/collection/collection_size_proxy_test.go index d1677259e..65c54ccb8 100644 --- a/collection/collection_size_proxy_test.go +++ b/collection/collection_size_proxy_test.go @@ -4,7 +4,7 @@ // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, diff --git a/collection/collection_translation_proxy_test.go b/collection/collection_translation_proxy_test.go index 4904975cc..a12a97680 100644 --- a/collection/collection_translation_proxy_test.go +++ b/collection/collection_translation_proxy_test.go @@ -4,7 +4,7 @@ // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, diff --git a/examples/http-server/go.mod b/examples/http-server/go.mod index b30691ff1..e8e3fd02f 100644 --- a/examples/http-server/go.mod +++ b/examples/http-server/go.mod @@ -7,7 +7,10 @@ require github.com/corazawaf/coraza/v3 v3.0.0-00010101000000-000000000000 require ( github.com/cloudflare/ahocorasick v0.0.0-20210425175752-730270c3e184 // indirect github.com/corazawaf/libinjection-go v0.0.0-20220207031228-44e9c4250eb5 // indirect + github.com/tidwall/gjson v1.14.1 // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.0 // indirect golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2 // indirect ) -replace github.com/corazawaf/coraza/v3 => ../.. +replace github.com/corazawaf/coraza/v3 => ../.. \ No newline at end of file diff --git a/examples/http-server/go.sum b/examples/http-server/go.sum index c42ab2901..7d19efb78 100644 --- a/examples/http-server/go.sum +++ b/examples/http-server/go.sum @@ -9,6 +9,12 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/miekg/dns v1.1.25 h1:dFwPR6SfLtrSwgDcIq2bcU/gVutB4sNApq2HBdqcakg= github.com/miekg/dns v1.1.25/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= +github.com/tidwall/gjson v1.14.1 h1:iymTbGkQBhveq21bEvAQ81I0LEBork8BFe1CUZXdyuo= +github.com/tidwall/gjson v1.14.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8= diff --git a/examples/http-server/main.go b/examples/http-server/main.go index 33a29e8d2..b80166bbd 100644 --- a/examples/http-server/main.go +++ b/examples/http-server/main.go @@ -7,6 +7,7 @@ import ( "net/http" "github.com/corazawaf/coraza/v3" + txhttp "github.com/corazawaf/coraza/v3/http" "github.com/corazawaf/coraza/v3/seclang" "github.com/corazawaf/coraza/v3/types" ) @@ -62,7 +63,7 @@ func corazaRequestHandler(h http.Handler) http.Handler { ProcessRequestHeaders and ProcessRequestBody. It fails if any of these functions returns an error and it stops on interruption. */ - if it, err := tx.ProcessRequest(r); err != nil { + if it, err := txhttp.ProcessRequest(tx, r); err != nil { showCorazaError(w, 500, err.Error()) return } else if it != nil { diff --git a/http/doc.go b/http/doc.go new file mode 100644 index 000000000..d329e7c2f --- /dev/null +++ b/http/doc.go @@ -0,0 +1,16 @@ +// Copyright 2022 Juan Pablo Tosso +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package http allows populating a coraza transaction with information from an HTTP Request. +package http diff --git a/http/http.go b/http/http.go index 8cd210a92..54014e991 100644 --- a/http/http.go +++ b/http/http.go @@ -1,3 +1,7 @@ +// tinygo does not support net.http so this package is not needed for it +//go:build !tinygo +// +build !tinygo + // Copyright 2022 Juan Pablo Tosso // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/http/http_test.go b/http/http_test.go index bb8844308..67e7291c4 100644 --- a/http/http_test.go +++ b/http/http_test.go @@ -1,3 +1,7 @@ +// tinygo does not support net.http so this package is not needed for it +//go:build !tinygo +// +build !tinygo + // Copyright 2022 Juan Pablo Tosso // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/loggers/concurrent_writer.go b/loggers/concurrent_writer.go index 4d33bf953..7503db8d4 100644 --- a/loggers/concurrent_writer.go +++ b/loggers/concurrent_writer.go @@ -1,3 +1,6 @@ +//go:build !tinygo +// +build !tinygo + // Copyright 2022 Juan Pablo Tosso // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/loggers/concurrent_writer_test.go b/loggers/concurrent_writer_test.go index 3234e3828..b9ad2b75e 100644 --- a/loggers/concurrent_writer_test.go +++ b/loggers/concurrent_writer_test.go @@ -1,3 +1,6 @@ +//go:build !tinygo +// +build !tinygo + // Copyright 2022 Juan Pablo Tosso // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/loggers/formats.go b/loggers/formats.go index b3b6aa8f7..2b26ac262 100644 --- a/loggers/formats.go +++ b/loggers/formats.go @@ -1,21 +1,6 @@ -/* -Package loggers implements a set of log formatters and writers -for audit logging. +//go:build !tinygo +// +build !tinygo -The following log formats are supported: - -- JSON -- Coraza -- Native - -The following log writers are supported: - -- Serial -- Concurrent - -More writers and formatters can be registered using the RegisterWriter and -RegisterFormatter functions. -*/ // Copyright 2022 Juan Pablo Tosso // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -29,6 +14,23 @@ RegisterFormatter functions. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. + +// Package loggers implements a set of log formatters and writers +// for audit logging. +// +// The following log formats are supported: +// +// - JSON +// - Coraza +// - Native +// +// The following log writers are supported: +// +// - Serial +// - Concurrent +// +// More writers and formatters can be registered using the RegisterWriter and +// RegisterFormatter functions. package loggers import ( diff --git a/loggers/formats_test.go b/loggers/formats_test.go index 326b14a44..7af0593b8 100644 --- a/loggers/formats_test.go +++ b/loggers/formats_test.go @@ -1,3 +1,6 @@ +//go:build !tinygo +// +build !tinygo + // Copyright 2022 Juan Pablo Tosso // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/loggers/init.go b/loggers/init.go new file mode 100644 index 000000000..eb7c052d6 --- /dev/null +++ b/loggers/init.go @@ -0,0 +1,31 @@ +//go:build !tinygo +// +build !tinygo + +// Copyright 2022 Juan Pablo Tosso +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package loggers + +func init() { + RegisterLogWriter("concurrent", func() LogWriter { + return &concurrentWriter{} + }) + RegisterLogWriter("serial", func() LogWriter { + return &serialWriter{} + }) + + RegisterLogFormatter("json", jsonFormatter) + RegisterLogFormatter("jsonlegacy", legacyJSONFormatter) + RegisterLogFormatter("native", nativeFormatter) +} diff --git a/loggers/init_tinygo.go b/loggers/init_tinygo.go new file mode 100644 index 000000000..d12894b79 --- /dev/null +++ b/loggers/init_tinygo.go @@ -0,0 +1,31 @@ +//go:build tinygo +// +build tinygo + +// Copyright 2022 Juan Pablo Tosso +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package loggers + +func init() { + RegisterLogWriter("concurrent", func() LogWriter { + return noopWriter{} + }) + RegisterLogWriter("serial", func() LogWriter { + return noopWriter{} + }) + + RegisterLogFormatter("json", noopFormater) + RegisterLogFormatter("jsonlegacy", noopFormater) + RegisterLogFormatter("native", noopFormater) +} diff --git a/loggers/logger.go b/loggers/logger.go index ae7235c93..c3f4fdbf0 100644 --- a/loggers/logger.go +++ b/loggers/logger.go @@ -76,16 +76,3 @@ func GetLogFormatter(name string) (LogFormatter, error) { } return formatter, nil } - -func init() { - RegisterLogWriter("concurrent", func() LogWriter { - return &concurrentWriter{} - }) - RegisterLogWriter("serial", func() LogWriter { - return &serialWriter{} - }) - - RegisterLogFormatter("json", jsonFormatter) - RegisterLogFormatter("jsonlegacy", legacyJSONFormatter) - RegisterLogFormatter("native", nativeFormatter) -} diff --git a/loggers/logger_test.go b/loggers/logger_test.go index 2db6a9151..39b112a36 100644 --- a/loggers/logger_test.go +++ b/loggers/logger_test.go @@ -26,6 +26,3 @@ func TestDefaultWriters(t *testing.T) { } } } -func TestWriterPlugins(t *testing.T) { - -} diff --git a/loggers/noop_formater.go b/loggers/noop_formater.go new file mode 100644 index 000000000..cc6e50c8d --- /dev/null +++ b/loggers/noop_formater.go @@ -0,0 +1,23 @@ +// Currently only used with TinyGo +//go:build tinygo +// +build tinygo + +// Copyright 2022 Juan Pablo Tosso +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package loggers + +func noopFormater(al *AuditLog) ([]byte, error) { + return nil, nil +} diff --git a/loggers/noop_writer.go b/loggers/noop_writer.go new file mode 100644 index 000000000..33c59611a --- /dev/null +++ b/loggers/noop_writer.go @@ -0,0 +1,32 @@ +// Currently only used with TinyGo +//go:build tinygo +// +build tinygo + +// Copyright 2022 Juan Pablo Tosso +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package loggers + +import ( + "github.com/corazawaf/coraza/v3/types" +) + +// noopWriter is used to store logs in a single file +type noopWriter struct{} + +func (noopWriter) Init(types.Config) error { return nil } +func (noopWriter) Write(*AuditLog) error { return nil } +func (noopWriter) Close() error { return nil } + +var _ LogWriter = (*noopWriter)(nil) diff --git a/loggers/serial_writer.go b/loggers/serial_writer.go index 555e54f06..af5a18093 100644 --- a/loggers/serial_writer.go +++ b/loggers/serial_writer.go @@ -1,3 +1,6 @@ +//go:build !tinygo +// +build !tinygo + // Copyright 2022 Juan Pablo Tosso // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/loggers/serial_writer_test.go b/loggers/serial_writer_test.go index 0da0ae80c..619aa606d 100644 --- a/loggers/serial_writer_test.go +++ b/loggers/serial_writer_test.go @@ -1,3 +1,6 @@ +//go:build !tinygo +// +build !tinygo + // Copyright 2022 Juan Pablo Tosso // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/operators/detect_sqli.go b/operators/detect_sqli.go index 41d4c5f4b..cfdc82e3d 100644 --- a/operators/detect_sqli.go +++ b/operators/detect_sqli.go @@ -19,8 +19,7 @@ import ( "github.com/corazawaf/libinjection-go" ) -type detectSQLi struct { -} +type detectSQLi struct{} func (o *detectSQLi) Init(options coraza.RuleOperatorOptions) error { return nil diff --git a/operators/inspect_file.go b/operators/inspect_file.go index ee0fb221a..dd462944f 100644 --- a/operators/inspect_file.go +++ b/operators/inspect_file.go @@ -1,3 +1,6 @@ +//go:build !tinygo +// +build !tinygo + // Copyright 2022 Juan Pablo Tosso // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -27,9 +30,7 @@ type inspectFile struct { } func (o *inspectFile) Init(options coraza.RuleOperatorOptions) error { - data := options.Arguments - - o.path = data + o.path = options.Arguments return nil } diff --git a/operators/inspect_file_test.go b/operators/inspect_file_test.go index 078356335..b7ea7bf09 100644 --- a/operators/inspect_file_test.go +++ b/operators/inspect_file_test.go @@ -1,3 +1,6 @@ +//go:build !tinygo +// +build !tinygo + // Copyright 2022 Juan Pablo Tosso // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/operators/inspect_file_tinygo.go b/operators/inspect_file_tinygo.go new file mode 100644 index 000000000..5b13521a8 --- /dev/null +++ b/operators/inspect_file_tinygo.go @@ -0,0 +1,28 @@ +//go:build tinygo +// +build tinygo + +// Copyright 2022 Juan Pablo Tosso +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package operators + +import ( + "github.com/corazawaf/coraza/v3" +) + +type inspectFile struct{} + +func (*inspectFile) Init(coraza.RuleOperatorOptions) error { return nil } + +func (*inspectFile) Evaluate(*coraza.Transaction, string) bool { return true } diff --git a/operators/operators_test.go b/operators/operators_test.go index af1f0ff97..ac088b36a 100644 --- a/operators/operators_test.go +++ b/operators/operators_test.go @@ -16,7 +16,6 @@ package operators import ( "context" - "encoding/json" "io/ioutil" "os" "path/filepath" @@ -25,18 +24,19 @@ import ( "testing" "github.com/corazawaf/coraza/v3" + "github.com/tidwall/gjson" ) type Test struct { - Input string `json:"input"` - Param string `json:"param"` - Name string `json:"name"` - Ret int `json:"ret"` - Type string `json:"type"` + Input string + Param string + Name string + Ret int + Type string } -//https://github.com/SpiderLabs/secrules-language-tests/ -func TestTransformations(t *testing.T) { +// https://github.com/SpiderLabs/secrules-language-tests/ +func TestOperators(t *testing.T) { root := "../testdata/operators/" files := [][]byte{} if _, err := os.Stat(root); os.IsNotExist(err) { @@ -53,28 +53,26 @@ func TestTransformations(t *testing.T) { } waf := coraza.NewWaf() for _, f := range files { - - cases := []*Test{} - err := json.Unmarshal(f, &cases) - if err != nil { - t.Error("Cannot parse test case", err) - } + cases := unmarshalTests(f) for _, data := range cases { // UNMARSHALL does not transform \u0000 to binary data.Input = strings.ReplaceAll(data.Input, `\u0000`, "\u0000") data.Param = strings.ReplaceAll(data.Param, `\u0000`, "\u0000") if strings.Contains(data.Input, `\x`) { - data.Input, err = strconv.Unquote(`"` + data.Input + `"`) + in, err := strconv.Unquote(`"` + data.Input + `"`) if err != nil { t.Error("Cannot parse test case", err) + } else { + data.Input = in } } if strings.Contains(data.Param, `\x`) { - data.Param, err = strconv.Unquote(`"` + data.Param + `"`) + p, err := strconv.Unquote(`"` + data.Param + `"`) if err != nil { t.Error("Cannot parse test case", err) } + data.Param = p } op, err := Get(data.Name) if err != nil { @@ -108,3 +106,29 @@ func TestTransformations(t *testing.T) { } } } + +func unmarshalTests(json []byte) []Test { + var tests []Test + v := gjson.ParseBytes(json).Value() + for _, in := range v.([]interface{}) { + obj := in.(map[string]interface{}) + t := Test{} + if s, ok := obj["input"]; ok { + t.Input = s.(string) + } + if s, ok := obj["param"]; ok { + t.Param = s.(string) + } + if s, ok := obj["name"]; ok { + t.Name = s.(string) + } + if s, ok := obj["ret"]; ok { + t.Ret = int(s.(float64)) + } + if s, ok := obj["type"]; ok { + t.Type = s.(string) + } + tests = append(tests, t) + } + return tests +} diff --git a/operators/rbl.go b/operators/rbl.go index 1aab88029..ea744ba57 100644 --- a/operators/rbl.go +++ b/operators/rbl.go @@ -1,3 +1,6 @@ +//go:build !tinygo +// +build !tinygo + // Copyright 2022 Juan Pablo Tosso // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/operators/rbl_test.go b/operators/rbl_test.go index 3d71677b6..e5aee5e8d 100644 --- a/operators/rbl_test.go +++ b/operators/rbl_test.go @@ -1,3 +1,6 @@ +//go:build !tinygo +// +build !tinygo + // Copyright 2022 Juan Pablo Tosso // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/operators/rbl_tinygo.go b/operators/rbl_tinygo.go new file mode 100644 index 000000000..62e14634d --- /dev/null +++ b/operators/rbl_tinygo.go @@ -0,0 +1,28 @@ +//go:build tinygo +// +build tinygo + +// Copyright 2022 Juan Pablo Tosso +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package operators + +import ( + "github.com/corazawaf/coraza/v3" +) + +type rbl struct{} + +func (o *rbl) Init(_ coraza.RuleOperatorOptions) error { return nil } + +func (o *rbl) Evaluate(_ *coraza.Transaction, _ string) bool { return true } diff --git a/rule.go b/rule.go index e1bfff527..6519d9804 100644 --- a/rule.go +++ b/rule.go @@ -221,7 +221,6 @@ func (r *Rule) Evaluate(tx *Transaction) []types.MatchData { r.matchVariable(tx, md) } else { ecol := tx.ruleRemoveTargetByID[r.ID] - // captured := false for _, v := range r.variables { var values []types.MatchData for _, c := range ecol { @@ -288,13 +287,6 @@ func (r *Rule) Evaluate(tx *Transaction) []types.MatchData { } } } - /* - TODO: For some reason, it seems it's not necessary to reset captures, - it might lead to unexpected behavior in the future - if captured { - defer tx.resetCaptures() - } - */ } if len(matchedValues) == 0 { diff --git a/seclang/directives_log_test.go b/seclang/directives_log_test.go new file mode 100644 index 000000000..bb131dc85 --- /dev/null +++ b/seclang/directives_log_test.go @@ -0,0 +1,115 @@ +// Logs are currently disabled for tinygo + +//go:build !tinygo +// +build !tinygo + +package seclang + +import ( + "encoding/json" + "io/ioutil" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/corazawaf/coraza/v3" + utils "github.com/corazawaf/coraza/v3/internal/strings" + "github.com/corazawaf/coraza/v3/loggers" + "github.com/corazawaf/coraza/v3/types" +) + +func TestSecAuditLogDirectivesConcurrent(t *testing.T) { + waf := coraza.NewWaf() + auditpath := "/tmp/" + parser, _ := NewParser(waf) + if err := parser.FromString(` + SecAuditLog /tmp/audit.log + SecAuditLogFormat json + SecAuditLogDir /tmp + SecAuditLogDirMode 0777 + SecAuditLogFileMode 0777 + SecAuditLogType concurrent + `); err != nil { + t.Error(err) + } + id := utils.SafeRandom(10) + if waf.AuditLogWriter == nil { + t.Error("Invalid audit logger (nil)") + return + } + if err := waf.AuditLogWriter.Write(&loggers.AuditLog{ + Parts: types.AuditLogParts("ABCDEFGHIJKZ"), + Transaction: loggers.AuditTransaction{ + ID: id, + }, + }); err != nil { + t.Error(err) + } + f, err := findFileContaining(auditpath, id) + if err != nil { + t.Error(err) + } + data, err := os.ReadFile(f) + if err != nil { + t.Error(err) + } + if !strings.Contains(string(data), id) { + t.Error("failed to write audit log") + } + // we test it is a valid json + var j map[string]interface{} + if err := json.Unmarshal(data, &j); err != nil { + t.Error(err) + } +} + +func TestDebugDirectives(t *testing.T) { + waf := coraza.NewWaf() + tmp := filepath.Join(t.TempDir(), "tmp.log") + p, _ := NewParser(waf) + err := directiveSecDebugLog(&DirectiveOptions{ + Waf: waf, + Opts: tmp, + }) + if err != nil { + t.Error(err) + } + if err := directiveSecDebugLogLevel(&DirectiveOptions{ + Waf: waf, + Opts: "5", + }); err != nil { + t.Error(err) + } + p.options.Waf.Logger.Info("abc123") + data, err := os.ReadFile(tmp) + if err != nil { + t.Error(err) + } + if !strings.Contains(string(data), "abc123") { + t.Errorf("failed to write info log, got %q", data) + } +} + +// Find a file by name recursively containing some string +func findFileContaining(path string, search string) (string, error) { + files, err := ioutil.ReadDir(path) + if err != nil { + return "", err + } + for _, file := range files { + if file.IsDir() { + fullpath := path + "/" + file.Name() + file, err := findFileContaining(fullpath, search) + if err != nil { + return "", err + } + if file != "" { + return file, nil + } + } else if strings.Contains(file.Name(), search) { + return path + "/" + file.Name(), nil + } + } + return "", nil +} diff --git a/seclang/directives_test.go b/seclang/directives_test.go index 05a1108fe..700e6052f 100644 --- a/seclang/directives_test.go +++ b/seclang/directives_test.go @@ -15,15 +15,9 @@ package seclang import ( - "encoding/json" - "io/ioutil" - "os" - "strings" "testing" "github.com/corazawaf/coraza/v3" - utils "github.com/corazawaf/coraza/v3/internal/strings" - "github.com/corazawaf/coraza/v3/loggers" "github.com/corazawaf/coraza/v3/types" ) @@ -148,83 +142,6 @@ func Test_directive(t *testing.T) { } } -func TestDebugDirectives(t *testing.T) { - waf := coraza.NewWaf() - tmpf, err := ioutil.TempFile("/tmp", "*.log") - if err != nil { - t.Error(err) - } - defer os.Remove(tmpf.Name()) - tmp := tmpf.Name() - p, _ := NewParser(waf) - err = directiveSecDebugLog(&DirectiveOptions{ - Waf: waf, - Opts: tmp, - }) - if err != nil { - t.Error(err) - } - if err := directiveSecDebugLogLevel(&DirectiveOptions{ - Waf: waf, - Opts: "5", - }); err != nil { - t.Error(err) - } - p.options.Waf.Logger.Info("abc123") - data, err := os.ReadFile(tmp) - if err != nil { - t.Error(err) - } - if !strings.Contains(string(data), "abc123") { - t.Errorf("failed to write info log, got %q", data) - } -} - -func TestSecAuditLogDirectivesConcurrent(t *testing.T) { - waf := coraza.NewWaf() - auditpath := "/tmp/" - parser, _ := NewParser(waf) - if err := parser.FromString(` - SecAuditLog /tmp/audit.log - SecAuditLogFormat json - SecAuditLogDir /tmp - SecAuditLogDirMode 0777 - SecAuditLogFileMode 0777 - SecAuditLogType concurrent - `); err != nil { - t.Error(err) - } - id := utils.SafeRandom(10) - if waf.AuditLogWriter == nil { - t.Error("Invalid audit logger (nil)") - return - } - if err := waf.AuditLogWriter.Write(&loggers.AuditLog{ - Parts: types.AuditLogParts("ABCDEFGHIJKZ"), - Transaction: loggers.AuditTransaction{ - ID: id, - }, - }); err != nil { - t.Error(err) - } - f, err := findFileContaining(auditpath, id) - if err != nil { - t.Error(err) - } - data, err := ioutil.ReadFile(f) - if err != nil { - t.Error(err) - } - if !strings.Contains(string(data), id) { - t.Error("failed to write audit log") - } - // we test it is a valid json - var j map[string]interface{} - if err := json.Unmarshal(data, &j); err != nil { - t.Error(err) - } -} - func TestSecRuleUpdateTargetBy(t *testing.T) { waf := coraza.NewWaf() rule, err := ParseRule(RuleOptions{ @@ -250,29 +167,6 @@ func TestSecRuleUpdateTargetBy(t *testing.T) { } -// Find a file by name recursively containing some string -func findFileContaining(path string, search string) (string, error) { - files, err := ioutil.ReadDir(path) - if err != nil { - return "", err - } - for _, file := range files { - if file.IsDir() { - fullpath := path + "/" + file.Name() - file, err := findFileContaining(fullpath, search) - if err != nil { - return "", err - } - if file != "" { - return file, nil - } - } else if strings.Contains(file.Name(), search) { - return path + "/" + file.Name(), nil - } - } - return "", nil -} - func TestInvalidBooleanForDirectives(t *testing.T) { waf := coraza.NewWaf() p, _ := NewParser(waf) diff --git a/seclang/rule_parser.go b/seclang/rule_parser.go index f01d726fa..f18d704f3 100644 --- a/seclang/rule_parser.go +++ b/seclang/rule_parser.go @@ -433,7 +433,7 @@ func getLastRuleExpectingChain(w *coraza.Waf) *coraza.Rule { // parseActions will assign the function name, arguments and // function (pkg.actions) for each action split by comma (,) -// Action arguments are allowed to wrap values between colons('') +// Action arguments are allowed to wrap values between colons(”) func parseActions(actions string) ([]ruleAction, error) { iskey := true ckey := "" diff --git a/seclang/rule_parser_test.go b/seclang/rule_parser_test.go index e27aa050c..13090b173 100644 --- a/seclang/rule_parser_test.go +++ b/seclang/rule_parser_test.go @@ -30,7 +30,7 @@ func TestInvalidRule(t *testing.T) { t.Errorf("unexpected error: %v", err) } - err = p.FromString("SecRule") + err = p.FromString("SecRule ") if err == nil { t.Error("expected malformed rule error") } diff --git a/seclang/rules_http_test.go b/seclang/rules_http_test.go new file mode 100644 index 000000000..28c63d2e2 --- /dev/null +++ b/seclang/rules_http_test.go @@ -0,0 +1,78 @@ +// tinygo does not support net.http so this package is not needed for it +//go:build !tinygo +// +build !tinygo + +package seclang + +import ( + "bufio" + "bytes" + "context" + "net/http" + "strconv" + "strings" + "testing" + + "github.com/corazawaf/coraza/v3" + txhttp "github.com/corazawaf/coraza/v3/http" +) + +// from issue https://github.com/corazawaf/coraza/issues/159 @zpeasystart +func TestDirectiveSecAuditLog(t *testing.T) { + waf := coraza.NewWaf() + p, _ := NewParser(waf) + if err := p.FromString(` + SecRule REQUEST_FILENAME "@unconditionalMatch" "id:100, phase:2, t:none, log, setvar:'tx.count=+1',chain" + SecRule ARGS:username "@unconditionalMatch" "t:none, setvar:'tx.count=+2',chain" + SecRule ARGS:password "@unconditionalMatch" "t:none, setvar:'tx.count=+3'" + `); err != nil { + t.Error(err) + } + tx := waf.NewTransaction(context.Background()) + tx.RequestBodyAccess = true + tx.ForceRequestBodyVariable = true + // request + rdata := []string{ + "POST /login HTTP/1.1", + "Accept: */*", + "Accept-Encoding: gzip, deflate", + "Connection: close", + "Origin: http://test.com", + "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36", + "Content-Type: application/x-www-form-urlencoded; charset=UTF-8", + "Referer: http://somehost.com/login.jsp", + "X-Requested-With: XMLHttpRequest", + "Content-Length: 59", + "Accept-Language: zh-CN,zh;q=0.9", + "", + "username=root&password=123&rememberMe=on&time=1644979180757", + } + data := bytes.NewBuffer([]byte(strings.Join(rdata, "\r\n"))) + req, err := http.ReadRequest(bufio.NewReader(data)) + if err != nil { + t.Errorf("Description HTTP request parsing failed") + } + _, err = txhttp.ProcessRequest(tx, req) + if err != nil { + t.Errorf("Failed to load the HTTP request") + } + // There is no problem loading the rules + c := 0 + r := waf.Rules.FindByID(100) + for r != nil { + c++ + r = r.Chain + } + if c != 3 { + t.Errorf("failed to compile multiple chains, expected 3, got %d", c) + } + // Why is the number of matches 4 + macro, err := coraza.NewMacro("%{tx.count}") + if err != nil { + t.Error(err) + } + c, _ = strconv.Atoi(macro.Expand(tx)) + if c != 6 { + t.Errorf("Why is the number of matches %d", c) + } +} diff --git a/seclang/rules_test.go b/seclang/rules_test.go index 098f6f3ab..d0eeef435 100644 --- a/seclang/rules_test.go +++ b/seclang/rules_test.go @@ -15,18 +15,13 @@ package seclang import ( - "bufio" - "bytes" "context" "io" - "net/http" "regexp" - "strconv" "strings" "testing" "github.com/corazawaf/coraza/v3" - txhttp "github.com/corazawaf/coraza/v3/http" "github.com/corazawaf/coraza/v3/types" ) @@ -360,66 +355,6 @@ func TestTXIssue147(t *testing.T) { } } -// from issue https://github.com/corazawaf/coraza/issues/159 @zpeasystart -func TestDirectiveSecAuditLog(t *testing.T) { - waf := coraza.NewWaf() - p, _ := NewParser(waf) - if err := p.FromString(` - SecRule REQUEST_FILENAME "@unconditionalMatch" "id:100, phase:2, t:none, log, setvar:'tx.count=+1',chain" - SecRule ARGS:username "@unconditionalMatch" "t:none, setvar:'tx.count=+2',chain" - SecRule ARGS:password "@unconditionalMatch" "t:none, setvar:'tx.count=+3'" - `); err != nil { - t.Error(err) - } - tx := waf.NewTransaction(context.Background()) - tx.RequestBodyAccess = true - tx.ForceRequestBodyVariable = true - // request - rdata := []string{ - "POST /login HTTP/1.1", - "Accept: */*", - "Accept-Encoding: gzip, deflate", - "Connection: close", - "Origin: http://test.com", - "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36", - "Content-Type: application/x-www-form-urlencoded; charset=UTF-8", - "Referer: http://somehost.com/login.jsp", - "X-Requested-With: XMLHttpRequest", - "Content-Length: 59", - "Accept-Language: zh-CN,zh;q=0.9", - "", - "username=root&password=123&rememberMe=on&time=1644979180757", - } - data := bytes.NewBuffer([]byte(strings.Join(rdata, "\r\n"))) - req, err := http.ReadRequest(bufio.NewReader(data)) - if err != nil { - t.Errorf("Description HTTP request parsing failed") - } - _, err = txhttp.ProcessRequest(tx, req) - if err != nil { - t.Errorf("Failed to load the HTTP request") - } - // There is no problem loading the rules - c := 0 - r := waf.Rules.FindByID(100) - for r != nil { - c++ - r = r.Chain - } - if c != 3 { - t.Errorf("failed to compile multiple chains, expected 3, got %d", c) - } - // Why is the number of matches 4 - macro, err := coraza.NewMacro("%{tx.count}") - if err != nil { - t.Error(err) - } - c, _ = strconv.Atoi(macro.Expand(tx)) - if c != 6 { - t.Errorf("Why is the number of matches %d", c) - } -} - func TestIssue176(t *testing.T) { waf := coraza.NewWaf() rules := `SecRule REQUEST_COOKIES:sessionId "test" "id:1,phase:1,deny,log,msg:'test rule',logdata:'Matched %{MATCHED_VAR_NAME}'"` diff --git a/testing/auditlog_test.go b/testing/auditlog_test.go index b3dd665c7..27297ff98 100644 --- a/testing/auditlog_test.go +++ b/testing/auditlog_test.go @@ -1,3 +1,8 @@ +// Audit logs are currently disabled for tinygo + +//go:build !tinygo +// +build !tinygo + // Copyright 2022 Juan Pablo Tosso // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/testing/coraza_test.go b/testing/coraza_test.go index e27339ad2..bf38af04b 100644 --- a/testing/coraza_test.go +++ b/testing/coraza_test.go @@ -28,7 +28,7 @@ func TestEngine(t *testing.T) { if len(profile.Profiles) == 0 { t.Error("failed to find tests") } - fmt.Printf("Loading %d profiles\n", len(profile.Profiles)) + t.Logf("Loading %d profiles\n", len(profile.Profiles)) for _, p := range profile.Profiles { t.Run(p.Meta.Name, func(t *testing.T) { tt, err := testList(&p, nil) @@ -37,7 +37,6 @@ func TestEngine(t *testing.T) { } for _, test := range tt { testname := p.Tests[0].Title - t.Run(testname, func(t *testing.T) { if err := test.RunPhases(); err != nil { t.Errorf("%s, ERROR: %s", test.Name, err) diff --git a/testing/coreruleset_test.go b/testing/coreruleset_test.go index c63988c06..41b9ceebc 100644 --- a/testing/coreruleset_test.go +++ b/testing/coreruleset_test.go @@ -1,3 +1,7 @@ +// These benchmarks don't currently compile with TinyGo +//go:build !tinygo +// +build !tinygo + // Copyright 2022 Juan Pablo Tosso // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/testing/engine.go b/testing/engine.go index dd431fb55..81d854417 100644 --- a/testing/engine.go +++ b/testing/engine.go @@ -18,7 +18,6 @@ import ( "context" b64 "encoding/base64" "fmt" - "reflect" "strconv" "strings" @@ -177,16 +176,20 @@ func (t *Test) RunPhases() error { t.transaction.AddRequestHeader(k, v) } t.transaction.ProcessRequestHeaders() + if _, err := t.transaction.ProcessRequestBody(); err != nil { return err } for k, v := range t.ResponseHeaders { t.transaction.AddResponseHeader(k, v) } + t.transaction.ProcessResponseHeaders(t.ResponseCode, t.ResponseProtocol) + if _, err := t.transaction.ProcessResponseBody(); err != nil { return err } + t.transaction.ProcessLogging() return nil } @@ -307,15 +310,14 @@ func NewTest(name string, waf *coraza.Waf) *Test { func bodyToString(iface interface{}) string { data := "" - v := reflect.ValueOf(iface) - switch v.Kind() { - case reflect.Slice: - for i := 0; i < v.Len(); i++ { - data += fmt.Sprintf("%s\r\n", v.Index(i)) + switch v := iface.(type) { + case []string: + for i := 0; i < len(v); i++ { + data += fmt.Sprintf("%s\r\n", v[i]) } data += "\r\n" - case reflect.String: - data = iface.(string) + case string: + data = v default: panic("Error: bodyToString() only accepts slices and strings") } diff --git a/testing/profile/profile.go b/testing/profile/profile.go index 10a04d3f5..94a5bcee5 100644 --- a/testing/profile/profile.go +++ b/testing/profile/profile.go @@ -29,7 +29,7 @@ type StageInput struct { Method string `yaml:"method,omitempty"` URI string `yaml:"uri,omitempty"` Version string `yaml:"version,omitempty"` - Data interface{} `yaml:"data,omitempty"` // Accepts array or string + Data string `yaml:"data,omitempty"` // Accepts array or string Headers map[string]string `yaml:"headers,omitempty"` RawRequest []byte `yaml:"raw_request,omitempty"` EncodedRequest string `yaml:"encoded_request,omitempty"` diff --git a/transaction.go b/transaction.go index 0a99fa83e..3c5e254ef 100644 --- a/transaction.go +++ b/transaction.go @@ -457,8 +457,10 @@ func (tx *Transaction) AddArgument(orig string, key string, value string) { // expected to be executed prior to the virtual host resolution, when the // connection arrives on the server. // note: There is no direct connection between this function and any phase of -// the SecLanguages phases. It is something that may occur between the -// SecLanguage phase 1 and 2. +// +// the SecLanguages phases. It is something that may occur between the +// SecLanguage phase 1 and 2. +// // note: This function won't add GET arguments, they must be added with AddArgument func (tx *Transaction) ProcessURI(uri string, method string, httpVersion string) { tx.Variables.RequestMethod.Set(method) @@ -611,7 +613,6 @@ func (tx *Transaction) ProcessRequestBody() (*types.Interruption, error) { // that the headers should be added prior to the execution of this function. // // note: Remember to check for a possible intervention. -// func (tx *Transaction) ProcessResponseHeaders(code int, proto string) *types.Interruption { c := strconv.Itoa(code) tx.Variables.ResponseStatus.Set(c) diff --git a/transformations/transformations_test.go b/transformations/transformations_test.go index 186110d12..6deab7c06 100644 --- a/transformations/transformations_test.go +++ b/transformations/transformations_test.go @@ -15,24 +15,25 @@ package transformations import ( - "encoding/json" "io/ioutil" "os" "path/filepath" "strconv" "strings" "testing" + + "github.com/tidwall/gjson" ) type Test struct { - Input string `json:"input"` - Output string `json:"output"` - Name string `json:"name"` - Ret int `json:"ret"` - Type string `json:"type"` + Input string + Output string + Name string + Ret int + Type string } -//https://github.com/SpiderLabs/secrules-language-tests/ +// https://github.com/SpiderLabs/secrules-language-tests/ func TestTransformations(t *testing.T) { root := "../testdata/transformations/" files := [][]byte{} @@ -49,12 +50,7 @@ func TestTransformations(t *testing.T) { t.Error("Error walking files") } for _, f := range files { - - cases := []*Test{} - err := json.Unmarshal(f, &cases) - if err != nil { - t.Error("Cannot parse test case") - } + cases := unmarshalTests(f) for _, data := range cases { // UNMARSHALL does not transform \u0000 to binary data.Input = strings.ReplaceAll(data.Input, `\u0000`, "\u0000") @@ -91,3 +87,29 @@ func TestTransformationsAreCaseInsensitive(t *testing.T) { t.Error(err) } } + +func unmarshalTests(json []byte) []Test { + var tests []Test + v := gjson.ParseBytes(json).Value() + for _, in := range v.([]interface{}) { + obj := in.(map[string]interface{}) + t := Test{} + if s, ok := obj["input"]; ok { + t.Input = s.(string) + } + if s, ok := obj["output"]; ok { + t.Output = s.(string) + } + if s, ok := obj["name"]; ok { + t.Name = s.(string) + } + if s, ok := obj["ret"]; ok { + t.Ret = int(s.(float64)) + } + if s, ok := obj["type"]; ok { + t.Type = s.(string) + } + tests = append(tests, t) + } + return tests +} diff --git a/types/variables/variables_test.go b/types/variables/variables_test.go index d0bf99e00..5e87cd3fc 100644 --- a/types/variables/variables_test.go +++ b/types/variables/variables_test.go @@ -4,7 +4,7 @@ // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, diff --git a/types/waf.go b/types/waf.go index 0340673ac..c20966ba6 100644 --- a/types/waf.go +++ b/types/waf.go @@ -179,9 +179,10 @@ const ( // Interruption is used to notify the Coraza implementation // that the transaction must be disrupted, for example: -// if it := tx.Interruption; it != nil { -// return show403() -//} +// +// if it := tx.Interruption; it != nil { +// return show403() +// } type Interruption struct { // Rule that caused the interruption RuleID int