Skip to content

Commit 95672a3

Browse files
committed
add validation package and tests for usage report endpoint
1 parent bce6477 commit 95672a3

File tree

6 files changed

+199
-66
lines changed

6 files changed

+199
-66
lines changed

internal/configs/configmaps.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212

1313
"github.com/nginxinc/kubernetes-ingress/internal/configs/version1"
1414
nl "github.com/nginxinc/kubernetes-ingress/internal/logger"
15+
"github.com/nginxinc/kubernetes-ingress/internal/validation"
1516
)
1617

1718
const (
@@ -714,9 +715,20 @@ func ParseMGMTConfigMap(ctx context.Context, cfgm *v1.ConfigMap, eventLog record
714715
mgmtCfgParams.EnforceInitialReport = BoolToPointerBool(enforceInitialReport)
715716
}
716717
}
718+
717719
if endpoint, exists := cfgm.Data["usage-report-endpoint"]; exists {
718-
mgmtCfgParams.Endpoint = strings.TrimSpace(endpoint)
720+
endpoint := strings.TrimSpace(endpoint)
721+
err := validation.ValidateHost(endpoint)
722+
if err != nil {
723+
errorText := fmt.Sprintf("Configmap %s/%s: Invalid value for the usage-report-endpoint key: got %q: %v. Using default endpoint.", cfgm.GetNamespace(), cfgm.GetName(), endpoint, err)
724+
nl.Error(l, errorText)
725+
eventLog.Event(cfgm, v1.EventTypeWarning, invalidValueReason, errorText)
726+
configWarnings = true
727+
} else {
728+
mgmtCfgParams.Endpoint = strings.TrimSpace(endpoint)
729+
}
719730
}
731+
720732
if interval, exists := cfgm.Data["usage-report-interval"]; exists {
721733
i := strings.TrimSpace(interval)
722734
t, err := time.ParseDuration(i)

internal/configs/configmaps_test.go

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -722,13 +722,72 @@ func TestParseMGMTConfigMapResolverIPV6(t *testing.T) {
722722
if result.ResolverIPV6 == nil {
723723
t.Errorf("resolver-ipv6: want %v, got nil", *test.want.ResolverIPV6)
724724
}
725-
if *result.SSLVerify != *test.want.SSLVerify {
725+
if *result.ResolverIPV6 != *test.want.ResolverIPV6 {
726726
t.Errorf("resolver-ipv6: want %v, got %v", *test.want.ResolverIPV6, *result.ResolverIPV6)
727727
}
728728
})
729729
}
730730
}
731731

732+
func TestParseMGMTConfigMapUsageReportEndpoint(t *testing.T) {
733+
t.Parallel()
734+
tests := []struct {
735+
configMap *v1.ConfigMap
736+
want *MGMTConfigParams
737+
msg string
738+
}{
739+
{
740+
configMap: &v1.ConfigMap{
741+
Data: map[string]string{
742+
"license-token-secret-name": "license-token",
743+
"usage-report-endpoint": "product.connect.nginx.com",
744+
},
745+
},
746+
want: &MGMTConfigParams{
747+
Endpoint: "product.connect.nginx.com",
748+
Secrets: MGMTSecrets{
749+
License: "license-token",
750+
},
751+
},
752+
msg: "usage report endpoint set to product.connect.nginx.com",
753+
},
754+
{
755+
configMap: &v1.ConfigMap{
756+
Data: map[string]string{
757+
"license-token-secret-name": "license-token",
758+
"usage-report-endpoint": "product.connect.nginx.com:80",
759+
},
760+
},
761+
want: &MGMTConfigParams{
762+
Endpoint: "product.connect.nginx.com:80",
763+
Secrets: MGMTSecrets{
764+
License: "license-token",
765+
},
766+
},
767+
msg: "usage report endpoint set to product.connect.nginx.com with port 80",
768+
},
769+
}
770+
771+
for _, test := range tests {
772+
t.Run(test.msg, func(t *testing.T) {
773+
result, warnings, err := ParseMGMTConfigMap(context.Background(), test.configMap, makeEventLogger())
774+
if err != nil {
775+
t.Fatal(err)
776+
}
777+
if warnings {
778+
t.Error("Unexpected warnings")
779+
}
780+
781+
if result.Endpoint == "" {
782+
t.Errorf("UsageReportEndpoint: want %s, got empty string", test.want.Endpoint)
783+
}
784+
if result.Endpoint != test.want.Endpoint {
785+
t.Errorf("UsageReportEndpoint: want %v, got %v", test.want.Endpoint, result.Endpoint)
786+
}
787+
})
788+
}
789+
}
790+
732791
func makeEventLogger() record.EventRecorder {
733792
return record.NewFakeRecorder(1024)
734793
}

