Merge pull request #4779 from aledbf/update-image
Remove lua-resty-waf feature
This commit is contained in:
commit
a85d5ed93a
11 changed files with 12 additions and 565 deletions
|
@ -45,7 +45,6 @@ import (
|
||||||
"k8s.io/ingress-nginx/internal/ingress/annotations/ipwhitelist"
|
"k8s.io/ingress-nginx/internal/ingress/annotations/ipwhitelist"
|
||||||
"k8s.io/ingress-nginx/internal/ingress/annotations/loadbalancing"
|
"k8s.io/ingress-nginx/internal/ingress/annotations/loadbalancing"
|
||||||
"k8s.io/ingress-nginx/internal/ingress/annotations/log"
|
"k8s.io/ingress-nginx/internal/ingress/annotations/log"
|
||||||
"k8s.io/ingress-nginx/internal/ingress/annotations/luarestywaf"
|
|
||||||
"k8s.io/ingress-nginx/internal/ingress/annotations/mirror"
|
"k8s.io/ingress-nginx/internal/ingress/annotations/mirror"
|
||||||
"k8s.io/ingress-nginx/internal/ingress/annotations/opentracing"
|
"k8s.io/ingress-nginx/internal/ingress/annotations/opentracing"
|
||||||
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
|
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
|
||||||
|
@ -111,7 +110,6 @@ type Ingress struct {
|
||||||
XForwardedPrefix string
|
XForwardedPrefix string
|
||||||
SSLCiphers string
|
SSLCiphers string
|
||||||
Logs log.Config
|
Logs log.Config
|
||||||
LuaRestyWAF luarestywaf.Config
|
|
||||||
InfluxDB influxdb.Config
|
InfluxDB influxdb.Config
|
||||||
ModSecurity modsecurity.Config
|
ModSecurity modsecurity.Config
|
||||||
Mirror mirror.Config
|
Mirror mirror.Config
|
||||||
|
@ -160,7 +158,6 @@ func NewAnnotationExtractor(cfg resolver.Resolver) Extractor {
|
||||||
"XForwardedPrefix": xforwardedprefix.NewParser(cfg),
|
"XForwardedPrefix": xforwardedprefix.NewParser(cfg),
|
||||||
"SSLCiphers": sslcipher.NewParser(cfg),
|
"SSLCiphers": sslcipher.NewParser(cfg),
|
||||||
"Logs": log.NewParser(cfg),
|
"Logs": log.NewParser(cfg),
|
||||||
"LuaRestyWAF": luarestywaf.NewParser(cfg),
|
|
||||||
"InfluxDB": influxdb.NewParser(cfg),
|
"InfluxDB": influxdb.NewParser(cfg),
|
||||||
"BackendProtocol": backendprotocol.NewParser(cfg),
|
"BackendProtocol": backendprotocol.NewParser(cfg),
|
||||||
"ModSecurity": modsecurity.NewParser(cfg),
|
"ModSecurity": modsecurity.NewParser(cfg),
|
||||||
|
|
|
@ -1,126 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2016 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 luarestywaf
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
networking "k8s.io/api/networking/v1beta1"
|
|
||||||
|
|
||||||
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
|
|
||||||
"k8s.io/ingress-nginx/internal/ingress/errors"
|
|
||||||
"k8s.io/ingress-nginx/internal/ingress/resolver"
|
|
||||||
"k8s.io/ingress-nginx/internal/sets"
|
|
||||||
)
|
|
||||||
|
|
||||||
var luaRestyWAFModes = map[string]bool{"ACTIVE": true, "INACTIVE": true, "SIMULATE": true}
|
|
||||||
|
|
||||||
// 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"`
|
|
||||||
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
|
|
||||||
func (e1 *Config) Equal(e2 *Config) bool {
|
|
||||||
if e1 == e2 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if e1 == nil || e2 == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if e1.Mode != e2.Mode {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if e1.Debug != e2.Debug {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
match := sets.StringElementsMatch(e1.IgnoredRuleSets, e2.IgnoredRuleSets)
|
|
||||||
if !match {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
type luarestywaf struct {
|
|
||||||
r resolver.Resolver
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewParser creates a new CORS annotation parser
|
|
||||||
func NewParser(r resolver.Resolver) parser.IngressAnnotation {
|
|
||||||
return luarestywaf{r}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse parses the annotations contained in the ingress rule
|
|
||||||
// used to indicate if the location/s contains a fragment of
|
|
||||||
// configuration to be included inside the paths of the rules
|
|
||||||
func (a luarestywaf) Parse(ing *networking.Ingress) (interface{}, error) {
|
|
||||||
var err error
|
|
||||||
config := &Config{}
|
|
||||||
|
|
||||||
mode, err := parser.GetStringAnnotation("lua-resty-waf", ing)
|
|
||||||
if err != nil {
|
|
||||||
return &Config{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
config.Mode = strings.ToUpper(mode)
|
|
||||||
if _, ok := luaRestyWAFModes[config.Mode]; !ok {
|
|
||||||
return &Config{}, errors.NewInvalidAnnotationContent("lua-resty-waf", mode)
|
|
||||||
}
|
|
||||||
|
|
||||||
config.Debug, _ = parser.GetBoolAnnotation("lua-resty-waf-debug", ing)
|
|
||||||
|
|
||||||
ignoredRuleSetsStr, _ := parser.GetStringAnnotation("lua-resty-waf-ignore-rulesets", ing)
|
|
||||||
config.IgnoredRuleSets = strings.FieldsFunc(ignoredRuleSetsStr, func(c rune) bool {
|
|
||||||
strC := string(c)
|
|
||||||
return strC == "," || strC == " "
|
|
||||||
})
|
|
||||||
|
|
||||||
// TODO(elvinefendi) maybe validate the ruleset string here
|
|
||||||
config.ExtraRulesetString, _ = parser.GetStringAnnotation("lua-resty-waf-extra-rules", ing)
|
|
||||||
|
|
||||||
config.ScoreThreshold, _ = parser.GetIntAnnotation("lua-resty-waf-score-threshold", ing)
|
|
||||||
|
|
||||||
config.AllowUnknownContentTypes, _ = parser.GetBoolAnnotation("lua-resty-waf-allow-unknown-content-types", ing)
|
|
||||||
|
|
||||||
config.ProcessMultipartBody, err = parser.GetBoolAnnotation("lua-resty-waf-process-multipart-body", ing)
|
|
||||||
if err != nil {
|
|
||||||
config.ProcessMultipartBody = true
|
|
||||||
}
|
|
||||||
|
|
||||||
return config, nil
|
|
||||||
}
|
|
|
@ -1,86 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2017 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 luarestywaf
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
api "k8s.io/api/core/v1"
|
|
||||||
networking "k8s.io/api/networking/v1beta1"
|
|
||||||
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
|
|
||||||
"k8s.io/ingress-nginx/internal/ingress/resolver"
|
|
||||||
)
|
|
||||||
|
|
||||||
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 {
|
|
||||||
t.Fatalf("expected a parser.IngressAnnotation but returned nil")
|
|
||||||
}
|
|
||||||
|
|
||||||
testCases := []struct {
|
|
||||||
annotations map[string]string
|
|
||||||
expected *Config
|
|
||||||
}{
|
|
||||||
{nil, &Config{}},
|
|
||||||
{map[string]string{}, &Config{}},
|
|
||||||
|
|
||||||
{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{}, 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",
|
|
||||||
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{}, 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 := &networking.Ingress{
|
|
||||||
ObjectMeta: meta_v1.ObjectMeta{
|
|
||||||
Name: "foo",
|
|
||||||
Namespace: api.NamespaceDefault,
|
|
||||||
},
|
|
||||||
Spec: networking.IngressSpec{},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, testCase := range testCases {
|
|
||||||
ing.SetAnnotations(testCase.annotations)
|
|
||||||
result, _ := ap.Parse(ing)
|
|
||||||
config := result.(*Config)
|
|
||||||
if !config.Equal(testCase.expected) {
|
|
||||||
t.Errorf("expected %v but returned %v, annotations: %s", testCase.expected, result, testCase.annotations)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -610,10 +610,6 @@ type Configuration struct {
|
||||||
// +optional
|
// +optional
|
||||||
GlobalExternalAuth GlobalExternalAuth `json:"global-external-auth"`
|
GlobalExternalAuth GlobalExternalAuth `json:"global-external-auth"`
|
||||||
|
|
||||||
// DisableLuaRestyWAF disables lua-resty-waf globally regardless
|
|
||||||
// of whether there's an ingress that has enabled the WAF using annotation
|
|
||||||
DisableLuaRestyWAF bool `json:"disable-lua-resty-waf"`
|
|
||||||
|
|
||||||
// EnableInfluxDB enables the nginx InfluxDB extension
|
// EnableInfluxDB enables the nginx InfluxDB extension
|
||||||
// http://github.com/influxdata/nginx-influxdb-module/
|
// http://github.com/influxdata/nginx-influxdb-module/
|
||||||
// By default this is disabled
|
// By default this is disabled
|
||||||
|
|
|
@ -1178,7 +1178,6 @@ func locationApplyAnnotations(loc *ingress.Location, anns *annotations.Ingress)
|
||||||
loc.UsePortInRedirects = anns.UsePortInRedirects
|
loc.UsePortInRedirects = anns.UsePortInRedirects
|
||||||
loc.Connection = anns.Connection
|
loc.Connection = anns.Connection
|
||||||
loc.Logs = anns.Logs
|
loc.Logs = anns.Logs
|
||||||
loc.LuaRestyWAF = anns.LuaRestyWAF
|
|
||||||
loc.InfluxDB = anns.InfluxDB
|
loc.InfluxDB = anns.InfluxDB
|
||||||
loc.DefaultBackend = anns.DefaultBackend
|
loc.DefaultBackend = anns.DefaultBackend
|
||||||
loc.BackendProtocol = anns.BackendProtocol
|
loc.BackendProtocol = anns.BackendProtocol
|
||||||
|
|
|
@ -93,11 +93,6 @@ func (t *Template) Write(conf config.TemplateConfig) ([]byte, error) {
|
||||||
outCmdBuf := t.bp.Get()
|
outCmdBuf := t.bp.Get()
|
||||||
defer t.bp.Put(outCmdBuf)
|
defer t.bp.Put(outCmdBuf)
|
||||||
|
|
||||||
// TODO: remove once we found a fix for coredump running luarocks install lrexlib
|
|
||||||
if runtime.GOARCH == "arm" {
|
|
||||||
conf.Cfg.DisableLuaRestyWAF = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if klog.V(3) {
|
if klog.V(3) {
|
||||||
b, err := json.Marshal(conf)
|
b, err := json.Marshal(conf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -134,7 +129,6 @@ var (
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
"escapeLiteralDollar": escapeLiteralDollar,
|
"escapeLiteralDollar": escapeLiteralDollar,
|
||||||
"shouldConfigureLuaRestyWAF": shouldConfigureLuaRestyWAF,
|
|
||||||
"buildLuaSharedDictionaries": buildLuaSharedDictionaries,
|
"buildLuaSharedDictionaries": buildLuaSharedDictionaries,
|
||||||
"luaConfigurationRequestBodySize": luaConfigurationRequestBodySize,
|
"luaConfigurationRequestBodySize": luaConfigurationRequestBodySize,
|
||||||
"buildLocation": buildLocation,
|
"buildLocation": buildLocation,
|
||||||
|
@ -225,15 +219,7 @@ func quote(input interface{}) string {
|
||||||
return fmt.Sprintf("%q", inputStr)
|
return fmt.Sprintf("%q", inputStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func shouldConfigureLuaRestyWAF(disableLuaRestyWAF bool, mode string) bool {
|
func buildLuaSharedDictionaries(c interface{}, s interface{}) string {
|
||||||
if !disableLuaRestyWAF && len(mode) > 0 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildLuaSharedDictionaries(c interface{}, s interface{}, disableLuaRestyWAF bool) string {
|
|
||||||
var out []string
|
var out []string
|
||||||
|
|
||||||
cfg, ok := c.(config.Configuration)
|
cfg, ok := c.(config.Configuration)
|
||||||
|
@ -241,7 +227,8 @@ func buildLuaSharedDictionaries(c interface{}, s interface{}, disableLuaRestyWAF
|
||||||
klog.Errorf("expected a 'config.Configuration' type but %T was returned", c)
|
klog.Errorf("expected a 'config.Configuration' type but %T was returned", c)
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
servers, ok := s.([]*ingress.Server)
|
|
||||||
|
_, ok = s.([]*ingress.Server)
|
||||||
if !ok {
|
if !ok {
|
||||||
klog.Errorf("expected an '[]*ingress.Server' type but %T was returned", s)
|
klog.Errorf("expected an '[]*ingress.Server' type but %T was returned", s)
|
||||||
return ""
|
return ""
|
||||||
|
@ -251,23 +238,6 @@ func buildLuaSharedDictionaries(c interface{}, s interface{}, disableLuaRestyWAF
|
||||||
out = append(out, fmt.Sprintf("lua_shared_dict %s %dM", name, size))
|
out = append(out, fmt.Sprintf("lua_shared_dict %s %dM", name, size))
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: there must be a better place for this
|
|
||||||
if _, ok := cfg.LuaSharedDicts["waf_storage"]; !ok && !disableLuaRestyWAF {
|
|
||||||
luaRestyWAFEnabled := func() bool {
|
|
||||||
for _, server := range servers {
|
|
||||||
for _, location := range server.Locations {
|
|
||||||
if len(location.LuaRestyWAF.Mode) > 0 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}()
|
|
||||||
if luaRestyWAFEnabled {
|
|
||||||
out = append(out, "lua_shared_dict waf_storage 64M")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Strings(out)
|
sort.Strings(out)
|
||||||
|
|
||||||
return strings.Join(out, ";\n") + ";\n"
|
return strings.Join(out, ";\n") + ";\n"
|
||||||
|
|
|
@ -38,7 +38,6 @@ import (
|
||||||
"k8s.io/ingress-nginx/internal/ingress"
|
"k8s.io/ingress-nginx/internal/ingress"
|
||||||
"k8s.io/ingress-nginx/internal/ingress/annotations/authreq"
|
"k8s.io/ingress-nginx/internal/ingress/annotations/authreq"
|
||||||
"k8s.io/ingress-nginx/internal/ingress/annotations/influxdb"
|
"k8s.io/ingress-nginx/internal/ingress/annotations/influxdb"
|
||||||
"k8s.io/ingress-nginx/internal/ingress/annotations/luarestywaf"
|
|
||||||
"k8s.io/ingress-nginx/internal/ingress/annotations/modsecurity"
|
"k8s.io/ingress-nginx/internal/ingress/annotations/modsecurity"
|
||||||
"k8s.io/ingress-nginx/internal/ingress/annotations/ratelimit"
|
"k8s.io/ingress-nginx/internal/ingress/annotations/ratelimit"
|
||||||
"k8s.io/ingress-nginx/internal/ingress/annotations/rewrite"
|
"k8s.io/ingress-nginx/internal/ingress/annotations/rewrite"
|
||||||
|
@ -189,7 +188,7 @@ func TestBuildLuaSharedDictionaries(t *testing.T) {
|
||||||
"configuration_data": 10, "certificate_data": 20,
|
"configuration_data": 10, "certificate_data": 20,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
actual := buildLuaSharedDictionaries(cfg, invalidType, true)
|
actual := buildLuaSharedDictionaries(cfg, invalidType)
|
||||||
|
|
||||||
if !reflect.DeepEqual(expected, actual) {
|
if !reflect.DeepEqual(expected, actual) {
|
||||||
t.Errorf("Expected '%v' but returned '%v'", expected, actual)
|
t.Errorf("Expected '%v' but returned '%v'", expected, actual)
|
||||||
|
@ -198,32 +197,23 @@ func TestBuildLuaSharedDictionaries(t *testing.T) {
|
||||||
servers := []*ingress.Server{
|
servers := []*ingress.Server{
|
||||||
{
|
{
|
||||||
Hostname: "foo.bar",
|
Hostname: "foo.bar",
|
||||||
Locations: []*ingress.Location{{Path: "/", LuaRestyWAF: luarestywaf.Config{}}},
|
Locations: []*ingress.Location{{Path: "/"}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Hostname: "another.host",
|
Hostname: "another.host",
|
||||||
Locations: []*ingress.Location{{Path: "/", LuaRestyWAF: luarestywaf.Config{}}},
|
Locations: []*ingress.Location{{Path: "/"}},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
// returns value from config
|
// returns value from config
|
||||||
configuration := buildLuaSharedDictionaries(cfg, servers, false)
|
configuration := buildLuaSharedDictionaries(cfg, servers)
|
||||||
if !strings.Contains(configuration, "lua_shared_dict configuration_data 10M;\n") {
|
if !strings.Contains(configuration, "lua_shared_dict configuration_data 10M;\n") {
|
||||||
t.Errorf("expected to include 'configuration_data' but got %s", configuration)
|
t.Errorf("expected to include 'configuration_data' but got %s", configuration)
|
||||||
}
|
}
|
||||||
if !strings.Contains(configuration, "lua_shared_dict certificate_data 20M;\n") {
|
if !strings.Contains(configuration, "lua_shared_dict certificate_data 20M;\n") {
|
||||||
t.Errorf("expected to include 'certificate_data' but got %s", configuration)
|
t.Errorf("expected to include 'certificate_data' but got %s", configuration)
|
||||||
}
|
}
|
||||||
if strings.Contains(configuration, "waf_storage") {
|
|
||||||
t.Errorf("expected to not include 'waf_storage' but got %s", configuration)
|
|
||||||
}
|
|
||||||
|
|
||||||
servers[1].Locations[0].LuaRestyWAF = luarestywaf.Config{Mode: "ACTIVE"}
|
|
||||||
configuration = buildLuaSharedDictionaries(cfg, servers, false)
|
|
||||||
if !strings.Contains(configuration, "lua_shared_dict waf_storage") {
|
|
||||||
t.Errorf("expected to configure 'waf_storage', but got %s", configuration)
|
|
||||||
}
|
|
||||||
// test invalid config
|
// test invalid config
|
||||||
configuration = buildLuaSharedDictionaries(invalidType, servers, false)
|
configuration = buildLuaSharedDictionaries(invalidType, servers)
|
||||||
if configuration != "" {
|
if configuration != "" {
|
||||||
t.Errorf("expected an empty string, but got %s", configuration)
|
t.Errorf("expected an empty string, but got %s", configuration)
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,6 @@ import (
|
||||||
"k8s.io/ingress-nginx/internal/ingress/annotations/influxdb"
|
"k8s.io/ingress-nginx/internal/ingress/annotations/influxdb"
|
||||||
"k8s.io/ingress-nginx/internal/ingress/annotations/ipwhitelist"
|
"k8s.io/ingress-nginx/internal/ingress/annotations/ipwhitelist"
|
||||||
"k8s.io/ingress-nginx/internal/ingress/annotations/log"
|
"k8s.io/ingress-nginx/internal/ingress/annotations/log"
|
||||||
"k8s.io/ingress-nginx/internal/ingress/annotations/luarestywaf"
|
|
||||||
"k8s.io/ingress-nginx/internal/ingress/annotations/mirror"
|
"k8s.io/ingress-nginx/internal/ingress/annotations/mirror"
|
||||||
"k8s.io/ingress-nginx/internal/ingress/annotations/modsecurity"
|
"k8s.io/ingress-nginx/internal/ingress/annotations/modsecurity"
|
||||||
"k8s.io/ingress-nginx/internal/ingress/annotations/opentracing"
|
"k8s.io/ingress-nginx/internal/ingress/annotations/opentracing"
|
||||||
|
@ -308,8 +307,6 @@ type Location struct {
|
||||||
// Logs allows to enable or disable the nginx logs
|
// Logs allows to enable or disable the nginx logs
|
||||||
// By default access logs are enabled and rewrite logs are disabled
|
// By default access logs are enabled and rewrite logs are disabled
|
||||||
Logs log.Config `json:"logs,omitempty"`
|
Logs log.Config `json:"logs,omitempty"`
|
||||||
// LuaRestyWAF contains parameters to configure lua-resty-waf
|
|
||||||
LuaRestyWAF luarestywaf.Config `json:"luaRestyWAF"`
|
|
||||||
// InfluxDB allows to monitor the incoming request by sending them to an influxdb database
|
// InfluxDB allows to monitor the incoming request by sending them to an influxdb database
|
||||||
// +optional
|
// +optional
|
||||||
InfluxDB influxdb.Config `json:"influxDB,omitempty"`
|
InfluxDB influxdb.Config `json:"influxDB,omitempty"`
|
||||||
|
|
|
@ -404,9 +404,6 @@ func (l1 *Location) Equal(l2 *Location) bool {
|
||||||
if !(&l1.Logs).Equal(&l2.Logs) {
|
if !(&l1.Logs).Equal(&l2.Logs) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if !(&l1.LuaRestyWAF).Equal(&l2.LuaRestyWAF) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if !(&l1.InfluxDB).Equal(&l2.InfluxDB) {
|
if !(&l1.InfluxDB).Equal(&l2.InfluxDB) {
|
||||||
return false
|
return false
|
||||||
|
|
|
@ -50,16 +50,11 @@ events {
|
||||||
http {
|
http {
|
||||||
lua_package_path "/etc/nginx/lua/?.lua;;";
|
lua_package_path "/etc/nginx/lua/?.lua;;";
|
||||||
|
|
||||||
{{ buildLuaSharedDictionaries $cfg $servers $all.Cfg.DisableLuaRestyWAF }}
|
{{ buildLuaSharedDictionaries $cfg $servers }}
|
||||||
|
|
||||||
init_by_lua_block {
|
init_by_lua_block {
|
||||||
collectgarbage("collect")
|
collectgarbage("collect")
|
||||||
|
|
||||||
{{ if not $all.Cfg.DisableLuaRestyWAF }}
|
|
||||||
local lua_resty_waf = require("resty.waf")
|
|
||||||
lua_resty_waf.init()
|
|
||||||
{{ end }}
|
|
||||||
|
|
||||||
-- init modules
|
-- init modules
|
||||||
local ok, res
|
local ok, res
|
||||||
|
|
||||||
|
@ -989,78 +984,20 @@ stream {
|
||||||
plugins.run()
|
plugins.run()
|
||||||
}
|
}
|
||||||
|
|
||||||
{{ if shouldConfigureLuaRestyWAF $all.Cfg.DisableLuaRestyWAF $location.LuaRestyWAF.Mode }}
|
|
||||||
# be careful with `access_by_lua_block` and `satisfy any` directives as satisfy any
|
# be careful with `access_by_lua_block` and `satisfy any` directives as satisfy any
|
||||||
# will always succeed when there's `access_by_lua_block` that does not have any lua code doing `ngx.exit(ngx.DECLINED)`
|
# will always succeed when there's `access_by_lua_block` that does not have any lua code doing `ngx.exit(ngx.DECLINED)`
|
||||||
# that means currently `satisfy any` and lua-resty-waf together will potentiall render any
|
|
||||||
# other authentication method such as basic auth or external auth useless - all requests will be allowed.
|
# other authentication method such as basic auth or external auth useless - all requests will be allowed.
|
||||||
access_by_lua_block {
|
#access_by_lua_block {
|
||||||
local lua_resty_waf = require("resty.waf")
|
#}
|
||||||
local waf = lua_resty_waf:new()
|
|
||||||
|
|
||||||
waf:set_option("mode", {{ $location.LuaRestyWAF.Mode | quote }})
|
|
||||||
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)
|
|
||||||
waf:set_option("event_log_request_body", true)
|
|
||||||
waf:set_option("event_log_request_headers", true)
|
|
||||||
waf:set_option("req_tid_header", true)
|
|
||||||
waf:set_option("res_tid_header", true)
|
|
||||||
{{ end }}
|
|
||||||
|
|
||||||
{{ range $ruleset := $location.LuaRestyWAF.IgnoredRuleSets }}
|
|
||||||
waf:set_option("ignore_ruleset", {{ $ruleset | quote }})
|
|
||||||
{{ end }}
|
|
||||||
|
|
||||||
{{ if gt (len $location.LuaRestyWAF.ExtraRulesetString) 0 }}
|
|
||||||
waf:set_option("add_ruleset_string", "10000_extra_rules", {{ $location.LuaRestyWAF.ExtraRulesetString }})
|
|
||||||
{{ end }}
|
|
||||||
|
|
||||||
waf:exec()
|
|
||||||
}
|
|
||||||
{{ end }}
|
|
||||||
|
|
||||||
header_filter_by_lua_block {
|
header_filter_by_lua_block {
|
||||||
{{ if shouldConfigureLuaRestyWAF $all.Cfg.DisableLuaRestyWAF $location.LuaRestyWAF.Mode }}
|
|
||||||
local lua_resty_waf = require "resty.waf"
|
|
||||||
local waf = lua_resty_waf:new()
|
|
||||||
waf:exec()
|
|
||||||
{{ end }}
|
|
||||||
|
|
||||||
plugins.run()
|
plugins.run()
|
||||||
}
|
}
|
||||||
|
|
||||||
body_filter_by_lua_block {
|
body_filter_by_lua_block {
|
||||||
{{ if shouldConfigureLuaRestyWAF $all.Cfg.DisableLuaRestyWAF $location.LuaRestyWAF.Mode }}
|
|
||||||
local lua_resty_waf = require "resty.waf"
|
|
||||||
local waf = lua_resty_waf:new()
|
|
||||||
waf:exec()
|
|
||||||
{{ end }}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
log_by_lua_block {
|
log_by_lua_block {
|
||||||
{{ if shouldConfigureLuaRestyWAF $all.Cfg.DisableLuaRestyWAF $location.LuaRestyWAF.Mode }}
|
|
||||||
local lua_resty_waf = require "resty.waf"
|
|
||||||
local waf = lua_resty_waf:new()
|
|
||||||
waf:exec()
|
|
||||||
{{ end }}
|
|
||||||
balancer.log()
|
balancer.log()
|
||||||
{{ if $all.EnableMetrics }}
|
{{ if $all.EnableMetrics }}
|
||||||
monitor.call()
|
monitor.call()
|
||||||
|
|
|
@ -1,224 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2017 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 annotations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
. "github.com/onsi/ginkgo"
|
|
||||||
. "github.com/onsi/gomega"
|
|
||||||
"github.com/parnurzeal/gorequest"
|
|
||||||
|
|
||||||
"k8s.io/ingress-nginx/test/e2e/framework"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ = framework.IngressNginxDescribe("Annotations - lua-resty-waf", func() {
|
|
||||||
f := framework.NewDefaultFramework("luarestywaf")
|
|
||||||
|
|
||||||
BeforeEach(func() {
|
|
||||||
f.NewEchoDeployment()
|
|
||||||
})
|
|
||||||
|
|
||||||
Context("when lua-resty-waf is enabled", func() {
|
|
||||||
It("should return 403 for a malicious request that matches a default WAF rule and 200 for other requests", func() {
|
|
||||||
host := "foo"
|
|
||||||
createIngress(f, host, framework.EchoService, 80, map[string]string{"nginx.ingress.kubernetes.io/lua-resty-waf": "active"})
|
|
||||||
|
|
||||||
url := fmt.Sprintf("%s?msg=<A href=\"http://mysite.com/\">XSS</A>", f.GetURL(framework.HTTP))
|
|
||||||
resp, _, errs := gorequest.New().
|
|
||||||
Get(url).
|
|
||||||
Set("Host", host).
|
|
||||||
End()
|
|
||||||
|
|
||||||
Expect(len(errs)).Should(Equal(0))
|
|
||||||
Expect(resp.StatusCode).Should(Equal(http.StatusForbidden))
|
|
||||||
})
|
|
||||||
It("should not apply ignored rulesets", func() {
|
|
||||||
host := "foo"
|
|
||||||
createIngress(f, host, framework.EchoService, 80, map[string]string{
|
|
||||||
"nginx.ingress.kubernetes.io/lua-resty-waf": "active",
|
|
||||||
"nginx.ingress.kubernetes.io/lua-resty-waf-ignore-rulesets": "41000_sqli, 42000_xss"})
|
|
||||||
|
|
||||||
url := fmt.Sprintf("%s?msg=<A href=\"http://mysite.com/\">XSS</A>", f.GetURL(framework.HTTP))
|
|
||||||
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 apply the score threshold", func() {
|
|
||||||
host := "foo"
|
|
||||||
createIngress(f, host, framework.EchoService, 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.GetURL(framework.HTTP))
|
|
||||||
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, framework.EchoService, 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.GetURL(framework.HTTP))
|
|
||||||
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, framework.EchoService, 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.GetURL(framework.HTTP))
|
|
||||||
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, framework.EchoService, 80, map[string]string{
|
|
||||||
"nginx.ingress.kubernetes.io/lua-resty-waf": "active"})
|
|
||||||
|
|
||||||
url := fmt.Sprintf("%s?msg=my-message", f.GetURL(framework.HTTP))
|
|
||||||
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, framework.EchoService, 80, map[string]string{
|
|
||||||
"nginx.ingress.kubernetes.io/lua-resty-waf": "active",
|
|
||||||
"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":[]
|
|
||||||
}
|
|
||||||
]=]`,
|
|
||||||
})
|
|
||||||
|
|
||||||
url := fmt.Sprintf("%s?msg=my-message", f.GetURL(framework.HTTP))
|
|
||||||
resp, _, errs := gorequest.New().
|
|
||||||
Get(url).
|
|
||||||
Set("Host", host).
|
|
||||||
End()
|
|
||||||
|
|
||||||
Expect(len(errs)).Should(Equal(0))
|
|
||||||
Expect(resp.StatusCode).Should(Equal(http.StatusOK))
|
|
||||||
|
|
||||||
url = fmt.Sprintf("%s?msg=my-foo-message", f.GetURL(framework.HTTP))
|
|
||||||
resp, _, errs = gorequest.New().
|
|
||||||
Get(url).
|
|
||||||
Set("Host", host).
|
|
||||||
End()
|
|
||||||
|
|
||||||
Expect(len(errs)).Should(Equal(0))
|
|
||||||
Expect(resp.StatusCode).Should(Equal(http.StatusForbidden))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
Context("when lua-resty-waf is not enabled", func() {
|
|
||||||
It("should return 200 even for a malicious request", func() {
|
|
||||||
host := "foo"
|
|
||||||
createIngress(f, host, framework.EchoService, 80, map[string]string{})
|
|
||||||
|
|
||||||
url := fmt.Sprintf("%s?msg=<A href=\"http://mysite.com/\">XSS</A>", f.GetURL(framework.HTTP))
|
|
||||||
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 run in simulate mode", func() {
|
|
||||||
host := "foo"
|
|
||||||
createIngress(f, host, framework.EchoService, 80, map[string]string{"nginx.ingress.kubernetes.io/lua-resty-waf": "simulate"})
|
|
||||||
|
|
||||||
url := fmt.Sprintf("%s?msg=<A href=\"http://mysite.com/\">XSS</A>", f.GetURL(framework.HTTP))
|
|
||||||
resp, _, errs := gorequest.New().
|
|
||||||
Get(url).
|
|
||||||
Set("Host", host).
|
|
||||||
End()
|
|
||||||
|
|
||||||
Expect(len(errs)).Should(Equal(0))
|
|
||||||
Expect(resp.StatusCode).Should(Equal(http.StatusOK))
|
|
||||||
|
|
||||||
time.Sleep(5 * time.Second)
|
|
||||||
log, err := f.NginxLogs()
|
|
||||||
Expect(err).ToNot(HaveOccurred())
|
|
||||||
Expect(log).To(ContainSubstring("Request score greater than score threshold"))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
func createIngress(f *framework.Framework, host, service string, port int, annotations map[string]string) {
|
|
||||||
ing := framework.NewSingleIngress(host, "/", host, f.Namespace, service, port, &annotations)
|
|
||||||
f.EnsureIngress(ing)
|
|
||||||
|
|
||||||
f.WaitForNginxServer(host,
|
|
||||||
func(server string) bool {
|
|
||||||
return Expect(server).Should(ContainSubstring(fmt.Sprintf("server_name %v", host)))
|
|
||||||
})
|
|
||||||
|
|
||||||
time.Sleep(1 * time.Second)
|
|
||||||
|
|
||||||
resp, body, errs := gorequest.New().
|
|
||||||
Get(f.GetURL(framework.HTTP)).
|
|
||||||
Set("Host", host).
|
|
||||||
End()
|
|
||||||
|
|
||||||
Expect(len(errs)).Should(Equal(0))
|
|
||||||
Expect(resp.StatusCode).Should(Equal(http.StatusOK))
|
|
||||||
Expect(body).Should(ContainSubstring(fmt.Sprintf("host=%v", host)))
|
|
||||||
}
|
|
Loading…
Reference in a new issue