diff --git a/.github/workflows/close-issues.yml b/.github/workflows/close-issues.yml index f8eef9dc6..ade1c5480 100644 --- a/.github/workflows/close-issues.yml +++ b/.github/workflows/close-issues.yml @@ -10,7 +10,7 @@ jobs: issues: write pull-requests: write steps: - - uses: actions/stale@v3 + - uses: actions/stale@v9 with: days-before-issue-stale: 30 days-before-issue-close: 14 diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 06d21f0f9..0648fd0ab 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -10,15 +10,15 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: go - name: Autobuild - uses: github/codeql-action/autobuild@v2 + uses: github/codeql-action/autobuild@v3 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/fix-dependabot.yml b/.github/workflows/fix-dependabot.yml index 7933a27e0..11f9154b7 100644 --- a/.github/workflows/fix-dependabot.yml +++ b/.github/workflows/fix-dependabot.yml @@ -9,9 +9,9 @@ jobs: runs-on: ubuntu-latest if: ${{ github.actor == 'dependabot[bot]'}} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install Go - uses: actions/setup-go@v3 + uses: actions/setup-go@v5 with: go-version: v1.19.x cache: true diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml index cf58eed17..036aa930b 100644 --- a/.github/workflows/fuzz.yml +++ b/.github/workflows/fuzz.yml @@ -11,8 +11,8 @@ jobs: name: Fuzz tests runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/setup-go@v1 + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 with: go-version: '>=1.19.0' - run: go run mage.go fuzz diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 09a77a785..96eb95a6c 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -18,9 +18,9 @@ jobs: lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install Go - uses: actions/setup-go@v3 + uses: actions/setup-go@v5 with: go-version: v1.19.x cache: true diff --git a/.github/workflows/regression.yml b/.github/workflows/regression.yml index 07bdd84cc..41fa5d382 100644 --- a/.github/workflows/regression.yml +++ b/.github/workflows/regression.yml @@ -21,40 +21,40 @@ jobs: runs-on: ${{ matrix.os }} steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install Go - uses: actions/setup-go@v3 + uses: actions/setup-go@v5 with: go-version: ${{ matrix.go-version }} cache: true - name: Tests and coverage run: go run mage.go coverage - name: "Codecov: General" - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 if: ${{ matrix.go-version == '1.19.x' }} with: files: build/coverage.txt flags: default - name: "Codecov: Examples" - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 if: ${{ matrix.go-version == '1.19.x' }} with: files: build/coverage-examples.txt flags: examples - name: "Codecov: FTW" - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 if: ${{ matrix.go-version == '1.19.x' }} with: files: build/coverage-ftw.txt flags: ftw - name: "Codecov: FTW Multiphase tag" - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 if: ${{ matrix.go-version == '1.19.x' }} with: files: build/coverage-ftw-multiphase.txt flags: ftw-multiphase - name: "Codecov: Tinygo" - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 if: ${{ matrix.go-version == '1.19.x' }} with: files: build/coverage-tinygo.txt diff --git a/.github/workflows/tinygo.yml b/.github/workflows/tinygo.yml index b714ce1b2..709cc5b8f 100644 --- a/.github/workflows/tinygo.yml +++ b/.github/workflows/tinygo.yml @@ -23,16 +23,16 @@ jobs: runs-on: ${{ matrix.os }} steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install Go - uses: actions/setup-go@v3 + uses: actions/setup-go@v5 with: go-version: ${{ matrix.go-version }} cache: true - name: setup tinygo - uses: acifani/setup-tinygo@v1 + uses: acifani/setup-tinygo@v2 with: tinygo-version: 0.27.0 diff --git a/.gitignore b/.gitignore index 5bd131a25..09d9a69ba 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,5 @@ coraza-waf __debug_bin build/ + +go.work.sum diff --git a/README.md b/README.md index 6bd87a292..33d314993 100644 --- a/README.md +++ b/README.md @@ -58,32 +58,34 @@ Coraza can be used as a library for your Go program to implement a security midd package main import ( - "fmt" - "github.com/corazawaf/coraza/v3" + "fmt" + + "github.com/corazawaf/coraza/v3" ) func main() { - // First we initialize our waf and our seclang parser - waf, err := coraza.NewWAF(coraza.NewWAFConfig(). - WithDirectives(`SecRule REMOTE_ADDR "@rx .*" "id:1,phase:1,deny,status:403"`)) - // Now we parse our rules - if err != nil { - fmt.Println(err) - } - - // Then we create a transaction and assign some variables - tx := waf.NewTransaction() - defer func() { - tx.ProcessLogging() - tx.Close() - }() - tx.ProcessConnection("127.0.0.1", 8080, "127.0.0.1", 12345) - - // Finally we process the request headers phase, which may return an interruption - if it := tx.ProcessRequestHeaders(); it != nil { - fmt.Printf("Transaction was interrupted with status %d\n", it.Status) - } + // First we initialize our waf and our seclang parser + waf, err := coraza.NewWAF(coraza.NewWAFConfig(). + WithDirectives(`SecRule REMOTE_ADDR "@rx .*" "id:1,phase:1,deny,status:403"`)) + // Now we parse our rules + if err != nil { + fmt.Println(err) + } + + // Then we create a transaction and assign some variables + tx := waf.NewTransaction() + defer func() { + tx.ProcessLogging() + tx.Close() + }() + tx.ProcessConnection("127.0.0.1", 8080, "127.0.0.1", 12345) + + // Finally we process the request headers phase, which may return an interruption + if it := tx.ProcessRequestHeaders(); it != nil { + fmt.Printf("Transaction was interrupted with status %d\n", it.Status) + } } + ``` [Examples/http-server](./examples/http-server/) provides an example to practice with Coraza. @@ -132,8 +134,8 @@ Coraza only requires Go for development. You can run `mage.go` to issue developm See the list of commands -```shell -go run mage.go -l +``` +$ go run mage.go -l Targets: check runs lint and tests. coverage runs tests with coverage and race detector enabled. @@ -163,8 +165,8 @@ Our vulnerability management team will respond within 3 working days of your rep ## Thanks -* Modsecurity team for creating ModSecurity * OWASP Coreruleset team for the CRS and their help +* Ivan Ristić for creating ModSecurity ### Coraza on Twitter diff --git a/coraza.conf-recommended b/coraza.conf-recommended index 3b9d8a741..532817ca9 100644 --- a/coraza.conf-recommended +++ b/coraza.conf-recommended @@ -45,7 +45,8 @@ SecRequestBodyLimit 13107200 SecRequestBodyInMemoryLimit 131072 -SecRequestBodyNoFilesLimit 131072 +# SecRequestBodyNoFilesLimit is currently not supported by Coraza +# SecRequestBodyNoFilesLimit 131072 # What to do if the request body size is above our configured limit. # Keep in mind that this setting will automatically be set to ProcessPartial diff --git a/experimental/waf.go b/experimental/waf.go new file mode 100644 index 000000000..bc167a2a9 --- /dev/null +++ b/experimental/waf.go @@ -0,0 +1,17 @@ +// Copyright 2024 Juan Pablo Tosso and the OWASP Coraza contributors +// SPDX-License-Identifier: Apache-2.0 + +package experimental + +import ( + "github.com/corazawaf/coraza/v3/internal/corazawaf" + "github.com/corazawaf/coraza/v3/types" +) + +type Options = corazawaf.Options + +// WAFWithOptions is an interface that allows to create transactions +// with options +type WAFWithOptions interface { + NewTransactionWithOptions(Options) types.Transaction +} diff --git a/experimental/waf_test.go b/experimental/waf_test.go new file mode 100644 index 000000000..6d97c22fc --- /dev/null +++ b/experimental/waf_test.go @@ -0,0 +1,32 @@ +// Copyright 2024 Juan Pablo Tosso and the OWASP Coraza contributors +// SPDX-License-Identifier: Apache-2.0 + +package experimental_test + +import ( + "fmt" + + "github.com/corazawaf/coraza/v3" + "github.com/corazawaf/coraza/v3/experimental" +) + +func ExampleWAFWithOptions_NewTransactionWithOptions() { + waf, err := coraza.NewWAF(coraza.NewWAFConfig()) + if err != nil { + panic(err) + } + + oWAF, ok := waf.(experimental.WAFWithOptions) + if !ok { + panic("WAF does not implement WAFWithOptions") + } + + tx := oWAF.NewTransactionWithOptions(experimental.Options{ + ID: "abc123", + }) + + fmt.Println("Transaction ID:", tx.ID()) + + // Output: + // Transaction ID: abc123 +} diff --git a/go.mod b/go.mod index 5777aa907..072adcf18 100644 --- a/go.mod +++ b/go.mod @@ -17,14 +17,14 @@ go 1.19 require ( github.com/anuraaga/go-modsecurity v0.0.0-20220824035035-b9a4099778df - github.com/corazawaf/libinjection-go v0.1.2 + github.com/corazawaf/libinjection-go v0.1.3 github.com/foxcpp/go-mockdns v1.0.0 github.com/magefile/mage v1.15.0 - github.com/mccutchen/go-httpbin/v2 v2.9.0 + github.com/mccutchen/go-httpbin/v2 v2.13.2 github.com/petar-dambovaliev/aho-corasick v0.0.0-20230725210150-fb29fc3c913e github.com/tidwall/gjson v1.17.0 - golang.org/x/net v0.18.0 - golang.org/x/sync v0.4.0 + golang.org/x/net v0.21.0 + golang.org/x/sync v0.6.0 rsc.io/binaryregexp v0.2.0 ) @@ -32,7 +32,7 @@ require ( github.com/miekg/dns v1.1.50 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.1 // indirect - golang.org/x/mod v0.8.0 // indirect - golang.org/x/sys v0.14.0 // indirect - golang.org/x/tools v0.6.0 // indirect + golang.org/x/mod v0.11.0 // indirect + golang.org/x/sys v0.17.0 // indirect + golang.org/x/tools v0.10.0 // indirect ) diff --git a/go.sum b/go.sum index 8e677b549..8f4b8640c 100644 --- a/go.sum +++ b/go.sum @@ -1,13 +1,13 @@ github.com/anuraaga/go-modsecurity v0.0.0-20220824035035-b9a4099778df h1:YWiVl53v0R8Knj/k+4slO0SXPL67Y4dXWiOIWNzrkew= github.com/anuraaga/go-modsecurity v0.0.0-20220824035035-b9a4099778df/go.mod h1:7jguE759ADzy2EkxGRXigiC0ER1Yq2IFk2qNtwgzc7U= -github.com/corazawaf/libinjection-go v0.1.2 h1:oeiV9pc5rvJ+2oqOqXEAMJousPpGiup6f7Y3nZj5GoM= -github.com/corazawaf/libinjection-go v0.1.2/go.mod h1:OP4TM7xdJ2skyXqNX1AN1wN5nNZEmJNuWbNPOItn7aw= +github.com/corazawaf/libinjection-go v0.1.3 h1:PUplAYho1BBl0tIVbhDsNRuVGIeUYSiCEc9oQpb2rJU= +github.com/corazawaf/libinjection-go v0.1.3/go.mod h1:OP4TM7xdJ2skyXqNX1AN1wN5nNZEmJNuWbNPOItn7aw= github.com/foxcpp/go-mockdns v1.0.0 h1:7jBqxd3WDWwi/6WhDvacvH1XsN3rOLXyHM1uhvIx6FI= github.com/foxcpp/go-mockdns v1.0.0/go.mod h1:lgRN6+KxQBawyIghpnl5CezHFGS9VLzvtVlwxvzXTQ4= github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg= github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= -github.com/mccutchen/go-httpbin/v2 v2.9.0 h1:0c8loz/kMEdBmcHJZh0MUgKX84U19AlLk7h6nf2Wkx4= -github.com/mccutchen/go-httpbin/v2 v2.9.0/go.mod h1:+DBHcmg6EOeoizuiOI8iL12VIHXx+9YQNlz+gjB9uxk= +github.com/mccutchen/go-httpbin/v2 v2.13.2 h1:Cx5wpyP4T81d9EJKL/H4927sG7abE1psdLa2H6T/V94= +github.com/mccutchen/go-httpbin/v2 v2.13.2/go.mod h1:f4DUXYlU6yH0V81O4lJIwqpmYdTXXmYwzxMnYEimFPk= github.com/miekg/dns v1.1.25/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA= github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= @@ -25,19 +25,19 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU= +golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= -golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= +golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= -golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -47,8 +47,8 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= -golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= @@ -58,8 +58,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.10.0 h1:tvDr/iQoUqNdohiYm0LmmKcBk+q86lb9EprIUFhHHGg= +golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/go.work.sum b/go.work.sum deleted file mode 100644 index f1cc8c310..000000000 --- a/go.work.sum +++ /dev/null @@ -1,12 +0,0 @@ -github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/pelletier/go-toml v1.9.1/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA= -golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= -golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= diff --git a/http/interceptor.go b/http/interceptor.go index 6b14aead3..2f3352e97 100644 --- a/http/interceptor.go +++ b/http/interceptor.go @@ -108,7 +108,23 @@ func (i *rwInterceptor) Header() http.Header { return i.w.Header() } -var _ http.ResponseWriter = (*rwInterceptor)(nil) +func (i *rwInterceptor) ReadFrom(r io.Reader) (n int64, err error) { + return io.Copy(struct{ io.Writer }{i}, r) +} + +func (i *rwInterceptor) Flush() { + if !i.wroteHeader { + i.WriteHeader(http.StatusOK) + } +} + +type responseWriter interface { + http.ResponseWriter + io.ReaderFrom + http.Flusher +} + +var _ responseWriter = (*rwInterceptor)(nil) // wrap wraps the interceptor into a response writer that also preserves // the http interfaces implemented by the original response writer to avoid @@ -168,110 +184,28 @@ func wrap(w http.ResponseWriter, r *http.Request, tx types.Transaction) ( var ( hijacker, isHijacker = i.w.(http.Hijacker) pusher, isPusher = i.w.(http.Pusher) - flusher, isFlusher = i.w.(http.Flusher) - reader, isReader = i.w.(io.ReaderFrom) ) switch { - case !isHijacker && !isPusher && !isFlusher && !isReader: + case !isHijacker && isPusher: return struct { - http.ResponseWriter - }{i}, responseProcessor - case !isHijacker && !isPusher && !isFlusher && isReader: - return struct { - http.ResponseWriter - io.ReaderFrom - }{i, reader}, responseProcessor - case !isHijacker && !isPusher && isFlusher && !isReader: - return struct { - http.ResponseWriter - http.Flusher - }{i, flusher}, responseProcessor - case !isHijacker && !isPusher && isFlusher && isReader: - return struct { - http.ResponseWriter - http.Flusher - io.ReaderFrom - }{i, flusher, reader}, responseProcessor - case !isHijacker && isPusher && !isFlusher && !isReader: - return struct { - http.ResponseWriter + responseWriter http.Pusher }{i, pusher}, responseProcessor - case !isHijacker && isPusher && !isFlusher && isReader: - return struct { - http.ResponseWriter - http.Pusher - io.ReaderFrom - }{i, pusher, reader}, responseProcessor - case !isHijacker && isPusher && isFlusher && !isReader: - return struct { - http.ResponseWriter - http.Pusher - http.Flusher - }{i, pusher, flusher}, responseProcessor - case !isHijacker && isPusher && isFlusher && isReader: + case isHijacker && !isPusher: return struct { - http.ResponseWriter - http.Pusher - http.Flusher - io.ReaderFrom - }{i, pusher, flusher, reader}, responseProcessor - case isHijacker && !isPusher && !isFlusher && !isReader: - return struct { - http.ResponseWriter + responseWriter http.Hijacker }{i, hijacker}, responseProcessor - case isHijacker && !isPusher && !isFlusher && isReader: + case isHijacker && isPusher: return struct { - http.ResponseWriter - http.Hijacker - io.ReaderFrom - }{i, hijacker, reader}, responseProcessor - case isHijacker && !isPusher && isFlusher && !isReader: - return struct { - http.ResponseWriter - http.Hijacker - http.Flusher - }{i, hijacker, flusher}, responseProcessor - case isHijacker && !isPusher && isFlusher && isReader: - return struct { - http.ResponseWriter - http.Hijacker - http.Flusher - io.ReaderFrom - }{i, hijacker, flusher, reader}, responseProcessor - case isHijacker && isPusher && !isFlusher && !isReader: - return struct { - http.ResponseWriter + responseWriter http.Hijacker http.Pusher }{i, hijacker, pusher}, responseProcessor - case isHijacker && isPusher && !isFlusher && isReader: - return struct { - http.ResponseWriter - http.Hijacker - http.Pusher - io.ReaderFrom - }{i, hijacker, pusher, reader}, responseProcessor - case isHijacker && isPusher && isFlusher && !isReader: - return struct { - http.ResponseWriter - http.Hijacker - http.Pusher - http.Flusher - }{i, hijacker, pusher, flusher}, responseProcessor - case isHijacker && isPusher && isFlusher && isReader: - return struct { - http.ResponseWriter - http.Hijacker - http.Pusher - http.Flusher - io.ReaderFrom - }{i, hijacker, pusher, flusher, reader}, responseProcessor default: return struct { - http.ResponseWriter + responseWriter }{i}, responseProcessor } } diff --git a/http/interceptor_test.go b/http/interceptor_test.go index e8424705b..d232996bf 100644 --- a/http/interceptor_test.go +++ b/http/interceptor_test.go @@ -8,6 +8,10 @@ package http import ( + "bufio" + "bytes" + "io" + "net" "net/http" "net/http/httptest" "testing" @@ -44,3 +48,286 @@ func TestWriteHeader(t *testing.T) { t.Errorf("unexpected status code, want %d, have %d", want, have) } } + +func TestWrite(t *testing.T) { + waf, err := coraza.NewWAF(coraza.NewWAFConfig()) + if err != nil { + t.Fatal(err) + } + + tx := waf.NewTransaction() + req, _ := http.NewRequest("GET", "", nil) + res := httptest.NewRecorder() + + rw, responseProcessor := wrap(res, req, tx) + _, err = rw.Write([]byte("hello")) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + _, err = rw.Write([]byte("world")) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + err = responseProcessor(tx, req) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + if want, have := 200, res.Code; want != have { + t.Errorf("unexpected status code, want %d, have %d", want, have) + } +} + +func TestWriteWithWriteHeader(t *testing.T) { + waf, err := coraza.NewWAF(coraza.NewWAFConfig()) + if err != nil { + t.Fatal(err) + } + + tx := waf.NewTransaction() + req, _ := http.NewRequest("GET", "", nil) + res := httptest.NewRecorder() + + rw, responseProcessor := wrap(res, req, tx) + rw.WriteHeader(204) + // although we called WriteHeader, status code should be applied until + // responseProcessor is called. + if unwanted, have := 204, res.Code; unwanted == have { + t.Errorf("unexpected status code %d", have) + } + + _, err = rw.Write([]byte("hello")) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + _, err = rw.Write([]byte("world")) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + err = responseProcessor(tx, req) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + if want, have := 204, res.Code; want != have { + t.Errorf("unexpected status code, want %d, have %d", want, have) + } +} + +func TestFlush(t *testing.T) { + waf, err := coraza.NewWAF(coraza.NewWAFConfig()) + if err != nil { + t.Fatal(err) + } + + t.Run("WriteHeader before Flush", func(t *testing.T) { + tx := waf.NewTransaction() + req, _ := http.NewRequest("GET", "", nil) + res := httptest.NewRecorder() + rw, responseProcessor := wrap(res, req, tx) + rw.WriteHeader(204) + rw.(http.Flusher).Flush() + // although we called WriteHeader, status code should be applied until + // responseProcessor is called. + if unwanted, have := 204, res.Code; unwanted == have { + t.Errorf("unexpected status code %d", have) + } + + err = responseProcessor(tx, req) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + if want, have := 204, res.Code; want != have { + t.Errorf("unexpected status code, want %d, have %d", want, have) + } + }) + + t.Run("Flush before WriteHeader", func(t *testing.T) { + tx := waf.NewTransaction() + req, _ := http.NewRequest("GET", "", nil) + res := httptest.NewRecorder() + rw, responseProcessor := wrap(res, req, tx) + rw.(http.Flusher).Flush() + rw.WriteHeader(204) + + if want, have := 200, res.Code; want != have { + t.Errorf("unexpected status code, want %d, have %d", want, have) + } + + err = responseProcessor(tx, req) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + if want, have := 200, res.Code; want != have { + t.Errorf("unexpected status code, want %d, have %d", want, have) + } + }) +} + +type testReaderFrom struct { + io.Writer +} + +func (x *testReaderFrom) ReadFrom(r io.Reader) (n int64, err error) { + return io.Copy(x, r) +} + +func TestReadFrom(t *testing.T) { + waf, err := coraza.NewWAF(coraza.NewWAFConfig()) + if err != nil { + t.Fatal(err) + } + + tx := waf.NewTransaction() + req, _ := http.NewRequest("GET", "", nil) + res := httptest.NewRecorder() + + type responseWriter interface { + http.ResponseWriter + http.Flusher + } + + resWithReaderFrom := struct { + responseWriter + io.ReaderFrom + }{ + res, + &testReaderFrom{res}, + } + + rw, responseProcessor := wrap(resWithReaderFrom, req, tx) + rw.WriteHeader(204) + // although we called WriteHeader, status code should be applied until + // responseProcessor is called. + if unwanted, have := 204, res.Code; unwanted == have { + t.Errorf("unexpected status code %d", have) + } + + _, err = rw.(io.ReaderFrom).ReadFrom(bytes.NewBuffer([]byte("hello world"))) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + _, err = rw.(io.ReaderFrom).ReadFrom(struct{ io.Reader }{bytes.NewBuffer([]byte("hello world"))}) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + err = responseProcessor(tx, req) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + if want, have := 204, res.Code; want != have { + t.Errorf("unexpected status code, want %d, have %d", want, have) + } +} + +type testPusher struct{} + +func (x *testPusher) Push(string, *http.PushOptions) error { + return nil +} + +type testHijacker struct{} + +func (x *testHijacker) Hijack() (net.Conn, *bufio.ReadWriter, error) { + return nil, nil, nil +} + +func TestInterface(t *testing.T) { + waf, err := coraza.NewWAF(coraza.NewWAFConfig()) + if err != nil { + t.Fatal(err) + } + + tx := waf.NewTransaction() + req, _ := http.NewRequest("GET", "", nil) + res := httptest.NewRecorder() + + t.Run("default", func(t *testing.T) { + rw, _ := wrap(struct { + http.ResponseWriter + }{ + res, + }, req, tx) + + _, ok := rw.(http.Pusher) + if ok { + t.Errorf("expected the wrapped ResponseWriter to not implement http.Pusher") + } + + _, ok = rw.(http.Hijacker) + if ok { + t.Errorf("expected the wrapped ResponseWriter to not implement http.Hijacker") + } + }) + + t.Run("http.Pusher", func(t *testing.T) { + rw, _ := wrap(struct { + http.ResponseWriter + http.Pusher + }{ + res, + &testPusher{}, + }, req, tx) + + _, ok := rw.(http.Pusher) + if !ok { + t.Errorf("expected the wrapped ResponseWriter to implement http.Pusher") + } + + _, ok = rw.(http.Hijacker) + if ok { + t.Errorf("expected the wrapped ResponseWriter to not implement http.Hijacker") + } + }) + + t.Run("http.Hijacker", func(t *testing.T) { + rw, _ := wrap(struct { + http.ResponseWriter + http.Hijacker + }{ + res, + &testHijacker{}, + }, req, tx) + + _, ok := rw.(http.Hijacker) + if !ok { + t.Errorf("expected the wrapped ResponseWriter to implement http.Hijacker") + } + + _, ok = rw.(http.Pusher) + if ok { + t.Errorf("expected the wrapped ResponseWriter to not implement http.Pusher") + } + }) + + t.Run("http.Hijacker and http.Pusher", func(t *testing.T) { + rw, _ := wrap(struct { + http.ResponseWriter + http.Hijacker + http.Pusher + }{ + res, + &testHijacker{}, + &testPusher{}, + }, req, tx) + + _, ok := rw.(http.Hijacker) + if !ok { + t.Errorf("expected the wrapped ResponseWriter to implement http.Hijacker") + } + + _, ok = rw.(http.Pusher) + if !ok { + t.Errorf("expected the wrapped ResponseWriter to implement http.Pusher") + } + }) +} diff --git a/http/middleware.go b/http/middleware.go index 666f56399..f8c4477ee 100644 --- a/http/middleware.go +++ b/http/middleware.go @@ -15,6 +15,7 @@ import ( "strings" "github.com/corazawaf/coraza/v3" + "github.com/corazawaf/coraza/v3/experimental" "github.com/corazawaf/coraza/v3/types" ) @@ -117,8 +118,20 @@ func WrapHandler(waf coraza.WAF, h http.Handler) http.Handler { return h } + newTX := func(*http.Request) types.Transaction { + return waf.NewTransaction() + } + + if ctxwaf, ok := waf.(experimental.WAFWithOptions); ok { + newTX = func(r *http.Request) types.Transaction { + return ctxwaf.NewTransactionWithOptions(experimental.Options{ + Context: r.Context(), + }) + } + } + fn := func(w http.ResponseWriter, r *http.Request) { - tx := waf.NewTransaction() + tx := newTX(r) defer func() { // We run phase 5 rules and create audit logs (if enabled) tx.ProcessLogging() diff --git a/http/middleware_test.go b/http/middleware_test.go index 404dfaa2d..d1bc2cf08 100644 --- a/http/middleware_test.go +++ b/http/middleware_test.go @@ -101,8 +101,7 @@ SecRule &REQUEST_HEADERS:Transfer-Encoding "!@eq 0" "id:1,phase:1,deny" } if it == nil { t.Fatal("Expected interruption") - } - if it.RuleID != 1 { + } else if it.RuleID != 1 { t.Fatalf("Expected rule 1 to be triggered, got rule %d", it.RuleID) } if err := tx.Close(); err != nil { diff --git a/internal/auditlog/concurrent_writer.go b/internal/auditlog/concurrent_writer.go index fab2382cc..ab6d4bcc0 100644 --- a/internal/auditlog/concurrent_writer.go +++ b/internal/auditlog/concurrent_writer.go @@ -55,6 +55,15 @@ func (cl concurrentWriter) Write(al plugintypes.AuditLog) error { return nil } + formattedAL, err := cl.formatter.Format(al) + if err != nil { + return err + } + + if len(formattedAL) == 0 { + return nil + } + // 192.168.3.130 192.168.3.1 - - [22/Aug/2009:13:24:20 +0100] "GET / HTTP/1.1" 200 56 "-" "-" SojdH8AAQEAAAugAQAAAAAA "-" /20090822/20090822-1324/20090822-132420-SojdH8AAQEAAAugAQAAAAAA 0 1248 t := time.Unix(0, al.Transaction().UnixTimestamp()) @@ -67,11 +76,6 @@ func (cl concurrentWriter) Write(al plugintypes.AuditLog) error { return err } - formattedAL, err := cl.formatter.Format(al) - if err != nil { - return err - } - filepath := path.Join(logdir, filename) if err = os.WriteFile(filepath, formattedAL, cl.logFileMode); err != nil { return err diff --git a/internal/auditlog/concurrent_writer_test.go b/internal/auditlog/concurrent_writer_test.go index 1cbde6613..f3511bec5 100644 --- a/internal/auditlog/concurrent_writer_test.go +++ b/internal/auditlog/concurrent_writer_test.go @@ -8,6 +8,7 @@ package auditlog import ( "encoding/json" + "errors" "fmt" "io/fs" "os" @@ -46,7 +47,51 @@ func TestConcurrentWriterFailsOnInit(t *testing.T) { } } -func TestConcurrentWriterWrites(t *testing.T) { +type mockFormatter struct { + plugintypes.AuditLogFormatter + formatted []byte + err error +} + +func (ef mockFormatter) Format(plugintypes.AuditLog) ([]byte, error) { + return ef.formatted, ef.err +} + +func TestConcurrentWriter(t *testing.T) { + t.Run("empty formatted", func(t *testing.T) { + config := plugintypes.AuditLogConfig{ + Target: os.DevNull, + Formatter: mockFormatter{}, + } + + writer := &concurrentWriter{} + if err := writer.Init(config); err != nil { + t.Errorf("unexpected error: %v", err) + } + + if err := writer.Write(nil); err != nil { + t.Errorf("unexpected error: %v", err) + } + }) + + t.Run("formatting error", func(t *testing.T) { + config := plugintypes.AuditLogConfig{ + Target: os.DevNull, + Formatter: mockFormatter{err: errors.New("formatting error")}, + } + + writer := &concurrentWriter{} + if err := writer.Init(config); err != nil { + t.Errorf("unexpected error: %v", err) + } + + if err := writer.Write(nil); err == nil { + t.Errorf("expected error: %v", err) + } + }) +} + +func TestConcurrentWriterSuccess(t *testing.T) { dir := t.TempDir() file, err := os.Create(filepath.Join(dir, "audit.log")) if err != nil { @@ -59,6 +104,12 @@ func TestConcurrentWriterWrites(t *testing.T) { DirMode: fs.FileMode(0777), Formatter: &jsonFormatter{}, } + + writer := &concurrentWriter{} + if err := writer.Init(config); err != nil { + t.Error("failed to init concurrent logger", err) + } + ts := time.Now() expectedLog := &Log{ Transaction_: Transaction{ @@ -74,10 +125,6 @@ func TestConcurrentWriterWrites(t *testing.T) { }, }, } - writer := &concurrentWriter{} - if err := writer.Init(config); err != nil { - t.Error("failed to init concurrent logger", err) - } if err := writer.Write(expectedLog); err != nil { t.Error("failed to write to logger: ", err) } diff --git a/internal/auditlog/formats.go b/internal/auditlog/formats.go index 82ad71d05..dab2c91c4 100644 --- a/internal/auditlog/formats.go +++ b/internal/auditlog/formats.go @@ -31,6 +31,10 @@ import ( type nativeFormatter struct{} func (nativeFormatter) Format(al plugintypes.AuditLog) ([]byte, error) { + if len(al.Parts()) == 0 { + return nil, nil + } + boundaryPrefix := fmt.Sprintf("--%s-", utils.RandomString(10)) var res strings.Builder @@ -56,31 +60,36 @@ func (nativeFormatter) Format(al plugintypes.AuditLog) ([]byte, error) { // Content-Length: 6 _, _ = fmt.Fprintf( &res, - "%s %s %s\n", + "\n%s %s %s", al.Transaction().Request().Method(), al.Transaction().Request().URI(), al.Transaction().Request().Protocol(), ) for k, vv := range al.Transaction().Request().Headers() { for _, v := range vv { + res.WriteByte('\n') res.WriteString(k) res.WriteString(": ") res.WriteString(v) - res.WriteByte('\n') } } case types.AuditLogPartRequestBody: - // b=test - res.WriteString(al.Transaction().Request().Body()) + if body := al.Transaction().Request().Body(); body != "" { + res.WriteByte('\n') + res.WriteString(body) + } case types.AuditLogPartIntermediaryResponseBody: - res.WriteString(al.Transaction().Response().Body()) + if body := al.Transaction().Response().Body(); body != "" { + res.WriteByte('\n') + res.WriteString(al.Transaction().Response().Body()) + } case types.AuditLogPartResponseHeaders: for k, vv := range al.Transaction().Response().Headers() { for _, v := range vv { + res.WriteByte('\n') res.WriteString(k) res.WriteString(": ") res.WriteString(v) - res.WriteByte('\n') } } case types.AuditLogPartAuditLogTrailer: @@ -91,11 +100,11 @@ func (nativeFormatter) Format(al plugintypes.AuditLog) ([]byte, error) { // Producer: ModSecurity for Apache/2.9.1 (http://www.modsecurity.org/). // Server: Apache // Engine-Mode: "ENABLED" - _, _ = fmt.Fprintf(&res, "Stopwatch: %s\nResponse-Body-Transformed: %s\nProducer: %s\nServer: %s", "", "", "", "") + _, _ = fmt.Fprintf(&res, "\nStopwatch: %s\nResponse-Body-Transformed: %s\nProducer: %s\nServer: %s", "", "", "", "") case types.AuditLogPartRulesMatched: for _, r := range al.Messages() { - res.WriteString(r.Data().Raw()) res.WriteByte('\n') + res.WriteString(r.Data().Raw()) } } res.WriteByte('\n') diff --git a/internal/auditlog/formats_test.go b/internal/auditlog/formats_test.go index 68712a2d1..91381aad4 100644 --- a/internal/auditlog/formats_test.go +++ b/internal/auditlog/formats_test.go @@ -4,7 +4,9 @@ package auditlog import ( + "bufio" "bytes" + "fmt" "strings" "testing" @@ -12,20 +14,77 @@ import ( "github.com/corazawaf/coraza/v3/types" ) +func checkLine(t *testing.T, lines []string, index int, expected string) { + t.Helper() + if lines[index] != expected { + auditLog := &strings.Builder{} + auditLog.WriteByte('\n') + for i, line := range lines { + auditLog.WriteString(fmt.Sprintf("Line %d: ", i)) + auditLog.WriteString(line) + auditLog.WriteByte('\n') + } + t.Log(auditLog.String()) + t.Fatalf("unexpected line %d, \ngot: %q\nwant: %q\n", index, lines[index], expected) + } +} + +func mutateSeparator(separator string, part byte) string { + return separator[:len(separator)-3] + string(part) + separator[len(separator)-2:] +} + func TestNativeFormatter(t *testing.T) { - al := createAuditLog() f := &nativeFormatter{} - data, err := f.Format(al) - if err != nil { - t.Error(err) - } - if !strings.Contains(f.MIME(), "x-coraza-auditlog-native") { - t.Errorf("failed to match MIME, expected json and got %s", f.MIME()) - } - // Log contains random strings, do a simple sanity check - if !bytes.Contains(data, []byte("[02/Jan/2006:15:04:20 -0700] 123 0 0")) { - t.Errorf("failed to match log, \ngot: %s\n", string(data)) - } + + t.Run("empty parts", func(t *testing.T) { + al := &Log{} + l, err := f.Format(al) + if l != nil { + t.Error("expected nil log") + } + if err != nil { + t.Error("unexpected error") + } + }) + + t.Run("success", func(t *testing.T) { + al := createAuditLog() + data, err := f.Format(al) + if err != nil { + t.Error(err) + } + if !strings.Contains(f.MIME(), "x-coraza-auditlog-native") { + t.Errorf("failed to match MIME, expected json and got %s", f.MIME()) + } + // Log contains random strings, do a simple sanity check + if !bytes.Contains(data, []byte("[02/Jan/2006:15:04:20 -0700] 123 0 0")) { + t.Errorf("failed to match log, \ngot: %s\n", string(data)) + } + + scanner := bufio.NewScanner(bytes.NewReader(data)) + + var lines []string + for scanner.Scan() { + lines = append(lines, scanner.Text()) + } + separator := lines[0] + + checkLine(t, lines, 2, "GET /test.php HTTP/1.1") + checkLine(t, lines, 3, "some: request header") + checkLine(t, lines, 4, mutateSeparator(separator, 'C')) + checkLine(t, lines, 6, "some request body") + checkLine(t, lines, 7, mutateSeparator(separator, 'E')) + checkLine(t, lines, 9, "some response body") + checkLine(t, lines, 10, mutateSeparator(separator, 'F')) + checkLine(t, lines, 12, "some: response header") + checkLine(t, lines, 13, mutateSeparator(separator, 'H')) + checkLine(t, lines, 15, "Stopwatch: ") + checkLine(t, lines, 16, "Response-Body-Transformed: ") + checkLine(t, lines, 17, "Producer: ") + checkLine(t, lines, 18, "Server: ") + checkLine(t, lines, 19, mutateSeparator(separator, 'K')) + checkLine(t, lines, 21, `SecAction "id:100"`) + }) } func createAuditLog() *Log { @@ -47,17 +106,20 @@ func createAuditLog() *Log { Method_: "GET", Headers_: map[string][]string{ "some": { - "somedata", + "request header", }, }, + Body_: "some request body", + Protocol_: "HTTP/1.1", }, Response_: &TransactionResponse{ Status_: 200, Headers_: map[string][]string{ "some": { - "somedata", + "response header", }, }, + Body_: "some response body", }, Producer_: &TransactionProducer{ Connector_: "some connector", diff --git a/internal/auditlog/serial_writer.go b/internal/auditlog/serial_writer.go index d752d541a..9626db0ae 100644 --- a/internal/auditlog/serial_writer.go +++ b/internal/auditlog/serial_writer.go @@ -54,6 +54,11 @@ func (sl *serialWriter) Write(al plugintypes.AuditLog) error { if err != nil { return err } + + if len(bts) == 0 { + return nil + } + sl.logger.Println(string(bts)) return nil } diff --git a/internal/bodyprocessors/raw.go b/internal/bodyprocessors/raw.go new file mode 100644 index 000000000..6f7cf7316 --- /dev/null +++ b/internal/bodyprocessors/raw.go @@ -0,0 +1,43 @@ +// Copyright 2024 Juan Pablo Tosso and the OWASP Coraza contributors +// SPDX-License-Identifier: Apache-2.0 + +package bodyprocessors + +import ( + "io" + "strconv" + "strings" + + "github.com/corazawaf/coraza/v3/experimental/plugins/plugintypes" + "github.com/corazawaf/coraza/v3/internal/collections" +) + +type rawBodyProcessor struct { +} + +func (*rawBodyProcessor) ProcessRequest(reader io.Reader, v plugintypes.TransactionVariables, _ plugintypes.BodyProcessorOptions) error { + var buf strings.Builder + if _, err := io.Copy(&buf, reader); err != nil { + return err + } + + b := buf.String() + + v.RequestBody().(*collections.Single).Set(b) + v.RequestBodyLength().(*collections.Single).Set(strconv.Itoa(len(b))) + return nil +} + +func (*rawBodyProcessor) ProcessResponse(io.Reader, plugintypes.TransactionVariables, plugintypes.BodyProcessorOptions) error { + return nil +} + +var ( + _ plugintypes.BodyProcessor = &rawBodyProcessor{} +) + +func init() { + RegisterBodyProcessor("raw", func() plugintypes.BodyProcessor { + return &rawBodyProcessor{} + }) +} diff --git a/internal/bodyprocessors/raw_test.go b/internal/bodyprocessors/raw_test.go new file mode 100644 index 000000000..28b9ae749 --- /dev/null +++ b/internal/bodyprocessors/raw_test.go @@ -0,0 +1,34 @@ +// Copyright 2024 Juan Pablo Tosso and the OWASP Coraza contributors +// SPDX-License-Identifier: Apache-2.0 + +package bodyprocessors_test + +import ( + "strconv" + "strings" + "testing" + + "github.com/corazawaf/coraza/v3/experimental/plugins/plugintypes" + "github.com/corazawaf/coraza/v3/internal/bodyprocessors" + "github.com/corazawaf/coraza/v3/internal/corazawaf" +) + +func TestRAW(t *testing.T) { + bp, err := bodyprocessors.GetBodyProcessor("raw") + if err != nil { + t.Fatal(err) + } + v := corazawaf.NewTransactionVariables() + + body := `this is a body +without &any=meaning` + if err := bp.ProcessRequest(strings.NewReader(body), v, plugintypes.BodyProcessorOptions{}); err != nil { + t.Error(err) + } + if v.RequestBody().Get() != body { + t.Errorf("Expected %s, got %s", body, v.RequestBody().Get()) + } + if rbl, _ := strconv.Atoi(v.RequestBodyLength().Get()); rbl != len(body) { + t.Errorf("Expected %d, got %s", len(body), v.RequestBodyLength().Get()) + } +} diff --git a/internal/bodyprocessors/urlencoded.go b/internal/bodyprocessors/urlencoded.go index 3a4c26e81..fe0176bc4 100644 --- a/internal/bodyprocessors/urlencoded.go +++ b/internal/bodyprocessors/urlencoded.go @@ -10,7 +10,7 @@ import ( "github.com/corazawaf/coraza/v3/experimental/plugins/plugintypes" "github.com/corazawaf/coraza/v3/internal/collections" - "github.com/corazawaf/coraza/v3/internal/url" + urlutil "github.com/corazawaf/coraza/v3/internal/url" ) type urlencodedBodyProcessor struct { @@ -23,7 +23,7 @@ func (*urlencodedBodyProcessor) ProcessRequest(reader io.Reader, v plugintypes.T } b := buf.String() - values := url.ParseQuery(b, '&') + values := urlutil.ParseQuery(b, '&') argsCol := v.ArgsPost() for k, vs := range values { argsCol.Set(k, vs) diff --git a/internal/cookies/cookies.go b/internal/cookies/cookies.go new file mode 100644 index 000000000..4da61f2b4 --- /dev/null +++ b/internal/cookies/cookies.go @@ -0,0 +1,39 @@ +// Copyright 2024 Juan Pablo Tosso and the OWASP Coraza contributors +// SPDX-License-Identifier: Apache-2.0 + +package cookies + +import ( + "net/textproto" + "strings" +) + +// ParseCookies parses cookies and splits in name, value pairs. Won't check for valid names nor values. +// If there are multiple cookies with the same name, it will append to the list with the same name key. +// Loosely based in the stdlib src/net/http/cookie.go +func ParseCookies(rawCookies string) map[string][]string { + cookies := make(map[string][]string) + + rawCookies = textproto.TrimString(rawCookies) + + if rawCookies == "" { + return cookies + } + + var part string + for len(rawCookies) > 0 { // continue since we have rest + part, rawCookies, _ = strings.Cut(rawCookies, ";") + part = textproto.TrimString(part) + if part == "" { + continue + } + name, val, _ := strings.Cut(part, "=") + name = textproto.TrimString(name) + // if name is empty (eg: "Cookie: =foo;") skip it + if name == "" { + continue + } + cookies[name] = append(cookies[name], val) + } + return cookies +} diff --git a/internal/cookies/cookies_test.go b/internal/cookies/cookies_test.go new file mode 100644 index 000000000..d1595a778 --- /dev/null +++ b/internal/cookies/cookies_test.go @@ -0,0 +1,100 @@ +// Copyright 2024 Juan Pablo Tosso and the OWASP Coraza contributors +// SPDX-License-Identifier: Apache-2.0 + +package cookies + +import ( + "testing" +) + +func equalMaps(map1 map[string][]string, map2 map[string][]string) bool { + if len(map1) != len(map2) { + return false + } + + // Iterate through the key-value pairs of the first map + for key, slice1 := range map1 { + // Check if the key exists in the second map + slice2, ok := map2[key] + if !ok { + return false + } + + // Compare the values of the corresponding keys + for i, val1 := range slice1 { + val2 := slice2[i] + + // Compare the elements + if val1 != val2 { + return false + } + } + } + + return true +} + +func TestParseCookies(t *testing.T) { + type args struct { + rawCookies string + } + tests := []struct { + name string + args args + want map[string][]string + }{ + { + name: "EmptyString", + args: args{rawCookies: " "}, + want: map[string][]string{}, + }, + { + name: "SimpleCookie", + args: args{rawCookies: "test=test_value"}, + want: map[string][]string{"test": {"test_value"}}, + }, + { + name: "MultipleCookies", + args: args{rawCookies: "test1=test_value1; test2=test_value2"}, + want: map[string][]string{"test1": {"test_value1"}, "test2": {"test_value2"}}, + }, + { + name: "SpacesInCookieName", + args: args{rawCookies: " test1 =test_value1; test2 =test_value2"}, + want: map[string][]string{"test1": {"test_value1"}, "test2": {"test_value2"}}, + }, + { + name: "SpacesInCookieValue", + args: args{rawCookies: "test1=test _value1; test2 =test_value2"}, + want: map[string][]string{"test1": {"test _value1"}, "test2": {"test_value2"}}, + }, + { + name: "EmptyCookie", + args: args{rawCookies: ";;foo=bar"}, + want: map[string][]string{"foo": {"bar"}}, + }, + { + name: "EmptyName", + args: args{rawCookies: "=bar;"}, + want: map[string][]string{}, + }, + { + name: "MultipleEqualsInValues", + args: args{rawCookies: "test1=val==ue1;test2=value2"}, + want: map[string][]string{"test1": {"val==ue1"}, "test2": {"value2"}}, + }, + { + name: "RepeatedCookieNameShouldGiveList", + args: args{rawCookies: "test1=value1;test1=value2"}, + want: map[string][]string{"test1": {"value1", "value2"}}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := ParseCookies(tt.args.rawCookies) + if !equalMaps(got, tt.want) { + t.Errorf("ParseCookies() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/internal/corazarules/rule_match.go b/internal/corazarules/rule_match.go index 8810bc2d6..ff5ca05f6 100644 --- a/internal/corazarules/rule_match.go +++ b/internal/corazarules/rule_match.go @@ -4,6 +4,7 @@ package corazarules import ( + "context" "fmt" "strconv" "strings" @@ -30,6 +31,8 @@ type MatchData struct { ChainLevel_ int } +var _ types.MatchData = (*MatchData)(nil) + func (m *MatchData) Variable() variables.RuleVariable { return m.Variable_ } @@ -100,8 +103,12 @@ type MatchedRule struct { MatchedDatas_ []types.MatchData Rule_ types.RuleMetadata + + Context_ context.Context } +var _ types.MatchedRule = (*MatchedRule)(nil) + func (mr *MatchedRule) Message() string { return mr.Message_ } @@ -142,6 +149,35 @@ func (mr *MatchedRule) Rule() types.RuleMetadata { return mr.Rule_ } +// Context returns the context associated with the transaction +// This is useful for logging purposes where you want to add +// additional information to the log. +// The context can be easily retrieved in the logger using +// an ancillary interface: +// ``` +// +// type Contexter interface { +// Context() context.Context +// } +// +// ``` +// and then using it like this: +// +// ``` +// +// func errorLogCb(mr types.MatchedRule) { +// ctx := context.Background() +// if ctxer, ok := mr.(Contexter); ok { +// ctx = ctxer.Context() +// } +// logger.Context(ctx).Error().Msg("...") +// } +// +// ``` +func (mr *MatchedRule) Context() context.Context { + return mr.Context_ +} + const maxSizeLogMessage = 200 func (mr MatchedRule) writeDetails(log *strings.Builder, matchData types.MatchData) { diff --git a/internal/corazawaf/rule.go b/internal/corazawaf/rule.go index 2209d9ca6..ce55f57f3 100644 --- a/internal/corazawaf/rule.go +++ b/internal/corazawaf/rule.go @@ -12,6 +12,7 @@ import ( "sync" "unsafe" + "github.com/corazawaf/coraza/v3/debuglog" "github.com/corazawaf/coraza/v3/experimental/plugins/macro" "github.com/corazawaf/coraza/v3/experimental/plugins/plugintypes" "github.com/corazawaf/coraza/v3/internal/corazarules" @@ -169,12 +170,21 @@ const chainLevelZero = 0 func (r *Rule) Evaluate(phase types.RulePhase, tx plugintypes.TransactionState, cache map[transformationKey]*transformationValue) { // collectiveMatchedValues lives across recursive calls of doEvaluate var collectiveMatchedValues []types.MatchData - r.doEvaluate(phase, tx.(*Transaction), &collectiveMatchedValues, chainLevelZero, cache) + + var logger debuglog.Logger + + if r.ID_ == noID { + logger = tx.DebugLogger().With(debuglog.Str("rule_ref", fmt.Sprintf("%s#L%d", r.File_, r.Line_))) + } else { + logger = tx.DebugLogger().With(debuglog.Int("rule_id", r.ID_)) + } + + r.doEvaluate(logger, phase, tx.(*Transaction), &collectiveMatchedValues, chainLevelZero, cache) } const noID = 0 -func (r *Rule) doEvaluate(phase types.RulePhase, tx *Transaction, collectiveMatchedValues *[]types.MatchData, chainLevel int, cache map[transformationKey]*transformationValue) []types.MatchData { +func (r *Rule) doEvaluate(logger debuglog.Logger, phase types.RulePhase, tx *Transaction, collectiveMatchedValues *[]types.MatchData, chainLevel int, cache map[transformationKey]*transformationValue) []types.MatchData { tx.Capture = r.Capture rid := r.ID_ @@ -188,8 +198,9 @@ func (r *Rule) doEvaluate(phase types.RulePhase, tx *Transaction, collectiveMatc var matchedValues []types.MatchData // we log if we are the parent rule - tx.DebugLogger().Debug().Int("rule_id", rid).Msg("Evaluating rule") - defer tx.DebugLogger().Debug().Int("rule_id", rid).Msg("Finish evaluating rule") + logger.Debug().Msg("Evaluating rule") + defer logger.Debug().Msg("Finished rule evaluation") + ruleCol := tx.variables.rule ruleCol.SetIndex("id", 0, strconv.Itoa(rid)) if r.Msg != nil { @@ -202,7 +213,7 @@ func (r *Rule) doEvaluate(phase types.RulePhase, tx *Transaction, collectiveMatc ruleCol.SetIndex("severity", 0, r.Severity_.String()) // SecMark and SecAction uses nil operator if r.operator == nil { - tx.DebugLogger().Debug().Int("rule_id", rid).Msg("Forcing rule to match") + logger.Debug().Msg("Forcing rule to match") md := &corazarules.MatchData{} if r.ParentID_ != noID || r.MultiMatch { // In order to support Msg and LogData for inner rules, we need to expand them now @@ -233,26 +244,30 @@ func (r *Rule) doEvaluate(phase types.RulePhase, tx *Transaction, collectiveMatc } values = tx.GetField(v) - tx.DebugLogger().Debug(). - Int("rule_id", rid). - Str("variable", v.Variable.Name()). - Msg("Expanding arguments for rule") + + vLog := logger.With(debuglog.Str("variable", v.Variable.Name())) + vLog.Debug().Msg("Expanding arguments for rule") + for i, arg := range values { - tx.DebugLogger().Debug().Int("rule_id", rid).Msg("Transforming argument for rule") args, errs := r.transformArg(arg, i, cache) if len(errs) > 0 { - log := tx.DebugLogger().Debug().Int("rule_id", rid) - if log.IsEnabled() { - for i, err := range errs { - log = log.Str(fmt.Sprintf("errors[%d]", i), err.Error()) + vWarnLog := vLog.Warn() + if vWarnLog.IsEnabled() { + for _, err := range errs { + vWarnLog = vWarnLog.Err(err) } - log.Msg("Error transforming argument for rule") + vWarnLog.Msg("Error transforming argument for rule") } } - tx.DebugLogger().Debug().Int("rule_id", rid).Msg("Arguments transformed for rule") // args represents the transformed variables for _, carg := range args { + evalLog := vLog. + Debug(). + Str("operator_function", r.operator.Function). + Str("operator_data", r.operator.Data). + Str("arg", carg) + match := r.executeOperator(carg, tx) if match { mr := &corazarules.MatchData{ @@ -290,7 +305,7 @@ func (r *Rule) doEvaluate(phase types.RulePhase, tx *Transaction, collectiveMatc tx.matchVariable(mr) for _, a := range r.actions { if a.Function.Type() == plugintypes.ActionTypeNondisruptive { - tx.DebugLogger().Debug().Str("action", a.Name).Msg("Evaluating action") + vLog.Debug().Str("action", a.Name).Msg("Evaluating action") a.Function.Evaluate(r, tx) } } @@ -303,19 +318,9 @@ func (r *Rule) doEvaluate(phase types.RulePhase, tx *Transaction, collectiveMatc } } - tx.DebugLogger().Debug(). - Int("rule_id", rid). - Str("operator_function", r.operator.Function). - Str("operator_data", r.operator.Data). - Str("arg", carg). - Msg("Evaluating operator: MATCH") + evalLog.Msg("Evaluating operator: MATCH") } else { - tx.DebugLogger().Debug(). - Int("rule_id", rid). - Str("operator_function", r.operator.Function). - Str("operator_data", r.operator.Data). - Str("arg", carg). - Msg("Evaluating operator: NO MATCH") + evalLog.Msg("Evaluating operator: NO MATCH") } } } @@ -332,8 +337,15 @@ func (r *Rule) doEvaluate(phase types.RulePhase, tx *Transaction, collectiveMatc // we only run the chains for the parent rule for nr := r.Chain; nr != nil; { chainLevel++ - tx.DebugLogger().Debug().Int("rule_id", rid).Msg("Evaluating rule chain") - matchedChainValues := nr.doEvaluate(phase, tx, collectiveMatchedValues, chainLevel, cache) + + var nrLogger debuglog.Logger + if nr.ID_ == noID { + nrLogger = logger.With(debuglog.Str("chain_rule_ref", fmt.Sprintf("%s#L%d", nr.File_, nr.Line_))) + } else { + nrLogger = logger.With(debuglog.Int("chain_rule_id", nr.ID_)) + } + + matchedChainValues := nr.doEvaluate(nrLogger, phase, tx, collectiveMatchedValues, chainLevel, cache) if len(matchedChainValues) == 0 { return matchedChainValues } @@ -355,11 +367,11 @@ func (r *Rule) doEvaluate(phase types.RulePhase, tx *Transaction, collectiveMatc for _, a := range r.actions { if a.Function.Type() == plugintypes.ActionTypeFlow { // Flow actions are evaluated also if the rule engine is set to DetectionOnly - tx.DebugLogger().Debug().Int("rule_id", rid).Str("action", a.Name).Int("phase", int(phase)).Msg("Evaluating flow action for rule") + logger.Debug().Str("action", a.Name).Int("phase", int(phase)).Msg("Evaluating flow action for rule") a.Function.Evaluate(r, tx) } else if a.Function.Type() == plugintypes.ActionTypeDisruptive && tx.RuleEngine == types.RuleEngineOn { // The parser enforces that the disruptive action is just one per rule (if more than one, only the last one is kept) - tx.DebugLogger().Debug().Int("rule_id", rid).Str("action", a.Name).Msg("Executing disruptive action for rule") + logger.Debug().Str("action", a.Name).Msg("Executing disruptive action for rule") a.Function.Evaluate(r, tx) } } diff --git a/internal/corazawaf/rule_test.go b/internal/corazawaf/rule_test.go index 87b20b413..74af3efd4 100644 --- a/internal/corazawaf/rule_test.go +++ b/internal/corazawaf/rule_test.go @@ -8,6 +8,7 @@ import ( "strconv" "testing" + "github.com/corazawaf/coraza/v3/debuglog" "github.com/corazawaf/coraza/v3/experimental/plugins/macro" "github.com/corazawaf/coraza/v3/experimental/plugins/plugintypes" "github.com/corazawaf/coraza/v3/internal/corazarules" @@ -31,7 +32,7 @@ func TestMatchEvaluate(t *testing.T) { tx.AddGetRequestArgument("test", "0") var matchedValues []types.MatchData - matchdata := r.doEvaluate(types.PhaseRequestHeaders, tx, &matchedValues, 0, tx.transformationCache) + matchdata := r.doEvaluate(debuglog.Noop(), types.PhaseRequestHeaders, tx, &matchedValues, 0, tx.transformationCache) if len(matchdata) != 1 { t.Errorf("Expected 1 matchdata from a SecActions rule, got %d", len(matchdata)) } @@ -54,7 +55,7 @@ func TestNoMatchEvaluate(t *testing.T) { tx.AddGetRequestArgument("test", "999") var matchedValues []types.MatchData - matchdata := r.doEvaluate(types.PhaseRequestHeaders, tx, &matchedValues, 0, tx.transformationCache) + matchdata := r.doEvaluate(debuglog.Noop(), types.PhaseRequestHeaders, tx, &matchedValues, 0, tx.transformationCache) if len(matchdata) != 0 { t.Errorf("Expected 0 matchdata from a SecActions rule, got %d", len(matchdata)) } @@ -79,7 +80,7 @@ func TestNoMatchEvaluateBecauseOfException(t *testing.T) { tx.AddGetRequestArgument("test", "0") tx.RemoveRuleTargetByID(1, variables.ArgsGet, "test") var matchedValues []types.MatchData - matchdata := r.doEvaluate(types.PhaseRequestHeaders, tx, &matchedValues, 0, tx.transformationCache) + matchdata := r.doEvaluate(debuglog.Noop(), types.PhaseRequestHeaders, tx, &matchedValues, 0, tx.transformationCache) if len(matchdata) != 0 { t.Errorf("Expected 0 matchdata, got %d", len(matchdata)) } @@ -113,7 +114,7 @@ func TestFlowActionIfDetectionOnlyEngine(t *testing.T) { tx.RuleEngine = types.RuleEngineDetectionOnly var matchedValues []types.MatchData - matchdata := r.doEvaluate(types.PhaseRequestHeaders, tx, &matchedValues, 0, tx.transformationCache) + matchdata := r.doEvaluate(debuglog.Noop(), types.PhaseRequestHeaders, tx, &matchedValues, 0, tx.transformationCache) if len(matchdata) != 1 { t.Errorf("Expected 1 matchdata, got %d", len(matchdata)) } @@ -165,7 +166,7 @@ func TestDisruptiveActionFromChainNotEvaluated(t *testing.T) { tx := NewWAF().NewTransaction() var matchedValues []types.MatchData - matchdata := r.doEvaluate(types.PhaseRequestHeaders, tx, &matchedValues, 0, tx.transformationCache) + matchdata := r.doEvaluate(debuglog.Noop(), types.PhaseRequestHeaders, tx, &matchedValues, 0, tx.transformationCache) if len(matchdata) != 2 { t.Errorf("Expected 2 matchdata from a SecActions chained rule (total 2 rules), got %d", len(matchdata)) } @@ -183,7 +184,7 @@ func TestRuleDetailsTransferredToTransaction(t *testing.T) { tx := NewWAF().NewTransaction() var matchedValues []types.MatchData - r.doEvaluate(types.PhaseRequestHeaders, tx, &matchedValues, 0, tx.transformationCache) + r.doEvaluate(debuglog.Noop(), types.PhaseRequestHeaders, tx, &matchedValues, 0, tx.transformationCache) if tx.variables.rule.Get("id")[0] != strconv.Itoa(r.ParentID()) { t.Errorf("Expected id: %d (parent id), got %s", r.ParentID(), tx.variables.rule.Get("id")[0]) } @@ -207,7 +208,7 @@ func TestSecActionMessagePropagationInMatchData(t *testing.T) { r.operator = nil tx := NewWAF().NewTransaction() var matchedValues []types.MatchData - matchdata := r.doEvaluate(types.PhaseRequestHeaders, tx, &matchedValues, 0, tx.transformationCache) + matchdata := r.doEvaluate(debuglog.Noop(), types.PhaseRequestHeaders, tx, &matchedValues, 0, tx.transformationCache) if len(matchdata) != 1 { t.Errorf("Expected 1 matchdata from a SecActions rule, got %d", len(matchdata)) } @@ -544,7 +545,7 @@ func TestCaptureNotPropagatedToInnerChainRule(t *testing.T) { r.Chain = chainedRule tx := NewWAF().NewTransaction() var matchedValues []types.MatchData - r.doEvaluate(types.PhaseRequestHeaders, tx, &matchedValues, 0, tx.transformationCache) + r.doEvaluate(debuglog.Noop(), types.PhaseRequestHeaders, tx, &matchedValues, 0, tx.transformationCache) // We expect that capture is false after doEvaluate. if tx.Capture { t.Errorf("Expected capture to be false. The parent rule enables capture, but inner rule should disable it.") @@ -580,7 +581,7 @@ func TestExpandMacroAfterWholeRuleEvaluation(t *testing.T) { tx.AddGetRequestArgument("test", "0") var matchedValues []types.MatchData - matchdata := r.doEvaluate(types.PhaseRequestHeaders, tx, &matchedValues, 0, tx.transformationCache) + matchdata := r.doEvaluate(debuglog.Noop(), types.PhaseRequestHeaders, tx, &matchedValues, 0, tx.transformationCache) if len(matchdata) != 2 { t.Errorf("Expected 2 matchdata from a chained rule (total 2 rules), got %d", len(matchdata)) } diff --git a/internal/corazawaf/transaction.go b/internal/corazawaf/transaction.go index 3f0be3842..b41efc8f4 100644 --- a/internal/corazawaf/transaction.go +++ b/internal/corazawaf/transaction.go @@ -5,6 +5,7 @@ package corazawaf import ( "bufio" + "context" "errors" "fmt" "io" @@ -22,6 +23,7 @@ import ( "github.com/corazawaf/coraza/v3/internal/auditlog" "github.com/corazawaf/coraza/v3/internal/bodyprocessors" "github.com/corazawaf/coraza/v3/internal/collections" + "github.com/corazawaf/coraza/v3/internal/cookies" "github.com/corazawaf/coraza/v3/internal/corazarules" "github.com/corazawaf/coraza/v3/internal/corazatypes" stringsutil "github.com/corazawaf/coraza/v3/internal/strings" @@ -41,6 +43,9 @@ type Transaction struct { // Transaction ID id string + // The context associated to the transaction. + context context.Context + // Contains the list of matched rules and associated match information matchedRules []types.MatchedRule @@ -320,8 +325,21 @@ func (tx *Transaction) AddRequestHeader(key string, value string) { tx.variables.reqbodyProcessor.Set("MULTIPART") } case "cookie": - // Cookies use the same syntax as GET params but with semicolon (;) separator - values := urlutil.ParseQuery(value, ';') + // 4.2. Cookie + // + // 4.2.1. Syntax + // + // The user agent sends stored cookies to the origin server in the + // Cookie header. If the server conforms to the requirements in + // Section 4.1 (and the user agent conforms to the requirements in + // Section 5), the user agent will send a Cookie header that conforms to + // the following grammar: + // + // cookie-header = "Cookie:" OWS cookie-string OWS + // cookie-string = cookie-pair *( ";" SP cookie-pair ) + // + // There is no URL Decode performed no the cookies + values := cookies.ParseCookies(value) for k, vr := range values { for _, v := range vr { tx.variables.requestCookies.Add(k, v) @@ -487,6 +505,7 @@ func (tx *Transaction) MatchRule(r *Rule, mds []types.MatchData) { Rule_: &r.RuleMetadata, Log_: r.Log, MatchedDatas_: mds, + Context_: tx.context, } // Populate MatchedRule disruption related fields only if the Engine is capable of performing disruptive actions if tx.RuleEngine == types.RuleEngineOn { @@ -535,7 +554,7 @@ func (tx *Transaction) GetStopWatch() string { } // GetField Retrieve data from collections applying exceptions -// In future releases we may remove de exceptions slice and +// In future releases we may remove the exceptions slice and // make it easier to use func (tx *Transaction) GetField(rv ruleVariableParams) []types.MatchData { col := tx.Collection(rv.Variable) @@ -1342,22 +1361,25 @@ func (tx *Transaction) AuditLog() *auditlog.Log { HostIP_: tx.variables.serverAddr.Get(), HostPort_: hostPort, ServerID_: tx.variables.serverName.Get(), // TODO check + Request_: &auditlog.TransactionRequest{ + Method_: tx.variables.requestMethod.Get(), + URI_: tx.variables.requestURI.Get(), + Protocol_: tx.variables.requestProtocol.Get(), + }, } for _, part := range tx.AuditLogParts { switch part { case types.AuditLogPartRequestHeaders: - if al.Transaction_.Request_ == nil { - al.Transaction_.Request_ = &auditlog.TransactionRequest{} - } al.Transaction_.Request_.Headers_ = tx.variables.requestHeaders.Data() case types.AuditLogPartRequestBody: - if al.Transaction_.Request_ == nil { - al.Transaction_.Request_ = &auditlog.TransactionRequest{} + reader, err := tx.requestBodyBuffer.Reader() + if err == nil { + content, err := io.ReadAll(reader) + if err == nil { + al.Transaction_.Request_.Body_ = string(content) + } } - // TODO maybe change to: - // al.Transaction.Request.Body = tx.RequestBodyBuffer.String() - al.Transaction_.Request_.Body_ = tx.variables.requestBody.Get() /* * TODO: diff --git a/internal/corazawaf/transaction_test.go b/internal/corazawaf/transaction_test.go index 938aef632..d229d3dd8 100644 --- a/internal/corazawaf/transaction_test.go +++ b/internal/corazawaf/transaction_test.go @@ -162,10 +162,11 @@ func TestWriteRequestBody(t *testing.T) { ) testCases := []struct { - name string - requestBodyLimit int - requestBodyLimitAction types.BodyLimitAction - shouldInterrupt bool + name string + requestBodyLimit int + requestBodyLimitAction types.BodyLimitAction + avoidRequestBodyLimitActionInit bool + shouldInterrupt bool }{ { name: "LimitNotReached", @@ -178,6 +179,14 @@ func TestWriteRequestBody(t *testing.T) { requestBodyLimitAction: types.BodyLimitActionReject, shouldInterrupt: true, }, + { + name: "LimitReachedAndRejectsDefaultValue", + requestBodyLimit: urlencodedBodyLen - 3, + // Omitting requestBodyLimitAction defaults to Reject + // requestBodyLimitAction: types.BodyLimitActionReject, + avoidRequestBodyLimitActionInit: true, + shouldInterrupt: true, + }, { name: "LimitReachedAndPartialProcessing", requestBodyLimit: urlencodedBodyLen - 3, @@ -201,8 +210,9 @@ func TestWriteRequestBody(t *testing.T) { waf.RuleEngine = types.RuleEngineOn waf.RequestBodyAccess = true waf.RequestBodyLimit = int64(testCase.requestBodyLimit) - waf.RequestBodyLimitAction = testCase.requestBodyLimitAction - + if !testCase.avoidRequestBodyLimitActionInit { + waf.RequestBodyLimitAction = testCase.requestBodyLimitAction + } tx := waf.NewTransaction() tx.AddRequestHeader("content-type", "application/x-www-form-urlencoded") @@ -472,6 +482,12 @@ func TestWriteResponseBody(t *testing.T) { responseBodyLimit: urlencodedBodyLen - 3, responseBodyLimitAction: types.BodyLimitActionProcessPartial, }, + { + name: "LimitReachedAndPartialProcessingDefaultValue", + responseBodyLimit: urlencodedBodyLen - 3, + // Omitting requestBodyLimitAction defaults to ProcessPartial + // responseBodyLimitAction: types.BodyLimitActionProcessPartial, + }, } urlencodedBodyLenThird := urlencodedBodyLen / 3 @@ -806,6 +822,57 @@ func TestHeaderSetters(t *testing.T) { } } +func TestCookiesNotUrldecoded(t *testing.T) { + waf := NewWAF() + tx := waf.NewTransaction() + fullCookie := "abc=%7Bd+e+f%7D;hij=%7Bklm%7D" + expectedUrlencodedAbcCookieValue := "%7Bd+e+f%7D" + unexpectedUrldencodedAbcCookieValue := "{d e f}" + tx.AddRequestHeader("cookie", fullCookie) + c := tx.variables.requestCookies.Get("abc")[0] + if c != expectedUrlencodedAbcCookieValue { + if c == unexpectedUrldencodedAbcCookieValue { + t.Errorf("failed to set cookie, unexpected urldecoding. Got: %q, expected: %q", unexpectedUrldencodedAbcCookieValue, expectedUrlencodedAbcCookieValue) + } else { + t.Errorf("failed to set cookie, got %q", c) + } + } + if tx.variables.requestHeaders.Get("cookie")[0] != fullCookie { + t.Errorf("failed to set request header, got: %q, expected: %q", tx.variables.requestHeaders.Get("cookie")[0], fullCookie) + } + if !utils.InSlice("cookie", collectionValues(t, tx.variables.requestHeadersNames)) { + t.Error("failed to set header name", collectionValues(t, tx.variables.requestHeadersNames)) + } + if !utils.InSlice("abc", collectionValues(t, tx.variables.requestCookiesNames)) { + t.Error("failed to set cookie name") + } + if err := tx.Close(); err != nil { + t.Error(err) + } +} + +func TestMultipleCookiesWithSpaceBetweenThem(t *testing.T) { + waf := NewWAF() + tx := waf.NewTransaction() + multipleCookies := "cookie1=value1; cookie2=value2; cookie1=value2" + tx.AddRequestHeader("cookie", multipleCookies) + v11 := tx.variables.requestCookies.Get("cookie1")[0] + if v11 != "value1" { + t.Errorf("failed to set cookie, got %q", v11) + } + v12 := tx.variables.requestCookies.Get("cookie1")[1] + if v12 != "value2" { + t.Errorf("failed to set cookie, got %q", v12) + } + v2 := tx.variables.requestCookies.Get("cookie2")[0] + if v2 != "value2" { + t.Errorf("failed to set cookie, got %q", v2) + } + if err := tx.Close(); err != nil { + t.Error(err) + } +} + func collectionValues(t *testing.T, col collection.Collection) []string { t.Helper() var values []string diff --git a/internal/corazawaf/waf.go b/internal/corazawaf/waf.go index 8656110c1..e6946a3da 100644 --- a/internal/corazawaf/waf.go +++ b/internal/corazawaf/waf.go @@ -4,6 +4,7 @@ package corazawaf import ( + "context" "errors" "fmt" "io" @@ -11,7 +12,6 @@ import ( "os" "regexp" "strconv" - "strings" "time" "github.com/corazawaf/coraza/v3/debuglog" @@ -91,6 +91,7 @@ type WAF struct { UploadDir string // Request body in memory limit excluding the size of any files being transported in the request. + // TODO: SecRequestBodyNoFilesLimit directive is retrieving the value, but no logic based on it is implemented. See https://github.com/corazawaf/coraza/issues/896 RequestBodyNoFilesLimit int64 RequestBodyLimitAction types.BodyLimitAction @@ -133,24 +134,40 @@ type WAF struct { ArgumentLimit int } +// Options is used to pass options to the WAF instance +type Options struct { + ID string + Context context.Context +} + // NewTransaction Creates a new initialized transaction for this WAF instance func (w *WAF) NewTransaction() *Transaction { - return w.newTransactionWithID(stringutils.RandomString(19)) + return w.newTransaction(Options{ + ID: stringutils.RandomString(19), + Context: context.Background(), + }) } -func (w *WAF) NewTransactionWithID(id string) *Transaction { - if len(strings.TrimSpace(id)) == 0 { - id = stringutils.RandomString(19) - w.Logger.Warn().Msg("Empty ID passed for new transaction") +// NewTransactionWithOptions Creates a new initialized transaction for this WAF +// instance with the provided options +func (w *WAF) NewTransactionWithOptions(opts Options) *Transaction { + if opts.ID == "" { + opts.ID = stringutils.RandomString(19) } - return w.newTransactionWithID(id) + + if opts.Context == nil { + opts.Context = context.Background() + } + + return w.newTransaction(opts) } // NewTransactionWithID Creates a new initialized transaction for this WAF instance // Using the specified ID -func (w *WAF) newTransactionWithID(id string) *Transaction { +func (w *WAF) newTransaction(opts Options) *Transaction { tx := w.txPool.Get().(*Transaction) - tx.id = id + tx.id = opts.ID + tx.context = opts.Context tx.matchedRules = []types.MatchedRule{} tx.interruption = nil tx.Logdata = "" // Deprecated, this variable is not used. Logdata for each matched rule is stored in the MatchData field. @@ -180,7 +197,7 @@ func (w *WAF) newTransactionWithID(id string) *Transaction { // Always non-nil if buffers / collections were already initialized so we don't do any of them // based on the presence of RequestBodyBuffer. if tx.requestBodyBuffer == nil { - // if no requestBodyInMemoryLimit has been set we default to the + // if no requestBodyInMemoryLimit has been set we default to the requestBodyLimit var requestBodyInMemoryLimit int64 = w.RequestBodyLimit if w.requestBodyInMemoryLimit != nil { requestBodyInMemoryLimit = int64(*w.requestBodyInMemoryLimit) @@ -222,7 +239,7 @@ func (w *WAF) newTransactionWithID(id string) *Transaction { tx.variables.highestSeverity.Set("0") tx.variables.uniqueID.Set(tx.id) - w.Logger.Debug().Msg("New transaction created") + tx.debugLogger.Debug().Msg("Transaction started") return tx } @@ -275,14 +292,21 @@ func NewWAF() *WAF { // These defaults are unavoidable as they are zero values for the variables RuleEngine: types.RuleEngineOn, RequestBodyAccess: false, - RequestBodyLimit: _1gb, + RequestBodyLimit: 134217728, // Hard limit equal to _1gb + RequestBodyLimitAction: types.BodyLimitActionReject, ResponseBodyAccess: false, - ResponseBodyLimit: _1gb, + ResponseBodyLimit: 524288, // Hard limit equal to _1gb auditLogWriter: logWriter, auditLogWriterInitialized: false, AuditLogWriterConfig: auditlog.NewConfig(), - Logger: logger, - ArgumentLimit: 1000, + AuditLogParts: types.AuditLogParts{ + types.AuditLogPartRequestHeaders, + types.AuditLogPartRequestBody, + types.AuditLogPartResponseHeaders, + types.AuditLogPartAuditLogTrailer, + }, + Logger: logger, + ArgumentLimit: 1000, } if environment.HasAccessToFS { diff --git a/internal/corazawaf/waf_test.go b/internal/corazawaf/waf_test.go index 883c09337..771667c35 100644 --- a/internal/corazawaf/waf_test.go +++ b/internal/corazawaf/waf_test.go @@ -15,7 +15,7 @@ func TestNewTransaction(t *testing.T) { waf.ResponseBodyAccess = true waf.RequestBodyLimit = 1044 - tx := waf.NewTransactionWithID("test") + tx := waf.NewTransactionWithOptions(Options{ID: "test"}) if !tx.RequestBodyAccess { t.Error("Request body access not enabled") } @@ -28,7 +28,7 @@ func TestNewTransaction(t *testing.T) { if tx.id != "test" { t.Error("ID not set") } - tx = waf.NewTransactionWithID("") + tx = waf.NewTransactionWithOptions(Options{ID: ""}) if tx.id == "" { t.Error("ID not set") } diff --git a/internal/seclang/directives.go b/internal/seclang/directives.go index 7a158c349..f5536ce93 100644 --- a/internal/seclang/directives.go +++ b/internal/seclang/directives.go @@ -229,7 +229,7 @@ func directiveSecResponseBodyAccess(options *DirectiveOptions) error { } // Description: Configures the maximum request body size Coraza will accept for buffering. -// Default: 134217728 (131072 KB) +// Default: 134217728 (128 Mib) // Syntax: SecRequestBodyLimit [LIMIT_IN_BYTES] // --- // Anything over the limit will be rejected with status code 413 (Request Entity Too Large). @@ -306,6 +306,19 @@ func directiveSecServerSignature(options *DirectiveOptions) error { return nil } +// Description: Removes the matching rules from the current configuration context. +// Syntax: SecRuleRemoveByTag [TAG] +// --- +// Normally, you would use `SecRuleRemoveById` to remove rules, but it may occasionally +// be easier to disable an entire group of rules with `SecRuleRemoveByTag`. Matching is +// by case-sensitive string equality. +// +// Example: +// ```apache +// SecRuleRemoveByTag attack-dos +// ``` +// +// Note: OWASP CRS has a list of supported tags https://coreruleset.org/docs/rules/metadata/ func directiveSecRuleRemoveByTag(options *DirectiveOptions) error { if len(options.Opts) == 0 { return errEmptyOptions @@ -410,7 +423,7 @@ func directiveSecResponseBodyLimitAction(options *DirectiveOptions) error { // Description: Configures the maximum response body size that will be accepted for buffering. // Syntax: SecResponseBodyLimit [LIMIT_IN_BYTES] -// Default: 524288 (512 KB) +// Default: 524288 (512 Kib) // --- // Anything over this limit will be rejected with status code 500 (Internal Server Error). // This setting will not affect the responses with MIME types that are not selected for @@ -448,7 +461,7 @@ func directiveSecRequestBodyLimitAction(options *DirectiveOptions) error { } // Description: Configures the maximum request body size that Coraza will store in memory. -// Default: 131072 (128 KB) +// Default: defaults to RequestBodyLimit // Syntax: SecRequestBodyInMemoryLimit [LIMIT_IN_BYTES] // --- // When a `multipart/form-data` request is being processed, once the in-memory limit is reached, @@ -890,6 +903,7 @@ func directiveSecUploadDir(options *DirectiveOptions) error { // Generally speaking, the default value is not small enough. For most applications, you // should be able to reduce it down to 128 KB or lower. Anything over the limit will be // rejected with status code 413 (Request Entity Too Large). There is a hard limit of 1 GB. +// Note: not implemented yet func directiveSecRequestBodyNoFilesLimit(options *DirectiveOptions) error { if len(options.Opts) == 0 { return errEmptyOptions diff --git a/internal/seclang/parser.go b/internal/seclang/parser.go index 1606f4900..2532d9eaf 100644 --- a/internal/seclang/parser.go +++ b/internal/seclang/parser.go @@ -64,7 +64,7 @@ func (p *Parser) FromFile(profilePath string) error { return fmt.Errorf("failed to readfile: %s", err.Error()) } - err = p.FromString(string(file)) + err = p.parseString(string(file)) if err != nil { // we don't use defer for this as tinygo does not seem to like it p.currentDir = originalDir @@ -85,6 +85,14 @@ func (p *Parser) FromFile(profilePath string) error { // It will return error if any directive fails to parse // or arguments are invalid func (p *Parser) FromString(data string) error { + oldCurrentFile := p.currentFile + p.currentFile = "_inline_" + err := p.parseString(data) + p.currentFile = oldCurrentFile + return err +} + +func (p *Parser) parseString(data string) error { scanner := bufio.NewScanner(strings.NewReader(data)) var linebuffer strings.Builder inBackticks := false diff --git a/internal/seclang/rule_parser.go b/internal/seclang/rule_parser.go index 27761472c..fbfc82dde 100644 --- a/internal/seclang/rule_parser.go +++ b/internal/seclang/rule_parser.go @@ -379,7 +379,6 @@ func ParseRule(options RuleOptions) (*corazawaf.Rule, error) { } } rule := rp.Rule() - rule.Raw_ = options.Raw rule.File_ = options.ParserConfig.ConfigFile rule.Line_ = options.ParserConfig.LastLine @@ -392,7 +391,12 @@ func ParseRule(options RuleOptions) (*corazawaf.Rule, error) { // TODO we must remove defaultactions from chains rule.Phase_ = 0 lastChain.Chain = rule + // This way we store the raw rule in the parent + parent.Raw_ += " \n" + options.Raw return nil, nil + } else { + // we only want Raw for the parent + rule.Raw_ = options.Raw } return rule, nil } diff --git a/internal/seclang/rule_parser_test.go b/internal/seclang/rule_parser_test.go index b186d068d..8e61d894e 100644 --- a/internal/seclang/rule_parser_test.go +++ b/internal/seclang/rule_parser_test.go @@ -251,6 +251,29 @@ func TestInvalidOperatorRuleData(t *testing.T) { } } +func TestRawChainedRules(t *testing.T) { + waf := corazawaf.NewWAF() + p := NewParser(waf) + if err := p.FromString(` + SecRule REQUEST_URI "abc" "id:7,phase:2,chain" + SecRule REQUEST_URI "def" "chain" + SecRule REQUEST_URI "ghi" "" + `); err != nil { + t.Errorf("unexpected error: %s", err.Error()) + } + raw := waf.Rules.GetRules()[0].Raw() + spl := strings.Split(raw, "\n") + if len(spl) != 3 { + t.Errorf("unexpected number of chained rules, want 3, have %d", len(spl)) + } + for i, r := range spl { + // we test that all lines begin with SecRule REQUEST_URI " + if !strings.HasPrefix(r, "SecRule REQUEST_URI ") { + t.Errorf("unexpected rule at line %d: %s", i, r) + } + } +} + func BenchmarkParseActions(b *testing.B) { actionsToBeParsed := "id:980170,phase:5,pass,t:none,noauditlog,msg:'Anomaly Scores:Inbound Scores - Outbound Scores',tag:test" for i := 0; i < b.N; i++ { diff --git a/internal/transformations/base64decode.go b/internal/transformations/base64decode.go index c4be9897c..76345599d 100644 --- a/internal/transformations/base64decode.go +++ b/internal/transformations/base64decode.go @@ -3,18 +3,82 @@ package transformations -import ( - "encoding/base64" +import "strings" - stringsutil "github.com/corazawaf/coraza/v3/internal/strings" -) +var base64DecMap = []byte{ + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 62, 127, 127, 127, 63, 52, 53, + 54, 55, 56, 57, 58, 59, 60, 61, 127, 127, + 127, 64, 127, 127, 127, 0, 1, 2, 3, 4, + 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 127, 127, 127, 127, 127, 127, 26, 27, 28, + 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, + 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, + 49, 50, 51, 127, 127, 127, 127, 127, +} // base64decode decodes a Base64-encoded string. +// Padding is optional. +// Partial decoding is returned up to the first invalid character (if any). +// New line characters (\r and \n) are ignored. +// Note: a custom base64 decoder is used in order to return partial decoding when an error arises. It +// would be possible to use the standard library only relying on undocumented behaviors of the decoder. +// For more context, see https://github.com/corazawaf/coraza/pull/940 func base64decode(data string) (string, bool, error) { - dec, err := base64.StdEncoding.DecodeString(data) - if err != nil { - // Forgiving implementation, which ignores invalid characters - return data, false, nil + res := doBase64decode(data) + return res, true, nil +} + +func doBase64decode(src string) string { + slen := len(src) + if slen == 0 { + return src + } + + var n, x int + var dst strings.Builder + dst.Grow(slen) + + for i := 0; i < slen; i++ { + currChar := src[i] + // new line characters are ignored. + if currChar == '\r' || currChar == '\n' { + continue + } + // If invalid character or padding reached, we stop decoding + if currChar == '=' || currChar == ' ' || currChar > 127 { + break + } + decodedChar := base64DecMap[currChar] + // Another condition of invalid character + if decodedChar == 127 { + break + } + + x = (x << 6) | int(decodedChar&0x3F) + n++ + if n == 4 { + dst.WriteByte(byte(x >> 16)) + dst.WriteByte(byte(x >> 8)) + dst.WriteByte(byte(x)) + n = 0 + x = 0 + } } - return stringsutil.WrapUnsafe(dec), true, nil + + // Handle any remaining characters + if n == 2 { + x <<= 12 + dst.WriteByte(byte(x >> 16)) + } else if n == 3 { + x <<= 6 + dst.WriteByte(byte(x >> 16)) + dst.WriteByte(byte(x >> 8)) + } + + return dst.String() } diff --git a/internal/transformations/base64decode_test.go b/internal/transformations/base64decode_test.go index d022b1659..f6842c07d 100644 --- a/internal/transformations/base64decode_test.go +++ b/internal/transformations/base64decode_test.go @@ -10,17 +10,173 @@ import ( "testing" ) -var b64DecodeTests = []string{ - "VGVzdENhc2U=", - "P.HNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==", - "VGVzdABDYXNl", +var b64DecodeTests = []struct { + name string + input string + expected string +}{ + { + name: "Valid", + input: "VGVzdENhc2U=", + expected: "TestCase", + }, + { + name: "Valid with \u0000", + input: "VGVzdABDYXNl", + expected: "Test\x00Case", + }, + { + name: "Valid without padding", + input: "VGVzdENhc2U", + expected: "TestCase", + }, + { + name: "Valid without longer padding", + input: "PA==", + expected: "<", + }, + { + name: "valid ", + input: "PFRFU1Q+", + expected: "", + }, + { + name: "Malformed base64 encoding", + input: "PHNjcmlwd", + expected: "= 'a' && hi <= 'f': - hi -= 'a' - 10 - case hi >= 'A' && hi <= 'F': - hi -= 'A' - 10 - default: + hi, ok := hexDigitToByte(input[i+1]) + if !ok { res.WriteByte(ci) continue } - switch { - case lo >= '0' && lo <= '9': - lo -= '0' - case lo >= 'a' && lo <= 'f': - lo -= 'a' - 10 - case lo >= 'A' && lo <= 'F': - lo -= 'A' - 10 - default: + lo, ok := hexDigitToByte(input[i+2]) + if !ok { res.WriteByte(ci) continue } @@ -81,3 +72,16 @@ func QueryUnescape(input string) string { } return res.String() } + +func hexDigitToByte(digit byte) (byte, bool) { + switch { + case digit >= '0' && digit <= '9': + return digit - '0', true + case digit >= 'a' && digit <= 'f': + return digit - 'a' + 10, true + case digit >= 'A' && digit <= 'F': + return digit - 'A' + 10, true + default: + return 0, false + } +} diff --git a/internal/url/url_test.go b/internal/url/url_test.go index 72c068a4f..7194f4a53 100644 --- a/internal/url/url_test.go +++ b/internal/url/url_test.go @@ -7,27 +7,45 @@ import ( "testing" ) +var parseQueryInput = `var=EmptyValue'||(select extractvalue(xmltype('%awpsd;` + func TestUrlPayloads(t *testing.T) { - out := `var=EmptyValue'||(select extractvalue(xmltype('%awpsd;` - q := ParseQuery(out, '&') + q := ParseQuery(parseQueryInput, '&') if len(q["var"]) == 0 { t.Error("var is empty") } } -func TestQueryUnescape(t *testing.T) { - payloads := map[string]string{ - "sample": "sample", - "s%20ample": "s ample", - "s+ample": "s ample", - "s%2fample": "s/ample", - "s% ample": "s% ample", // non-strict sample - "s%ssample": "s%ssample", // non-strict sample - "s%00ample": "s\x00ample", +func BenchmarkParseQuery(b *testing.B) { + for i := 0; i < b.N; i++ { + ParseQuery(parseQueryInput, '&') } - for k, v := range payloads { - if out := QueryUnescape(k); out != v { +} + +var queryUnescapePayloads = map[string]string{ + "sample": "sample", + "s%20ample": "s ample", + "s+ample": "s ample", + "s%2fample": "s/ample", + "s% ample": "s% ample", // non-strict sample + "s%ssample": "s%ssample", // non-strict sample + "s%00ample": "s\x00ample", + "%7B%%7d": "{%}", + "%7B+%+%7d": "{ % }", +} + +func TestQueryUnescape(t *testing.T) { + for k, v := range queryUnescapePayloads { + if out := queryUnescape(k); out != v { t.Errorf("Error parsing %q, got %q and expected %q", k, out, v) } } } + +func BenchmarkQueryUnescape(b *testing.B) { + for i := 0; i < b.N; i++ { + for k := range queryUnescapePayloads { + queryUnescape(k) + } + } +} diff --git a/testing/auditlog_test.go b/testing/auditlog_test.go index 324ef1f7e..fdbfb2c5c 100644 --- a/testing/auditlog_test.go +++ b/testing/auditlog_test.go @@ -13,6 +13,7 @@ import ( "fmt" "os" "path/filepath" + "strings" "testing" "github.com/corazawaf/coraza/v3/internal/auditlog" @@ -224,3 +225,111 @@ func TestAuditLogOnNoLog(t *testing.T) { t.Error(err) } } + +func TestAuditLogRequestMethodURIProtocol(t *testing.T) { + waf := corazawaf.NewWAF() + parser := seclang.NewParser(waf) + if err := parser.FromString(` + SecRuleEngine DetectionOnly + SecAuditEngine On + SecAuditLogFormat json + SecAuditLogType serial + `); 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() + + uri := "/some-url" + method := "POST" + proto := "HTTP/1.1" + + tx.ProcessURI(uri, method, proto) + // now we read file + if _, err := file.Seek(0, 0); err != nil { + t.Error(err) + } + tx.ProcessLogging() + var al2 auditlog.Log + if err := json.NewDecoder(file).Decode(&al2); err != nil { + t.Error(err) + } + trans := al2.Transaction() + if trans == nil { + t.Fatalf("Expected 1 transaction, got nil") + } + req := trans.Request() + if req == nil { + t.Fatalf("Expected 1 request, got nil") + } + if req.URI() != uri { + t.Fatalf("Expected %s uri, got %s", uri, req.URI()) + } + if req.Method() != method { + t.Fatalf("Expected %s method, got %s", method, req.Method()) + } + if req.Protocol() != proto { + t.Fatalf("Expected %s protocol, got %s", proto, req.Protocol()) + } +} + +func TestAuditLogRequestBody(t *testing.T) { + waf := corazawaf.NewWAF() + parser := seclang.NewParser(waf) + if err := parser.FromString(` + SecRuleEngine DetectionOnly + SecAuditEngine On + SecAuditLogFormat json + SecAuditLogType serial + SecRequestBodyAccess On + `); 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() + params := "somepost=data" + _, _, err = tx.ReadRequestBodyFrom(strings.NewReader(params)) + if err != nil { + t.Error(err) + } + _, err = tx.ProcessRequestBody() + if err != nil { + t.Error(err) + } + // now we read file + if _, err := file.Seek(0, 0); err != nil { + t.Error(err) + } + tx.ProcessLogging() + var al2 auditlog.Log + if err := json.NewDecoder(file).Decode(&al2); err != nil { + t.Error(err) + } + trans := al2.Transaction() + if trans == nil { + t.Fatalf("Expected 1 transaction, got nil") + } + req := trans.Request() + if req == nil { + t.Fatalf("Expected 1 request, got nil") + } + if req.Body() != params { + t.Fatalf("Expected %s uri, got %s", params, req.Body()) + } +} diff --git a/testing/coreruleset/.ftw.yml b/testing/coreruleset/.ftw.yml index f2875a90e..6af95b80a 100644 --- a/testing/coreruleset/.ftw.yml +++ b/testing/coreruleset/.ftw.yml @@ -4,11 +4,8 @@ testoverride: 920100-4: 'Invalid uri, Coraza not reached - 404 page not found' 920100-5: 'Invalid uri, Coraza not reached - 404 page not found' 920100-8: 'Go/http allows a colon in the path. Test expects status 400 or 403 (Apache behaviour)' - 920170-3: 'HEAD request with data. Go/http does not allow it - 400 Bad Request' 920270-4: 'Rule works, log contains 920270. Test expects status 400 (Apache behaviour)' 920272-5: 'Rule works, log contains 920272. Test expects status 400 (Apache behaviour)' 920290-1: 'Rule works, log contains 920290. Test expects status 400 (Apache behaviour)' - 920420-8: 'HEAD request with data. Go/http does not allow it - 400 Bad Request' - 920430-5: 'Test has expect_error, Go/http and Envoy return 400' 920430-8: 'Go/http does no allow HTTP/3.0 - 505 HTTP Version Not Supported' 932200-13: 'wip' diff --git a/testing/coreruleset/coreruleset_test.go b/testing/coreruleset/coreruleset_test.go index 2f826cbbe..a86f49119 100644 --- a/testing/coreruleset/coreruleset_test.go +++ b/testing/coreruleset/coreruleset_test.go @@ -224,9 +224,9 @@ SecRule REQUEST_HEADERS:X-CRS-Test "@rx ^.*$" \ defer r.Body.Close() w.Header().Set("Content-Type", "text/plain") switch { - case r.URL.Path == "/anything": + case r.URL.Path == "/anything", r.URL.Path == "/post": body, err := io.ReadAll(r.Body) - // Emulated httpbin behaviour: /anything endpoint acts as an echo server, writing back the request body + // Emulated httpbin behaviour: /anything and /post endpoints act as an echo server, writing back the request body if r.Header.Get("Content-Type") == "application/x-www-form-urlencoded" { // Tests 954120-1 and 954120-2 are the only two calling /anything with a POST and payload is urlencoded if err != nil { @@ -261,11 +261,11 @@ SecRule REQUEST_HEADERS:X-CRS-Test "@rx ^.*$" \ if err != nil { return err } - t, err := test.GetTestFromYaml(yaml) + ftwt, err := test.GetTestFromYaml(yaml) if err != nil { return err } - tests = append(tests, t) + tests = append(tests, *ftwt) return nil }) if err != nil { diff --git a/testing/coreruleset/go.mod b/testing/coreruleset/go.mod index c429e68d5..146bce036 100644 --- a/testing/coreruleset/go.mod +++ b/testing/coreruleset/go.mod @@ -1,44 +1,52 @@ module github.com/corazawaf/coraza/v3/testing/coreruleset -go 1.18 +go 1.19 require ( - github.com/bmatcuk/doublestar/v4 v4.3.0 - github.com/corazawaf/coraza-coreruleset v0.0.0-20230330101229-43b851256042 - github.com/corazawaf/coraza/v3 v3.0.0-20221004054810-060cedcb166d - github.com/coreruleset/go-ftw v0.4.9 - github.com/rs/zerolog v1.28.0 + github.com/bmatcuk/doublestar/v4 v4.6.1 + github.com/corazawaf/coraza-coreruleset v0.0.0-20231103220038-fd5c847140a6 + github.com/corazawaf/coraza/v3 v3.0.4 + github.com/coreruleset/go-ftw v0.6.4-0.20240208104922-1da98b9f4fa7 + github.com/rs/zerolog v1.32.0 ) require ( github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver v1.5.0 // indirect github.com/Masterminds/sprig v2.22.0+incompatible // indirect - github.com/corazawaf/libinjection-go v0.1.2 // indirect - github.com/fatih/color v1.13.0 // indirect - github.com/fsnotify/fsnotify v1.6.0 // indirect - github.com/goccy/go-yaml v1.8.10 // indirect - github.com/google/uuid v1.3.0 // indirect - github.com/huandu/xstrings v1.3.3 // indirect - github.com/icza/backscanner v0.0.0-20220812133752-2e60bffed4a2 // indirect - github.com/imdario/mergo v0.3.13 // indirect - github.com/knadh/koanf v1.4.4 // indirect - github.com/kyokomi/emoji v2.2.4+incompatible // indirect + github.com/corazawaf/libinjection-go v0.1.3 // indirect + github.com/coreruleset/ftw-tests-schema v1.1.0 // indirect + github.com/fatih/color v1.16.0 // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 // indirect + github.com/goccy/go-yaml v1.11.3 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/huandu/xstrings v1.4.0 // indirect + github.com/icza/backscanner v0.0.0-20230330133933-bf6beb754c70 // indirect + github.com/imdario/mergo v0.3.16 // indirect + github.com/knadh/koanf/maps v0.1.1 // indirect + github.com/knadh/koanf/parsers/yaml v0.1.0 // indirect + github.com/knadh/koanf/providers/env v0.1.0 // indirect + github.com/knadh/koanf/providers/file v0.1.0 // indirect + github.com/knadh/koanf/providers/rawbytes v0.1.0 // indirect + github.com/knadh/koanf/v2 v2.1.0 // indirect + github.com/kyokomi/emoji/v2 v2.2.12 // indirect github.com/magefile/mage v1.15.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.16 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect - github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/petar-dambovaliev/aho-corasick v0.0.0-20230725210150-fb29fc3c913e // indirect github.com/tidwall/gjson v1.17.0 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.1 // indirect github.com/yargevad/filepathx v1.0.0 // indirect - golang.org/x/crypto v0.15.0 // indirect - golang.org/x/net v0.18.0 // indirect - golang.org/x/sys v0.14.0 // indirect - golang.org/x/tools v0.6.0 // indirect - golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect + golang.org/x/crypto v0.19.0 // indirect + golang.org/x/net v0.21.0 // indirect + golang.org/x/sync v0.6.0 // indirect + golang.org/x/sys v0.17.0 // indirect + golang.org/x/tools v0.10.0 // indirect + golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + rsc.io/binaryregexp v0.2.0 // indirect ) diff --git a/testing/coreruleset/go.sum b/testing/coreruleset/go.sum index ef6176f6c..b70807184 100644 --- a/testing/coreruleset/go.sum +++ b/testing/coreruleset/go.sum @@ -1,296 +1,93 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= 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/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= -github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= -github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= -github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/aws/aws-sdk-go-v2 v1.9.2/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4= -github.com/aws/aws-sdk-go-v2/config v1.8.3/go.mod h1:4AEiLtAb8kLs7vgw2ZV3p2VZ1+hBavOc84hqxVNpCyw= -github.com/aws/aws-sdk-go-v2/credentials v1.4.3/go.mod h1:FNNC6nQZQUuyhq5aE5c7ata8o9e4ECGmS4lAXC7o1mQ= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.6.0/go.mod h1:gqlclDEZp4aqJOancXK6TN24aKhT0W0Ae9MHk3wzTMM= -github.com/aws/aws-sdk-go-v2/internal/ini v1.2.4/go.mod h1:ZcBrrI3zBKlhGFNYWvju0I3TR93I7YIgAfy82Fh4lcQ= -github.com/aws/aws-sdk-go-v2/service/appconfig v1.4.2/go.mod h1:FZ3HkCe+b10uFZZkFdvf98LHW21k49W8o8J366lqVKY= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.3.2/go.mod h1:72HRZDLMtmVQiLG2tLfQcaWLCssELvGl+Zf2WVxMmR8= -github.com/aws/aws-sdk-go-v2/service/sso v1.4.2/go.mod h1:NBvT9R1MEF+Ud6ApJKM0G+IkPchKS7p7c2YPKwHmBOk= -github.com/aws/aws-sdk-go-v2/service/sts v1.7.2/go.mod h1:8EzeIqfWt2wWT4rJVu3f21TfrhJ8AEMzVybRNSb/b4g= -github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= -github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bmatcuk/doublestar/v4 v4.3.0 h1:Ct0GphHCZaXvUh2Gqtk37Mzj1qWvXcW9XnXQs1GL9S0= -github.com/bmatcuk/doublestar/v4 v4.3.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/corazawaf/coraza-coreruleset v0.0.0-20230330101229-43b851256042 h1:WMAVBbS+u1zopf0gT1UBTtmmxVRCj9gY1dmnT14PsZM= -github.com/corazawaf/coraza-coreruleset v0.0.0-20230330101229-43b851256042/go.mod h1:h7fBXlh00atH/uVC9Lpjawg/RlJCsHjvyVk+bP3ylq8= -github.com/corazawaf/coraza/v3 v3.0.0-20221004054810-060cedcb166d h1:e7nLsrnie6309FYWPZg2kY2yQWhHslmfkzZTPVnpeqg= -github.com/corazawaf/coraza/v3 v3.0.0-20221004054810-060cedcb166d/go.mod h1:+ypLPFkX5j1GwKi+rqRZ57W3lSHReBdeVLh0o8qirI4= -github.com/corazawaf/libinjection-go v0.1.2 h1:oeiV9pc5rvJ+2oqOqXEAMJousPpGiup6f7Y3nZj5GoM= -github.com/corazawaf/libinjection-go v0.1.2/go.mod h1:OP4TM7xdJ2skyXqNX1AN1wN5nNZEmJNuWbNPOItn7aw= -github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/coreruleset/go-ftw v0.4.9 h1:4s4GPnn07d1S58QhoZlWy6UTdpjCTzhnhsfmh1cJ7E8= -github.com/coreruleset/go-ftw v0.4.9/go.mod h1:VLRHyrid8L2gB5AsnHpQnHfizBkZpKZuy3OUq0s8rCc= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I= +github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= +github.com/corazawaf/coraza-coreruleset v0.0.0-20231103220038-fd5c847140a6 h1:MjSFYff3j1L4zo3MNuqnQ19Jp5ps/sibntdtS/Kq/yk= +github.com/corazawaf/coraza-coreruleset v0.0.0-20231103220038-fd5c847140a6/go.mod h1:7rsocqNDkTCira5T0M7buoKR2ehh7YZiPkzxRuAgvVU= +github.com/corazawaf/coraza/v3 v3.0.4 h1:Llemgoh0hp2NggCwcWN8lNiV4Pfe+AWzf1oEcasT234= +github.com/corazawaf/coraza/v3 v3.0.4/go.mod h1:3fTYjY5BZv3nezLpH6NAap0gr3jZfbQWUAu2GF17ET4= +github.com/corazawaf/libinjection-go v0.1.3 h1:PUplAYho1BBl0tIVbhDsNRuVGIeUYSiCEc9oQpb2rJU= +github.com/corazawaf/libinjection-go v0.1.3/go.mod h1:OP4TM7xdJ2skyXqNX1AN1wN5nNZEmJNuWbNPOItn7aw= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/coreruleset/ftw-tests-schema v1.1.0 h1:3+NYrdLE3HVmOc3nGrisRBBvY9lGjePUrV+YkT5Ay3s= +github.com/coreruleset/ftw-tests-schema v1.1.0/go.mod h1:gRd9wBxjUI85HypWRDxJzbk1JqHC4KTxl0l/Y2p9QK4= +github.com/coreruleset/go-ftw v0.6.4-0.20240208104922-1da98b9f4fa7 h1:QLlaeBg39rV9zOCYIvtbfQnzJZ0kN8MwbZ7TOblnmx0= +github.com/coreruleset/go-ftw v0.6.4-0.20240208104922-1da98b9f4fa7/go.mod h1:cMCg+6N7YM4ZSVl6DB7pe9jZaNmja69SutMj8+XWEAQ= 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/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= -github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= -github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= -github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= -github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= +github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/foxcpp/go-mockdns v1.0.0 h1:7jBqxd3WDWwi/6WhDvacvH1XsN3rOLXyHM1uhvIx6FI= -github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= -github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= -github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/foxcpp/go-mockdns v1.0.0/go.mod h1:lgRN6+KxQBawyIghpnl5CezHFGS9VLzvtVlwxvzXTQ4= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= -github.com/goccy/go-yaml v1.8.10 h1:XpBOLD8cmOZlLYjUFPqSZZ+Ubi4/UKxO2eXyhg5WuAA= -github.com/goccy/go-yaml v1.8.10/go.mod h1:U/jl18uSupI5rdI2jmuCswEA2htH9eXfferR3KfscvA= +github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 h1:TQcrn6Wq+sKGkpyPvppOz99zsMBaUOKXq6HSv655U1c= +github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/goccy/go-yaml v1.11.3 h1:B3W9IdWbvrUu2OYQGwvU1nZtvMQJPBKgBUuweJjLj6I= +github.com/goccy/go-yaml v1.11.3/go.mod h1:wKnAMd44+9JAAnGQpWVEgBzGt3YuTaQ4uXoHvE4m7WU= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/hashicorp/consul/api v1.13.0/go.mod h1:ZlVrynguJKcYr54zGaDbaL3fOvKC9m72FhPvA8T35KQ= -github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI= -github.com/hashicorp/go-hclog v0.8.0/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= -github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= -github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= -github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= -github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= -github.com/hashicorp/go-plugin v1.0.1/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY= -github.com/hashicorp/go-retryablehttp v0.5.4/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= -github.com/hashicorp/go-rootcerts v1.0.1/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= -github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= -github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= -github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= -github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= -github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= -github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc= -github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= -github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4= -github.com/hashicorp/vault/api v1.0.4/go.mod h1:gDcqh3WGcR1cpF5AJz/B1UFheUEneMoIospckxBxk6Q= -github.com/hashicorp/vault/sdk v0.1.13/go.mod h1:B+hVj7TpuQY1Y/GPbCpffmgd+tSEwvhkWnjtSYCaS2M= -github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= -github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= -github.com/hjson/hjson-go/v4 v4.0.0 h1:wlm6IYYqHjOdXH1gHev4VoXCaW20HdQAGCxdOEEg2cs= -github.com/hjson/hjson-go/v4 v4.0.0/go.mod h1:KaYt3bTw3zhBjYqnXkYywcYctk0A2nxeEFTse3rH13E= -github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4= -github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= -github.com/icza/backscanner v0.0.0-20220812133752-2e60bffed4a2 h1:Cra36ePFgA2F/pnhd1qq++SB/VL5RyDkPOIH2a3RlDc= -github.com/icza/backscanner v0.0.0-20220812133752-2e60bffed4a2/go.mod h1:GYeBD1CF7AqnKZK+UCytLcY3G+UKo0ByXX/3xfdNyqQ= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU= +github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/icza/backscanner v0.0.0-20230330133933-bf6beb754c70 h1:xrd41BUTgqxyYFfFwGdt/bnwS8KNYzPraj8WgvJ5NWk= +github.com/icza/backscanner v0.0.0-20230330133933-bf6beb754c70/go.mod h1:GYeBD1CF7AqnKZK+UCytLcY3G+UKo0ByXX/3xfdNyqQ= github.com/icza/mighty v0.0.0-20180919140131-cfd07d671de6 h1:8UsGZ2rr2ksmEru6lToqnXgA8Mz1DP11X4zSJ159C3k= github.com/icza/mighty v0.0.0-20180919140131-cfd07d671de6/go.mod h1:xQig96I1VNBDIWGCdTt54nHt6EeI639SmHycLYL7FkA= -github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= -github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= -github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= -github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= -github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= -github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= -github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= -github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/knadh/koanf v1.4.4 h1:d2jY5nCCeoaiqvEKSBW9rEc93EfNy/XWgWsSB3j7JEA= -github.com/knadh/koanf v1.4.4/go.mod h1:Hgyjp4y8v44hpZtPzs7JZfRAW5AhN7KfZcwv1RYggDs= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= -github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kyokomi/emoji v2.2.4+incompatible h1:np0woGKwx9LiHAQmwZx79Oc0rHpNw3o+3evou4BEPv4= -github.com/kyokomi/emoji v2.2.4+incompatible/go.mod h1:mZ6aGCD7yk8j6QY6KICwnZ2pxoszVseX1DNoGtU2tBA= +github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= +github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= +github.com/knadh/koanf/maps v0.1.1 h1:G5TjmUh2D7G2YWf5SQQqSiHRJEjaicvU0KpypqB3NIs= +github.com/knadh/koanf/maps v0.1.1/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI= +github.com/knadh/koanf/parsers/yaml v0.1.0 h1:ZZ8/iGfRLvKSaMEECEBPM1HQslrZADk8fP1XFUxVI5w= +github.com/knadh/koanf/parsers/yaml v0.1.0/go.mod h1:cvbUDC7AL23pImuQP0oRw/hPuccrNBS2bps8asS0CwY= +github.com/knadh/koanf/providers/env v0.1.0 h1:LqKteXqfOWyx5Ab9VfGHmjY9BvRXi+clwyZozgVRiKg= +github.com/knadh/koanf/providers/env v0.1.0/go.mod h1:RE8K9GbACJkeEnkl8L/Qcj8p4ZyPXZIQ191HJi44ZaQ= +github.com/knadh/koanf/providers/file v0.1.0 h1:fs6U7nrV58d3CFAFh8VTde8TM262ObYf3ODrc//Lp+c= +github.com/knadh/koanf/providers/file v0.1.0/go.mod h1:rjJ/nHQl64iYCtAW2QQnF0eSmDEX/YZ/eNFj5yR6BvA= +github.com/knadh/koanf/providers/rawbytes v0.1.0 h1:dpzgu2KO6uf6oCb4aP05KDmKmAmI51k5pe8RYKQ0qME= +github.com/knadh/koanf/providers/rawbytes v0.1.0/go.mod h1:mMTB1/IcJ/yE++A2iEZbY1MLygX7vttU+C+S/YmPu9c= +github.com/knadh/koanf/v2 v2.1.0 h1:eh4QmHHBuU8BybfIJ8mB8K8gsGCD/AUQTdwGq/GzId8= +github.com/knadh/koanf/v2 v2.1.0/go.mod h1:4mnTRbZCK+ALuBXHZMjDfG9y714L7TykVnZkXbMU3Es= +github.com/kyokomi/emoji/v2 v2.2.12 h1:sSVA5nH9ebR3Zji1o31wu3yOwD1zKXQA2z0zUyeit60= +github.com/kyokomi/emoji/v2 v2.2.12/go.mod h1:JUcn42DTdsXJo1SWanHh4HKDEyPaR5CqkmoirZZP9qE= github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg= github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= -github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= -github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= -github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA= -github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= -github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= -github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= +github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= -github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= -github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/npillmayer/nestext v0.1.3/go.mod h1:h2lrijH8jpicr25dFY+oAJLyzlya6jhnuG+zWp9L0Uk= -github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= -github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= -github.com/pelletier/go-toml v1.9.1 h1:a6qW1EVNZWH9WGI6CsYdD8WAylkoXBS5yv0XHlh17Tc= github.com/petar-dambovaliev/aho-corasick v0.0.0-20230725210150-fb29fc3c913e h1:POJco99aNgosh92lGqmx7L1ei+kCymivB/419SD15PQ= github.com/petar-dambovaliev/aho-corasick v0.0.0-20230725210150-fb29fc3c913e/go.mod h1:EHPiTAKtiFmrMldLUNswFwfZ2eJIYBHktdaUTZxYWRw= -github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= -github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= -github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= -github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/rhnvrm/simples3 v0.6.1/go.mod h1:Y+3vYm2V7Y4VijFoJHHTrja6OgPrJ2cBti8dPGkC3sA= -github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= -github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= -github.com/rs/zerolog v1.28.0 h1:MirSo27VyNi7RJYP3078AA1+Cyzd2GB66qy3aUHvsWY= -github.com/rs/zerolog v1.28.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0= -github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= -github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0= +github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/tidwall/gjson v1.17.0 h1:/Jocvlh98kcTfpN2+JzGQWQcqrPQwDrVEMApx/M5ZwM= github.com/tidwall/gjson v1.17.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= @@ -300,172 +97,26 @@ github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/yargevad/filepathx v1.0.0 h1:SYcT+N3tYGi+NvazubCNlvgIPbzAk7i7y2dwg3I5FYc= github.com/yargevad/filepathx v1.0.0/go.mod h1:BprfX/gpYNJHJfc35GjRRpVcwWXS89gGulUIU5tK3tA= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -go.etcd.io/etcd/api/v3 v3.5.4/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A= -go.etcd.io/etcd/client/pkg/v3 v3.5.4/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= -go.etcd.io/etcd/client/v3 v3.5.4/go.mod h1:ZaRkVgBZC+L+dLCjTcF1hRXpgZXQPOvnA/Ak/gq3kiY= -go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= -go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -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/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA= -golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= -golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= -golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU= +golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= -golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= -golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= -google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= -gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/tools v0.10.0 h1:tvDr/iQoUqNdohiYm0LmmKcBk+q86lb9EprIUFhHHGg= +golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= +rsc.io/binaryregexp v0.2.0 h1:HfqmD5MEmC0zvwBuF187nq9mdnXjXsSivRiXN7SmRkE= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= diff --git a/types/rule_match.go b/types/rule_match.go index 8254661e8..a45e332af 100644 --- a/types/rule_match.go +++ b/types/rule_match.go @@ -45,5 +45,6 @@ type MatchedRule interface { Rule() RuleMetadata AuditLog() string + ErrorLog() string } diff --git a/waf.go b/waf.go index 348cafad3..219519018 100644 --- a/waf.go +++ b/waf.go @@ -4,8 +4,11 @@ package coraza import ( + "context" "fmt" + "strings" + "github.com/corazawaf/coraza/v3/experimental" "github.com/corazawaf/coraza/v3/internal/corazawaf" "github.com/corazawaf/coraza/v3/internal/seclang" "github.com/corazawaf/coraza/v3/types" @@ -57,19 +60,7 @@ func NewWAF(config WAFConfig) (WAF, error) { } } - if a := c.auditLog; a != nil { - if a.relevantOnly { - waf.AuditEngine = types.AuditEngineRelevantOnly - } else { - waf.AuditEngine = types.AuditEngineOn - } - - waf.AuditLogParts = a.parts - - if a.writer != nil { - waf.SetAuditLogWriter(a.writer) - } - } + populateAuditLog(waf, c) if err := waf.InitAuditLogWriter(); err != nil { return nil, fmt.Errorf("invalid WAF config from audit log: %w", err) @@ -110,6 +101,26 @@ func NewWAF(config WAFConfig) (WAF, error) { return wafWrapper{waf: waf}, nil } +func populateAuditLog(waf *corazawaf.WAF, c *wafConfig) { + if c.auditLog == nil { + return + } + + if c.auditLog.relevantOnly { + waf.AuditEngine = types.AuditEngineRelevantOnly + } else { + waf.AuditEngine = types.AuditEngineOn + } + + if len(c.auditLog.parts) > 0 { + waf.AuditLogParts = c.auditLog.parts + } + + if c.auditLog.writer != nil { + waf.SetAuditLogWriter(c.auditLog.writer) + } +} + type wafWrapper struct { waf *corazawaf.WAF } @@ -121,5 +132,15 @@ func (w wafWrapper) NewTransaction() types.Transaction { // NewTransactionWithID implements the same method on WAF. func (w wafWrapper) NewTransactionWithID(id string) types.Transaction { - return w.waf.NewTransactionWithID(id) + id = strings.TrimSpace(id) + if len(id) == 0 { + w.waf.Logger.Warn().Msg("Empty ID passed for new transaction") + } + + return w.waf.NewTransactionWithOptions(corazawaf.Options{Context: context.Background(), ID: id}) +} + +// NewTransaction implements the same method on WAF. +func (w wafWrapper) NewTransactionWithOptions(opts experimental.Options) types.Transaction { + return w.waf.NewTransactionWithOptions(opts) } diff --git a/waf_test.go b/waf_test.go index 08d52dfb6..d4ee3e9e6 100644 --- a/waf_test.go +++ b/waf_test.go @@ -5,7 +5,12 @@ package coraza import ( "errors" + "reflect" "testing" + + "github.com/corazawaf/coraza/v3/experimental/plugins/plugintypes" + "github.com/corazawaf/coraza/v3/internal/corazawaf" + "github.com/corazawaf/coraza/v3/types" ) func TestRequestBodyLimit(t *testing.T) { @@ -103,3 +108,73 @@ func TestResponseBodyLimit(t *testing.T) { }) } } + +type testAuditLogWriter struct { + plugintypes.AuditLogWriter +} + +func (*testAuditLogWriter) Init(plugintypes.AuditLogConfig) error { + return nil +} + +func TestPopulateAuditLog(t *testing.T) { + writer := &testAuditLogWriter{} + + testCases := map[string]struct { + config *wafConfig + check func(*testing.T, *corazawaf.WAF) + }{ + "empty config": { + config: &wafConfig{}, + check: func(*testing.T, *corazawaf.WAF) {}, + }, + "with relevant only": { + config: &wafConfig{ + auditLog: &auditLogConfig{ + relevantOnly: true, + }, + }, + check: func(t *testing.T, waf *corazawaf.WAF) { + if waf.AuditEngine != types.AuditEngineRelevantOnly { + t.Fatal("expected AuditLogRelevantOnly to be true") + } + }, + }, + "with parts": { + config: &wafConfig{ + auditLog: &auditLogConfig{ + parts: []types.AuditLogPart{ + types.AuditLogPartRequestHeaders, + types.AuditLogPartResponseBody, + }, + }, + }, + check: func(t *testing.T, waf *corazawaf.WAF) { + if want, have := []types.AuditLogPart{ + types.AuditLogPartRequestHeaders, + types.AuditLogPartResponseBody, + }, waf.AuditLogParts; len(want) != len(have) { + t.Fatalf("unexpected AuditLogParts: want %v, have %v", want, have) + } + }, + }, + "with audit log writer": { + config: &wafConfig{ + auditLog: &auditLogConfig{writer: writer}, + }, + check: func(t *testing.T, waf *corazawaf.WAF) { + if reflect.DeepEqual(waf.AuditLogWriter(), &writer) { + t.Fatal("expected AuditLogWriter to be set") + } + }, + }, + } + + for name, tCase := range testCases { + t.Run(name, func(t *testing.T) { + waf := &corazawaf.WAF{} + populateAuditLog(waf, tCase.config) + tCase.check(t, waf) + }) + } +}