Skip to content

Commit

Permalink
Merge pull request #3187 from DesmondHoLLM/feature/annotations-resty-lua
Browse files Browse the repository at this point in the history
UPT: annotation enhancement for resty-lua-waf
  • Loading branch information
k8s-ci-robot authored Oct 25, 2018
2 parents 464f579 + bf03046 commit 063f652
Show file tree
Hide file tree
Showing 5 changed files with 156 additions and 17 deletions.
29 changes: 29 additions & 0 deletions docs/user-guide/nginx-configuration/annotations.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ You can add these Kubernetes annotations to specific Ingress objects to customiz
|[nginx.ingress.kubernetes.io/lua-resty-waf-debug](#lua-resty-waf)|"true" or "false"|
|[nginx.ingress.kubernetes.io/lua-resty-waf-ignore-rulesets](#lua-resty-waf)|string|
|[nginx.ingress.kubernetes.io/lua-resty-waf-extra-rules](#lua-resty-waf)|string|
|[nginx.ingress.kubernetes.io/lua-resty-waf-allow-unknown-content-types](#lua-resty-waf)|"true" or "false"|
|[nginx.ingress.kubernetes.io/lua-resty-waf-score-threshold](#lua-resty-waf)|number|
|[nginx.ingress.kubernetes.io/lua-resty-waf-process-multipart-body](#lua-resty-waf)|"true" or "false"|
|[nginx.ingress.kubernetes.io/enable-influxdb](#influxdb)|"true" or "false"|
|[nginx.ingress.kubernetes.io/influxdb-measurement](#influxdb)|string|
|[nginx.ingress.kubernetes.io/influxdb-port](#influxdb)|string|
Expand Down Expand Up @@ -558,6 +561,32 @@ It is also possible to configure custom WAF rules per ingress using the `nginx.i
nginx.ingress.kubernetes.io/lua-resty-waf-extra-rules: '[=[ { "access": [ { "actions": { "disrupt" : "DENY" }, "id": 10001, "msg": "my custom rule", "operator": "STR_CONTAINS", "pattern": "foo", "vars": [ { "parse": [ "values", 1 ], "type": "REQUEST_ARGS" } ] } ], "body_filter": [], "header_filter":[] } ]=]'
```

Since the default allowed contents were `"text/html", "text/json", "application/json"`
We can enable the following annotation for allow all contents type:


```yaml
nginx.ingress.kubernetes.io/lua-resty-waf-allow-unknown-content-types: "true"
```

The default score of lua-resty-waf is 5, which usually triggered if hitting 2 default rules, you can modify the score threshold with following annotation:


```yaml
nginx.ingress.kubernetes.io/lua-resty-waf-score-threshold: "10"
```

When you enabled HTTPS in the endpoint and since resty-lua will return 500 error when processing "multipart" contents
Reference for this [issue](https://github.com/p0pr0ck5/lua-resty-waf/issues/166)

By default, it will be "true"

You may enable the following annotation for work around:

```yaml
nginx.ingress.kubernetes.io/lua-resty-waf-process-multipart-body: "false"
```

For details on how to write WAF rules, please refer to [https://github.com/p0pr0ck5/lua-resty-waf](https://github.com/p0pr0ck5/lua-resty-waf).

[configmap]: ./configmap.md
Expand Down
40 changes: 32 additions & 8 deletions internal/ingress/annotations/luarestywaf/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,13 @@ var luaRestyWAFModes = map[string]bool{"ACTIVE": true, "INACTIVE": true, "SIMULA

// Config returns lua-resty-waf configuration for an Ingress rule
type Config struct {
Mode string `json:"mode"`
Debug bool `json:"debug"`
IgnoredRuleSets []string `json:"ignored-rulesets"`
ExtraRulesetString string `json:"extra-ruleset-string"`
Mode string `json:"mode"`
Debug bool `json:"debug"`
IgnoredRuleSets []string `json:"ignored-rulesets"`
ExtraRulesetString string `json:"extra-ruleset-string"`
ScoreThreshold int `json:"score-threshold"`
AllowUnknownContentTypes bool `json:"allow-unknown-content-types"`
ProcessMultipartBody bool `json:"process-multipart-body"`
}

// Equal tests for equality between two Config types
Expand All @@ -57,6 +60,15 @@ func (e1 *Config) Equal(e2 *Config) bool {
if e1.ExtraRulesetString != e2.ExtraRulesetString {
return false
}
if e1.ScoreThreshold != e2.ScoreThreshold {
return false
}
if e1.AllowUnknownContentTypes != e2.AllowUnknownContentTypes {
return false
}
if e1.ProcessMultipartBody != e2.ProcessMultipartBody {
return false
}

return true
}
Expand Down Expand Up @@ -95,10 +107,22 @@ func (a luarestywaf) Parse(ing *extensions.Ingress) (interface{}, error) {
// TODO(elvinefendi) maybe validate the ruleset string here
extraRulesetString, _ := parser.GetStringAnnotation("lua-resty-waf-extra-rules", ing)

scoreThreshold, _ := parser.GetIntAnnotation("lua-resty-waf-score-threshold", ing)

allowUnknownContentTypes, _ := parser.GetBoolAnnotation("lua-resty-waf-allow-unknown-content-types", ing)

processMultipartBody, err := parser.GetBoolAnnotation("lua-resty-waf-process-multipart-body", ing)
if err != nil {
processMultipartBody = true
}

return &Config{
Mode: mode,
Debug: debug,
IgnoredRuleSets: ignoredRuleSets,
ExtraRulesetString: extraRulesetString,
Mode: mode,
Debug: debug,
IgnoredRuleSets: ignoredRuleSets,
ExtraRulesetString: extraRulesetString,
ScoreThreshold: scoreThreshold,
AllowUnknownContentTypes: allowUnknownContentTypes,
ProcessMultipartBody: processMultipartBody,
}, nil
}
25 changes: 16 additions & 9 deletions internal/ingress/annotations/luarestywaf/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ func TestParse(t *testing.T) {
luaRestyWAFAnnotation := parser.GetAnnotationWithPrefix("lua-resty-waf")
luaRestyWAFDebugAnnotation := parser.GetAnnotationWithPrefix("lua-resty-waf-debug")
luaRestyWAFIgnoredRuleSetsAnnotation := parser.GetAnnotationWithPrefix("lua-resty-waf-ignore-rulesets")
luaRestyWAFScoreThresholdAnnotation := parser.GetAnnotationWithPrefix("lua-resty-waf-score-threshold")
luaRestyWAFAllowUnknownContentTypesAnnotation := parser.GetAnnotationWithPrefix("lua-resty-waf-allow-unknown-content-types")
luaRestyWAFProcessMultipartBody := parser.GetAnnotationWithPrefix("lua-resty-waf-process-multipart-body")

ap := NewParser(&resolver.Mock{})
if ap == nil {
Expand All @@ -43,21 +46,25 @@ func TestParse(t *testing.T) {
{nil, &Config{}},
{map[string]string{}, &Config{}},

{map[string]string{luaRestyWAFAnnotation: "active"}, &Config{Mode: "ACTIVE", Debug: false, IgnoredRuleSets: []string{}}},
{map[string]string{luaRestyWAFAnnotation: "active"}, &Config{Mode: "ACTIVE", Debug: false, IgnoredRuleSets: []string{}, ProcessMultipartBody: true}},
{map[string]string{luaRestyWAFDebugAnnotation: "true"}, &Config{Debug: false}},

{map[string]string{luaRestyWAFAnnotation: "active", luaRestyWAFDebugAnnotation: "true"}, &Config{Mode: "ACTIVE", Debug: true, IgnoredRuleSets: []string{}}},
{map[string]string{luaRestyWAFAnnotation: "active", luaRestyWAFDebugAnnotation: "false"}, &Config{Mode: "ACTIVE", Debug: false, IgnoredRuleSets: []string{}}},
{map[string]string{luaRestyWAFAnnotation: "inactive", luaRestyWAFDebugAnnotation: "true"}, &Config{Mode: "INACTIVE", Debug: true, IgnoredRuleSets: []string{}}},
{map[string]string{luaRestyWAFAnnotation: "active", luaRestyWAFDebugAnnotation: "true"}, &Config{Mode: "ACTIVE", Debug: true, IgnoredRuleSets: []string{}, ProcessMultipartBody: true}},
{map[string]string{luaRestyWAFAnnotation: "active", luaRestyWAFDebugAnnotation: "false"}, &Config{Mode: "ACTIVE", Debug: false, IgnoredRuleSets: []string{}, ProcessMultipartBody: true}},
{map[string]string{luaRestyWAFAnnotation: "inactive", luaRestyWAFDebugAnnotation: "true"}, &Config{Mode: "INACTIVE", Debug: true, IgnoredRuleSets: []string{}, ProcessMultipartBody: true}},

{map[string]string{
luaRestyWAFAnnotation: "active",
luaRestyWAFDebugAnnotation: "true",
luaRestyWAFIgnoredRuleSetsAnnotation: "ruleset1, ruleset2 ruleset3, another.ruleset"},
&Config{Mode: "ACTIVE", Debug: true, IgnoredRuleSets: []string{"ruleset1", "ruleset2", "ruleset3", "another.ruleset"}}},
luaRestyWAFAnnotation: "active",
luaRestyWAFDebugAnnotation: "true",
luaRestyWAFIgnoredRuleSetsAnnotation: "ruleset1, ruleset2 ruleset3, another.ruleset",
luaRestyWAFScoreThresholdAnnotation: "10",
luaRestyWAFAllowUnknownContentTypesAnnotation: "true"},
&Config{Mode: "ACTIVE", Debug: true, IgnoredRuleSets: []string{"ruleset1", "ruleset2", "ruleset3", "another.ruleset"}, ScoreThreshold: 10, AllowUnknownContentTypes: true, ProcessMultipartBody: true}},

{map[string]string{luaRestyWAFAnnotation: "siMulate", luaRestyWAFDebugAnnotation: "true"}, &Config{Mode: "SIMULATE", Debug: true, IgnoredRuleSets: []string{}}},
{map[string]string{luaRestyWAFAnnotation: "siMulate", luaRestyWAFDebugAnnotation: "true"}, &Config{Mode: "SIMULATE", Debug: true, IgnoredRuleSets: []string{}, ProcessMultipartBody: true}},
{map[string]string{luaRestyWAFAnnotation: "siMulateX", luaRestyWAFDebugAnnotation: "true"}, &Config{Debug: false}},

{map[string]string{luaRestyWAFAnnotation: "active", luaRestyWAFProcessMultipartBody: "false"}, &Config{Mode: "ACTIVE", ProcessMultipartBody: false, IgnoredRuleSets: []string{}}},
}

ing := &extensions.Ingress{
Expand Down
14 changes: 14 additions & 0 deletions rootfs/etc/nginx/template/nginx.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -891,9 +891,23 @@ stream {

waf:set_option("mode", "{{ $location.LuaRestyWAF.Mode }}")
waf:set_option("storage_zone", "waf_storage")

{{ if $location.LuaRestyWAF.AllowUnknownContentTypes }}
waf:set_option("allow_unknown_content_types", true)
{{ else }}
waf:set_option("allowed_content_types", { "text/html", "text/json", "application/json" })
{{ end }}

waf:set_option("event_log_level", ngx.WARN)

{{ if gt $location.LuaRestyWAF.ScoreThreshold 0 }}
waf:set_option("score_threshold", {{ $location.LuaRestyWAF.ScoreThreshold }})
{{ end }}

{{ if not $location.LuaRestyWAF.ProcessMultipartBody }}
waf:set_option("process_multipart_body", false)
{{ end }}

{{ if $location.LuaRestyWAF.Debug }}
waf:set_option("debug", true)
waf:set_option("event_log_request_arguments", true)
Expand Down
65 changes: 65 additions & 0 deletions test/e2e/annotations/luarestywaf.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,71 @@ var _ = framework.IngressNginxDescribe("Annotations - lua-resty-waf", func() {
Expect(len(errs)).Should(Equal(0))
Expect(resp.StatusCode).Should(Equal(http.StatusOK))
})
It("should apply the score threshold", func() {
host := "foo"
createIngress(f, host, "http-svc", 80, map[string]string{
"nginx.ingress.kubernetes.io/lua-resty-waf": "active",
"nginx.ingress.kubernetes.io/lua-resty-waf-score-threshold": "20"})

url := fmt.Sprintf("%s?msg=<A href=\"http://mysite.com/\">XSS</A>", f.IngressController.HTTPURL)
resp, _, errs := gorequest.New().
Get(url).
Set("Host", host).
End()

Expect(len(errs)).Should(Equal(0))
Expect(resp.StatusCode).Should(Equal(http.StatusOK))
})
It("should not reject request with an unknown content type", func() {
host := "foo"
contenttype := "application/octet-stream"
createIngress(f, host, "http-svc", 80, map[string]string{
"nginx.ingress.kubernetes.io/lua-resty-waf-allow-unknown-content-types": "true",
"nginx.ingress.kubernetes.io/lua-resty-waf": "active"})

url := fmt.Sprintf("%s?msg=my-message", f.IngressController.HTTPURL)
resp, _, errs := gorequest.New().
Get(url).
Set("Host", host).
Set("Content-Type", contenttype).
End()

Expect(len(errs)).Should(Equal(0))
Expect(resp.StatusCode).Should(Equal(http.StatusOK))
})
It("should not fail a request with multipart content type when multipart body processing disabled", func() {
contenttype := "multipart/form-data; boundary=alamofire.boundary.3fc2e849279e18fc"
host := "foo"
createIngress(f, host, "http-svc", 80, map[string]string{
"nginx.ingress.kubernetes.io/lua-resty-waf-process-multipart-body": "false",
"nginx.ingress.kubernetes.io/lua-resty-waf": "active"})

url := fmt.Sprintf("%s?msg=my-message", f.IngressController.HTTPURL)
resp, _, errs := gorequest.New().
Get(url).
Set("Host", host).
Set("Content-Type", contenttype).
End()

Expect(len(errs)).Should(Equal(0))
Expect(resp.StatusCode).Should(Equal(http.StatusOK))
})
It("should fail a request with multipart content type when multipart body processing enabled by default", func() {
contenttype := "multipart/form-data; boundary=alamofire.boundary.3fc2e849279e18fc"
host := "foo"
createIngress(f, host, "http-svc", 80, map[string]string{
"nginx.ingress.kubernetes.io/lua-resty-waf": "active"})

url := fmt.Sprintf("%s?msg=my-message", f.IngressController.HTTPURL)
resp, _, errs := gorequest.New().
Get(url).
Set("Host", host).
Set("Content-Type", contenttype).
End()

Expect(len(errs)).Should(Equal(0))
Expect(resp.StatusCode).Should(Equal(http.StatusBadRequest))
})
It("should apply configured extra rules", func() {
host := "foo"
createIngress(f, host, "http-svc", 80, map[string]string{
Expand Down

0 comments on commit 063f652

Please sign in to comment.