internal/validation/validation.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package validation
2+
3+
import (
4+
"fmt"
5+
"regexp"
6+
"strconv"
7+
"strings"
8+
)
9+
10+
var (
11+
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})?$`)
12+
validIPRegex = regexp.MustCompile(`^(\d{1,3}\.){3}\d{1,3}(?::\d{1,5})?$`)
13+
validLocalhostRegex = regexp.MustCompile(`^localhost(?::\d{1,5})?$`)
14+
)
15+
16+
func ValidatePort(value string) error {
17+
port, err := strconv.Atoi(value)
18+
if err != nil {
19+
return fmt.Errorf("error parsing port number: %w", err)
20+
}
21+
if port > 65535 || port < 1 {
22+
return fmt.Errorf("error parsing port: %v not a valid port number", port)
23+
}
24+
return nil
25+
}
26+
27+
func ValidateHost(host string) error {
28+
if host == "" {
29+
return fmt.Errorf("error parsing host: empty host")
30+
}
31+
32+
if validIPRegex.MatchString(host) || validDNSRegex.MatchString(host) || validLocalhostRegex.MatchString(host) {
33+
chunks := strings.Split(host, ":")
34+
err := ValidatePort(chunks[1])
35+
if err != nil {
36+
return fmt.Errorf("invalid port: %w", err)
37+
}
38+
return nil
39+
}
40+
return fmt.Errorf("invalid host: %s", host)
41+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package validation
2+
3+
import (
4+
"strings"
5+
"testing"
6+
)
7+
8+
func TestValidatePort_IsValidOnValidInput(t *testing.T) {
9+
t.Parallel()
10+
11+
ports := []string{"1", "65535"}
12+
for _, p := range ports {
13+
if err := ValidatePort(p); err != nil {
14+
t.Error(err)
15+
}
16+
}
17+
}
18+
19+
func TestValidatePort_ErrorsOnInvalidString(t *testing.T) {
20+
t.Parallel()
21+
22+
if err := ValidatePort(""); err == nil {
23+
t.Error("want error, got nil")
24+
}
25+
}
26+
27+
func TestValidatePort_ErrorsOnInvalidRange(t *testing.T) {
28+
t.Parallel()
29+
30+
ports := []string{"0", "-1", "65536"}
31+
for _, p := range ports {
32+
if err := ValidatePort(p); err == nil {
33+
t.Error("want error, got nil")
34+
}
35+
}
36+
}
37+
38+
func TestValidateHost(t *testing.T) {
39+
t.Parallel()
40+
// Positive test cases
41+
posHosts := []string{
42+
"10.10.1.1:514",
43+
"localhost:514",
44+
"dns.test.svc.cluster.local:514",
45+
"cluster.local:514",
46+
"dash-test.cluster.local:514",
47+
"product.example.com",
48+
}
49+
50+
// Negative test cases item, expected error message
51+
negHosts := [][]string{
52+
{"NotValid", "invalid host: NotValid"},
53+
{"cluster.local", "invalid host: cluster.local"},
54+
{"-cluster.local:514", "invalid host: -cluster.local:514"},
55+
{"10.10.1.1:99999", "not a valid port number"},
56+
}
57+
58+
for _, tCase := range posHosts {
59+
err := ValidateHost(tCase)
60+
if err != nil {
61+
t.Errorf("expected nil, got %v", err)
62+
}
63+
}
64+
65+
for _, nTCase := range negHosts {
66+
err := ValidateHost(nTCase[0])
67+
if err == nil {
68+
t.Errorf("got no error expected error containing '%s'", nTCase[1])
69+
} else {
70+
if !strings.Contains(err.Error(), nTCase[1]) {
71+
t.Errorf("got '%v', expected: '%s'", err, nTCase[1])
72+
}
73+
}
74+
}
75+
}

pkg/apis/dos/validation/dos.go

Lines changed: 5 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,14 @@ package validation
22

33
import (
44
"fmt"
5-
"net"
6-
"net/url"
7-
"regexp"
8-
"strconv"
9-
"strings"
10-
5+
k8svalidation "github.com/nginxinc/kubernetes-ingress/internal/validation"
116
validation2 "github.com/nginxinc/kubernetes-ingress/pkg/apis/configuration/validation"
127
"github.com/nginxinc/kubernetes-ingress/pkg/apis/dos/v1beta1"
138
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
149
"k8s.io/apimachinery/pkg/util/validation"
1510
"k8s.io/apimachinery/pkg/util/validation/field"
11+
"net"
12+
"net/url"
1613
)
1714

1815
var appProtectDosPolicyRequiredFields = [][]string{
@@ -116,34 +113,13 @@ func ValidateAppProtectDosLogConf(logConf *unstructured.Unstructured) (string, e
116113
return warning, nil
117114
}
118115

119-
var (
120-
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}$`)
121-
validIPRegex = regexp.MustCompile(`^(\d{1,3}\.){3}\d{1,3}:\d{1,5}$`)
122-
validLocalhostRegex = regexp.MustCompile(`^localhost:\d{1,5}$`)
123-
)
124-
125116
func validateAppProtectDosLogDest(dstAntn string) error {
126117
if dstAntn == "stderr" {
127118
return nil
128119
}
129-
if validIPRegex.MatchString(dstAntn) || validDNSRegex.MatchString(dstAntn) || validLocalhostRegex.MatchString(dstAntn) {
130-
chunks := strings.Split(dstAntn, ":")
131-
err := validatePort(chunks[1])
132-
if err != nil {
133-
return fmt.Errorf("invalid log destination: %w", err)
134-
}
135-
return nil
136-
}
137-
return fmt.Errorf("invalid log destination: %s, must follow format: <ip-address | localhost | dns name>:<port> or stderr", dstAntn)
138-
}
139-
140-
func validatePort(value string) error {
141-
port, err := strconv.Atoi(value)
120+
err := k8svalidation.ValidateHost(dstAntn)
142121
if err != nil {
143-
return fmt.Errorf("error parsing port number: %w", err)
144-
}
145-
if port > 65535 || port < 1 {
146-
return fmt.Errorf("error parsing port: %v not a valid port number", port)
122+
return fmt.Errorf("%w, must follow format: <ip-address | localhost | dns name>:<port> or stderr", err)
147123
}
148124
return nil
149125
}

