Skip to content

Commit

Permalink
Revert "remove example directory to keep things simple"
Browse files Browse the repository at this point in the history
This reverts commit 825e8ea.
  • Loading branch information
blotus committed Dec 6, 2023
1 parent 942968c commit 778ce24
Show file tree
Hide file tree
Showing 11 changed files with 286 additions and 0 deletions.
1 change: 1 addition & 0 deletions examples/http-server/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
http-server
57 changes: 57 additions & 0 deletions examples/http-server/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# HTTP-Server with Coraza

This example is intended to provide a straightforward way to spin up Coraza and grasp its behaviour.

## Run the example

```bash
go run .
```

The server will be reachable at `http://localhost:8090`.

```bash
# True positive request (403 Forbidden)
curl -i 'localhost:8090/hello?id=0'
# True negative request (200 OK)
curl -i 'localhost:8090/hello'
```

You can customise the rules to be used by using the `DIRECTIVES_FILE` environment variable to load a directives file:

```bash
DIRECTIVES_FILE=my_directives.conf go run .
```

You can also customise response body and response headers by using `RESPONSE_HEADERS` and `RESPONSE_BODY` environment variables respectively:

```bash
RESPONSE_BODY=creditcard go run .
```

And then

```bash
# True positive request (403 Forbidden) due to matching response body
curl -i 'localhost:8090/hello'
```

## Customize WAF rules

