Implement pathType validation (#9511)
This commit is contained in:
parent
e6dcd6845e
commit
da98c744b9
15 changed files with 373 additions and 207 deletions
|
@ -311,6 +311,7 @@ Kubernetes: `>=1.20.0-0`
|
|||
| controller.containerPort | object | `{"http":80,"https":443}` | Configures the ports that the nginx-controller listens on |
|
||||
| controller.customTemplate.configMapKey | string | `""` | |
|
||||
| controller.customTemplate.configMapName | string | `""` | |
|
||||
| controller.disablePathTypeValidation | bool | `false` | This configuration defines if Ingress Controller should validate pathType. If this is true, special characters will be allowed on paths of any pathType. If false, special characters are only allowed on paths with pathType = ImplementationSpecific |
|
||||
| controller.dnsConfig | object | `{}` | Optionally customize the pod dnsConfig. |
|
||||
| controller.dnsPolicy | string | `"ClusterFirst"` | Optionally change this to ClusterFirstWithHostNet in case you have 'hostNetwork: true'. By default, while using host network, name resolution uses the host's DNS. If you wish nginx-controller to keep resolving names inside the k8s network, use ClusterFirstWithHostNet. |
|
||||
| controller.electionID | string | `""` | Election ID to use for status update, by default it uses the controller name combined with a suffix of 'leader' |
|
||||
|
|
|
@ -14,6 +14,7 @@ metadata:
|
|||
namespace: {{ .Release.Namespace }}
|
||||
data:
|
||||
allow-snippet-annotations: "{{ .Values.controller.allowSnippetAnnotations }}"
|
||||
disable-pathtype-validation: "{{ .Values.controller.disablePathTypeValidation }}"
|
||||
{{- if .Values.controller.addHeaders }}
|
||||
add-headers: {{ .Release.Namespace }}/{{ include "ingress-nginx.fullname" . }}-custom-add-headers
|
||||
{{- end }}
|
||||
|
|
|
@ -87,6 +87,11 @@ controller:
|
|||
# Global snippets in ConfigMap are still respected
|
||||
allowSnippetAnnotations: true
|
||||
|
||||
# -- This configuration defines if Ingress Controller should validate pathType.
|
||||
# If this is true, special characters will be allowed on paths of any pathType. If
|
||||
# false, special characters are only allowed on paths with pathType = ImplementationSpecific
|
||||
disablePathTypeValidation: false
|
||||
|
||||
# -- Required for use with CNI based kubernetes installations (such as ones set up by kubeadm),
|
||||
# since CNI and hostport don't mix yet. Can be deprecated once https://github.com/kubernetes/kubernetes/issues/23920
|
||||
# is merged
|
||||
|
|
|
@ -216,6 +216,8 @@ The following table shows a configuration option's name, type, and the default v
|
|||
|[service-upstream](#service-upstream)|bool|"false"|
|
||||
|[ssl-reject-handshake](#ssl-reject-handshake)|bool|"false"|
|
||||
|[debug-connections](#debug-connections)|[]string|"127.0.0.1,1.1.1.1/24"|
|
||||
|[disable-pathtype-validation](#disable-pathtype-validation)|bool|"false"|
|
||||
|[path-additional-allowed-chars](#path-additional-allowed-chars)|string|"^%$[](){}*+?"|
|
||||
|
||||
## add-headers
|
||||
|
||||
|
@ -1326,3 +1328,24 @@ _**default:**_ ""
|
|||
|
||||
_References:_
|
||||
[http://nginx.org/en/docs/ngx_core_module.html#debug_connection](http://nginx.org/en/docs/ngx_core_module.html#debug_connection)
|
||||
|
||||
## disable-pathtype-validation
|
||||
Ingress Controller validates the pathType, and only allows special characters on "path" if pathType is
|
||||
ImplementationSpecific.
|
||||
|
||||
The only characters allowed on ingresses with pathType not ImplementationSpecific
|
||||
will be 0-9, a-z, A-Z, "-", ".", "_", "~", "/".
|
||||
|
||||
If the validation is disabled, the [#path-additional-allowed-chars](#path-additional-allowed-chars) will
|
||||
be allowed on any pathType.
|
||||
|
||||
This behavior can be disabled, so special characters are accepted regardless of pathType
|
||||
_**default:**_ "false"
|
||||
|
||||
## path-additional-allowed-chars
|
||||
When validating path on Ingress resources, defines the additional set of special characters that
|
||||
will be allowed.
|
||||
|
||||
See also [#disable-pathtype-validation](#disable-pathtype-validation).
|
||||
|
||||
_**default:**_ "^%$[](){}*+?|"
|
|
@ -782,6 +782,17 @@ type Configuration struct {
|
|||
// http://nginx.org/en/docs/ngx_core_module.html#debug_connection
|
||||
// Default: ""
|
||||
DebugConnections []string `json:"debug-connections"`
|
||||
|
||||
// DisablePathTypeValidation allows the admin to disable the pathType validation.
|
||||
// If PathTypeValidation is enabled, the Controller will only allow alphanumeric
|
||||
// characters on path (0-9, a-z, A-Z, "-", ".", "_", "~", "/")
|
||||
DisablePathTypeValidation bool `json:"disable-pathtype-validation"`
|
||||
|
||||
// PathAdditionalAllowedChars allows the admin to specify what are the additional
|
||||
// characters allowed in case of pathType=ImplementationSpecific.
|
||||
// Case disable-pathtype-validation=true, this characters will be allowed on any path.
|
||||
// Defaults to: "^%$[](){}*+?"
|
||||
PathAdditionalAllowedChars string `json:"path-additional-allowed-chars"`
|
||||
}
|
||||
|
||||
// NewDefault returns the default nginx configuration
|
||||
|
@ -817,6 +828,8 @@ func NewDefault() Configuration {
|
|||
ClientHeaderTimeout: 60,
|
||||
ClientBodyBufferSize: "8k",
|
||||
ClientBodyTimeout: 60,
|
||||
DisablePathTypeValidation: false,
|
||||
PathAdditionalAllowedChars: "^%$[](){}*+?|",
|
||||
EnableUnderscoresInHeaders: false,
|
||||
ErrorLogLevel: errorLevel,
|
||||
UseForwardedHeaders: false,
|
||||
|
|
|
@ -325,6 +325,10 @@ func (n *NGINXController) CheckIngress(ing *networking.Ingress) error {
|
|||
|
||||
k8s.SetDefaultNGINXPathType(ing)
|
||||
|
||||
if err := utilingress.ValidateIngressPath(ing, cfg.DisablePathTypeValidation, cfg.PathAdditionalAllowedChars); err != nil {
|
||||
return fmt.Errorf("ingress contains invalid characters: %s", err)
|
||||
}
|
||||
|
||||
allIngresses := n.store.ListIngresses()
|
||||
|
||||
filter := func(toCheck *ingress.Ingress) bool {
|
||||
|
|
|
@ -201,6 +201,97 @@ func TestCheckIngress(t *testing.T) {
|
|||
},
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("when validating pathType", func(t *testing.T) {
|
||||
t.Run("When ingress contains invalid path and pathType validation is not disabled", func(t *testing.T) {
|
||||
nginx.store = fakeIngressStore{
|
||||
ingresses: []*ingress.Ingress{},
|
||||
configuration: ngx_config.Configuration{
|
||||
DisablePathTypeValidation: false,
|
||||
},
|
||||
}
|
||||
nginx.command = testNginxTestCommand{
|
||||
t: t,
|
||||
err: nil,
|
||||
expected: "",
|
||||
}
|
||||
nginx.cfg.IngressClassConfiguration = &ingressclass.IngressClassConfiguration{
|
||||
WatchWithoutClass: true,
|
||||
}
|
||||
ingPath := &networking.Ingress{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-ingress1",
|
||||
Namespace: "user-namespace1",
|
||||
Annotations: map[string]string{
|
||||
"kubernetes.io/ingress.class": "nginx",
|
||||
},
|
||||
},
|
||||
Spec: networking.IngressSpec{
|
||||
Rules: []networking.IngressRule{
|
||||
{
|
||||
Host: "example.com",
|
||||
IngressRuleValue: networking.IngressRuleValue{
|
||||
HTTP: &networking.HTTPIngressRuleValue{
|
||||
Paths: []networking.HTTPIngressPath{
|
||||
{
|
||||
Path: "/xpto/(a+)",
|
||||
PathType: &pathTypePrefix,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if nginx.CheckIngress(ingPath) == nil {
|
||||
t.Errorf("invalid path on pathTypePrefix and validation enabled should return an error")
|
||||
}
|
||||
})
|
||||
t.Run("When ingress contains invalid path and pathType validation is disabled", func(t *testing.T) {
|
||||
nginx.store = fakeIngressStore{
|
||||
ingresses: []*ingress.Ingress{},
|
||||
configuration: ngx_config.Configuration{
|
||||
DisablePathTypeValidation: true,
|
||||
PathAdditionalAllowedChars: "^%$[](){}*+?|",
|
||||
},
|
||||
}
|
||||
nginx.command = testNginxTestCommand{
|
||||
t: t,
|
||||
err: nil,
|
||||
expected: "_,example.com",
|
||||
}
|
||||
|
||||
ingPath := &networking.Ingress{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-ingress2",
|
||||
Namespace: "user-namespace2",
|
||||
},
|
||||
Spec: networking.IngressSpec{
|
||||
Rules: []networking.IngressRule{
|
||||
{
|
||||
Host: "example.com",
|
||||
IngressRuleValue: networking.IngressRuleValue{
|
||||
HTTP: &networking.HTTPIngressRuleValue{
|
||||
Paths: []networking.HTTPIngressPath{
|
||||
{
|
||||
Path: "/example(/|$)(.*)",
|
||||
PathType: &pathTypePrefix,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if nginx.CheckIngress(ingPath) != nil {
|
||||
t.Errorf("invalid path on pathTypePrefix and validation disabled should not return an error: %s", nginx.CheckIngress(ingPath))
|
||||
}
|
||||
})
|
||||
})
|
||||
t.Run("when the class is the nginx one", func(t *testing.T) {
|
||||
ing.ObjectMeta.Annotations["kubernetes.io/ingress.class"] = "nginx"
|
||||
nginx.command = testNginxTestCommand{
|
||||
|
|
|
@ -846,6 +846,11 @@ func (s *k8sStore) syncIngress(ing *networkingv1.Ingress) {
|
|||
copyIng := &networkingv1.Ingress{}
|
||||
ing.ObjectMeta.DeepCopyInto(©Ing.ObjectMeta)
|
||||
|
||||
if err := ingressutils.ValidateIngressPath(ing, s.backendConfig.DisablePathTypeValidation, s.backendConfig.PathAdditionalAllowedChars); err != nil {
|
||||
klog.Errorf("ingress %s contains invalid path and will be skipped: %s", key, err)
|
||||
return
|
||||
}
|
||||
|
||||
if s.backendConfig.AnnotationValueWordBlocklist != "" {
|
||||
if err := checkBadAnnotationValue(copyIng.Annotations, s.backendConfig.AnnotationValueWordBlocklist); err != nil {
|
||||
klog.Warningf("skipping ingress %s: %s", key, err)
|
||||
|
@ -865,10 +870,6 @@ func (s *k8sStore) syncIngress(ing *networkingv1.Ingress) {
|
|||
if path.Path == "" {
|
||||
copyIng.Spec.Rules[ri].HTTP.Paths[pi].Path = "/"
|
||||
}
|
||||
if !ingressutils.IsSafePath(copyIng, path.Path) {
|
||||
klog.Warningf("ingress %s contains invalid path %s", key, path.Path)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -180,9 +180,6 @@ func SetDefaultNGINXPathType(ing *networkingv1.Ingress) {
|
|||
p.PathType = &defaultPathType
|
||||
}
|
||||
|
||||
if *p.PathType == networkingv1.PathTypeImplementationSpecific {
|
||||
p.PathType = &defaultPathType
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,6 @@ import (
|
|||
|
||||
networkingv1 "k8s.io/api/networking/v1"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
|
||||
"k8s.io/ingress-nginx/internal/k8s"
|
||||
"k8s.io/ingress-nginx/internal/net/ssl"
|
||||
"k8s.io/ingress-nginx/pkg/apis/ingress"
|
||||
|
@ -31,15 +30,15 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
alphaNumericChars = `\-\.\_\~a-zA-Z0-9/`
|
||||
regexEnabledChars = `\^\$\[\]\(\)\{\}\*\+`
|
||||
alphaNumericChars = `A-Za-z0-9\-\.\_\~\/` // This is the default allowed set on paths
|
||||
)
|
||||
|
||||
var (
|
||||
// pathAlphaNumeric is a regex validation of something like "^/[a-zA-Z]+$" on path
|
||||
pathAlphaNumeric = regexp.MustCompile("^/[" + alphaNumericChars + "]*$").MatchString
|
||||
// pathRegexEnabled is a regex validation of paths that may contain regex.
|
||||
pathRegexEnabled = regexp.MustCompile("^/[" + alphaNumericChars + regexEnabledChars + "]*$").MatchString
|
||||
// pathAlphaNumeric is a regex validation that allows only (0-9, a-z, A-Z, "-", ".", "_", "~", "/")
|
||||
pathAlphaNumericRegex = regexp.MustCompile("^[" + alphaNumericChars + "]*$").MatchString
|
||||
|
||||
// default path type is Prefix to not break existing definitions
|
||||
defaultPathType = networkingv1.PathTypePrefix
|
||||
)
|
||||
|
||||
func GetRemovedHosts(rucfg, newcfg *ingress.Configuration) []string {
|
||||
|
@ -247,12 +246,45 @@ func BuildRedirects(servers []*ingress.Server) []*redirect {
|
|||
return redirectServers
|
||||
}
|
||||
|
||||
// IsSafePath verifies if the path used in ingress object contains only valid characters.
|
||||
// It will behave differently if regex is enabled or not
|
||||
func IsSafePath(copyIng *networkingv1.Ingress, path string) bool {
|
||||
isRegex, _ := parser.GetBoolAnnotation("use-regex", copyIng)
|
||||
if isRegex {
|
||||
return pathRegexEnabled(path)
|
||||
func ValidateIngressPath(copyIng *networkingv1.Ingress, disablePathTypeValidation bool, additionalChars string) error {
|
||||
|
||||
if copyIng == nil {
|
||||
return nil
|
||||
}
|
||||
return pathAlphaNumeric(path)
|
||||
|
||||
escapedAdditionalChars := regexp.QuoteMeta(additionalChars)
|
||||
regexPath, err := regexp.Compile("^[" + alphaNumericChars + escapedAdditionalChars + "]*$")
|
||||
if err != nil {
|
||||
return fmt.Errorf("ingress has misconfigured validation regex on configmap: %s - %w", additionalChars, err)
|
||||
}
|
||||
|
||||
for _, rule := range copyIng.Spec.Rules {
|
||||
if rule.HTTP == nil {
|
||||
continue
|
||||
}
|
||||
if err := checkPath(rule.HTTP.Paths, disablePathTypeValidation, regexPath); err != nil {
|
||||
return fmt.Errorf("error validating ingressPath: %w", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkPath(paths []networkingv1.HTTPIngressPath, disablePathTypeValidation bool, regexSpecificChars *regexp.Regexp) error {
|
||||
for _, path := range paths {
|
||||
if path.PathType == nil {
|
||||
path.PathType = &defaultPathType
|
||||
}
|
||||
|
||||
if disablePathTypeValidation || *path.PathType == networkingv1.PathTypeImplementationSpecific {
|
||||
if !regexSpecificChars.MatchString(path.Path) {
|
||||
return fmt.Errorf("path %s of type %s contains invalid characters", path.Path, *path.PathType)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if !pathAlphaNumericRegex(path.Path) {
|
||||
return fmt.Errorf("path %s of type %s contains invalid characters", path.Path, *path.PathType)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -17,13 +17,10 @@ limitations under the License.
|
|||
package ingress
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
networkingv1 "k8s.io/api/networking/v1"
|
||||
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/ingress-nginx/pkg/apis/ingress"
|
||||
)
|
||||
|
||||
|
@ -136,81 +133,168 @@ func TestIsDynamicConfigurationEnough(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func generateDumbIngressforPathTest(regexEnabled bool) *networkingv1.Ingress {
|
||||
var annotations = make(map[string]string)
|
||||
regexAnnotation := fmt.Sprintf("%s/use-regex", parser.AnnotationsPrefix)
|
||||
if regexEnabled {
|
||||
annotations[regexAnnotation] = "true"
|
||||
}
|
||||
func generateDumbIngressforPathTest(pathType *networkingv1.PathType, path string) *networkingv1.Ingress {
|
||||
return &networkingv1.Ingress{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "dumb",
|
||||
Namespace: "default",
|
||||
Annotations: annotations,
|
||||
},
|
||||
Spec: networkingv1.IngressSpec{
|
||||
Rules: []networkingv1.IngressRule{
|
||||
{
|
||||
Host: "test.com",
|
||||
IngressRuleValue: networkingv1.IngressRuleValue{
|
||||
HTTP: &networkingv1.HTTPIngressRuleValue{
|
||||
Paths: []networkingv1.HTTPIngressPath{
|
||||
{
|
||||
PathType: pathType,
|
||||
Path: path,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsSafePath(t *testing.T) {
|
||||
func generateComplexIngress(ing *networkingv1.Ingress) *networkingv1.Ingress {
|
||||
|
||||
oldRules := ing.Spec.DeepCopy().Rules
|
||||
ing.Spec.Rules = []networkingv1.IngressRule{
|
||||
{
|
||||
Host: "test1.com",
|
||||
IngressRuleValue: networkingv1.IngressRuleValue{
|
||||
HTTP: &networkingv1.HTTPIngressRuleValue{
|
||||
Paths: []networkingv1.HTTPIngressPath{
|
||||
{
|
||||
PathType: &pathTypeExact,
|
||||
Path: "/xpto",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Host: "test2.com",
|
||||
IngressRuleValue: networkingv1.IngressRuleValue{
|
||||
HTTP: &networkingv1.HTTPIngressRuleValue{
|
||||
Paths: []networkingv1.HTTPIngressPath{
|
||||
{
|
||||
PathType: &pathTypeExact,
|
||||
Path: "/someotherpath",
|
||||
},
|
||||
{
|
||||
PathType: &pathTypePrefix,
|
||||
Path: "/someprefix/~xpto/lala123",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
// we want to invert the order to test better :)
|
||||
ing.Spec.Rules = append(ing.Spec.Rules, oldRules...)
|
||||
|
||||
return ing
|
||||
}
|
||||
|
||||
var (
|
||||
pathTypeExact = networkingv1.PathTypeExact
|
||||
pathTypePrefix = networkingv1.PathTypePrefix
|
||||
pathTypeImplSpecific = networkingv1.PathTypeImplementationSpecific
|
||||
)
|
||||
|
||||
const (
|
||||
defaultAdditionalChars = "^%$[](){}*+?"
|
||||
)
|
||||
|
||||
func TestValidateIngressPath(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
copyIng *networkingv1.Ingress
|
||||
path string
|
||||
want bool
|
||||
disablePathTypeValidation bool
|
||||
additionalChars string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "should accept valid path with regex disabled",
|
||||
want: true,
|
||||
copyIng: generateDumbIngressforPathTest(false),
|
||||
path: "/xpto/~user/t-e_st.exe",
|
||||
name: "should return nil when ingress = nil",
|
||||
wantErr: false,
|
||||
copyIng: nil,
|
||||
},
|
||||
{
|
||||
name: "should accept valid path / with regex disabled",
|
||||
want: true,
|
||||
copyIng: generateDumbIngressforPathTest(false),
|
||||
path: "/",
|
||||
name: "should accept valid path on pathType Exact",
|
||||
wantErr: false,
|
||||
copyIng: generateDumbIngressforPathTest(&pathTypeExact, "/xpto/~user9/t-e_st.exe"),
|
||||
},
|
||||
{
|
||||
name: "should reject invalid path with invalid chars",
|
||||
want: false,
|
||||
copyIng: generateDumbIngressforPathTest(false),
|
||||
path: "/foo/bar/;xpto",
|
||||
name: "should accept valid path on pathType Prefix",
|
||||
wantErr: false,
|
||||
copyIng: generateDumbIngressforPathTest(&pathTypePrefix, "/xpto/~user9/t-e_st.exe"),
|
||||
},
|
||||
{
|
||||
name: "should reject regex path when regex is disabled",
|
||||
want: false,
|
||||
copyIng: generateDumbIngressforPathTest(false),
|
||||
path: "/foo/bar/(.+)",
|
||||
name: "should accept valid simple path on pathType Impl Specific",
|
||||
wantErr: false,
|
||||
copyIng: generateDumbIngressforPathTest(&pathTypeImplSpecific, "/xpto/~user9/t-e_st.exe"),
|
||||
},
|
||||
{
|
||||
name: "should accept valid path / with regex enabled",
|
||||
want: true,
|
||||
copyIng: generateDumbIngressforPathTest(true),
|
||||
path: "/",
|
||||
name: "should accept valid path on pathType nil",
|
||||
wantErr: false,
|
||||
copyIng: generateDumbIngressforPathTest(nil, "/xpto/~user/t-e_st.exe"),
|
||||
},
|
||||
{
|
||||
name: "should accept regex path when regex is enabled",
|
||||
want: true,
|
||||
copyIng: generateDumbIngressforPathTest(true),
|
||||
path: "/foo/bar/(.+)",
|
||||
name: "should accept empty path",
|
||||
wantErr: false,
|
||||
copyIng: generateDumbIngressforPathTest(&pathTypePrefix, ""),
|
||||
},
|
||||
{
|
||||
name: "should reject regex path when regex is enabled but the path is invalid",
|
||||
want: false,
|
||||
copyIng: generateDumbIngressforPathTest(true),
|
||||
path: "/foo/bar/;xpto",
|
||||
name: "should deny path with bad characters and pathType not implementationSpecific",
|
||||
wantErr: true,
|
||||
additionalChars: defaultAdditionalChars,
|
||||
copyIng: generateDumbIngressforPathTest(&pathTypeExact, "/foo/bar/(.+)"),
|
||||
},
|
||||
{
|
||||
name: "should reject regex path when regex is enabled but the path is invalid",
|
||||
want: false,
|
||||
copyIng: generateDumbIngressforPathTest(true),
|
||||
path: ";xpto",
|
||||
name: "should accept path with regex characters and pathType implementationSpecific",
|
||||
wantErr: false,
|
||||
additionalChars: defaultAdditionalChars,
|
||||
copyIng: generateDumbIngressforPathTest(&pathTypeImplSpecific, "/foo/bar/(.+)"),
|
||||
},
|
||||
{
|
||||
name: "should accept path with regex characters and pathType exact, but pathType validation disabled",
|
||||
wantErr: false,
|
||||
additionalChars: defaultAdditionalChars,
|
||||
disablePathTypeValidation: true,
|
||||
copyIng: generateDumbIngressforPathTest(&pathTypeImplSpecific, "/foo/bar/(.+)"),
|
||||
},
|
||||
{
|
||||
name: "should reject path when the allowed additional set does not match",
|
||||
wantErr: true,
|
||||
additionalChars: "().?",
|
||||
disablePathTypeValidation: false,
|
||||
copyIng: generateDumbIngressforPathTest(&pathTypeImplSpecific, "/foo/bar/(.+)"),
|
||||
},
|
||||
{
|
||||
name: "should accept path when the allowed additional set does match",
|
||||
wantErr: false,
|
||||
additionalChars: "().?",
|
||||
copyIng: generateDumbIngressforPathTest(&pathTypeImplSpecific, "/foo/bar/(.?)"),
|
||||
},
|
||||
{
|
||||
name: "should block if at least one path is bad",
|
||||
wantErr: true,
|
||||
copyIng: generateComplexIngress(generateDumbIngressforPathTest(&pathTypeExact, "/foo/bar/(.?)")),
|
||||
},
|
||||
{
|
||||
name: "should block if at least one path is bad",
|
||||
wantErr: true,
|
||||
copyIng: generateComplexIngress(generateDumbIngressforPathTest(&pathTypeImplSpecific, "/foo/bar/(.?)")),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := IsSafePath(tt.copyIng, tt.path); got != tt.want {
|
||||
t.Errorf("IsSafePath() = %v, want %v", got, tt.want)
|
||||
if err := ValidateIngressPath(tt.copyIng, tt.disablePathTypeValidation, tt.additionalChars); (err != nil) != tt.wantErr {
|
||||
t.Errorf("ValidateIngressPath() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -30,6 +30,14 @@ import (
|
|||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"k8s.io/ingress-nginx/test/e2e/framework"
|
||||
|
||||
networkingv1 "k8s.io/api/networking/v1"
|
||||
)
|
||||
|
||||
var (
|
||||
pathExact = networkingv1.PathTypeExact
|
||||
pathPrefix = networkingv1.PathTypePrefix
|
||||
pathImplSpecific = networkingv1.PathTypeImplementationSpecific
|
||||
)
|
||||
|
||||
var _ = framework.IngressNginxDescribe("[Serial] admission controller", func() {
|
||||
|
@ -152,6 +160,32 @@ var _ = framework.IngressNginxDescribe("[Serial] admission controller", func() {
|
|||
assert.NotNil(ginkgo.GinkgoT(), err, "creating an ingress with invalid annotation value should return an error")
|
||||
})
|
||||
|
||||
ginkgo.It("should reject ingress with bad characters and pathType != ImplementationSpecific", func() {
|
||||
host := "admission-test"
|
||||
|
||||
firstIngress := framework.NewSingleIngress("first-ingress", "/xpto*", host, f.Namespace, framework.EchoService, 80, nil)
|
||||
firstIngress.Spec.Rules[0].IngressRuleValue.HTTP.Paths[0].PathType = &pathPrefix
|
||||
_, err := f.KubeClientSet.NetworkingV1().Ingresses(f.Namespace).Create(context.TODO(), firstIngress, metav1.CreateOptions{})
|
||||
assert.NotNil(ginkgo.GinkgoT(), err, "creating an ingress with invalid path value should return an error")
|
||||
|
||||
secondIngress := framework.NewSingleIngress("second-ingress", "/abc123*", host, f.Namespace, framework.EchoService, 80, nil)
|
||||
secondIngress.Spec.Rules[0].IngressRuleValue.HTTP.Paths[0].PathType = &pathImplSpecific
|
||||
_, err = f.KubeClientSet.NetworkingV1().Ingresses(f.Namespace).Create(context.TODO(), secondIngress, metav1.CreateOptions{})
|
||||
assert.Nil(ginkgo.GinkgoT(), err, "creating an ingress with regex on path and pathType ImplementationSpecific should not return an error")
|
||||
|
||||
})
|
||||
|
||||
ginkgo.It("should not validate characters on ingress when validation of pathType is disabled", func() {
|
||||
host := "admission-test"
|
||||
|
||||
f.UpdateNginxConfigMapData("disable-pathtype-validation", "true")
|
||||
|
||||
firstIngress := framework.NewSingleIngress("first-ingress", "/xpto*", host, f.Namespace, framework.EchoService, 80, nil)
|
||||
firstIngress.Spec.Rules[0].IngressRuleValue.HTTP.Paths[0].PathType = &pathPrefix
|
||||
_, err := f.KubeClientSet.NetworkingV1().Ingresses(f.Namespace).Create(context.TODO(), firstIngress, metav1.CreateOptions{})
|
||||
assert.Nil(ginkgo.GinkgoT(), err, "creating an ingress with regex chars on path and pathType validation disabled should be accepted")
|
||||
})
|
||||
|
||||
ginkgo.It("should return an error if there is a forbidden value in some annotation", func() {
|
||||
host := "admission-test"
|
||||
|
||||
|
|
|
@ -35,6 +35,8 @@ import (
|
|||
var _ = framework.DescribeAnnotation("affinity session-cookie-name", func() {
|
||||
f := framework.NewDefaultFramework("affinity")
|
||||
|
||||
pathImpl := networking.PathTypeImplementationSpecific
|
||||
|
||||
ginkgo.BeforeEach(func() {
|
||||
f.NewEchoDeployment(framework.WithDeploymentReplicas(2))
|
||||
})
|
||||
|
@ -276,6 +278,8 @@ var _ = framework.DescribeAnnotation("affinity session-cookie-name", func() {
|
|||
annotations["nginx.ingress.kubernetes.io/session-cookie-path"] = "/foo/bar"
|
||||
|
||||
ing := framework.NewSingleIngress(host, "/foo/.*", host, f.Namespace, framework.EchoService, 80, annotations)
|
||||
ing.Spec.Rules[0].IngressRuleValue.HTTP.Paths[0].PathType = &pathImpl
|
||||
|
||||
f.EnsureIngress(ing)
|
||||
|
||||
f.WaitForNginxServer(host,
|
||||
|
@ -299,6 +303,8 @@ var _ = framework.DescribeAnnotation("affinity session-cookie-name", func() {
|
|||
annotations["nginx.ingress.kubernetes.io/use-regex"] = "true"
|
||||
|
||||
ing := framework.NewSingleIngress(host, "/foo/.*", host, f.Namespace, framework.EchoService, 80, annotations)
|
||||
ing.Spec.Rules[0].IngressRuleValue.HTTP.Paths[0].PathType = &pathImpl
|
||||
|
||||
f.EnsureIngress(ing)
|
||||
|
||||
f.WaitForNginxServer(host,
|
||||
|
|
|
@ -24,12 +24,15 @@ import (
|
|||
"github.com/onsi/ginkgo/v2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
networking "k8s.io/api/networking/v1"
|
||||
"k8s.io/ingress-nginx/test/e2e/framework"
|
||||
)
|
||||
|
||||
var _ = framework.DescribeAnnotation("rewrite-target use-regex enable-rewrite-log", func() {
|
||||
f := framework.NewDefaultFramework("rewrite")
|
||||
|
||||
pathImpl := networking.PathTypeImplementationSpecific
|
||||
|
||||
ginkgo.BeforeEach(func() {
|
||||
f.NewEchoDeployment()
|
||||
})
|
||||
|
@ -126,6 +129,7 @@ var _ = framework.DescribeAnnotation("rewrite-target use-regex enable-rewrite-lo
|
|||
"nginx.ingress.kubernetes.io/rewrite-target": "/new/backend",
|
||||
}
|
||||
ing = framework.NewSingleIngress("regex", "/foo.+", host, f.Namespace, framework.EchoService, 80, annotations)
|
||||
ing.Spec.Rules[0].IngressRuleValue.HTTP.Paths[0].PathType = &pathImpl
|
||||
f.EnsureIngress(ing)
|
||||
|
||||
f.WaitForNginxServer(host,
|
||||
|
@ -168,6 +172,8 @@ var _ = framework.DescribeAnnotation("rewrite-target use-regex enable-rewrite-lo
|
|||
"nginx.ingress.kubernetes.io/rewrite-target": "/new/backend",
|
||||
}
|
||||
ing = framework.NewSingleIngress("regex", "/foo/bar/[a-z]{3}", host, f.Namespace, framework.EchoService, 80, annotations)
|
||||
ing.Spec.Rules[0].IngressRuleValue.HTTP.Paths[0].PathType = &pathImpl
|
||||
|
||||
f.EnsureIngress(ing)
|
||||
|
||||
f.WaitForNginxServer(host,
|
||||
|
@ -196,6 +202,8 @@ var _ = framework.DescribeAnnotation("rewrite-target use-regex enable-rewrite-lo
|
|||
"nginx.ingress.kubernetes.io/rewrite-target": "/new/backend/$1",
|
||||
}
|
||||
ing := framework.NewSingleIngress("regex", "/foo/bar/(.+)", host, f.Namespace, framework.EchoService, 80, annotations)
|
||||
ing.Spec.Rules[0].IngressRuleValue.HTTP.Paths[0].PathType = &pathImpl
|
||||
|
||||
f.EnsureIngress(ing)
|
||||
|
||||
f.WaitForNginxServer(host,
|
||||
|
|
|
@ -1,134 +0,0 @@
|
|||
/*
|
||||
Copyright 2022 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 security
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/onsi/ginkgo/v2"
|
||||
|
||||
"k8s.io/ingress-nginx/test/e2e/framework"
|
||||
)
|
||||
|
||||
const (
|
||||
validPath = "/xpto/~user/t-e_st.exe"
|
||||
invalidPath = "/foo/bar/;xpto"
|
||||
regexPath = "/foo/bar/(.+)"
|
||||
host = "securitytest.com"
|
||||
)
|
||||
|
||||
var (
|
||||
annotationRegex = map[string]string{
|
||||
"nginx.ingress.kubernetes.io/use-regex": "true",
|
||||
}
|
||||
)
|
||||
|
||||
var _ = framework.IngressNginxDescribe("[Security] validate path fields", func() {
|
||||
f := framework.NewDefaultFramework("validate-path")
|
||||
|
||||
ginkgo.BeforeEach(func() {
|
||||
f.NewEchoDeployment()
|
||||
})
|
||||
|
||||
ginkgo.It("should accept an ingress with valid path", func() {
|
||||
|
||||
ing := framework.NewSingleIngress(host, validPath, host, f.Namespace, framework.EchoService, 80, nil)
|
||||
|
||||
f.EnsureIngress(ing)
|
||||
|
||||
f.WaitForNginxServer(host,
|
||||
func(server string) bool {
|
||||
return strings.Contains(server, fmt.Sprintf("server_name %s ;", host))
|
||||
})
|
||||
|
||||
f.HTTPTestClient().
|
||||
GET(validPath).
|
||||
WithHeader("Host", host).
|
||||
Expect().
|
||||
Status(http.StatusOK)
|
||||
})
|
||||
|
||||
ginkgo.It("should drop an ingress with invalid path", func() {
|
||||
|
||||
ing := framework.NewSingleIngress(host, invalidPath, host, f.Namespace, framework.EchoService, 80, nil)
|
||||
f.EnsureIngress(ing)
|
||||
|
||||
f.WaitForNginxServer(host,
|
||||
func(server string) bool {
|
||||
return !strings.Contains(server, fmt.Sprintf("server_name %s ;", host))
|
||||
})
|
||||
|
||||
f.HTTPTestClient().
|
||||
GET(invalidPath).
|
||||
WithHeader("Host", host).
|
||||
Expect().
|
||||
Status(http.StatusNotFound)
|
||||
})
|
||||
|
||||
ginkgo.It("should drop an ingress with regex path and regex disabled", func() {
|
||||
|
||||
ing := framework.NewSingleIngress(host, regexPath, host, f.Namespace, framework.EchoService, 80, nil)
|
||||
f.EnsureIngress(ing)
|
||||
|
||||
f.WaitForNginxServer(host,
|
||||
func(server string) bool {
|
||||
return !strings.Contains(server, fmt.Sprintf("server_name %s ;", host))
|
||||
})
|
||||
|
||||
f.HTTPTestClient().
|
||||
GET("/foo/bar/lalala").
|
||||
WithHeader("Host", host).
|
||||
Expect().
|
||||
Status(http.StatusNotFound)
|
||||
})
|
||||
|
||||
ginkgo.It("should accept an ingress with regex path and regex enabled", func() {
|
||||
|
||||
ing := framework.NewSingleIngress(host, regexPath, host, f.Namespace, framework.EchoService, 80, annotationRegex)
|
||||
f.EnsureIngress(ing)
|
||||
|
||||
f.WaitForNginxServer(host,
|
||||
func(server string) bool {
|
||||
return strings.Contains(server, fmt.Sprintf("server_name %s ;", host))
|
||||
})
|
||||
|
||||
f.HTTPTestClient().
|
||||
GET("/foo/bar/lalala").
|
||||
WithHeader("Host", host).
|
||||
Expect().
|
||||
Status(http.StatusOK)
|
||||
})
|
||||
|
||||
ginkgo.It("should reject an ingress with invalid path and regex enabled", func() {
|
||||
|
||||
ing := framework.NewSingleIngress(host, invalidPath, host, f.Namespace, framework.EchoService, 80, annotationRegex)
|
||||
f.EnsureIngress(ing)
|
||||
|
||||
f.WaitForNginxServer(host,
|
||||
func(server string) bool {
|
||||
return !strings.Contains(server, fmt.Sprintf("server_name %s ;", host))
|
||||
})
|
||||
|
||||
f.HTTPTestClient().
|
||||
GET(invalidPath).
|
||||
WithHeader("Host", host).
|
||||
Expect().
|
||||
Status(http.StatusNotFound)
|
||||
})
|
||||
})
|
Loading…
Reference in a new issue