Skip to content

Commit c50ef91

Browse files
Merge pull request #20357 from ramr/fix-haproxy-whitelist
HAProxy router fix for ip whitelist exceeding max config arguments that haproxy allows.
2 parents 95c3a80 + e23f8c0 commit c50ef91

File tree

5 files changed

+159
-3
lines changed

5 files changed

+159
-3
lines changed

images/router/haproxy/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ RUN INSTALL_PKGS="haproxy18 rsyslog" && \
99
yum install -y $INSTALL_PKGS && \
1010
rpm -V $INSTALL_PKGS && \
1111
yum clean all && \
12-
mkdir -p /var/lib/haproxy/router/{certs,cacerts} && \
12+
mkdir -p /var/lib/haproxy/router/{certs,cacerts,whitelists} && \
1313
mkdir -p /var/lib/haproxy/{conf/.tmp,run,bin,log} && \
1414
touch /var/lib/haproxy/conf/{{os_http_be,os_edge_reencrypt_be,os_tcp_be,os_sni_passthrough,os_route_http_redirect,cert_config,os_wildcard_domain}.map,haproxy.config} && \
1515
setcap 'cap_net_bind_service=ep' /usr/sbin/haproxy && \

images/router/haproxy/conf/haproxy-config.template

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -388,7 +388,13 @@ backend {{genBackendNamePrefix $cfg.TLSTermination}}:{{$cfgIdx}}
388388
balance {{ if gt $cfg.ActiveServiceUnits 1 }}roundrobin{{ else }}leastconn{{ end }}
389389
{{- end }}
390390
{{- with $ip_whiteList := firstMatch $cidrListPattern (index $cfg.Annotations "haproxy.router.openshift.io/ip_whitelist") }}
391-
acl whitelist src {{ $ip_whiteList }}
391+
{{- if validateHAProxyWhiteList $ip_whiteList }}
392+
acl whitelist src {{$ip_whiteList}}
393+
{{- else }}
394+
{{- with $whiteListFileName := generateHAProxyWhiteListFile $workingDir $cfgIdx $ip_whiteList}}
395+
acl whitelist src -f {{$whiteListFileName}}
396+
{{- end }}
397+
{{- end }}
392398
tcp-request content reject if !whitelist
393399
{{- end }}
394400
{{- with $value := firstMatch $timeSpecPattern (index $cfg.Annotations "haproxy.router.openshift.io/timeout")}}
@@ -510,7 +516,13 @@ backend {{genBackendNamePrefix $cfg.TLSTermination}}:{{$cfgIdx}}
510516
balance {{ if gt $cfg.ActiveServiceUnits 1 }}roundrobin{{ else }}source{{ end }}
511517
{{- end }}
512518
{{- with $ip_whiteList := firstMatch $cidrListPattern (index $cfg.Annotations "haproxy.router.openshift.io/ip_whitelist") }}
519+
{{- if validateHAProxyWhiteList $ip_whiteList }}
513520
acl whitelist src {{$ip_whiteList}}
521+
{{- else }}
522+
{{- with $whiteListFileName := generateHAProxyWhiteListFile $workingDir $cfgIdx $ip_whiteList}}
523+
acl whitelist src -f {{$whiteListFileName}}
524+
{{- end }}
525+
{{- end }}
514526
tcp-request content reject if !whitelist
515527
{{- end }}
516528
{{- with $value := firstMatch $timeSpecPattern (index $cfg.Annotations "haproxy.router.openshift.io/timeout")}}

pkg/router/template/template_helper.go

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package templaterouter
22

