Merge pull request #3309 from diazjf/modsecurity-location

Customize ModSecurity to be used in Locations
This commit is contained in:
k8s-ci-robot 2018-11-07 09:29:01 -08:00 committed by GitHub
commit d97999c07d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 307 additions and 7 deletions

View file

@ -20,6 +20,7 @@ import (
"github.com/golang/glog"
"github.com/imdario/mergo"
"k8s.io/ingress-nginx/internal/ingress/annotations/canary"
"k8s.io/ingress-nginx/internal/ingress/annotations/modsecurity"
"k8s.io/ingress-nginx/internal/ingress/annotations/sslcipher"
apiv1 "k8s.io/api/core/v1"
@ -98,6 +99,7 @@ type Ingress struct {
Logs log.Config
LuaRestyWAF luarestywaf.Config
InfluxDB influxdb.Config
ModSecurity modsecurity.Config
}
// Extractor defines the annotation parsers to be used in the extraction of annotations
@ -140,6 +142,7 @@ func NewAnnotationExtractor(cfg resolver.Resolver) Extractor {
"LuaRestyWAF": luarestywaf.NewParser(cfg),
"InfluxDB": influxdb.NewParser(cfg),
"BackendProtocol": backendprotocol.NewParser(cfg),
"ModSecurity": modsecurity.NewParser(cfg),
},
}
}

View file

@ -0,0 +1,88 @@
/*
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 modsecurity
import (
extensions "k8s.io/api/extensions/v1beta1"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
"k8s.io/ingress-nginx/internal/ingress/resolver"
)
// Config contains the AuthSSLCert used for mutual authentication
// and the configured ValidationDepth
type Config struct {
Enable bool `json:"enable-modsecurity"`
OWASPRules bool `json:"enable-owasp-core-rules"`
TransactionID string `json:"modsecurity-transaction-id"`
}
// Equal tests for equality between two Config types
func (modsec1 *Config) Equal(modsec2 *Config) bool {
if modsec1 == modsec2 {
return true
}
if modsec1 == nil || modsec2 == nil {
return false
}
if modsec1.Enable != modsec2.Enable {
return false
}
if modsec1.OWASPRules != modsec2.OWASPRules {
return false
}
if modsec1.TransactionID != modsec2.TransactionID {
return false
}
return true
}
// NewParser creates a new ModSecurity annotation parser
func NewParser(resolver resolver.Resolver) parser.IngressAnnotation {
return modSecurity{resolver}
}
type modSecurity struct {
r resolver.Resolver
}
// Parse parses the annotations contained in the ingress
// rule used to enable ModSecurity in a particular location
func (a modSecurity) Parse(ing *extensions.Ingress) (interface{}, error) {
enableModSecurity, err := parser.GetBoolAnnotation("enable-modsecurity", ing)
if err != nil {
enableModSecurity = false
}
owaspRules, err := parser.GetBoolAnnotation("enable-owasp-core-rules", ing)
if err != nil {
owaspRules = false
}
transactionID, err := parser.GetStringAnnotation("modsecurity-transaction-id", ing)
if err != nil {
transactionID = ""
}
return Config{
Enable: enableModSecurity,
OWASPRules: owaspRules,
TransactionID: transactionID,
}, nil
}

View file

@ -0,0 +1,73 @@
/*
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 modsecurity
import (
"testing"
api "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/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) {
enable := parser.GetAnnotationWithPrefix("enable-modsecurity")
owasp := parser.GetAnnotationWithPrefix("enable-owasp-core-rules")
transID := parser.GetAnnotationWithPrefix("modsecurity-transaction-id")
ap := NewParser(&resolver.Mock{})
if ap == nil {
t.Fatalf("expected a parser.IngressAnnotation but returned nil")
}
testCases := []struct {
annotations map[string]string
expected Config
}{
{map[string]string{enable: "true"}, Config{true, false, ""}},
{map[string]string{enable: "false"}, Config{false, false, ""}},
{map[string]string{enable: ""}, Config{false, false, ""}},
{map[string]string{owasp: "true"}, Config{false, true, ""}},
{map[string]string{owasp: "false"}, Config{false, false, ""}},
{map[string]string{owasp: ""}, Config{false, false, ""}},
{map[string]string{transID: "ok"}, Config{false, false, "ok"}},
{map[string]string{transID: ""}, Config{false, false, ""}},
{map[string]string{}, Config{false, false, ""}},
{nil, Config{false, false, ""}},
}
ing := &extensions.Ingress{
ObjectMeta: meta_v1.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
Spec: extensions.IngressSpec{},
}
for _, testCase := range testCases {
ing.SetAnnotations(testCase.annotations)
result, _ := ap.Parse(ing)
if result != testCase.expected {
t.Errorf("expected %v but returned %v, annotations: %s", testCase.expected, result, testCase.annotations)
}
}
}

View file

@ -355,6 +355,7 @@ func (n *NGINXController) getBackendServers(ingresses []*extensions.Ingress) ([]
loc.DefaultBackend = anns.DefaultBackend
loc.BackendProtocol = anns.BackendProtocol
loc.CustomHTTPErrors = anns.CustomHTTPErrors
loc.ModSecurity = anns.ModSecurity
if loc.Redirect.FromToWWW {
server.RedirectFromToWWW = true
@ -396,6 +397,7 @@ func (n *NGINXController) getBackendServers(ingresses []*extensions.Ingress) ([]
DefaultBackend: anns.DefaultBackend,
BackendProtocol: anns.BackendProtocol,
CustomHTTPErrors: anns.CustomHTTPErrors,
ModSecurity: anns.ModSecurity,
}
if loc.Redirect.FromToWWW {
@ -848,6 +850,7 @@ func (n *NGINXController) createServers(data []*extensions.Ingress,
defLoc.LuaRestyWAF = anns.LuaRestyWAF
defLoc.InfluxDB = anns.InfluxDB
defLoc.BackendProtocol = anns.BackendProtocol
defLoc.ModSecurity = anns.ModSecurity
} else {
glog.V(3).Infof("Ingress %q defines both a backend and rules. Using its backend as default upstream for all its rules.",
ingKey)

View file

@ -20,6 +20,7 @@ import (
apiv1 "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/ingress-nginx/internal/ingress/annotations/modsecurity"
"k8s.io/ingress-nginx/internal/ingress/annotations/auth"
"k8s.io/ingress-nginx/internal/ingress/annotations/authreq"
@ -280,6 +281,9 @@ type Location struct {
// CustomHTTPErrors specifies the error codes that should be intercepted.
// +optional
CustomHTTPErrors []int `json:"custom-http-errors"`
// ModSecurity allows to enable and configure modsecurity
// +optional
ModSecurity modsecurity.Config `json:"modsecurity"`
}
// SSLPassthroughBackend describes a SSL upstream server configured

View file

@ -380,6 +380,10 @@ func (l1 *Location) Equal(l2 *Location) bool {
return false
}
if !(&l1.ModSecurity).Equal(&l2.ModSecurity) {
return false
}
return true
}