pkg/apis/dos/validation/dos_test.go

Lines changed: 5 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ func TestValidateDosProtectedResource(t *testing.T) {
6363
DosAccessLogDest: "bad&$%^logdest",
6464
},
6565
},
66-
expectErr: "error validating DosProtectedResource: invalid field: dosAccessLogDest err: invalid log destination: bad&$%^logdest, must follow format: <ip-address | localhost | dns name>:<port> or stderr",
66+
expectErr: "error validating DosProtectedResource: invalid field: dosAccessLogDest err: invalid host: bad&$%^logdest, must follow format: <ip-address | localhost | dns name>:<port> or stderr",
6767
msg: "invalid DosAccessLogDest specified",
6868
},
6969
{
@@ -105,7 +105,7 @@ func TestValidateDosProtectedResource(t *testing.T) {
105105
DosSecurityLog: &v1beta1.DosSecurityLog{},
106106
},
107107
},
108-
expectErr: "error validating DosProtectedResource: invalid field: dosSecurityLog/dosLogDest err: invalid log destination: , must follow format: <ip-address | localhost | dns name>:<port> or stderr",
108+
expectErr: "error validating DosProtectedResource: invalid field: dosSecurityLog/dosLogDest err: error parsing host: empty host, must follow format: <ip-address | localhost | dns name>:<port> or stderr",
109109
msg: "empty DosSecurityLog specified",
110110
},
111111
{
@@ -191,9 +191,9 @@ func TestValidateAppProtectDosAccessLogDest(t *testing.T) {
191191

192192
// Negative test cases item, expected error message
193193
negDstAntns := [][]string{
194-
{"NotValid", "invalid log destination: NotValid, must follow format: <ip-address | localhost | dns name>:<port> or stderr"},
195-
{"cluster.local", "invalid log destination: cluster.local, must follow format: <ip-address | localhost | dns name>:<port> or stderr"},
196-
{"-cluster.local:514", "invalid log destination: -cluster.local:514, must follow format: <ip-address | localhost | dns name>:<port> or stderr"},
194+
{"NotValid", "invalid host: NotValid, must follow format: <ip-address | localhost | dns name>:<port> or stderr"},
195+
{"cluster.local", "invalid host: cluster.local, must follow format: <ip-address | localhost | dns name>:<port> or stderr"},
196+
{"-cluster.local:514", "invalid host: -cluster.local:514, must follow format: <ip-address | localhost | dns name>:<port> or stderr"},
197197
{"10.10.1.1:99999", "not a valid port number"},
198198
}
199199

@@ -450,36 +450,6 @@ func TestValidateAppProtectDosMonitor(t *testing.T) {
450450
}
451451
}
452452

453-
func TestValidatePort_IsValidOnValidInput(t *testing.T) {
454-
t.Parallel()
455-
456-
ports := []string{"1", "65535"}
457-
for _, p := range ports {
458-
if err := validatePort(p); err != nil {
459-
t.Error(err)
460-
}
461-
}
462-
}
463-
464-
func TestValidatePort_ErrorsOnInvalidString(t *testing.T) {
465-
t.Parallel()
466-
467-
if err := validatePort(""); err == nil {
468-
t.Error("want error, got nil")
469-
}
470-
}
471-
472-
func TestValidatePort_ErrorsOnInvalidRange(t *testing.T) {
473-
t.Parallel()
474-
475-
ports := []string{"0", "-1", "65536"}
476-
for _, p := range ports {
477-
if err := validatePort(p); err == nil {
478-
t.Error("want error, got nil")
479-
}
480-
}
481-
}
482-
483453
func TestValidateAppProtectDosLogDest_ValidOnDestinationStdErr(t *testing.T) {
484454
t.Parallel()
485455

0 commit comments

Comments
 (0)