Skip to content

Commit 1ed6100

Browse files
authored
Feature/validate tcp load balancer address (#387)
Load Balancer Validation part of troubleshoot pre-flight checks
1 parent 39350b5 commit 1ed6100

File tree

4 files changed

+180
-11
lines changed

4 files changed

+180
-11
lines changed

examples/preflight/host/tcp-load-balancer.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ spec:
1212
- tcpLoadBalancer:
1313
collectorName: loadbalancer
1414
outcomes:
15+
- fail:
16+
when: "invalid-address"
17+
message: The Load Balancer address is not valid.
1518
- fail:
1619
when: "connection-refused"
1720
message: Connection to port 7443 via load balancer was refused.

pkg/collect/host_network.go

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,15 @@ package collect
33
import (
44
"bytes"
55
"net"
6+
"regexp"
7+
"strconv"
68
"strings"
79
"time"
810

911
"github.com/pkg/errors"
1012
"github.com/replicatedhq/troubleshoot/pkg/debug"
1113
"github.com/segmentio/ksuid"
14+
validation "k8s.io/apimachinery/pkg/util/validation"
1215
)
1316

1417
type NetworkStatus string
@@ -20,13 +23,59 @@ const (
2023
NetworkStatusConnected = "connected"
2124
NetworkStatusErrorOther = "error"
2225
NetworkStatusBindPermissionDenied = "bind-permission-denied"
26+
NetworkStatusInvalidAddress = "invalid-address"
2327
)
2428

2529
type NetworkStatusResult struct {
26-
Status NetworkStatus `json:"status"`
30+
Status NetworkStatus `json:"status"`
31+
Message string `json:"message"`
32+
}
33+
34+
var ipRegexp = regexp.MustCompile(`^[0-9.]+$`)
35+
36+
func isValidLoadBalancerAddress(address string) bool {
37+
splitString := strings.Split(address, ":")
38+
39+
if len(splitString) != 2 { // should be hostAddress:port
40+
return false
41+
}
42+
hostAddress := splitString[0]
43+
port, err := strconv.Atoi(splitString[1])
44+
if err != nil {
45+
return false
46+
}
47+
portErrors := validation.IsValidPortNum(port)
48+
49+
if len(portErrors) > 0 {
50+
return false
51+
}
52+
53+
// Checking for uppercase letters
54+
if strings.ToLower(hostAddress) != hostAddress {
55+
return false
56+
}
57+
58+
// Checking if it's all numbers and .
59+
if ipRegexp.MatchString(hostAddress) {
60+
61+
// Check for isValidIP
62+
63+
test := validation.IsValidIP(hostAddress)
64+
return len(test) == 0
65+
66+
}
67+
68+
errs := validation.IsQualifiedName(hostAddress)
69+
70+
return len(errs) == 0
2771
}
2872

2973
func checkTCPConnection(progressChan chan<- interface{}, listenAddress string, dialAddress string, timeout time.Duration) (NetworkStatus, error) {
74+
75+
if !isValidLoadBalancerAddress(dialAddress) {
76+
return NetworkStatusInvalidAddress, errors.Errorf("Invalid Load Balancer Address: %v", dialAddress)
77+
}
78+
3079
lstn, err := net.Listen("tcp", listenAddress)
3180
if err != nil {
3281
if strings.Contains(err.Error(), "address already in use") {
@@ -42,7 +91,6 @@ func checkTCPConnection(progressChan chan<- interface{}, listenAddress string, d
4291
// token until the server responds with its token.
4392
requestToken := ksuid.New().Bytes()
4493
responseToken := ksuid.New().Bytes()
45-
4694
go func() {
4795
for {
4896
conn, err := lstn.Accept()
@@ -60,14 +108,19 @@ func checkTCPConnection(progressChan chan<- interface{}, listenAddress string, d
60108

61109
for {
62110
if time.Now().After(stopAfter) {
111+
debug.Printf("Timeout")
112+
63113
return NetworkStatusConnectionTimeout, nil
64114
}
65115

66116
conn, err := net.DialTimeout("tcp", dialAddress, 50*time.Millisecond)
67117
if err != nil {
118+
debug.Printf("Error: %s", err)
119+
68120
if strings.Contains(err.Error(), "i/o timeout") {
69121
progressChan <- err
70122
time.Sleep(time.Second)
123+
71124
continue
72125
}
73126
if strings.Contains(err.Error(), "connection refused") {
@@ -83,6 +136,7 @@ func checkTCPConnection(progressChan chan<- interface{}, listenAddress string, d
83136
progressChan <- errors.New("failed to verify connection to server")
84137
time.Sleep(time.Second)
85138
}
139+
86140
}
87141

88142
func handleTestConnection(conn net.Conn, requestToken []byte, responseToken []byte) bool {

pkg/collect/host_network_test.go

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package collect
2+
3+
import (
4+
"testing"
5+
)
6+
7+
func Test_isValidLoadBalancerAddress(t *testing.T) {
8+
type args struct {
9+
address string
10+
}
11+
tests := []struct {
12+
name string
13+
args args
14+
want bool
15+
}{
16+
{
17+
name: "Valid IP and Port",
18+
args: args{address: "1.2.3.4:6443"},
19+
want: true,
20+
},
21+
{
22+
name: "Too many :'s in address",
23+
args: args{address: "1.2.3.4:64:6443"},
24+
want: false,
25+
},
26+
{
27+
name: "Valid domain and Port ",
28+
args: args{address: "replicated.com:80"},
29+
want: true,
30+
},
31+
{
32+
name: "Valid subdomain and Port ",
33+
args: args{address: "sub.replicated.com:80"},
34+
want: true,
35+
},
36+
{
37+
name: "Valid subdomain with '-' and Port ",
38+
args: args{address: "sub-domain.replicated.com:80"},
39+
want: true,
40+
},
41+
{
42+
name: "Special Character",
43+
args: args{address: "sw!$$.com:80"},
44+
want: false,
45+
},
46+
{
47+
name: "Too many characters",
48+
args: args{address: "howlongcanwemakethiswithoutrunningoutofwordsbecasueweneedtohitatleast64.com:80"},
49+
want: false,
50+
},
51+
{
52+
name: "Capital Letters",
53+
args: args{address: "testDomain.com:80"},
54+
want: false,
55+
},
56+
{
57+
name: "Invalid IP",
58+
args: args{address: "55.555.51.23:80"},
59+
want: false,
60+
},
61+
{
62+
name: "Too many consecutive .",
63+
args: args{address: "55..55.51.23:80"},
64+
want: false,
65+
},
66+
{
67+
name: "Invalid Port too low",
68+
args: args{address: "55.55.51.23:0"},
69+
want: false,
70+
},
71+
{
72+
name: "Invalid Port too large",
73+
args: args{address: "55.55.51.23:999990"},
74+
want: false,
75+
},
76+
{
77+
name: "Invalid Port Character",
78+
args: args{address: "55.55.51.23:port"},
79+
want: false,
80+
},
81+
{
82+
name: "Invalid Port Number",
83+
args: args{address: "55.55.51.23:32.5"},
84+
want: false,
85+
},
86+
{
87+
name: "Codes in addresses",
88+
args: args{address: "192.168.0.1"},
89+
want: false,
90+
}, {
91+
name: "Codes in addresses",
92+
args: args{address: "\033[34m192.168.0.1\033[00m\n "},
93+
want: false,
94+
},
95+
}
96+
for _, tt := range tests {
97+
t.Run(tt.name, func(t *testing.T) {
98+
if got := isValidLoadBalancerAddress(tt.args.address); got != tt.want {
99+
t.Errorf("checkValidTCPAddress() = %v, want %v for %v", got, tt.want, tt.args.address)
100+
}
101+
})
102+
}
103+
}

pkg/collect/host_tcploadbalancer.go

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,20 +26,34 @@ func (c *CollectHostTCPLoadBalancer) Collect(progressChan chan<- interface{}) (m
2626
listenAddress := fmt.Sprintf("0.0.0.0:%d", c.hostCollector.Port)
2727
dialAddress := c.hostCollector.Address
2828

29+
name := path.Join("tcpLoadBalancer", "tcpLoadBalancer.json")
30+
if c.hostCollector.CollectorName != "" {
31+
name = path.Join("tcpLoadBalancer", fmt.Sprintf("%s.json", c.hostCollector.CollectorName))
32+
}
33+
2934
timeout := 60 * time.Minute
3035
if c.hostCollector.Timeout != "" {
3136
var err error
3237
timeout, err = time.ParseDuration(c.hostCollector.Timeout)
3338
if err != nil {
34-
return nil, errors.Wrap(err, "failed to parse durection")
39+
return nil, errors.Wrap(err, "failed to parse duration")
3540
}
3641
}
37-
3842
networkStatus, err := checkTCPConnection(progressChan, listenAddress, dialAddress, timeout)
3943
if err != nil {
40-
return nil, err
41-
}
44+
result := NetworkStatusResult{
45+
Status: networkStatus,
46+
Message: err.Error(),
47+
}
48+
b, err := json.Marshal(result)
49+
if err != nil {
50+
return nil, errors.Wrap(err, "failed to marshal result")
51+
}
4252

53+
return map[string][]byte{
54+
name: b,
55+
}, err
56+
}
4357
result := NetworkStatusResult{
4458
Status: networkStatus,
4559
}
@@ -49,11 +63,6 @@ func (c *CollectHostTCPLoadBalancer) Collect(progressChan chan<- interface{}) (m
4963
return nil, errors.Wrap(err, "failed to marshal result")
5064
}
5165

52-
name := path.Join("tcpLoadBalancer", "tcpLoadBalancer.json")
53-
if c.hostCollector.CollectorName != "" {
54-
name = path.Join("tcpLoadBalancer", fmt.Sprintf("%s.json", c.hostCollector.CollectorName))
55-
}
56-
5766
return map[string][]byte{
5867
name: b,
5968
}, nil

0 commit comments

Comments
 (0)