33
import (
44
"fmt"
5+
"io/ioutil"
56
"math/rand"
67
"os"
78
"path"
@@ -182,6 +183,25 @@ func generateHAProxyCertConfigMap(td templateData) []string {
182183
return lines
183184
}
184185

186+
// validateHAProxyWhiteList validates a whitelist for use with an haproxy acl.
187+
func validateHAProxyWhiteList(value string) bool {
188+
_, valid := haproxyutil.ValidateWhiteList(value)
189+
return valid
190+
}
191+
192+
// generateHAProxyWhiteListFile generates a whitelist file for use with an haproxy acl.
193+
func generateHAProxyWhiteListFile(workingDir, id, value string) string {
194+
name := path.Join(workingDir, "whitelists", fmt.Sprintf("%s.txt", id))
195+
cidrs, _ := haproxyutil.ValidateWhiteList(value)
196+
data := []byte(strings.Join(cidrs, "\n") + "\n")
197+
if err := ioutil.WriteFile(name, data, 0644); err != nil {
198+
glog.Errorf("Error writing haproxy whitelist contents: %v", err)
199+
return ""
200+
}
201+
202+
return name
203+
}
204+
185205
// getHTTPAliasesGroupedByHost returns HTTP(S) aliases grouped by their host.
186206
func getHTTPAliasesGroupedByHost(aliases map[string]ServiceAliasConfig) map[string]map[string]ServiceAliasConfig {
187207
result := make(map[string]map[string]ServiceAliasConfig)
@@ -270,5 +290,7 @@ var helperFunctions = template.FuncMap{
270290
"getHTTPAliasesGroupedByHost": getHTTPAliasesGroupedByHost, //returns HTTP(S) aliases grouped by their host
271291
"getPrimaryAliasKey": getPrimaryAliasKey, //returns the key of the primary alias for a group of aliases
272292

273-
"generateHAProxyMap": generateHAProxyMap, //generates a haproxy map content
293+
"generateHAProxyMap": generateHAProxyMap, //generates a haproxy map content
294+
"validateHAProxyWhiteList": validateHAProxyWhiteList, //validates a haproxy whitelist (acl) content
295+
"generateHAProxyWhiteListFile": generateHAProxyWhiteListFile, //generates a haproxy whitelist file for use in an acl
274296
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package haproxy
2+
3+
import (
4+
"strings"
5+
)
6+
7+
const (
8+
// HAPROXY_MAX_LINE_ARGS is the maximum number of arguments that haproxy
9+
// supports on a configuration line.
10+
// Ref: https://github.com/haproxy/haproxy/blob/master/include/common/defaults.h#L75
11+
HAPROXY_MAX_LINE_ARGS = 64
12+
13+
// HAPROXY_MAX_WHITELIST_LENGTH is the maximum number of CIDRs allowed
14+
// for an "acl whitelist src [<cidr>]*" config line.
15+
HAPROXY_MAX_WHITELIST_LENGTH = HAPROXY_MAX_LINE_ARGS - 3
16+
)
17+
18+
// ValidateWhiteList validates a haproxy acl whitelist from an annotation value.
19+
func ValidateWhiteList(value string) ([]string, bool) {
20+
values := strings.Split(value, " ")
21+
22+
cidrs := make([]string, 0)
23+
for _, v := range values {
24+
if len(v) > 0 {
25+
cidrs = append(cidrs, v)
26+
}
27+
}
28+
29+
return cidrs, len(cidrs) <= HAPROXY_MAX_WHITELIST_LENGTH
30+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package haproxy
2+
3+
import (
4+
"fmt"
5+
"reflect"
6+
"strings"
7+
"testing"
8+
9+
utilrand "k8s.io/apimachinery/pkg/util/rand"
10+
)
11+
12+
func generateTestData(n int) []string {
13+
cidrs := make([]string, 0)
14+
prefix := fmt.Sprintf("%d.%d.%d", utilrand.IntnRange(1, 254), utilrand.IntnRange(1, 254), utilrand.IntnRange(1, 254))
15+
for i := 1; i <= n; i++ {
16+
if i%254 == 0 {
17+
prefix = fmt.Sprintf("%d.%d.%d", utilrand.IntnRange(1, 254), utilrand.IntnRange(1, 254), utilrand.IntnRange(1, 254))
18+
}
19+
20+
cidr := fmt.Sprintf("%s.%d", prefix, (i%254)+1)
21+
if i%10 == 0 {
22+
cidr = fmt.Sprintf("%s/24", cidr)
23+
}
24+
cidrs = append(cidrs, cidr)
25+
}
26+
27+
return cidrs
28+
}
29+
30+
func TestValidateWhiteList(t *testing.T) {
31+
tests := []struct {
32+
name string
33+
data []string
34+
expectation []string
35+
}{
36+
{
37+
name: "empty list",
38+
data: []string{},
39+
expectation: []string{},
40+
},
41+
{
42+
name: "blanks",
43+
data: []string{"", " ", "", " ", " "},
44+
expectation: []string{},
45+
},
46+
{
47+
name: "one ip",
48+
data: []string{"1.2.3.4"},
49+
expectation: []string{"1.2.3.4"},
50+
},
51+
{
52+
name: "onesie",
53+
data: []string{"172.16.32.1/24"},
54+
expectation: []string{"172.16.32.1/24"},
55+
},
56+
{
57+
name: "duo",
58+
data: []string{"172.16.32.1/24", "10.1.2.3"},
59+
expectation: []string{"172.16.32.1/24", "10.1.2.3"},
60+
},
61+
{
62+
name: "interleaved blank entries",
63+
data: []string{"172.16.32.1/24", "", "1.2.3.4", "", "5.6.7.8", ""},
64+
expectation: []string{"172.16.32.1/24", "1.2.3.4", "5.6.7.8"},
65+
},
66+
}
67+
68+
for _, tc := range tests {
69+
values, ok := ValidateWhiteList(strings.Join(tc.data, " "))
70+
if !reflect.DeepEqual(tc.expectation, values) {
71+
t.Errorf("%s: expected validated data %+v, got %+v", tc.name, tc.expectation, values)
72+
}
73+
flagExpectation := len(tc.expectation) <= 61
74+
if ok != flagExpectation {
75+
t.Errorf("%s: expected flag %+v, got %+v", tc.name, flagExpectation, ok)
76+
}
77+
}
78+
79+
limitsTest := []int{9, 10, 16, 32, 60, 61, 62, 63, 64, 128, 253, 254, 255, 256, 512, 1024}
80+
for _, v := range limitsTest {
81+
name := fmt.Sprintf("limits-test-%d", v)
82+
data := generateTestData(v)
83+
values, ok := ValidateWhiteList(strings.Join(data, " "))
84+
if !reflect.DeepEqual(data, values) {
85+
t.Errorf("%s: expected validated data %+v, got %+v", name, data, values)
86+
}
87+
expectation := len(data) <= 61
88+
if ok != expectation {
89+
t.Errorf("%s: expected flag %+v, got %+v", name, expectation, ok)
90+
}
91+
}
92+
}

0 commit comments

Comments
 (0)