Skip to content

Commit

Permalink
Add support for Coraza via 'modsecurity-use-coraza'
Browse files Browse the repository at this point in the history
  • Loading branch information
mac-chaffee committed Nov 28, 2022
1 parent 635669c commit 4cc3494
Show file tree
Hide file tree
Showing 10 changed files with 178 additions and 6 deletions.
1 change: 1 addition & 0 deletions docs/.gitignore
@@ -1,4 +1,5 @@
package-lock.json
public/
resources/
!static/resources/
node_modules/
7 changes: 5 additions & 2 deletions docs/content/en/docs/configuration/keys.md
Expand Up @@ -415,6 +415,7 @@ The table below describes all supported configuration keys.
| [`modsecurity-timeout-hello`](#modsecurity) | time with suffix | Global | `100ms` |
| [`modsecurity-timeout-idle`](#modsecurity) | time with suffix | Global | `30s` |
| [`modsecurity-timeout-processing`](#modsecurity) | time with suffix | Global | `1s` |
| [`modsecurity-use-coraza`](#modsecurity) | [true\|false] | Global | `false` |
| [`nbproc-ssl`](#nbproc) | number of process | Global | `0` |
| [`nbthread`](#nbthread) | number of threads | Global | |
| [`no-tls-redirect-locations`](#ssl-redirect) | comma-separated list of URIs | Global | `/.well-known/acme-challenge` |
Expand Down Expand Up @@ -1997,6 +1998,7 @@ See also:
| `modsecurity-timeout-idle` | `Global` | `30s` | |
| `modsecurity-timeout-processing` | `Global` | `1s` | |
| `modsecurity-timeout-server` | `Global` | `5s` | v0.10 |
| `modsecurity-use-coraza` | `Global` | `false` | v0.14 |


Configure modsecurity agent. These options only have effect if `modsecurity-endpoints`
Expand All @@ -2019,10 +2021,11 @@ The following keys are supported:
* `modsecurity-timeout-idle`: Defines the maximum time to wait before close an idle connection. Default value is `30s`.
* `modsecurity-timeout-processing`: Defines the maximum time to wait for the whole ModSecurity processing. Default value is `1s`.
* `modsecurity-timeout-server`: Defines the maximum time to wait for an agent response. Configures the haproxy's timeout server. Defaults to `5s` if not configured.
* `modsecurity-use-coraza`: Defines whether the generated SPOE config should include Coraza-specific values. In order to use Coraza instead of Modsecurity, you must set this to "true" and also set `modsecurity-args` based on the instructions in the [coraza-spoa repository](https://github.com/corazawaf/coraza-spoa). A full example can be found [here]({{% relref "../examples/modsecurity#using-coraza-instead-of-modsecurity" %}}).

See also:

* [example]({{% relref "../examples/modsecurity" %}}) page.
* [modsecurity example]({{% relref "../examples/modsecurity" %}}) page.
* [`waf`](#waf) configuration key.
* https://www.haproxy.org/download/2.0/doc/SPOE.txt
* https://docs.haproxy.org/2.4/configuration.html#9.3
Expand Down Expand Up @@ -2858,7 +2861,7 @@ See also:
| `waf-mode` | `Path` | `deny` | v0.9 |

Defines which web application firewall (WAF) implementation should be used
to validate requests. Currently the only supported value is `modsecurity`.
to validate requests. Currently the only supported value is `modsecurity`, which also supports Coraza endpoints when `modsecurity-use-coraza` is set to "true".

This configuration has no effect if the ModSecurity endpoints are not configured.

Expand Down
29 changes: 27 additions & 2 deletions docs/content/en/docs/examples/modsecurity.md
Expand Up @@ -43,8 +43,8 @@ Check if the agent is up and running:

```
$ kubectl -n ingress-controller get deployment modsecurity-spoa
NAME READY UP-TO-DATE AVAILABLE AGE
modsecurity-spoa 3/3 3 3 7s
NAME READY UP-TO-DATE AVAILABLE AGE
modsecurity-spoa 3/3 3 3 7s
```


Expand Down Expand Up @@ -186,3 +186,28 @@ modsecurity-spoa-6596c6b444-cht27 2/2 Running 0 14m
modsecurity-spoa-6596c6b444-kw2tr 2/2 Running 0 14m
modsecurity-spoa-6596c6b444-mkndw 2/2 Running 0 14m
```

## Using Coraza instead of ModSecurity

Since the maintainers of ModSecurity are dropping support in 2024, [OWASP has created a replacement called Coraza](https://coreruleset.org/20211222/talking-about-modsecurity-and-the-new-coraza-waf/) which is a drop-in replacement for ModSecurity.

In order to use Coraza, the process is essentially the same as described in the above sections, with a few exceptions. First, in the haproxy-ingress ConfigMap, add the following two additional keys:

```yaml
modsecurity-use-coraza: true
modsecurity-args: "app=hdr(host) id=unique-id src-ip=src src-port=src_port dst-ip=dst dst-port=dst_port method=method path=path query=query version=req.ver headers=req.hdrs body=req.body"
```

You may require different `modsecurity-args` depending on your Coraza config and your version of coraza-spoa. Check the [coraza-spoa README](https://github.com/corazawaf/coraza-spoa) for full details.

Second, you'll need to change the spoa-modsecurity container image to a coraza-spoa image and create a configmap to hold the Coraza config.yaml. A complete example with all the changes can be found [here](/resources/coraza-deployment.yaml).

{{% alert title="Warning" color="warning" %}}
The coraza-spoa image that we provide in the above example is based on [an experimental branch of coraza-spoa](https://github.com/corazawaf/coraza-spoa/pull/36). For production environments, it would be best to wait until the experimental changes are merged and [an official image is released](https://github.com/corazawaf/coraza-spoa/issues/37).
{{% /alert %}}

### Troubleshooting Coraza

* Ensure that the `bind` port in Coraza's config.yaml matches the port in `modsecurity-endpoints`.
* Pay close attention to the `modsecurity-args` (specifically the `app` arg) and make sure they are set according to the coraza-spoa docs since the exact args may vary depending on your version of coraza-spoa.
* For more complex issues, it may help to set `log_level: debug` in Coraza.
73 changes: 73 additions & 0 deletions docs/static/resources/coraza-deployment.yaml
@@ -0,0 +1,73 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
run: coraza-spoa
name: coraza-spoa
namespace: ingress-controller
spec:
replicas: 3
selector:
matchLabels:
run: coraza-spoa
template:
metadata:
labels:
run: coraza-spoa
spec:
containers:
- name: coraza-spoa
# NOTE: Built based on this PR: https://github.com/corazawaf/coraza-spoa/pull/36
# An official coraza-spoa image will be released by the upstream project soon.
image: quay.io/jcmoraisjr/coraza-spoa:experimental
ports:
- containerPort: 12345
name: spop
protocol: TCP
resources:
limits:
cpu: 200m
memory: 150Mi
requests:
cpu: 200m
memory: 150Mi
livenessProbe:
failureThreshold: 3
initialDelaySeconds: 30
periodSeconds: 5
successThreshold: 1
tcpSocket:
port: 12345
timeoutSeconds: 4
volumeMounts:
- name: coraza-config
mountPath: /config.yaml
subPath: config.yaml
readOnly: true
volumes:
- name: coraza-config
configMap:
name: coraza-config
---
apiVersion: v1
kind: ConfigMap
metadata:
name: coraza-config
namespace: ingress-controller
data:
# Check the official documentation: https://github.com/corazawaf/coraza-spoa
config.yaml: |
bind: :12345
default_application: default_app
applications:
default_app:
include:
- /etc/coraza-spoa/coraza.conf
- /etc/coraza-spoa/crs-setup.conf
- /etc/coraza-spoa/rules/*.conf
transaction_ttl: 60000
transaction_active_limit: 100000
log_level: info
log_file: /dev/stdout
1 change: 1 addition & 0 deletions pkg/converters/ingress/annotations/global.go
Expand Up @@ -345,6 +345,7 @@ func (c *updater) buildGlobalModSecurity(d *globalData) {
d.global.ModSecurity.Timeout.Processing = c.validateTime(d.mapper.Get(ingtypes.GlobalModsecurityTimeoutProcessing))
d.global.ModSecurity.Timeout.Server = c.validateTime(d.mapper.Get(ingtypes.GlobalModsecurityTimeoutServer))
d.global.ModSecurity.Args = utils.Split(d.mapper.Get(ingtypes.GlobalModsecurityArgs).Value, " ")
d.global.ModSecurity.UseCoraza = d.mapper.Get(ingtypes.GlobalModsecurityUseCoraza).Bool()
}

func (c *updater) buildGlobalDNS(d *globalData) {
Expand Down
1 change: 1 addition & 0 deletions pkg/converters/ingress/types/global.go
Expand Up @@ -99,6 +99,7 @@ const (
GlobalModsecurityTimeoutIdle = "modsecurity-timeout-idle"
GlobalModsecurityTimeoutProcessing = "modsecurity-timeout-processing"
GlobalModsecurityTimeoutServer = "modsecurity-timeout-server"
GlobalModsecurityUseCoraza = "modsecurity-use-coraza"
GlobalNbprocBalance = "nbproc-balance"
GlobalNbprocSSL = "nbproc-ssl"
GlobalNbthread = "nbthread"
Expand Down
50 changes: 48 additions & 2 deletions pkg/haproxy/instance_test.go
Expand Up @@ -4801,6 +4801,8 @@ func TestModSecurity(t *testing.T) {
modsecExp string
modsecAgentArgs []string
modsecAgentExp string
modsecUseCoraza bool
modsecOtherExp string
}{
{
waf: "modsecurity",
Expand Down Expand Up @@ -4882,12 +4884,39 @@ func TestModSecurity(t *testing.T) {
timeout connect 1s
timeout server 2s
server modsec-spoa0 10.0.0.101:12345`,
modsecOtherExp: `
messages check-request
option var-prefix modsec`,

modsecAgentArgs: []string{"unique-id", "method", "path", "query", "req.ver", "req.hdrs_bin"},
modsecAgentExp: `
spoe-message check-request
args unique-id method path query req.ver req.hdrs_bin
event on-backend-http-request`,
},
// Test modsecurity-use-coraza
{
waf: "modsecurity",
wafmode: "deny",
endpoints: []string{"10.0.0.101:12345"},
backendExp: `
filter spoe engine modsecurity config /etc/haproxy/spoe-modsecurity.conf
http-request deny deny_status 504 if { var(txn.coraza.error) -m int gt 0 }
http-request deny if !{ var(txn.coraza.fail) -m int eq 0 }`,
modsecExp: `
timeout connect 1s
timeout server 2s
server modsec-spoa0 10.0.0.101:12345`,
modsecAgentArgs: []string{"app=hdr(host)", "id=unique-id", "src-ip=src", "src-port=src_port", "dst-ip=dst", "dst-port=dst_port", "method=method", "path=path", "query=query", "version=req.ver", "headers=req.hdrs", "body=req.body"},
modsecAgentExp: `
spoe-message coraza-req
args app=hdr(host) id=unique-id src-ip=src src-port=src_port dst-ip=dst dst-port=dst_port method=method path=path query=query version=req.ver headers=req.hdrs body=req.body
event on-backend-http-request`,
modsecOtherExp: `
messages coraza-req
option var-prefix coraza`,
modsecUseCoraza: true,
},
}
for _, test := range testCases {
c := setup(t)
Expand All @@ -4913,7 +4942,7 @@ func TestModSecurity(t *testing.T) {
globalModsec.Timeout.Connect = "1s"
globalModsec.Timeout.Server = "2s"
globalModsec.Args = test.modsecAgentArgs

globalModsec.UseCoraza = test.modsecUseCoraza
c.Update()

var modsec string
Expand All @@ -4922,18 +4951,35 @@ func TestModSecurity(t *testing.T) {
backend spoe-modsecurity
mode tcp` + test.modsecExp
}
c.checkConfig(`
// unique-id-format must be set when using Coraza
if test.modsecUseCoraza {
c.checkConfig(`
<<global>>
<<defaults>>
unique-id-format %[uuid()]
backend d1_app_8080
mode http` + test.backendExp + `
server s1 172.17.0.11:8080 weight 100
<<backends-default>>
<<frontends-default>>
<<support>>` + modsec)
} else {
c.checkConfig(`
<<global>>
<<defaults>>
backend d1_app_8080
mode http` + test.backendExp + `
server s1 172.17.0.11:8080 weight 100
<<backends-default>>
<<frontends-default>>
<<support>>` + modsec)
}
if test.modsecAgentExp != "" {
c.containsText("spoe-modsecurity.conf", c.readConfig(c.tempdir+"/spoe-modsecurity.conf"), test.modsecAgentExp)
}
if test.modsecOtherExp != "" {
c.containsText("spoe-modsecurity.conf", c.readConfig(c.tempdir+"/spoe-modsecurity.conf"), test.modsecOtherExp)
}

c.logger.CompareLogging(defaultLogging)
c.teardown()
Expand Down
1 change: 1 addition & 0 deletions pkg/haproxy/types/types.go
Expand Up @@ -183,6 +183,7 @@ type ModSecurityConfig struct {
Endpoints []string
Timeout ModSecurityTimeoutConfig
Args []string
UseCoraza bool
}

// CookieConfig ...
Expand Down
9 changes: 9 additions & 0 deletions rootfs/etc/templates/haproxy/haproxy.tmpl
Expand Up @@ -184,6 +184,10 @@ defaults
{{- if $global.Timeout.Tunnel }}
timeout tunnel {{ $global.Timeout.Tunnel }}
{{- end }}
{{- /* Coraza will crash if the unique-id isn't set, which requires us to set the format here */}}
{{- if $global.ModSecurity.UseCoraza }}
unique-id-format %[uuid()]
{{- end }}
{{- range $snippet := $global.CustomDefaults }}
{{ $snippet }}
{{- end }}
Expand Down Expand Up @@ -582,7 +586,12 @@ backend {{ $backend.ID }}
{{- range $i, $waf := $wafCfg.Items }}
{{- if eq $waf.Mode "deny" }}
{{- range $pathIDs := $wafCfg.PathIDs $i }}
{{- if $global.ModSecurity.UseCoraza }}
http-request deny deny_status 504 if { var(txn.coraza.error) -m int gt 0 }
http-request deny if !{ var(txn.coraza.fail) -m int eq 0 }
{{- else }}
http-request deny if { var(txn.modsec.code) -m int gt 0 }
{{- end }}
{{- if $pathIDs }} { var(txn.pathID) -m str {{ $pathIDs }} }{{ end }}
{{- end }}
{{- end }}
Expand Down
12 changes: 12 additions & 0 deletions rootfs/etc/templates/modsecurity/modsecurity.tmpl
Expand Up @@ -9,12 +9,24 @@
{{- $modsec := .Global.ModSecurity }}
[modsecurity]
spoe-agent modsecurity-agent
{{- if .Global.ModSecurity.UseCoraza }}
messages coraza-req
option var-prefix coraza
{{- else }}
messages check-request
option var-prefix modsec
{{- end }}
timeout hello {{ $modsec.Timeout.Hello }}
timeout idle {{ $modsec.Timeout.Idle }}
timeout processing {{ $modsec.Timeout.Processing }}
use-backend spoe-modsecurity
log global
option dontlog-normal

{{- if .Global.ModSecurity.UseCoraza }}
spoe-message coraza-req
{{- else }}
spoe-message check-request
{{- end }}
args {{ $modsec.Args | join " " }}
event on-backend-http-request

0 comments on commit 4cc3494

Please sign in to comment.