The configuration of the WAF relies on [default.conf](https://github.com/corazawaf/coraza/blob/main/examples/http-server/default.conf). Feel free to play with it.

## Customize server behaviour

The following snippet shows an example of code that may be added to the [exampleHandler](https://github.com/corazawaf/coraza/blob/main/examples/http-server/main.go#L17) in order to make the example capable of echoing the body request. It comes in handy for testing rules that match the response body.

```go
func exampleHandler(w http.ResponseWriter, req *http.Request) {
w.Header().Set("Content-Type", "text/plain")
var buf bytes.Buffer
_, err := io.Copy(&buf, req.Body)
if err != nil {
log.Fatalf("handler can not read request body: %v", err)
}
w.Write(buf.Bytes())
}
```
7 changes: 7 additions & 0 deletions examples/http-server/default.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
SecDebugLogLevel 9
SecDebugLog /dev/stdout

SecRule ARGS:id "@eq 0" "id:1, phase:1,deny, status:403,msg:'Invalid id',log,auditlog"

SecRequestBodyAccess On
SecRule REQUEST_BODY "@contains password" "id:100, phase:2,deny, status:403,msg:'Invalid request body',log,auditlog"
12 changes: 12 additions & 0 deletions examples/http-server/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
module github.com/corazawaf/coraza/v3/examples/http-server

go 1.19

require github.com/corazawaf/coraza/v3 v3.0.0-20220914101451-05d352c89b24

require (
github.com/magefile/mage v1.15.0 // 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
)
11 changes: 11 additions & 0 deletions examples/http-server/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
github.com/corazawaf/coraza/v3 v3.0.0-20220914101451-05d352c89b24 h1:dy3992o5ue40g1QWKupjsBwZTRWagsuiGcOsbV0b4xs=
github.com/corazawaf/coraza/v3 v3.0.0-20220914101451-05d352c89b24/go.mod h1:xhc7feR6FUfYgmBmRw3UObvLiyzT3XPQtlJD+huy+Mc=
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/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=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
62 changes: 62 additions & 0 deletions examples/http-server/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package main

import (
"fmt"
"log"
"net/http"
"os"
"strings"

"github.com/crowdsecurity/coraza/v3"
txhttp "github.com/crowdsecurity/coraza/v3/http"
"github.com/crowdsecurity/coraza/v3/types"
)

func exampleHandler(w http.ResponseWriter, req *http.Request) {
w.Header().Set("Content-Type", "text/plain")
resBody := "Hello world, transaction not disrupted."

if body := os.Getenv("RESPONSE_BODY"); body != "" {
resBody = body
}

if h := os.Getenv("RESPONSE_HEADERS"); h != "" {
key, val, _ := strings.Cut(h, ":")
w.Header().Set(key, val)
}

// The server generates the response
w.Write([]byte(resBody))
}

func main() {
waf := createWAF()

http.Handle("/", txhttp.WrapHandler(waf, http.HandlerFunc(exampleHandler)))

fmt.Println("Server is running. Listening port: 8090")

log.Fatal(http.ListenAndServe(":8090", nil))
}

func createWAF() coraza.WAF {
directivesFile := "./default.conf"
if s := os.Getenv("DIRECTIVES_FILE"); s != "" {
directivesFile = s
}

waf, err := coraza.NewWAF(
coraza.NewWAFConfig().
WithErrorCallback(logError).
WithDirectivesFromFile(directivesFile),
)
if err != nil {
log.Fatal(err)
}
return waf
}

func logError(error types.MatchedRule) {
msg := error.ErrorLog()
fmt.Printf("[logError][%s] %s\n", error.Rule().Severity(), msg)
}
115 changes: 115 additions & 0 deletions examples/http-server/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package main

import (
"bytes"
"log"
"net/http"
"net/http/httptest"
"os"
"testing"

txhttp "github.com/crowdsecurity/coraza/v3/http"
)

func setupTestServer(t *testing.T) *httptest.Server {
t.Helper()
waf := createWAF()
return httptest.NewServer(txhttp.WrapHandler(waf, http.HandlerFunc(exampleHandler)))
}

func doGetRequest(t *testing.T, getPath string) int {
t.Helper()
resp, err := http.Get(getPath)
if err != nil {
log.Fatalln(err)
}
resp.Body.Close()
return resp.StatusCode
}

func doPostRequest(t *testing.T, postPath string, data []byte) int {
t.Helper()
resp, err := http.Post(postPath, "application/x-www-form-urlencoded", bytes.NewBuffer(data))
if err != nil {
log.Fatalln(err)
}
resp.Body.Close()
return resp.StatusCode
}

func TestHttpServer(t *testing.T) {
tests := []struct {
name string
path string
expStatus int
envVars map[string]string
body []byte // if body is populated, POST request is sent
}{
{"negative", "/", 200, nil, nil},
{"positive for query parameter", "/?id=0", 403, nil, nil},
{
"positive for response body",
"/",
403,
map[string]string{
"DIRECTIVES_FILE": "./testdata/response-body.conf",
"RESPONSE_BODY": "creditcard",
},
nil,
},
{
"positive for response header",
"/",
403,
map[string]string{
"DIRECTIVES_FILE": "./testdata/response-headers.conf",
"RESPONSE_HEADERS": "foo:bar",
},
nil,
},
{
"negative for request body process partial (payload beyond processed body)",
"/",
200,
map[string]string{
"DIRECTIVES_FILE": "./testdata/request-body-limits-processpartial.conf",
},
[]byte("beyond the limit script"),
},
{
"positive for response body limit reject",
"/",
413,
map[string]string{
"DIRECTIVES_FILE": "./testdata/response-body-limits-reject.conf",
"RESPONSE_BODY": "response body beyond the limit",
},
nil,
},
}
// Perform tests
for _, tc := range tests {
tt := tc
var statusCode int
t.Run(tt.name, func(t *testing.T) {
if len(tt.envVars) > 0 {
for k, v := range tt.envVars {
os.Setenv(k, v)
defer os.Unsetenv(k)
}
}

// Spin up the test server
testServer := setupTestServer(t)
defer testServer.Close()
if tt.body == nil {
statusCode = doGetRequest(t, testServer.URL+tt.path)
} else {
statusCode = doPostRequest(t, testServer.URL+tt.path, tt.body)
}
if want, have := tt.expStatus, statusCode; want != have {
t.Errorf("Unexpected status code, want: %d, have: %d", want, have)
}
})
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
SecDebugLogLevel 9
SecDebugLog /dev/stdout
SecRequestBodyAccess On
SecRequestBodyInMemoryLimit 5
SecRequestBodyLimit 6
SecRequestBodyLimitAction ProcessPartial
SecRule REQUEST_BODY "@contains script" "id:200, phase:2, deny, status:403, msg:'Invalid request body',log,auditlog"
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
SecDebugLogLevel 9
SecDebugLog /dev/stdout
SecResponseBodyAccess On
SecResponseBodyMimeType text/plain
SecResponseBodyLimit 6
SecResponseBodyLimitAction Reject
5 changes: 5 additions & 0 deletions examples/http-server/testdata/response-body.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
SecDebugLogLevel 9
SecDebugLog /dev/stdout
SecResponseBodyAccess On
SecResponseBodyMimeType text/plain
SecRule RESPONSE_BODY "@contains creditcard" "id:200, phase:4,deny, status:403,msg:'Invalid response body',log,auditlog"
3 changes: 3 additions & 0 deletions examples/http-server/testdata/response-headers.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
SecDebugLogLevel 9
SecDebugLog /dev/stdout
SecRule RESPONSE_HEADERS:Foo "@pm bar" "id:199,phase:3,deny,t:lowercase,deny, status:403,msg:'Invalid response header',log,auditlog"

0 comments on commit 778ce24

Please sign in to comment.