diff --git a/internal/validation/validation.go b/internal/validation/validation.go index f7c6953f8..bfeaaef45 100644 --- a/internal/validation/validation.go +++ b/internal/validation/validation.go @@ -8,11 +8,12 @@ import ( ) var ( - validDNSRegex = regexp.MustCompile(`^(?:[A-Za-z0-9][A-Za-z0-9-]{1,62}\.)([A-Za-z0-9-]{1,63}\.)*[A-Za-z]{2,6}(?::\d{1,5})?$`) - validIPRegex = regexp.MustCompile(`^(\d{1,3}\.){3}\d{1,3}(?::\d{1,5})?$`) - validLocalhostRegex = regexp.MustCompile(`^localhost(?::\d{1,5})?$`) + validDNSRegex = regexp.MustCompile(`^(?:[A-Za-z0-9][A-Za-z0-9-]{1,62}\.)([A-Za-z0-9-]{1,63}\.)*[A-Za-z]{2,6}(?::\d{1,5})?$`) + validIPRegex = regexp.MustCompile(`^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(?::\d{1,5})?$`) + validHostnameRegex = regexp.MustCompile(`^[a-z][A-Za-z0-9-]{1,62}(?::\d{1,5})?$`) ) +// ValidatePort ensure port matches rfc6335 https://www.rfc-editor.org/rfc/rfc6335.html func ValidatePort(value string) error { port, err := strconv.Atoi(value) if err != nil { @@ -24,18 +25,21 @@ func ValidatePort(value string) error { return nil } +// ValidateHost ensures the host is a valid hostname/IP address or FQDN with an optional :port appended func ValidateHost(host string) error { if host == "" { return fmt.Errorf("error parsing host: empty host") } - if validIPRegex.MatchString(host) || validDNSRegex.MatchString(host) || validLocalhostRegex.MatchString(host) { + if validIPRegex.MatchString(host) || validDNSRegex.MatchString(host) || validHostnameRegex.MatchString(host) { chunks := strings.Split(host, ":") - err := ValidatePort(chunks[1]) - if err != nil { - return fmt.Errorf("invalid port: %w", err) + if len(chunks) > 1 { + err := ValidatePort(chunks[1]) + if err != nil { + return fmt.Errorf("invalid port: %w", err) + } } return nil } - return fmt.Errorf("invalid host: %s", host) + return fmt.Errorf("error parsing host: %s not a valid host", host) } diff --git a/internal/validation/validation_test.go b/internal/validation/validation_test.go index d64485668..9ed4cf6b2 100644 --- a/internal/validation/validation_test.go +++ b/internal/validation/validation_test.go @@ -39,20 +39,28 @@ func TestValidateHost(t *testing.T) { t.Parallel() // Positive test cases posHosts := []string{ - "10.10.1.1:514", - "localhost:514", - "dns.test.svc.cluster.local:514", - "cluster.local:514", - "dash-test.cluster.local:514", + "10.10.1.1:443", + "10.10.1.1", + "123.112.224.43:443", + "172.120.3.222", + "localhost:80", + "localhost", + "myhost:54321", + "myhost", + "my-host:54321", + "my-host", + "dns.test.svc.cluster.local:8443", + "cluster.local:8443", "product.example.com", + "product.example.com:443", } // Negative test cases item, expected error message negHosts := [][]string{ - {"NotValid", "invalid host: NotValid"}, - {"cluster.local", "invalid host: cluster.local"}, - {"-cluster.local:514", "invalid host: -cluster.local:514"}, + {"NotValid", "not a valid host"}, + {"-cluster.local:514", "not a valid host"}, {"10.10.1.1:99999", "not a valid port number"}, + {"333.333.333.333", "not a valid host"}, } for _, tCase := range posHosts { diff --git a/pkg/apis/dos/validation/dos.go b/pkg/apis/dos/validation/dos.go index 7274e03fd..9f3970fc3 100644 --- a/pkg/apis/dos/validation/dos.go +++ b/pkg/apis/dos/validation/dos.go @@ -2,14 +2,17 @@ package validation import ( "fmt" - k8svalidation "github.com/nginxinc/kubernetes-ingress/internal/validation" + "net" + "net/url" + "regexp" + "strings" + + internalValidation "github.com/nginxinc/kubernetes-ingress/internal/validation" validation2 "github.com/nginxinc/kubernetes-ingress/pkg/apis/configuration/validation" "github.com/nginxinc/kubernetes-ingress/pkg/apis/dos/v1beta1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/util/validation" "k8s.io/apimachinery/pkg/util/validation/field" - "net" - "net/url" ) var appProtectDosPolicyRequiredFields = [][]string{ @@ -113,15 +116,25 @@ func ValidateAppProtectDosLogConf(logConf *unstructured.Unstructured) (string, e return warning, nil } +var ( + validDNSRegex = regexp.MustCompile(`^([A-Za-z0-9][A-Za-z0-9-]{1,62}\.)([A-Za-z0-9-]{1,63}\.)*[A-Za-z]{2,6}:\d{1,5}$`) + validIPRegex = regexp.MustCompile(`^(\d{1,3}\.){3}\d{1,3}:\d{1,5}$`) + validLocalhostRegex = regexp.MustCompile(`^localhost:\d{1,5}$`) +) + func validateAppProtectDosLogDest(dstAntn string) error { if dstAntn == "stderr" { return nil } - err := k8svalidation.ValidateHost(dstAntn) - if err != nil { - return fmt.Errorf("%w, must follow format: : or stderr", err) + if validIPRegex.MatchString(dstAntn) || validDNSRegex.MatchString(dstAntn) || validLocalhostRegex.MatchString(dstAntn) { + chunks := strings.Split(dstAntn, ":") + err := internalValidation.ValidatePort(chunks[1]) + if err != nil { + return fmt.Errorf("invalid log destination: %w", err) + } + return nil } - return nil + return fmt.Errorf("invalid log destination: %s, must follow format: : or stderr", dstAntn) } func validateAppProtectDosName(name string) error { diff --git a/pkg/apis/dos/validation/dos_test.go b/pkg/apis/dos/validation/dos_test.go index ba2bbecae..b70dcd921 100644 --- a/pkg/apis/dos/validation/dos_test.go +++ b/pkg/apis/dos/validation/dos_test.go @@ -63,7 +63,7 @@ func TestValidateDosProtectedResource(t *testing.T) { DosAccessLogDest: "bad&$%^logdest", }, }, - expectErr: "error validating DosProtectedResource: invalid field: dosAccessLogDest err: invalid host: bad&$%^logdest, must follow format: : or stderr", + expectErr: "error validating DosProtectedResource: invalid field: dosAccessLogDest err: invalid log destination: bad&$%^logdest, must follow format: : or stderr", msg: "invalid DosAccessLogDest specified", }, { @@ -105,7 +105,7 @@ func TestValidateDosProtectedResource(t *testing.T) { DosSecurityLog: &v1beta1.DosSecurityLog{}, }, }, - expectErr: "error validating DosProtectedResource: invalid field: dosSecurityLog/dosLogDest err: error parsing host: empty host, must follow format: : or stderr", + expectErr: "error validating DosProtectedResource: invalid field: dosSecurityLog/dosLogDest err: invalid log destination: , must follow format: : or stderr", msg: "empty DosSecurityLog specified", }, { @@ -159,7 +159,6 @@ func TestValidateDosProtectedResource(t *testing.T) { msg: "DosSecurityLog with valid apDosLogConf", }, } - for _, test := range tests { err := ValidateDosProtectedResource(test.protected) if err != nil { @@ -191,9 +190,9 @@ func TestValidateAppProtectDosAccessLogDest(t *testing.T) { // Negative test cases item, expected error message negDstAntns := [][]string{ - {"NotValid", "invalid host: NotValid, must follow format: : or stderr"}, - {"cluster.local", "invalid host: cluster.local, must follow format: : or stderr"}, - {"-cluster.local:514", "invalid host: -cluster.local:514, must follow format: : or stderr"}, + {"NotValid", "invalid log destination: NotValid, must follow format: : or stderr"}, + {"cluster.local", "invalid log destination: cluster.local, must follow format: : or stderr"}, + {"-cluster.local:514", "invalid log destination: -cluster.local:514, must follow format: : or stderr"}, {"10.10.1.1:99999", "not a valid port number"}, } @@ -203,7 +202,6 @@ func TestValidateAppProtectDosAccessLogDest(t *testing.T) { t.Errorf("expected nil, got %v", err) } } - for _, nTCase := range negDstAntns { err := validateAppProtectDosLogDest(nTCase[0]) if err == nil { @@ -216,97 +214,6 @@ func TestValidateAppProtectDosAccessLogDest(t *testing.T) { } } -func TestValidateAppProtectDosLogConf(t *testing.T) { - t.Parallel() - tests := []struct { - logConf *unstructured.Unstructured - expectErr bool - expectWarn bool - msg string - }{ - { - logConf: &unstructured.Unstructured{ - Object: map[string]interface{}{ - "spec": map[string]interface{}{ - "filter": map[string]interface{}{}, - }, - }, - }, - expectErr: false, - expectWarn: false, - msg: "valid log conf", - }, - { - logConf: &unstructured.Unstructured{ - Object: map[string]interface{}{ - "spec": map[string]interface{}{}, - }, - }, - expectErr: true, - expectWarn: false, - msg: "invalid log conf with no filter field", - }, - { - logConf: &unstructured.Unstructured{ - Object: map[string]interface{}{ - "something": map[string]interface{}{ - "filter": map[string]interface{}{}, - }, - }, - }, - expectErr: true, - expectWarn: false, - msg: "invalid log conf with no spec field", - }, - { - logConf: &unstructured.Unstructured{ - Object: map[string]interface{}{ - "spec": map[string]interface{}{ - "content": map[string]interface{}{ - "format": "user-defined", - }, - "filter": map[string]interface{}{}, - }, - }, - }, - expectErr: false, - expectWarn: true, - msg: "Support only splunk format", - }, - { - logConf: &unstructured.Unstructured{ - Object: map[string]interface{}{ - "spec": map[string]interface{}{ - "filter": map[string]interface{}{}, - "content": map[string]interface{}{ - "format": "user-defined", - }, - }, - }, - }, - expectErr: false, - expectWarn: true, - msg: "valid log conf with warning filter field", - }, - } - - for _, test := range tests { - warn, err := ValidateAppProtectDosLogConf(test.logConf) - if test.expectErr && err == nil { - t.Errorf("validateAppProtectDosLogConf() returned no error for the case of %s", test.msg) - } - if !test.expectErr && err != nil { - t.Errorf("validateAppProtectDosLogConf() returned unexpected error %v for the case of %s", err, test.msg) - } - if test.expectWarn && warn == "" { - t.Errorf("validateAppProtectDosLogConf() returned no warning for the case of %s", test.msg) - } - if !test.expectWarn && warn != "" { - t.Errorf("validateAppProtectDosLogConf() returned unexpected warning: %s, for the case of %s", warn, test.msg) - } - } -} - func TestValidateAppProtectDosPolicy(t *testing.T) { t.Parallel() tests := []struct {