From bd6f933fd257aee194e278cbcf93dadca8916336 Mon Sep 17 00:00:00 2001 From: Joao Morais Date: Wed, 1 May 2019 18:22:50 -0300 Subject: [PATCH 01/30] Improve tis-auth configuration Check in an earlier stage if some tls-auth check is needed, skipping some lookup in maps whenever possible. --- pkg/haproxy/instance_test.go | 37 ++++++++++++++---------- pkg/haproxy/types/frontend.go | 2 +- rootfs/etc/haproxy/template/haproxy.tmpl | 14 +++++---- 3 files changed, 30 insertions(+), 23 deletions(-) diff --git a/pkg/haproxy/instance_test.go b/pkg/haproxy/instance_test.go index a2d17c524..372fb1f9b 100644 --- a/pkg/haproxy/instance_test.go +++ b/pkg/haproxy/instance_test.go @@ -296,15 +296,17 @@ frontend _front_001 http-request set-var(req.hostbackend) base,regsub(:[0-9]+/,/),map_beg(/etc/haproxy/maps/_front_001_host.map,_nomatch) http-request set-header x-ha-base %[ssl_fc_sni]%[path] http-request set-var(req.snibackend) hdr(x-ha-base),regsub(:[0-9]+/,/),map_beg(/etc/haproxy/maps/_front_001_sni.map,_nomatch) - acl tls-invalid-crt ssl_c_ca_err gt 0 - acl tls-invalid-crt ssl_c_err gt 0 acl tls-has-crt ssl_c_used - http-request set-var(req.tls_nocrt_redir) ssl_fc_sni,map(/etc/haproxy/maps/_front_001_no_crt_redir.map,_internal) if !tls-has-crt - http-request set-var(req.tls_invalidcrt_redir) ssl_fc_sni,map(/etc/haproxy/maps/_front_001_inv_crt_redir.map,_internal) if tls-invalid-crt + acl tls-need-crt ssl_fc_sni -i -f /etc/haproxy/maps/_front_001_no_crt.list + acl tls-has-invalid-crt ssl_c_ca_err gt 0 + acl tls-has-invalid-crt ssl_c_err gt 0 + acl tls-check-crt ssl_fc_sni -i -f /etc/haproxy/maps/_front_001_inv_crt.list + http-request set-var(req.tls_nocrt_redir) ssl_fc_sni,map(/etc/haproxy/maps/_front_001_no_crt_redir.map,_internal) if !tls-has-crt tls-need-crt + http-request set-var(req.tls_invalidcrt_redir) ssl_fc_sni,map(/etc/haproxy/maps/_front_001_inv_crt_redir.map,_internal) if tls-has-invalid-crt tls-check-crt http-request redirect location %[var(req.tls_nocrt_redir)] code 303 if { var(req.tls_nocrt_redir) -m found } !{ var(req.tls_nocrt_redir) _internal } http-request redirect location %[var(req.tls_invalidcrt_redir)] code 303 if { var(req.tls_invalidcrt_redir) -m found } !{ var(req.tls_invalidcrt_redir) _internal } - use_backend _error496 if { var(req.tls_nocrt_redir) _internal } { ssl_fc_sni -i -f /etc/haproxy/maps/_front_001_no_crt.list } - use_backend _error495 if { var(req.tls_invalidcrt_redir) _internal } { ssl_fc_sni -i -f /etc/haproxy/maps/_front_001_inv_crt.list } + use_backend _error496 if { var(req.tls_nocrt_redir) _internal } + use_backend _error495 if { var(req.tls_invalidcrt_redir) _internal } use_backend %[var(req.hostbackend)] unless { var(req.hostbackend) _nomatch } use_backend %[var(req.snibackend)] unless { var(req.snibackend) _nomatch } default_backend _default_backend @@ -429,15 +431,17 @@ frontend _front_001 http-request set-var(req.hostbackend) base,regsub(:[0-9]+/,/),map_beg(/etc/haproxy/maps/_front_001_host.map,_nomatch) http-request set-header x-ha-base %[ssl_fc_sni]%[path] http-request set-var(req.snibackend) hdr(x-ha-base),regsub(:[0-9]+/,/),map_beg(/etc/haproxy/maps/_front_001_sni.map,_nomatch) - acl tls-invalid-crt ssl_c_ca_err gt 0 - acl tls-invalid-crt ssl_c_err gt 0 acl tls-has-crt ssl_c_used - http-request set-var(req.tls_nocrt_redir) ssl_fc_sni,map(/etc/haproxy/maps/_front_001_no_crt_redir.map,_internal) if !tls-has-crt - http-request set-var(req.tls_invalidcrt_redir) ssl_fc_sni,map(/etc/haproxy/maps/_front_001_inv_crt_redir.map,_internal) if tls-invalid-crt + acl tls-need-crt ssl_fc_sni -i -f /etc/haproxy/maps/_front_001_no_crt.list + acl tls-has-invalid-crt ssl_c_ca_err gt 0 + acl tls-has-invalid-crt ssl_c_err gt 0 + acl tls-check-crt ssl_fc_sni -i -f /etc/haproxy/maps/_front_001_inv_crt.list + http-request set-var(req.tls_nocrt_redir) ssl_fc_sni,map(/etc/haproxy/maps/_front_001_no_crt_redir.map,_internal) if !tls-has-crt tls-need-crt + http-request set-var(req.tls_invalidcrt_redir) ssl_fc_sni,map(/etc/haproxy/maps/_front_001_inv_crt_redir.map,_internal) if tls-has-invalid-crt tls-check-crt http-request redirect location %[var(req.tls_nocrt_redir)] code 303 if { var(req.tls_nocrt_redir) -m found } !{ var(req.tls_nocrt_redir) _internal } http-request redirect location %[var(req.tls_invalidcrt_redir)] code 303 if { var(req.tls_invalidcrt_redir) -m found } !{ var(req.tls_invalidcrt_redir) _internal } - use_backend _error496 if { var(req.tls_nocrt_redir) _internal } { ssl_fc_sni -i -f /etc/haproxy/maps/_front_001_no_crt.list } - use_backend _error495 if { var(req.tls_invalidcrt_redir) _internal } { ssl_fc_sni -i -f /etc/haproxy/maps/_front_001_inv_crt.list } + use_backend _error496 if { var(req.tls_nocrt_redir) _internal } + use_backend _error495 if { var(req.tls_invalidcrt_redir) _internal } use_backend %[var(req.hostbackend)] unless { var(req.hostbackend) _nomatch } use_backend %[var(req.snibackend)] unless { var(req.snibackend) _nomatch } default_backend _default_backend @@ -448,11 +452,12 @@ frontend https-front_d1.local http-request set-var(req.hostbackend) base,regsub(:[0-9]+/,/),map_beg(/etc/haproxy/maps/https-front_d1.local_host.map,_nomatch) http-request set-header x-ha-base %[ssl_fc_sni]%[path] http-request set-var(req.snibackend) hdr(x-ha-base),regsub(:[0-9]+/,/),map_beg(/etc/haproxy/maps/https-front_d1.local_sni.map,_nomatch) - acl tls-invalid-crt ssl_c_ca_err gt 0 - acl tls-invalid-crt ssl_c_err gt 0 - http-request set-var(req.tls_invalidcrt_redir) ssl_fc_sni,map(/etc/haproxy/maps/https-front_d1.local_inv_crt_redir.map,_internal) if tls-invalid-crt + acl tls-has-invalid-crt ssl_c_ca_err gt 0 + acl tls-has-invalid-crt ssl_c_err gt 0 + acl tls-check-crt ssl_fc_sni -i -f /etc/haproxy/maps/https-front_d1.local_inv_crt.list + http-request set-var(req.tls_invalidcrt_redir) ssl_fc_sni,map(/etc/haproxy/maps/https-front_d1.local_inv_crt_redir.map,_internal) if tls-has-invalid-crt tls-check-crt http-request redirect location %[var(req.tls_invalidcrt_redir)] code 303 if { var(req.tls_invalidcrt_redir) -m found } !{ var(req.tls_invalidcrt_redir) _internal } - use_backend _error495 if { var(req.tls_invalidcrt_redir) _internal } { ssl_fc_sni -i -f /etc/haproxy/maps/https-front_d1.local_inv_crt.list } + use_backend _error495 if { var(req.tls_invalidcrt_redir) _internal } use_backend %[var(req.hostbackend)] unless { var(req.hostbackend) _nomatch } use_backend %[var(req.snibackend)] unless { var(req.snibackend) _nomatch } default_backend _default_backend diff --git a/pkg/haproxy/types/frontend.go b/pkg/haproxy/types/frontend.go index af6460988..ba918694a 100644 --- a/pkg/haproxy/types/frontend.go +++ b/pkg/haproxy/types/frontend.go @@ -63,7 +63,7 @@ func (f *Frontend) HasNoCrtErrorPage() bool { // HasTLSMandatory ... func (f *Frontend) HasTLSMandatory() bool { for _, host := range f.Hosts { - if !host.TLS.CAVerifyOptional { + if host.HasTLSAuth() && !host.TLS.CAVerifyOptional { return true } } diff --git a/rootfs/etc/haproxy/template/haproxy.tmpl b/rootfs/etc/haproxy/template/haproxy.tmpl index 8d0a1f01c..2463845e9 100644 --- a/rootfs/etc/haproxy/template/haproxy.tmpl +++ b/rootfs/etc/haproxy/template/haproxy.tmpl @@ -343,19 +343,23 @@ frontend {{ $frontend.Name }} {{- "" }},regsub(:[0-9]+/,/) {{- "" }},map_beg({{ $frontend.SNIBackendsMap }},_nomatch) {{- $mandatory := $frontend.HasTLSMandatory }} - acl tls-invalid-crt ssl_c_ca_err gt 0 - acl tls-invalid-crt ssl_c_err gt 0 {{- if $mandatory }} acl tls-has-crt ssl_c_used + acl tls-need-crt ssl_fc_sni -i -f {{ $frontend.TLSNoCrtErrorList }} +{{- end }} + acl tls-has-invalid-crt ssl_c_ca_err gt 0 + acl tls-has-invalid-crt ssl_c_err gt 0 + acl tls-check-crt ssl_fc_sni -i -f {{ $frontend.TLSInvalidCrtErrorList }} +{{- if $mandatory }} http-request set-var(req.tls_nocrt_redir) ssl_fc_sni {{- if $frontend.ConvertLowercase }},lower{{ end }} {{- "" }},map({{ $frontend.TLSNoCrtErrorPagesMap }},_internal) - {{- "" }} if !tls-has-crt + {{- "" }} if !tls-has-crt tls-need-crt {{- end }} http-request set-var(req.tls_invalidcrt_redir) ssl_fc_sni {{- if $frontend.ConvertLowercase }},lower{{ end }} {{- "" }},map({{ $frontend.TLSInvalidCrtErrorPagesMap }},_internal) - {{- "" }} if tls-invalid-crt + {{- "" }} if tls-has-invalid-crt tls-check-crt {{- if and $mandatory $frontend.HasNoCrtErrorPage }} http-request redirect location %[var(req.tls_nocrt_redir)] code 303 if {{- "" }} { var(req.tls_nocrt_redir) -m found } !{ var(req.tls_nocrt_redir) _internal } @@ -367,11 +371,9 @@ frontend {{ $frontend.Name }} {{- if $mandatory }} use_backend _error496 if {{- "" }} { var(req.tls_nocrt_redir) _internal } - {{- "" }} { ssl_fc_sni -i -f {{ $frontend.TLSNoCrtErrorList }} } {{- end }} use_backend _error495 if {{- "" }} { var(req.tls_invalidcrt_redir) _internal } - {{- "" }} { ssl_fc_sni -i -f {{ $frontend.TLSInvalidCrtErrorList }} } {{- end }} {{- /*------------------------------------*/}} From 80b8f29b6d7bbbd0de658753b7da10e7416a0e7e Mon Sep 17 00:00:00 2001 From: Joao Morais Date: Sun, 5 May 2019 20:34:10 -0300 Subject: [PATCH 02/30] Use self generated IDs on frontends, servers and sockets Frontends, servers and sockets with only one hostname used the hostname as part of the name. This is an exception use case and would fail if a special char is used, eg on wildcard hostnames. --- pkg/haproxy/config.go | 10 +- pkg/haproxy/instance_test.go | 176 +++++++++++------------ pkg/haproxy/types/frontend.go | 8 +- pkg/haproxy/types/frontend_test.go | 10 +- rootfs/etc/haproxy/template/haproxy.tmpl | 5 +- 5 files changed, 101 insertions(+), 108 deletions(-) diff --git a/pkg/haproxy/config.go b/pkg/haproxy/config.go index 2add7602d..39eeb2b39 100644 --- a/pkg/haproxy/config.go +++ b/pkg/haproxy/config.go @@ -205,14 +205,12 @@ func (c *config) BuildFrontendGroup() (*hatypes.FrontendGroup, error) { var i int for _, frontend := range frontends { for _, bind := range frontend.Binds { - var bindName string + i++ + bindName := fmt.Sprintf("_socket%03d", i) if len(bind.Hosts) == 1 { - bindName = bind.Hosts[0].Hostname bind.TLS.TLSCert = c.defaultX509Cert bind.TLS.TLSCertDir = bind.Hosts[0].TLS.TLSFilename } else { - i++ - bindName = fmt.Sprintf("_socket%03d", i) x509dir, err := c.createCertsDir(bindName, bind.Hosts) if err != nil { return nil, err @@ -221,7 +219,7 @@ func (c *config) BuildFrontendGroup() (*hatypes.FrontendGroup, error) { bind.TLS.TLSCertDir = x509dir } bind.Name = bindName - bind.Socket = fmt.Sprintf("unix@/var/run/front_%s.sock", bindName) + bind.Socket = fmt.Sprintf("unix@/var/run/%s.sock", bindName) bind.AcceptProxy = true } } @@ -252,7 +250,7 @@ func (c *config) BuildFrontendGroup() (*hatypes.FrontendGroup, error) { frontend.TLSNoCrtErrorPagesMap = mapsPrefix + "_no_crt_redir.map" frontend.VarNamespaceMap = mapsPrefix + "_k8s_ns.map" for _, bind := range frontend.Binds { - bind.UseServerList = mapsPrefix + "_bind_" + bind.Name + ".list" + bind.UseServerList = c.mapsDir + "/" + bind.Name + ".list" } } type mapEntry struct { diff --git a/pkg/haproxy/instance_test.go b/pkg/haproxy/instance_test.go index 372fb1f9b..6536b8829 100644 --- a/pkg/haproxy/instance_test.go +++ b/pkg/haproxy/instance_test.go @@ -81,10 +81,10 @@ frontend _front__http http-request set-var(req.backend) base,regsub(:[0-9]+/,/),map_beg(/etc/haproxy/maps/http-front.map,_nomatch) use_backend %%[var(req.backend)] unless { var(req.backend) _nomatch } default_backend _error404 -frontend https-front_empty +frontend _front001 mode http bind :443 ssl alpn h2,http/1.1 crt /var/haproxy/ssl/certs/default.pem - http-request set-var(req.hostbackend) base,regsub(:[0-9]+/,/),map_beg(/etc/haproxy/maps/https-front_empty_host.map,_nomatch) + http-request set-var(req.hostbackend) base,regsub(:[0-9]+/,/),map_beg(/etc/haproxy/maps/_front001_host.map,_nomatch) use_backend %%[var(req.hostbackend)] unless { var(req.hostbackend) _nomatch } default_backend _error404 ` @@ -95,7 +95,7 @@ frontend https-front_empty c.checkMap("http-front.map", ` empty/ default_empty_8080`) - c.checkMap("https-front_empty_host.map", ` + c.checkMap("_front001_host.map", ` empty/ default_empty_8080`) c.logger.CompareLogging(defaultLogging) @@ -144,18 +144,18 @@ frontend _front__http http-request set-var(req.base) base,regsub(:[0-9]+/,/) redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/redirect.map,_nomatch) yes } use_backend d1_app_8080 -frontend https-front_d2.local +frontend _front001 mode http bind :443 ssl alpn h2,http/1.1 crt /var/haproxy/ssl/certs/default.pem - http-request set-var(txn.namespace) base,regsub(:[0-9]+/,/),map_beg(/etc/haproxy/maps/https-front_d2.local_k8s_ns.map,-) - http-request set-var(req.hostbackend) base,regsub(:[0-9]+/,/),map_beg(/etc/haproxy/maps/https-front_d2.local_host.map,_nomatch) + http-request set-var(txn.namespace) base,regsub(:[0-9]+/,/),map_beg(/etc/haproxy/maps/_front001_k8s_ns.map,-) + http-request set-var(req.hostbackend) base,regsub(:[0-9]+/,/),map_beg(/etc/haproxy/maps/_front001_host.map,_nomatch) use_backend %[var(req.hostbackend)] unless { var(req.hostbackend) _nomatch } use_backend d1_app_8080 `) - c.checkMap("https-front_d2.local_k8s_ns.map", ` + c.checkMap("_front001_k8s_ns.map", ` d2.local/app d2`) - c.checkMap("https-front_d2.local_host.map", ` + c.checkMap("_front001_host.map", ` d2.local/app d2_app_8080`) c.checkMap("redirect.map", ` d2.local/app yes`) @@ -209,19 +209,19 @@ frontend _front__http http-request set-var(req.base) base,regsub(:[0-9]+/,/) redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/redirect.map,_nomatch) yes } default_backend _default_backend -frontend _front_001 +frontend _front001 mode http bind :443 ssl alpn h2,http/1.1 crt /var/haproxy/ssl/certs/default.pem crt /var/haproxy/certs/_public - http-request set-var(txn.namespace) base,regsub(:[0-9]+/,/),map_beg(/etc/haproxy/maps/_front_001_k8s_ns.map,-) - http-request set-var(req.hostbackend) base,regsub(:[0-9]+/,/),map_beg(/etc/haproxy/maps/_front_001_host.map,_nomatch) + http-request set-var(txn.namespace) base,regsub(:[0-9]+/,/),map_beg(/etc/haproxy/maps/_front001_k8s_ns.map,-) + http-request set-var(req.hostbackend) base,regsub(:[0-9]+/,/),map_beg(/etc/haproxy/maps/_front001_host.map,_nomatch) use_backend %[var(req.hostbackend)] unless { var(req.hostbackend) _nomatch } default_backend _default_backend `) - c.checkMap("_front_001_k8s_ns.map", ` + c.checkMap("_front001_k8s_ns.map", ` d1.local/ d1 d2.local/app -`) - c.checkMap("_front_001_host.map", ` + c.checkMap("_front001_host.map", ` d1.local/ d1_app_8080 d2.local/app d2_app_8080`) c.checkMap("redirect.map", ` @@ -277,11 +277,11 @@ listen _front__tls bind :443 tcp-request inspect-delay 5s tcp-request content accept if { req.ssl_hello_type 1 } - ## _front_001 - use-server _server_d1.local if { req.ssl_sni -i -f /etc/haproxy/maps/_front_001_bind_d1.local.list } - server _server_d1.local unix@/var/run/front_d1.local.sock send-proxy-v2 weight 0 - use-server _server_d2.local if { req.ssl_sni -i -f /etc/haproxy/maps/_front_001_bind_d2.local.list } - server _server_d2.local unix@/var/run/front_d2.local.sock send-proxy-v2 weight 0 + ## _front001 + use-server _server_socket001 if { req.ssl_sni -i -f /etc/haproxy/maps/_socket001.list } + server _server_socket001 unix@/var/run/_socket001.sock send-proxy-v2 weight 0 + use-server _server_socket002 if { req.ssl_sni -i -f /etc/haproxy/maps/_socket002.list } + server _server_socket002 unix@/var/run/_socket002.sock send-proxy-v2 weight 0 # TODO default backend frontend _front__http mode http @@ -289,20 +289,20 @@ frontend _front__http http-request set-var(req.base) base,regsub(:[0-9]+/,/) redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/redirect.map,_nomatch) yes } default_backend _default_backend -frontend _front_001 +frontend _front001 mode http - bind unix@/var/run/front_d1.local.sock accept-proxy ssl alpn h2,http/1.1 crt /var/haproxy/ssl/certs/default.pem ca-file /var/haproxy/ssl/ca/d1.local.pem verify optional ca-ignore-err all crt-ignore-err all - bind unix@/var/run/front_d2.local.sock accept-proxy ssl alpn h2,http/1.1 crt /var/haproxy/ssl/certs/default.pem ca-file /var/haproxy/ssl/ca/d2.local.pem verify optional ca-ignore-err all crt-ignore-err all - http-request set-var(req.hostbackend) base,regsub(:[0-9]+/,/),map_beg(/etc/haproxy/maps/_front_001_host.map,_nomatch) + bind unix@/var/run/_socket001.sock accept-proxy ssl alpn h2,http/1.1 crt /var/haproxy/ssl/certs/default.pem ca-file /var/haproxy/ssl/ca/d1.local.pem verify optional ca-ignore-err all crt-ignore-err all + bind unix@/var/run/_socket002.sock accept-proxy ssl alpn h2,http/1.1 crt /var/haproxy/ssl/certs/default.pem ca-file /var/haproxy/ssl/ca/d2.local.pem verify optional ca-ignore-err all crt-ignore-err all + http-request set-var(req.hostbackend) base,regsub(:[0-9]+/,/),map_beg(/etc/haproxy/maps/_front001_host.map,_nomatch) http-request set-header x-ha-base %[ssl_fc_sni]%[path] - http-request set-var(req.snibackend) hdr(x-ha-base),regsub(:[0-9]+/,/),map_beg(/etc/haproxy/maps/_front_001_sni.map,_nomatch) + http-request set-var(req.snibackend) hdr(x-ha-base),regsub(:[0-9]+/,/),map_beg(/etc/haproxy/maps/_front001_sni.map,_nomatch) acl tls-has-crt ssl_c_used - acl tls-need-crt ssl_fc_sni -i -f /etc/haproxy/maps/_front_001_no_crt.list + acl tls-need-crt ssl_fc_sni -i -f /etc/haproxy/maps/_front001_no_crt.list acl tls-has-invalid-crt ssl_c_ca_err gt 0 acl tls-has-invalid-crt ssl_c_err gt 0 - acl tls-check-crt ssl_fc_sni -i -f /etc/haproxy/maps/_front_001_inv_crt.list - http-request set-var(req.tls_nocrt_redir) ssl_fc_sni,map(/etc/haproxy/maps/_front_001_no_crt_redir.map,_internal) if !tls-has-crt tls-need-crt - http-request set-var(req.tls_invalidcrt_redir) ssl_fc_sni,map(/etc/haproxy/maps/_front_001_inv_crt_redir.map,_internal) if tls-has-invalid-crt tls-check-crt + acl tls-check-crt ssl_fc_sni -i -f /etc/haproxy/maps/_front001_inv_crt.list + http-request set-var(req.tls_nocrt_redir) ssl_fc_sni,map(/etc/haproxy/maps/_front001_no_crt_redir.map,_internal) if !tls-has-crt tls-need-crt + http-request set-var(req.tls_invalidcrt_redir) ssl_fc_sni,map(/etc/haproxy/maps/_front001_inv_crt_redir.map,_internal) if tls-has-invalid-crt tls-check-crt http-request redirect location %[var(req.tls_nocrt_redir)] code 303 if { var(req.tls_nocrt_redir) -m found } !{ var(req.tls_nocrt_redir) _internal } http-request redirect location %[var(req.tls_invalidcrt_redir)] code 303 if { var(req.tls_invalidcrt_redir) -m found } !{ var(req.tls_invalidcrt_redir) _internal } use_backend _error496 if { var(req.tls_nocrt_redir) _internal } @@ -312,24 +312,24 @@ frontend _front_001 default_backend _default_backend `) - c.checkMap("_front_001_inv_crt_redir.map", ` + c.checkMap("_front001_inv_crt_redir.map", ` d1.local http://d1.local/error.html`) - c.checkMap("_front_001_inv_crt.list", ` + c.checkMap("_front001_inv_crt.list", ` d1.local d2.local`) - c.checkMap("_front_001_no_crt_redir.map", ` + c.checkMap("_front001_no_crt_redir.map", ` d1.local http://d1.local/error.html`) - c.checkMap("_front_001_no_crt.list", ` + c.checkMap("_front001_no_crt.list", ` d1.local d2.local`) - c.checkMap("_front_001_host.map", ` + c.checkMap("_front001_host.map", ` `) - c.checkMap("_front_001_sni.map", ` + c.checkMap("_front001_sni.map", ` d1.local/ d_app_8080 d2.local/ d_app_8080`) - c.checkMap("_front_001_bind_d1.local.list", ` + c.checkMap("_socket001.list", ` d1.local`) - c.checkMap("_front_001_bind_d2.local.list", ` + c.checkMap("_socket002.list", ` d2.local`) c.checkMap("redirect.map", ` d1.local/ yes @@ -406,14 +406,14 @@ listen _front__tls bind :443 tcp-request inspect-delay 5s tcp-request content accept if { req.ssl_hello_type 1 } - ## _front_001 - use-server _server__socket001 if { req.ssl_sni -i -f /etc/haproxy/maps/_front_001_bind__socket001.list } - server _server__socket001 unix@/var/run/front__socket001.sock send-proxy-v2 weight 0 - use-server _server__socket002 if { req.ssl_sni -i -f /etc/haproxy/maps/_front_001_bind__socket002.list } - server _server__socket002 unix@/var/run/front__socket002.sock send-proxy-v2 weight 0 - ## https-front_d1.local - use-server _server_d1.local if { req.ssl_sni -i -f /etc/haproxy/maps/https-front_d1.local_bind_d1.local.list } - server _server_d1.local unix@/var/run/front_d1.local.sock send-proxy-v2 weight 0 + ## _front001 + use-server _server_socket001 if { req.ssl_sni -i -f /etc/haproxy/maps/_socket001.list } + server _server_socket001 unix@/var/run/_socket001.sock send-proxy-v2 weight 0 + ## _front002 + use-server _server_socket002 if { req.ssl_sni -i -f /etc/haproxy/maps/_socket002.list } + server _server_socket002 unix@/var/run/_socket002.sock send-proxy-v2 weight 0 + use-server _server_socket003 if { req.ssl_sni -i -f /etc/haproxy/maps/_socket003.list } + server _server_socket003 unix@/var/run/_socket003.sock send-proxy-v2 weight 0 # TODO default backend frontend _front__http mode http @@ -423,40 +423,40 @@ frontend _front__http redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/redirect.map,_nomatch) yes } use_backend %[var(req.backend)] unless { var(req.backend) _nomatch } default_backend _default_backend -frontend _front_001 +frontend _front001 mode http - bind unix@/var/run/front__socket001.sock accept-proxy ssl alpn h2,http/1.1 crt /var/haproxy/ssl/certs/default.pem crt /var/haproxy/certs/_socket001 ca-file /var/haproxy/ssl/ca/d2.local.pem verify optional ca-ignore-err all crt-ignore-err all - bind unix@/var/run/front__socket002.sock accept-proxy ssl alpn h2,http/1.1 crt /var/haproxy/ssl/certs/default.pem - timeout client 2s - http-request set-var(req.hostbackend) base,regsub(:[0-9]+/,/),map_beg(/etc/haproxy/maps/_front_001_host.map,_nomatch) + bind unix@/var/run/_socket001.sock accept-proxy ssl alpn h2,http/1.1 crt /var/haproxy/ssl/certs/default.pem ca-file /var/haproxy/ssl/ca/d1.local.pem verify optional ca-ignore-err all crt-ignore-err all + timeout client 1s + http-request set-var(req.hostbackend) base,regsub(:[0-9]+/,/),map_beg(/etc/haproxy/maps/_front001_host.map,_nomatch) http-request set-header x-ha-base %[ssl_fc_sni]%[path] - http-request set-var(req.snibackend) hdr(x-ha-base),regsub(:[0-9]+/,/),map_beg(/etc/haproxy/maps/_front_001_sni.map,_nomatch) - acl tls-has-crt ssl_c_used - acl tls-need-crt ssl_fc_sni -i -f /etc/haproxy/maps/_front_001_no_crt.list + http-request set-var(req.snibackend) hdr(x-ha-base),regsub(:[0-9]+/,/),map_beg(/etc/haproxy/maps/_front001_sni.map,_nomatch) acl tls-has-invalid-crt ssl_c_ca_err gt 0 acl tls-has-invalid-crt ssl_c_err gt 0 - acl tls-check-crt ssl_fc_sni -i -f /etc/haproxy/maps/_front_001_inv_crt.list - http-request set-var(req.tls_nocrt_redir) ssl_fc_sni,map(/etc/haproxy/maps/_front_001_no_crt_redir.map,_internal) if !tls-has-crt tls-need-crt - http-request set-var(req.tls_invalidcrt_redir) ssl_fc_sni,map(/etc/haproxy/maps/_front_001_inv_crt_redir.map,_internal) if tls-has-invalid-crt tls-check-crt - http-request redirect location %[var(req.tls_nocrt_redir)] code 303 if { var(req.tls_nocrt_redir) -m found } !{ var(req.tls_nocrt_redir) _internal } + acl tls-check-crt ssl_fc_sni -i -f /etc/haproxy/maps/_front001_inv_crt.list + http-request set-var(req.tls_invalidcrt_redir) ssl_fc_sni,map(/etc/haproxy/maps/_front001_inv_crt_redir.map,_internal) if tls-has-invalid-crt tls-check-crt http-request redirect location %[var(req.tls_invalidcrt_redir)] code 303 if { var(req.tls_invalidcrt_redir) -m found } !{ var(req.tls_invalidcrt_redir) _internal } - use_backend _error496 if { var(req.tls_nocrt_redir) _internal } use_backend _error495 if { var(req.tls_invalidcrt_redir) _internal } use_backend %[var(req.hostbackend)] unless { var(req.hostbackend) _nomatch } use_backend %[var(req.snibackend)] unless { var(req.snibackend) _nomatch } default_backend _default_backend -frontend https-front_d1.local +frontend _front002 mode http - bind unix@/var/run/front_d1.local.sock accept-proxy ssl alpn h2,http/1.1 crt /var/haproxy/ssl/certs/default.pem ca-file /var/haproxy/ssl/ca/d1.local.pem verify optional ca-ignore-err all crt-ignore-err all - timeout client 1s - http-request set-var(req.hostbackend) base,regsub(:[0-9]+/,/),map_beg(/etc/haproxy/maps/https-front_d1.local_host.map,_nomatch) + bind unix@/var/run/_socket002.sock accept-proxy ssl alpn h2,http/1.1 crt /var/haproxy/ssl/certs/default.pem crt /var/haproxy/certs/_socket002 ca-file /var/haproxy/ssl/ca/d2.local.pem verify optional ca-ignore-err all crt-ignore-err all + bind unix@/var/run/_socket003.sock accept-proxy ssl alpn h2,http/1.1 crt /var/haproxy/ssl/certs/default.pem + timeout client 2s + http-request set-var(req.hostbackend) base,regsub(:[0-9]+/,/),map_beg(/etc/haproxy/maps/_front002_host.map,_nomatch) http-request set-header x-ha-base %[ssl_fc_sni]%[path] - http-request set-var(req.snibackend) hdr(x-ha-base),regsub(:[0-9]+/,/),map_beg(/etc/haproxy/maps/https-front_d1.local_sni.map,_nomatch) + http-request set-var(req.snibackend) hdr(x-ha-base),regsub(:[0-9]+/,/),map_beg(/etc/haproxy/maps/_front002_sni.map,_nomatch) + acl tls-has-crt ssl_c_used + acl tls-need-crt ssl_fc_sni -i -f /etc/haproxy/maps/_front002_no_crt.list acl tls-has-invalid-crt ssl_c_ca_err gt 0 acl tls-has-invalid-crt ssl_c_err gt 0 - acl tls-check-crt ssl_fc_sni -i -f /etc/haproxy/maps/https-front_d1.local_inv_crt.list - http-request set-var(req.tls_invalidcrt_redir) ssl_fc_sni,map(/etc/haproxy/maps/https-front_d1.local_inv_crt_redir.map,_internal) if tls-has-invalid-crt tls-check-crt + acl tls-check-crt ssl_fc_sni -i -f /etc/haproxy/maps/_front002_inv_crt.list + http-request set-var(req.tls_nocrt_redir) ssl_fc_sni,map(/etc/haproxy/maps/_front002_no_crt_redir.map,_internal) if !tls-has-crt tls-need-crt + http-request set-var(req.tls_invalidcrt_redir) ssl_fc_sni,map(/etc/haproxy/maps/_front002_inv_crt_redir.map,_internal) if tls-has-invalid-crt tls-check-crt + http-request redirect location %[var(req.tls_nocrt_redir)] code 303 if { var(req.tls_nocrt_redir) -m found } !{ var(req.tls_nocrt_redir) _internal } http-request redirect location %[var(req.tls_invalidcrt_redir)] code 303 if { var(req.tls_invalidcrt_redir) -m found } !{ var(req.tls_invalidcrt_redir) _internal } + use_backend _error496 if { var(req.tls_nocrt_redir) _internal } use_backend _error495 if { var(req.tls_invalidcrt_redir) _internal } use_backend %[var(req.hostbackend)] unless { var(req.hostbackend) _nomatch } use_backend %[var(req.snibackend)] unless { var(req.snibackend) _nomatch } @@ -466,53 +466,53 @@ frontend https-front_d1.local c.checkMap("http-front.map", ` d3.local/ d_app_8080 d4.local/ d_app_8080`) - c.checkMap("_front_001_inv_crt_redir.map", ` + c.checkMap("_front001_inv_crt_redir.map", ` +d1.local http://d1.local/error.html`) + c.checkMap("_front001_inv_crt.list", ` +d1.local`) + c.checkMap("_front001_no_crt_redir.map", ` +`) + c.checkMap("_front001_host.map", ` +`) + c.checkMap("_front001_sni.map", ` +d1.local/ d_appca_8080`) + c.checkMap("_socket001.list", ` +d1.local`) + c.checkMap("_front002_inv_crt_redir.map", ` d21.local http://d21.local/error.html d22.local http://d22.local/error.html `) - c.checkMap("_front_001_inv_crt.list", ` + c.checkMap("_front002_inv_crt.list", ` d21.local d22.local`) - c.checkMap("_front_001_no_crt_redir.map", ` + c.checkMap("_front002_no_crt_redir.map", ` d22.local http://d22.local/error.html `) - c.checkMap("_front_001_no_crt.list", ` + c.checkMap("_front002_no_crt.list", ` d22.local`) - c.checkMap("_front_001_host.map", ` + c.checkMap("_front002_host.map", ` d3.local/ d_app_8080 d4.local/ d_app_8080`) - c.checkMap("_front_001_sni.map", ` + c.checkMap("_front002_sni.map", ` d21.local/ d_appca_8080 d22.local/ d_appca_8080`) - c.checkMap("_front_001_bind__socket001.list", ` + c.checkMap("_socket002.list", ` d21.local d22.local`) - c.checkMap("_front_001_bind__socket002.list", ` + c.checkMap("_socket003.list", ` d3.local d4.local`) - c.checkMap("https-front_d1.local_inv_crt_redir.map", ` -d1.local http://d1.local/error.html`) - c.checkMap("https-front_d1.local_inv_crt.list", ` -d1.local`) - c.checkMap("https-front_d1.local_no_crt_redir.map", ` -`) - c.checkMap("https-front_d1.local_host.map", ` -`) - c.checkMap("https-front_d1.local_sni.map", ` -d1.local/ d_appca_8080`) - c.checkMap("https-front_d1.local_bind_d1.local.list", ` -d1.local`) c.checkMap("redirect.map", ` +d1.local/ yes d21.local/ yes d22.local/ yes d3.local/ no d4.local/ no -d1.local/ yes `) c.checkCerts(` certdirs: -- dir: /var/haproxy/certs/_socket001 +- dir: /var/haproxy/certs/_socket002 certs: - /var/haproxy/ssl/certs/d.pem`) @@ -578,15 +578,15 @@ frontend _front__http redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/redirect.map,_nomatch) yes } use_backend %[var(req.backend)] unless { var(req.backend) _nomatch } default_backend _default_backend -frontend https-front_d.local +frontend _front001 mode http bind :443 ssl alpn h2,http/1.1 crt /var/haproxy/ssl/certs/default.pem - http-request set-var(req.hostbackend) base,regsub(:[0-9]+/,/),map_beg(/etc/haproxy/maps/https-front_d.local_host.map,_nomatch) + http-request set-var(req.hostbackend) base,regsub(:[0-9]+/,/),map_beg(/etc/haproxy/maps/_front001_host.map,_nomatch) use_backend %[var(req.hostbackend)] unless { var(req.hostbackend) _nomatch } default_backend _default_backend `) - c.checkMap("https-front_d.local_host.map", ` + c.checkMap("_front001_host.map", ` d.local/sub d_app3_8080 d.local/app/sub d_app2_8080 d.local/app d_app1_8080 diff --git a/pkg/haproxy/types/frontend.go b/pkg/haproxy/types/frontend.go index ba918694a..688ee7977 100644 --- a/pkg/haproxy/types/frontend.go +++ b/pkg/haproxy/types/frontend.go @@ -115,12 +115,8 @@ func BuildRawFrontends(hosts []*Host) (frontends []*Frontend, sslpassthrough []* // naming frontends var i int for _, frontend := range frontends { - if len(frontend.Hosts) == 1 { - frontend.Name = "https-front_" + frontend.Hosts[0].Hostname - } else { - i++ - frontend.Name = fmt.Sprintf("_front_%03d", i) - } + i++ + frontend.Name = fmt.Sprintf("_front%03d", i) } // sorting frontends sort.Slice(frontends, func(i, j int) bool { diff --git a/pkg/haproxy/types/frontend_test.go b/pkg/haproxy/types/frontend_test.go index cd88d713f..f062b7d76 100644 --- a/pkg/haproxy/types/frontend_test.go +++ b/pkg/haproxy/types/frontend_test.go @@ -50,7 +50,7 @@ func TestBuildFrontend(t *testing.T) { hosts: []*Host{h10_1, h10_2}, expected: []*Frontend{ { - Name: "_front_001", + Name: "_front001", Timeout: timeout10, Hosts: []*Host{h10_1, h10_2}, Binds: []*BindConfig{ @@ -66,7 +66,7 @@ func TestBuildFrontend(t *testing.T) { hosts: []*Host{h10_1, h20_1, h10_2}, expected: []*Frontend{ { - Name: "_front_001", + Name: "_front001", Timeout: timeout10, Hosts: []*Host{h10_1, h10_2}, Binds: []*BindConfig{ @@ -76,7 +76,7 @@ func TestBuildFrontend(t *testing.T) { }, }, { - Name: "https-front_h3.local", + Name: "_front002", Timeout: timeout20, Hosts: []*Host{h20_1}, Binds: []*BindConfig{ @@ -92,7 +92,7 @@ func TestBuildFrontend(t *testing.T) { hosts: []*Host{h10CA1_1, h10CA2_1, h10CA2_2}, expected: []*Frontend{ { - Name: "_front_001", + Name: "_front001", Timeout: timeout10, Hosts: []*Host{h10CA1_1, h10CA2_1, h10CA2_2}, Binds: []*BindConfig{ @@ -113,7 +113,7 @@ func TestBuildFrontend(t *testing.T) { hosts: []*Host{h10_1, h10_2, h10CA2_1, h10CA2_2}, expected: []*Frontend{ { - Name: "_front_001", + Name: "_front001", Timeout: timeout10, Hosts: []*Host{h10_1, h10_2, h10CA2_1, h10CA2_2}, Binds: []*BindConfig{ diff --git a/rootfs/etc/haproxy/template/haproxy.tmpl b/rootfs/etc/haproxy/template/haproxy.tmpl index 2463845e9..6929cc0b1 100644 --- a/rootfs/etc/haproxy/template/haproxy.tmpl +++ b/rootfs/etc/haproxy/template/haproxy.tmpl @@ -250,9 +250,8 @@ listen _front__tls {{- range $frontend := $frontends }} ## {{ $frontend.Name }} {{- range $bind := $frontend.Binds }} - use-server _server_{{ $bind.Name }} if - {{- "" }} { req.ssl_sni -i -f {{ $bind.UseServerList }} } - server _server_{{ $bind.Name }} {{ $bind.Socket }} send-proxy-v2 weight 0 + use-server _server{{ $bind.Name }} if { req.ssl_sni -i -f {{ $bind.UseServerList }} } + server _server{{ $bind.Name }} {{ $bind.Socket }} send-proxy-v2 weight 0 {{- end }} {{- end }} # TODO default backend From 3ff1b04eeaa19755e87f7609234d3a608619cfe6 Mon Sep 17 00:00:00 2001 From: Joao Morais Date: Sat, 11 May 2019 11:20:47 -0300 Subject: [PATCH 03/30] Move maps and lists of hostnames/paths to a single object There are some common operations regarding hostnames and paths, eg iterate in order to create the files on disk and check if the hostname is wildcard or has a regex. Moving them to an object will help to implement such common tasks --- pkg/haproxy/config.go | 141 ++++++++++++---------------------- pkg/haproxy/types/frontend.go | 32 ++++++++ pkg/haproxy/types/types.go | 57 ++++++++++---- 3 files changed, 122 insertions(+), 108 deletions(-) diff --git a/pkg/haproxy/config.go b/pkg/haproxy/config.go index 39eeb2b39..c381850ad 100644 --- a/pkg/haproxy/config.go +++ b/pkg/haproxy/config.go @@ -192,12 +192,14 @@ func (c *config) BuildFrontendGroup() (*hatypes.FrontendGroup, error) { return nil, fmt.Errorf("cannot create frontends without hosts") } frontends, sslpassthrough := hatypes.BuildRawFrontends(c.hosts) + fgroupMaps := hatypes.CreateMaps() fgroup := &hatypes.FrontendGroup{ Frontends: frontends, HasSSLPassthrough: len(sslpassthrough) > 0, - HTTPFrontsMap: c.mapsDir + "/http-front.map", - RedirectMap: c.mapsDir + "/redirect.map", - SSLPassthroughMap: c.mapsDir + "/sslpassthrough.map", + Maps: fgroupMaps, + SSLPassthroughMap: fgroupMaps.AddMap(c.mapsDir + "/sslpassthrough.map"), + RedirectMap: fgroupMaps.AddMap(c.mapsDir + "/redirect.map"), + HTTPFrontsMap: fgroupMaps.AddMap(c.mapsDir + "/http-front.map"), } if fgroup.HasTCPProxy() { // More than one HAProxy's frontend or bind, or using ssl-passthrough config, @@ -242,140 +244,97 @@ func (c *config) BuildFrontendGroup() (*hatypes.FrontendGroup, error) { } for _, frontend := range frontends { mapsPrefix := c.mapsDir + "/" + frontend.Name - frontend.HostBackendsMap = mapsPrefix + "_host.map" - frontend.SNIBackendsMap = mapsPrefix + "_sni.map" - frontend.TLSInvalidCrtErrorList = mapsPrefix + "_inv_crt.list" - frontend.TLSInvalidCrtErrorPagesMap = mapsPrefix + "_inv_crt_redir.map" - frontend.TLSNoCrtErrorList = mapsPrefix + "_no_crt.list" - frontend.TLSNoCrtErrorPagesMap = mapsPrefix + "_no_crt_redir.map" - frontend.VarNamespaceMap = mapsPrefix + "_k8s_ns.map" + frontend.Maps = hatypes.CreateMaps() + frontend.HostBackendsMap = frontend.Maps.AddMap(mapsPrefix + "_host.map") + frontend.SNIBackendsMap = frontend.Maps.AddMap(mapsPrefix + "_sni.map") + frontend.TLSInvalidCrtErrorList = frontend.Maps.AddMap(mapsPrefix + "_inv_crt.list") + frontend.TLSInvalidCrtErrorPagesMap = frontend.Maps.AddMap(mapsPrefix + "_inv_crt_redir.map") + frontend.TLSNoCrtErrorList = frontend.Maps.AddMap(mapsPrefix + "_no_crt.list") + frontend.TLSNoCrtErrorPagesMap = frontend.Maps.AddMap(mapsPrefix + "_no_crt_redir.map") + frontend.VarNamespaceMap = frontend.Maps.AddMap(mapsPrefix + "_k8s_ns.map") for _, bind := range frontend.Binds { - bind.UseServerList = c.mapsDir + "/" + bind.Name + ".list" + bind.Maps = hatypes.CreateMaps() + bind.UseServerList = bind.Maps.AddMap(c.mapsDir + "/" + bind.Name + ".list") } } - type mapEntry struct { - Key string - Value string - } - var sslpassthroughMap []mapEntry - var redirectMap []mapEntry - var httpFront []mapEntry yesno := map[bool]string{true: "yes", false: "no"} for _, sslpassHost := range sslpassthrough { rootPath := sslpassHost.FindPath("/") if rootPath == nil { return nil, fmt.Errorf("missing root path on host %s", sslpassHost.Hostname) } - sslpassthroughMap = append(sslpassthroughMap, mapEntry{ - Key: sslpassHost.Hostname, - Value: rootPath.BackendID, - }) - redirectMap = append(redirectMap, mapEntry{ - Key: sslpassHost.Hostname + "/", - Value: yesno[sslpassHost.HTTPPassthroughBackend == nil], - }) + fgroup.SSLPassthroughMap.Append(sslpassHost.Hostname, rootPath.BackendID) + fgroup.RedirectMap.Append(sslpassHost.Hostname+"/", yesno[sslpassHost.HTTPPassthroughBackend == nil]) if sslpassHost.HTTPPassthroughBackend != nil { - httpFront = append(httpFront, mapEntry{ - Key: sslpassHost.Hostname + "/", - Value: sslpassHost.HTTPPassthroughBackend.ID, - }) + fgroup.HTTPFrontsMap.Append(sslpassHost.Hostname+"/", sslpassHost.HTTPPassthroughBackend.ID) } else { fgroup.HasRedirectHTTPS = true } } for _, f := range frontends { - var hostBackendsMap []mapEntry - var sniBackendsMap []mapEntry - var invalidCrtList []mapEntry - var invalidCrtMap []mapEntry - var noCrtList []mapEntry - var noCrtMap []mapEntry - var varNamespaceMap []mapEntry for _, host := range f.Hosts { for _, path := range host.Paths { // TODO use only root path if all uri has the same conf - redirectMap = append(redirectMap, mapEntry{ - Key: host.Hostname + path.Path, - Value: yesno[path.Backend.SSLRedirect], - }) - entry := mapEntry{ - Key: host.Hostname + path.Path, - Value: path.BackendID, - } + fgroup.RedirectMap.Append(host.Hostname+path.Path, yesno[path.Backend.SSLRedirect]) + base := host.Hostname + path.Path + back := path.BackendID if host.HasTLSAuth() { - sniBackendsMap = append(sniBackendsMap, entry) + f.SNIBackendsMap.Append(base, back) } else { - hostBackendsMap = append(hostBackendsMap, entry) + f.HostBackendsMap.Append(base, back) } if path.Backend.SSLRedirect { fgroup.HasRedirectHTTPS = true } else { - httpFront = append(httpFront, entry) + fgroup.HTTPFrontsMap.Append(base, back) } + var ns string if host.VarNamespace { - entry.Value = path.Backend.Namespace + ns = path.Backend.Namespace } else { - entry.Value = "-" + ns = "-" } - varNamespaceMap = append(varNamespaceMap, entry) + f.VarNamespaceMap.Append(base, ns) } if host.HasTLSAuth() { - var entry mapEntry - entry.Key = host.Hostname - invalidCrtList = append(invalidCrtList, entry) + f.TLSInvalidCrtErrorList.Append(host.Hostname, "") if !host.TLS.CAVerifyOptional { - noCrtList = append(noCrtList, entry) + f.TLSNoCrtErrorList.Append(host.Hostname, "") } - if host.TLS.CAErrorPage != "" { - entry.Value = host.TLS.CAErrorPage - invalidCrtMap = append(invalidCrtMap, entry) + page := host.TLS.CAErrorPage + if page != "" { + f.TLSInvalidCrtErrorPagesMap.Append(host.Hostname, page) if !host.TLS.CAVerifyOptional { - noCrtMap = append(noCrtMap, entry) + f.TLSNoCrtErrorPagesMap.Append(host.Hostname, page) } } } } for _, bind := range f.Binds { - var useServerList []mapEntry for _, host := range bind.Hosts { - useServerList = append(useServerList, mapEntry{Key: host.Hostname}) + bind.UseServerList.Append(host.Hostname, "") } - if err := c.mapsTemplate.WriteOutput(useServerList, bind.UseServerList); err != nil { - return nil, err - } - } - if err := c.mapsTemplate.WriteOutput(hostBackendsMap, f.HostBackendsMap); err != nil { - return nil, err - } - if err := c.mapsTemplate.WriteOutput(sniBackendsMap, f.SNIBackendsMap); err != nil { - return nil, err - } - if err := c.mapsTemplate.WriteOutput(invalidCrtList, f.TLSInvalidCrtErrorList); err != nil { - return nil, err - } - if err := c.mapsTemplate.WriteOutput(invalidCrtMap, f.TLSInvalidCrtErrorPagesMap); err != nil { - return nil, err } - if err := c.mapsTemplate.WriteOutput(noCrtList, f.TLSNoCrtErrorList); err != nil { + } + for _, hmap := range fgroup.Maps.Items { + if err := c.mapsTemplate.WriteOutput(hmap.Entries, hmap.Filename); err != nil { return nil, err } - if err := c.mapsTemplate.WriteOutput(noCrtMap, f.TLSNoCrtErrorPagesMap); err != nil { - return nil, err + } + for _, f := range frontends { + for _, hmap := range f.Maps.Items { + if err := c.mapsTemplate.WriteOutput(hmap.Entries, hmap.Filename); err != nil { + return nil, err + } } - if err := c.mapsTemplate.WriteOutput(varNamespaceMap, f.VarNamespaceMap); err != nil { - return nil, err + for _, bind := range f.Binds { + for _, hmap := range bind.Maps.Items { + if err := c.mapsTemplate.WriteOutput(hmap.Entries, hmap.Filename); err != nil { + return nil, err + } + } } } - if err := c.mapsTemplate.WriteOutput(sslpassthroughMap, fgroup.SSLPassthroughMap); err != nil { - return nil, err - } - if err := c.mapsTemplate.WriteOutput(redirectMap, fgroup.RedirectMap); err != nil { - return nil, err - } - if err := c.mapsTemplate.WriteOutput(httpFront, fgroup.HTTPFrontsMap); err != nil { - return nil, err - } - fgroup.HasHTTPHost = len(httpFront) > 0 return fgroup, nil } diff --git a/pkg/haproxy/types/frontend.go b/pkg/haproxy/types/frontend.go index 688ee7977..8e898f871 100644 --- a/pkg/haproxy/types/frontend.go +++ b/pkg/haproxy/types/frontend.go @@ -22,6 +22,38 @@ import ( "sort" ) +// Append ... +func (hm *HostsMap) Append(key, value string) { + hm.Entries = append(hm.Entries, &HostsMapEntry{ + Key: key, + Value: value, + }) +} + +// String ... +func (hm *HostsMap) String() string { + return hm.Filename +} + +// CreateMaps ... +func CreateMaps() *HostsMaps { + return &HostsMaps{} +} + +// AddMap ... +func (hm *HostsMaps) AddMap(filename string) *HostsMap { + hmap := &HostsMap{ + Filename: filename, + } + hm.Items = append(hm.Items, hmap) + return hmap +} + +// HasHTTPHost ... +func (fg *FrontendGroup) HasHTTPHost() bool { + return len(fg.HTTPFrontsMap.Entries) > 0 +} + // HasTCPProxy ... func (fg *FrontendGroup) HasTCPProxy() bool { // short-circuit saves: diff --git a/pkg/haproxy/types/types.go b/pkg/haproxy/types/types.go index 4457799bf..9e17700fc 100644 --- a/pkg/haproxy/types/types.go +++ b/pkg/haproxy/types/types.go @@ -83,15 +83,34 @@ type ModSecurityTimeoutConfig struct { Processing string } +// HostsMapEntry ... +type HostsMapEntry struct { + Key string + Value string +} + +// HostsMap ... +type HostsMap struct { + Entries []*HostsMapEntry + Filename string +} + +// HostsMaps ... +type HostsMaps struct { + Items []*HostsMap +} + // FrontendGroup ... type FrontendGroup struct { - Frontends []*Frontend - HasHTTPHost bool + Frontends []*Frontend + // HasRedirectHTTPS bool HasSSLPassthrough bool - HTTPFrontsMap string - RedirectMap string - SSLPassthroughMap string + // + Maps *HostsMaps + HTTPFrontsMap *HostsMap + RedirectMap *HostsMap + SSLPassthroughMap *HostsMap } // Frontend ... @@ -100,15 +119,17 @@ type Frontend struct { Binds []*BindConfig Hosts []*Host // - ConvertLowercase bool - HostBackendsMap string - SNIBackendsMap string - Timeout HostTimeoutConfig - TLSInvalidCrtErrorList string - TLSNoCrtErrorList string - TLSInvalidCrtErrorPagesMap string - TLSNoCrtErrorPagesMap string - VarNamespaceMap string + ConvertLowercase bool + Timeout HostTimeoutConfig + // + Maps *HostsMaps + HostBackendsMap *HostsMap + SNIBackendsMap *HostsMap + TLSInvalidCrtErrorList *HostsMap + TLSInvalidCrtErrorPagesMap *HostsMap + TLSNoCrtErrorList *HostsMap + TLSNoCrtErrorPagesMap *HostsMap + VarNamespaceMap *HostsMap } // BindConfig ... @@ -117,9 +138,11 @@ type BindConfig struct { Socket string Hosts []*Host // - AcceptProxy bool - TLS BindTLSConfig - UseServerList string + AcceptProxy bool + TLS BindTLSConfig + // + Maps *HostsMaps + UseServerList *HostsMap } // BindTLSConfig ... From c4a81ffd956359d9ba1d979a141d81ec9d26628f Mon Sep 17 00:00:00 2001 From: Joao Morais Date: Sat, 11 May 2019 22:39:17 -0300 Subject: [PATCH 04/30] Add wildcard hostname support --- pkg/haproxy/config.go | 58 ++-- pkg/haproxy/instance_test.go | 371 ++++++++++++++++++----- pkg/haproxy/types/frontend.go | 45 ++- pkg/haproxy/types/frontend_test.go | 43 +++ pkg/haproxy/types/host.go | 1 + pkg/haproxy/types/types.go | 6 +- rootfs/etc/haproxy/template/haproxy.tmpl | 126 ++++++-- 7 files changed, 502 insertions(+), 148 deletions(-) diff --git a/pkg/haproxy/config.go b/pkg/haproxy/config.go index c381850ad..0cbbc1cf8 100644 --- a/pkg/haproxy/config.go +++ b/pkg/haproxy/config.go @@ -263,10 +263,10 @@ func (c *config) BuildFrontendGroup() (*hatypes.FrontendGroup, error) { if rootPath == nil { return nil, fmt.Errorf("missing root path on host %s", sslpassHost.Hostname) } - fgroup.SSLPassthroughMap.Append(sslpassHost.Hostname, rootPath.BackendID) - fgroup.RedirectMap.Append(sslpassHost.Hostname+"/", yesno[sslpassHost.HTTPPassthroughBackend == nil]) + fgroup.SSLPassthroughMap.AppendHostname(sslpassHost.Hostname, rootPath.BackendID) + fgroup.RedirectMap.AppendHostname(sslpassHost.Hostname+"/", yesno[sslpassHost.HTTPPassthroughBackend == nil]) if sslpassHost.HTTPPassthroughBackend != nil { - fgroup.HTTPFrontsMap.Append(sslpassHost.Hostname+"/", sslpassHost.HTTPPassthroughBackend.ID) + fgroup.HTTPFrontsMap.AppendHostname(sslpassHost.Hostname+"/", sslpassHost.HTTPPassthroughBackend.ID) } else { fgroup.HasRedirectHTTPS = true } @@ -275,18 +275,18 @@ func (c *config) BuildFrontendGroup() (*hatypes.FrontendGroup, error) { for _, host := range f.Hosts { for _, path := range host.Paths { // TODO use only root path if all uri has the same conf - fgroup.RedirectMap.Append(host.Hostname+path.Path, yesno[path.Backend.SSLRedirect]) + fgroup.RedirectMap.AppendHostname(host.Hostname+path.Path, yesno[path.Backend.SSLRedirect]) base := host.Hostname + path.Path back := path.BackendID if host.HasTLSAuth() { - f.SNIBackendsMap.Append(base, back) + f.SNIBackendsMap.AppendHostname(base, back) } else { - f.HostBackendsMap.Append(base, back) + f.HostBackendsMap.AppendHostname(base, back) } if path.Backend.SSLRedirect { fgroup.HasRedirectHTTPS = true } else { - fgroup.HTTPFrontsMap.Append(base, back) + fgroup.HTTPFrontsMap.AppendHostname(base, back) } var ns string if host.VarNamespace { @@ -294,50 +294,58 @@ func (c *config) BuildFrontendGroup() (*hatypes.FrontendGroup, error) { } else { ns = "-" } - f.VarNamespaceMap.Append(base, ns) + f.VarNamespaceMap.AppendHostname(base, ns) } if host.HasTLSAuth() { - f.TLSInvalidCrtErrorList.Append(host.Hostname, "") + f.TLSInvalidCrtErrorList.AppendHostname(host.Hostname, "") if !host.TLS.CAVerifyOptional { - f.TLSNoCrtErrorList.Append(host.Hostname, "") + f.TLSNoCrtErrorList.AppendHostname(host.Hostname, "") } page := host.TLS.CAErrorPage if page != "" { - f.TLSInvalidCrtErrorPagesMap.Append(host.Hostname, page) + f.TLSInvalidCrtErrorPagesMap.AppendHostname(host.Hostname, page) if !host.TLS.CAVerifyOptional { - f.TLSNoCrtErrorPagesMap.Append(host.Hostname, page) + f.TLSNoCrtErrorPagesMap.AppendHostname(host.Hostname, page) } } } } for _, bind := range f.Binds { for _, host := range bind.Hosts { - bind.UseServerList.Append(host.Hostname, "") + bind.UseServerList.AppendHostname(host.Hostname, "") } } } - for _, hmap := range fgroup.Maps.Items { - if err := c.mapsTemplate.WriteOutput(hmap.Entries, hmap.Filename); err != nil { - return nil, err - } + if err := writeMaps(fgroup.Maps, c.mapsTemplate); err != nil { + return nil, err } for _, f := range frontends { - for _, hmap := range f.Maps.Items { - if err := c.mapsTemplate.WriteOutput(hmap.Entries, hmap.Filename); err != nil { - return nil, err - } + if err := writeMaps(f.Maps, c.mapsTemplate); err != nil { + return nil, err } for _, bind := range f.Binds { - for _, hmap := range bind.Maps.Items { - if err := c.mapsTemplate.WriteOutput(hmap.Entries, hmap.Filename); err != nil { - return nil, err - } + if err := writeMaps(bind.Maps, c.mapsTemplate); err != nil { + return nil, err } } } return fgroup, nil } +func writeMaps(maps *hatypes.HostsMaps, template *template.Config) error { + for _, hmap := range maps.Items { + if err := template.WriteOutput(hmap.Match, hmap.MatchFile); err != nil { + return err + } + if len(hmap.Regex) > 0 { + if err := template.WriteOutput(hmap.Regex, hmap.RegexFile); err != nil { + return err + } + } + } + return nil +} + func (c *config) createCertsDir(bindName string, hosts []*hatypes.Host) (string, error) { certs := make([]string, 0, len(hosts)) added := map[string]bool{} diff --git a/pkg/haproxy/instance_test.go b/pkg/haproxy/instance_test.go index 6536b8829..0c2f5d9c7 100644 --- a/pkg/haproxy/instance_test.go +++ b/pkg/haproxy/instance_test.go @@ -75,7 +75,7 @@ backend _error496 mode http errorfile 400 /usr/local/etc/haproxy/errors/496.http http-request deny deny_status 400 -frontend _front__http +frontend _front_http mode http bind :80 http-request set-var(req.backend) base,regsub(:[0-9]+/,/),map_beg(/etc/haproxy/maps/http-front.map,_nomatch) @@ -138,27 +138,35 @@ backend d2_app_8080 backend _default_backend mode http server s0 172.17.0.99:8080 weight 100`, ` -frontend _front__http +frontend _front_http mode http bind :80 http-request set-var(req.base) base,regsub(:[0-9]+/,/) + http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/http-front.map,_nomatch) redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/redirect.map,_nomatch) yes } + use_backend %[var(req.backend)] unless { var(req.backend) _nomatch } use_backend d1_app_8080 frontend _front001 mode http bind :443 ssl alpn h2,http/1.1 crt /var/haproxy/ssl/certs/default.pem - http-request set-var(txn.namespace) base,regsub(:[0-9]+/,/),map_beg(/etc/haproxy/maps/_front001_k8s_ns.map,-) - http-request set-var(req.hostbackend) base,regsub(:[0-9]+/,/),map_beg(/etc/haproxy/maps/_front001_host.map,_nomatch) + http-request set-var(req.base) base,regsub(:[0-9]+/,/) + http-request set-var(req.hostbackend) var(req.base),map_beg(/etc/haproxy/maps/_front001_host.map,_nomatch) + http-request set-var(txn.namespace) var(req.base),map_beg(/etc/haproxy/maps/_front001_k8s_ns.map,-) use_backend %[var(req.hostbackend)] unless { var(req.hostbackend) _nomatch } use_backend d1_app_8080 `) + c.checkMap("http-front.map", ` +`) + c.checkMap("redirect.map", ` +d2.local/app yes +`) c.checkMap("_front001_k8s_ns.map", ` -d2.local/app d2`) +d2.local/app d2 +`) c.checkMap("_front001_host.map", ` -d2.local/app d2_app_8080`) - c.checkMap("redirect.map", ` -d2.local/app yes`) +d2.local/app d2_app_8080 +`) c.logger.CompareLogging(defaultLogging) } @@ -203,30 +211,38 @@ backend d2_app_8080 backend _default_backend mode http server s0 172.17.0.99:8080 weight 100`, ` -frontend _front__http +frontend _front_http mode http bind :80 http-request set-var(req.base) base,regsub(:[0-9]+/,/) + http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/http-front.map,_nomatch) redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/redirect.map,_nomatch) yes } + use_backend %[var(req.backend)] unless { var(req.backend) _nomatch } default_backend _default_backend frontend _front001 mode http bind :443 ssl alpn h2,http/1.1 crt /var/haproxy/ssl/certs/default.pem crt /var/haproxy/certs/_public - http-request set-var(txn.namespace) base,regsub(:[0-9]+/,/),map_beg(/etc/haproxy/maps/_front001_k8s_ns.map,-) - http-request set-var(req.hostbackend) base,regsub(:[0-9]+/,/),map_beg(/etc/haproxy/maps/_front001_host.map,_nomatch) + http-request set-var(req.base) base,regsub(:[0-9]+/,/) + http-request set-var(req.hostbackend) var(req.base),map_beg(/etc/haproxy/maps/_front001_host.map,_nomatch) + http-request set-var(txn.namespace) var(req.base),map_beg(/etc/haproxy/maps/_front001_k8s_ns.map,-) use_backend %[var(req.hostbackend)] unless { var(req.hostbackend) _nomatch } default_backend _default_backend `) - c.checkMap("_front001_k8s_ns.map", ` -d1.local/ d1 -d2.local/app -`) - c.checkMap("_front001_host.map", ` -d1.local/ d1_app_8080 -d2.local/app d2_app_8080`) + c.checkMap("http-front.map", ` +`) c.checkMap("redirect.map", ` d1.local/ yes -d2.local/app yes`) +d2.local/app yes +`) + c.checkMap("_front001_host.map", ` +d1.local/ d1_app_8080 +d2.local/app d2_app_8080 +`) + c.checkMap("_front001_k8s_ns.map", ` +d1.local/ d1 +d2.local/app - +`) c.checkCerts(` certdirs: @@ -277,17 +293,20 @@ listen _front__tls bind :443 tcp-request inspect-delay 5s tcp-request content accept if { req.ssl_hello_type 1 } - ## _front001 + ## _front001/_socket001 use-server _server_socket001 if { req.ssl_sni -i -f /etc/haproxy/maps/_socket001.list } server _server_socket001 unix@/var/run/_socket001.sock send-proxy-v2 weight 0 + ## _front001/_socket002 use-server _server_socket002 if { req.ssl_sni -i -f /etc/haproxy/maps/_socket002.list } server _server_socket002 unix@/var/run/_socket002.sock send-proxy-v2 weight 0 # TODO default backend -frontend _front__http +frontend _front_http mode http bind :80 http-request set-var(req.base) base,regsub(:[0-9]+/,/) + http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/http-front.map,_nomatch) redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/redirect.map,_nomatch) yes } + use_backend %[var(req.backend)] unless { var(req.backend) _nomatch } default_backend _default_backend frontend _front001 mode http @@ -312,28 +331,38 @@ frontend _front001 default_backend _default_backend `) - c.checkMap("_front001_inv_crt_redir.map", ` -d1.local http://d1.local/error.html`) - c.checkMap("_front001_inv_crt.list", ` -d1.local -d2.local`) - c.checkMap("_front001_no_crt_redir.map", ` -d1.local http://d1.local/error.html`) - c.checkMap("_front001_no_crt.list", ` + c.checkMap("_socket001.list", ` d1.local -d2.local`) - c.checkMap("_front001_host.map", ` `) - c.checkMap("_front001_sni.map", ` -d1.local/ d_app_8080 -d2.local/ d_app_8080`) - c.checkMap("_socket001.list", ` -d1.local`) c.checkMap("_socket002.list", ` -d2.local`) +d2.local +`) + c.checkMap("http-front.map", ` +`) c.checkMap("redirect.map", ` d1.local/ yes -d2.local/ yes`) +d2.local/ yes +`) + c.checkMap("_front001_host.map", ` +`) + c.checkMap("_front001_sni.map", ` +d1.local/ d_app_8080 +d2.local/ d_app_8080 +`) + c.checkMap("_front001_no_crt.list", ` +d1.local +d2.local +`) + c.checkMap("_front001_inv_crt.list", ` +d1.local +d2.local +`) + c.checkMap("_front001_no_crt_redir.map", ` +d1.local http://d1.local/error.html +`) + c.checkMap("_front001_inv_crt_redir.map", ` +d1.local http://d1.local/error.html +`) c.logger.CompareLogging(defaultLogging) } @@ -406,16 +435,17 @@ listen _front__tls bind :443 tcp-request inspect-delay 5s tcp-request content accept if { req.ssl_hello_type 1 } - ## _front001 + ## _front001/_socket001 use-server _server_socket001 if { req.ssl_sni -i -f /etc/haproxy/maps/_socket001.list } server _server_socket001 unix@/var/run/_socket001.sock send-proxy-v2 weight 0 - ## _front002 + ## _front002/_socket002 use-server _server_socket002 if { req.ssl_sni -i -f /etc/haproxy/maps/_socket002.list } server _server_socket002 unix@/var/run/_socket002.sock send-proxy-v2 weight 0 + ## _front002/_socket003 use-server _server_socket003 if { req.ssl_sni -i -f /etc/haproxy/maps/_socket003.list } server _server_socket003 unix@/var/run/_socket003.sock send-proxy-v2 weight 0 # TODO default backend -frontend _front__http +frontend _front_http mode http bind :80 http-request set-var(req.base) base,regsub(:[0-9]+/,/) @@ -463,51 +493,59 @@ frontend _front002 default_backend _default_backend `) + c.checkMap("_socket001.list", ` +d1.local +`) + c.checkMap("_socket002.list", ` +d21.local +d22.local +`) + c.checkMap("_socket003.list", ` +d3.local +d4.local +`) c.checkMap("http-front.map", ` d3.local/ d_app_8080 -d4.local/ d_app_8080`) - c.checkMap("_front001_inv_crt_redir.map", ` -d1.local http://d1.local/error.html`) - c.checkMap("_front001_inv_crt.list", ` -d1.local`) - c.checkMap("_front001_no_crt_redir.map", ` +d4.local/ d_app_8080 +`) + c.checkMap("redirect.map", ` +d1.local/ yes +d21.local/ yes +d22.local/ yes +d3.local/ no +d4.local/ no `) c.checkMap("_front001_host.map", ` `) c.checkMap("_front001_sni.map", ` -d1.local/ d_appca_8080`) - c.checkMap("_socket001.list", ` -d1.local`) - c.checkMap("_front002_inv_crt_redir.map", ` -d21.local http://d21.local/error.html -d22.local http://d22.local/error.html +d1.local/ d_appca_8080 `) - c.checkMap("_front002_inv_crt.list", ` -d21.local -d22.local`) - c.checkMap("_front002_no_crt_redir.map", ` -d22.local http://d22.local/error.html + c.checkMap("_front001_inv_crt.list", ` +d1.local`) + c.checkMap("_front001_inv_crt_redir.map", ` +d1.local http://d1.local/error.html `) - c.checkMap("_front002_no_crt.list", ` -d22.local`) c.checkMap("_front002_host.map", ` d3.local/ d_app_8080 -d4.local/ d_app_8080`) +d4.local/ d_app_8080 +`) c.checkMap("_front002_sni.map", ` d21.local/ d_appca_8080 -d22.local/ d_appca_8080`) - c.checkMap("_socket002.list", ` +d22.local/ d_appca_8080 +`) + c.checkMap("_front002_no_crt.list", ` +d22.local +`) + c.checkMap("_front002_inv_crt.list", ` d21.local -d22.local`) - c.checkMap("_socket003.list", ` -d3.local -d4.local`) - c.checkMap("redirect.map", ` -d1.local/ yes -d21.local/ yes -d22.local/ yes -d3.local/ no -d4.local/ no +d22.local +`) + c.checkMap("_front002_no_crt_redir.map", ` +d22.local http://d22.local/error.html +`) + c.checkMap("_front002_inv_crt_redir.map", ` +d21.local http://d21.local/error.html +d22.local http://d22.local/error.html `) c.checkCerts(` @@ -570,7 +608,7 @@ backend d_app3_8080 backend _default_backend mode http server s0 172.17.0.99:8080 weight 100`, ` -frontend _front__http +frontend _front_http mode http bind :80 http-request set-var(req.base) base,regsub(:[0-9]+/,/) @@ -586,21 +624,27 @@ frontend _front001 default_backend _default_backend `) - c.checkMap("_front001_host.map", ` + c.checkMap("http-front.map", ` d.local/sub d_app3_8080 d.local/app/sub d_app2_8080 -d.local/app d_app1_8080 -d.local/ d_app0_8080`) +`) c.checkMap("redirect.map", ` d.local/sub no d.local/app/sub no d.local/app yes -d.local/ yes`) +d.local/ yes +`) + c.checkMap("_front001_host.map", ` +d.local/sub d_app3_8080 +d.local/app/sub d_app2_8080 +d.local/app d_app1_8080 +d.local/ d_app0_8080 +`) c.logger.CompareLogging(defaultLogging) } -func TestSSLPassthrough(t *testing.T) { +func TestInstanceSSLPassthrough(t *testing.T) { c := setup(t) defer c.teardown() @@ -650,7 +694,7 @@ listen _front__tls tcp-request content set-var(req.backend) req.ssl_sni,lower,map(/etc/haproxy/maps/sslpassthrough.map,_nomatch) use_backend %[var(req.backend)] unless { var(req.backend) _nomatch } # TODO default backend -frontend _front__http +frontend _front_http mode http bind :80 http-request set-var(req.base) base,regsub(:[0-9]+/,/) @@ -671,6 +715,175 @@ d3.local/ no`) c.logger.CompareLogging(defaultLogging) } +func TestInstanceWildcardHostname(t *testing.T) { + c := setup(t) + defer c.teardown() + + c.configGlobal() + + var h *hatypes.Host + var b *hatypes.Backend + + b = c.config.AcquireBackend("d1", "app", 8080) + b.Endpoints = []*hatypes.Endpoint{endpointS1} + b.SSLRedirect = true + h = c.config.AcquireHost("d1.local") + h.AddPath(b, "/") + h = c.config.AcquireHost("*.app.d1.local") + h.AddPath(b, "/") + h = c.config.AcquireHost("*.sub.d1.local") + h.AddPath(b, "/") + h.TLS.CAFilename = "/var/haproxy/ssl/ca/d1.local.pem" + h.TLS.CAHash = "1" + h.TLS.CAVerifyOptional = true + h.TLS.CAErrorPage = "http://sub.d1.local/error.html" + + b = c.config.AcquireBackend("d2", "app", 8080) + b.Endpoints = []*hatypes.Endpoint{endpointS21} + b.SSLRedirect = false + h = c.config.AcquireHost("*.d2.local") + h.AddPath(b, "/") + h.Timeout.Client = "10s" + + c.instance.Update() + c.checkConfig(` +backend d1_app_8080 + mode http + server s1 172.17.0.11:8080 weight 100 +backend d2_app_8080 + mode http + server s21 172.17.0.121:8080 weight 100 +backend _error404 + mode http + errorfile 400 /usr/local/etc/haproxy/errors/404.http + http-request deny deny_status 400`, ` +listen _front__tls + mode tcp + bind :443 + tcp-request inspect-delay 5s + tcp-request content accept if { req.ssl_hello_type 1 } + ## _front001/_socket001 + use-server _server_socket001 if { req.ssl_sni -i -f /etc/haproxy/maps/_socket001.list } + server _server_socket001 unix@/var/run/_socket001.sock send-proxy-v2 weight 0 + ## _front001/_socket002 + use-server _server_socket002 if { req.ssl_sni -i -f /etc/haproxy/maps/_socket002.list } + server _server_socket002 unix@/var/run/_socket002.sock send-proxy-v2 weight 0 + ## _front002/_socket003 + use-server _server_socket003 if { req.ssl_sni -i -f /etc/haproxy/maps/_socket003.list } + server _server_socket003 unix@/var/run/_socket003.sock send-proxy-v2 weight 0 + ## _front001/_socket001 wildcard + use-server _server_socket001_wildcard if { req.ssl_sni -i -m reg -f /etc/haproxy/maps/_socket001_regex.list } + server _server_socket001_wildcard unix@/var/run/_socket001.sock send-proxy-v2 weight 0 + ## _front001/_socket002 wildcard + use-server _server_socket002_wildcard if { req.ssl_sni -i -m reg -f /etc/haproxy/maps/_socket002_regex.list } + server _server_socket002_wildcard unix@/var/run/_socket002.sock send-proxy-v2 weight 0 + ## _front002/_socket003 wildcard + use-server _server_socket003_wildcard if { req.ssl_sni -i -m reg -f /etc/haproxy/maps/_socket003_regex.list } + server _server_socket003_wildcard unix@/var/run/_socket003.sock send-proxy-v2 weight 0 + # TODO default backend +frontend _front_http + mode http + bind :80 + http-request set-var(req.base) base,regsub(:[0-9]+/,/) + http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/http-front.map,_nomatch) + http-request set-var(req.backend) var(req.base),map_reg(/etc/haproxy/maps/http-front_regex.map,_nomatch) if { var(req.backend) _nomatch } + http-request set-var(req.redir) var(req.base),map_beg(/etc/haproxy/maps/redirect.map,_nomatch) + redirect scheme https if { var(req.redir) yes } + redirect scheme https if { var(req.redir) _nomatch } { var(req.base),map_reg(/etc/haproxy/maps/redirect_regex.map,_nomatch) yes } + use_backend %[var(req.backend)] unless { var(req.backend) _nomatch } + default_backend _error404 +frontend _front001 + mode http + bind unix@/var/run/_socket001.sock accept-proxy ssl alpn h2,http/1.1 crt /var/haproxy/ssl/certs/default.pem + bind unix@/var/run/_socket002.sock accept-proxy ssl alpn h2,http/1.1 crt /var/haproxy/ssl/certs/default.pem ca-file /var/haproxy/ssl/ca/d1.local.pem verify optional ca-ignore-err all crt-ignore-err all + http-request set-var(req.base) base,regsub(:[0-9]+/,/) + http-request set-var(req.hostbackend) var(req.base),map_beg(/etc/haproxy/maps/_front001_host.map,_nomatch) + http-request set-var(req.hostbackend) var(req.base),map_reg(/etc/haproxy/maps/_front001_host_regex.map,_nomatch) if { var(req.hostbackend) _nomatch } + http-request set-header x-ha-base %[ssl_fc_sni]%[path] + http-request set-var(req.snibase) hdr(x-ha-base),regsub(:[0-9]+/,/) + http-request set-var(req.snibackend) var(req.snibase),map_beg(/etc/haproxy/maps/_front001_sni.map,_nomatch) + http-request set-var(req.snibackend) var(req.snibase),map_reg(/etc/haproxy/maps/_front001_sni_regex.map,_nomatch) if { var(req.snibackend) _nomatch } + acl tls-has-invalid-crt ssl_c_ca_err gt 0 + acl tls-has-invalid-crt ssl_c_err gt 0 + acl tls-check-crt ssl_fc_sni -i -f /etc/haproxy/maps/_front001_inv_crt.list + acl tls-check-crt ssl_fc_sni -i -m reg -f /etc/haproxy/maps/_front001_inv_crt_regex.list + http-request set-var(req.tls_invalidcrt_redir) ssl_fc_sni,map(/etc/haproxy/maps/_front001_inv_crt_redir.map,_internal) if tls-has-invalid-crt tls-check-crt + http-request set-var(req.tls_invalidcrt_redir) ssl_fc_sni,map_reg(/etc/haproxy/maps/_front001_inv_crt_redir_regex.map,_internal) if { var(req.tls_invalidcrt_redir) _internal } + http-request redirect location %[var(req.tls_invalidcrt_redir)] code 303 if { var(req.tls_invalidcrt_redir) -m found } !{ var(req.tls_invalidcrt_redir) _internal } + use_backend _error495 if { var(req.tls_invalidcrt_redir) _internal } + use_backend %[var(req.hostbackend)] unless { var(req.hostbackend) _nomatch } + use_backend %[var(req.snibackend)] unless { var(req.snibackend) _nomatch } + default_backend _error404 +frontend _front002 + mode http + bind unix@/var/run/_socket003.sock accept-proxy ssl alpn h2,http/1.1 crt /var/haproxy/ssl/certs/default.pem + timeout client 10s + http-request set-var(req.base) base,regsub(:[0-9]+/,/) + http-request set-var(req.hostbackend) var(req.base),map_beg(/etc/haproxy/maps/_front002_host.map,_nomatch) + http-request set-var(req.hostbackend) var(req.base),map_reg(/etc/haproxy/maps/_front002_host_regex.map,_nomatch) if { var(req.hostbackend) _nomatch } + use_backend %[var(req.hostbackend)] unless { var(req.hostbackend) _nomatch } + default_backend _error404 +`) + + c.checkMap("_socket001.list", ` +d1.local +`) + c.checkMap("_socket002.list", ` +`) + c.checkMap("_socket003.list", ` +`) + c.checkMap("_socket001_regex.list", ` +^[^.]+\.app\.d1\.local$ +`) + c.checkMap("_socket002_regex.list", ` +^[^.]+\.sub\.d1\.local$ +`) + c.checkMap("_socket003_regex.list", ` +^[^.]+\.d2\.local$ +`) + c.checkMap("http-front.map", ` +`) + c.checkMap("http-front_regex.map", ` +^[^.]+\.d2\.local/ d2_app_8080 +`) + c.checkMap("redirect.map", ` +d1.local/ yes +`) + c.checkMap("redirect_regex.map", ` +^[^.]+\.app\.d1\.local/ yes +^[^.]+\.sub\.d1\.local/ yes +^[^.]+\.d2\.local/ no +`) + c.checkMap("_front001_host.map", ` +d1.local/ d1_app_8080 +`) + c.checkMap("_front001_host_regex.map", ` +^[^.]+\.app\.d1\.local/ d1_app_8080 +`) + c.checkMap("_front001_sni.map", ` +`) + c.checkMap("_front001_sni_regex.map", ` +^[^.]+\.sub\.d1\.local/ d1_app_8080 +`) + c.checkMap("_front001_inv_crt.list", ` +`) + c.checkMap("_front001_inv_crt_regex.list", ` +^[^.]+\.sub\.d1\.local$ +`) + c.checkMap("_front001_inv_crt_redir.map", ` +`) + c.checkMap("_front001_inv_crt_redir_regex.map", ` +^[^.]+\.sub\.d1\.local$ http://sub.d1.local/error.html +`) + c.checkMap("_front002_host.map", ` +`) + c.checkMap("_front002_host_regex.map", ` +^[^.]+\.d2\.local/ d2_app_8080 +`) + + c.logger.CompareLogging(defaultLogging) +} + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * BUILDERS diff --git a/pkg/haproxy/types/frontend.go b/pkg/haproxy/types/frontend.go index 8e898f871..7ee19be46 100644 --- a/pkg/haproxy/types/frontend.go +++ b/pkg/haproxy/types/frontend.go @@ -20,19 +20,36 @@ import ( "fmt" "reflect" "sort" + "strings" ) -// Append ... -func (hm *HostsMap) Append(key, value string) { - hm.Entries = append(hm.Entries, &HostsMapEntry{ - Key: key, - Value: value, - }) +// AppendHostname ... +func (hm *HostsMap) AppendHostname(base, value string) { + if strings.HasPrefix(base, "*.") { + // *.example.local + key := "^" + strings.Replace(base, ".", "\\.", -1) + key = strings.Replace(key, "*", "[^.]+", 1) + if !strings.Contains(base, "/") { + // match eol if only the hostname is provided + // if has /path, need to match the begining of the string, a la map_beg() converter + key = key + "$" + } + hm.Regex = append(hm.Regex, &HostsMapEntry{ + Key: key, + Value: value, + }) + } else { + // sub.example.local + hm.Match = append(hm.Match, &HostsMapEntry{ + Key: base, + Value: value, + }) + } } -// String ... -func (hm *HostsMap) String() string { - return hm.Filename +// HasRegex ... +func (hm *HostsMap) HasRegex() bool { + return len(hm.Regex) > 0 } // CreateMaps ... @@ -42,18 +59,16 @@ func CreateMaps() *HostsMaps { // AddMap ... func (hm *HostsMaps) AddMap(filename string) *HostsMap { + matchFile := filename + regexFile := strings.Replace(filename, ".", "_regex.", 1) hmap := &HostsMap{ - Filename: filename, + MatchFile: matchFile, + RegexFile: regexFile, } hm.Items = append(hm.Items, hmap) return hmap } -// HasHTTPHost ... -func (fg *FrontendGroup) HasHTTPHost() bool { - return len(fg.HTTPFrontsMap.Entries) > 0 -} - // HasTCPProxy ... func (fg *FrontendGroup) HasTCPProxy() bool { // short-circuit saves: diff --git a/pkg/haproxy/types/frontend_test.go b/pkg/haproxy/types/frontend_test.go index f062b7d76..c5f73f4b4 100644 --- a/pkg/haproxy/types/frontend_test.go +++ b/pkg/haproxy/types/frontend_test.go @@ -23,6 +23,49 @@ import ( yaml "gopkg.in/yaml.v2" ) +func TestAppendHostname(t *testing.T) { + testCases := []struct { + hostname string + expectedMatch bool + expectedRegex string + }{ + // 0 + {hostname: "example.local", expectedMatch: true}, + // 1 + {hostname: "example.local/", expectedMatch: true}, + // 2 + {hostname: "*.example.local", expectedRegex: "^[^.]+\\.example\\.local$"}, + // 3 + {hostname: "*.example.local/", expectedRegex: "^[^.]+\\.example\\.local/"}, + // 4 + {hostname: "*.example.local/path", expectedRegex: "^[^.]+\\.example\\.local/path"}, + } + for i, test := range testCases { + hm := &HostsMap{} + hm.AppendHostname(test.hostname, "backend") + if test.expectedMatch { + if len(hm.Match) != 1 || len(hm.Regex) != 0 { + t.Errorf("item %d, expected len(match)==1 and len(regex)==0, but was '%d' and '%d'", i, len(hm.Match), len(hm.Regex)) + continue + } + if hm.Match[0].Key != test.hostname { + t.Errorf("item %d, expected key '%s', but was '%s'", i, test.hostname, hm.Match[0].Key) + continue + } + } else { + //regex + if len(hm.Match) != 0 || len(hm.Regex) != 1 { + t.Errorf("item %d, expected len(match)==0 and len(regex)==1, but was '%d' and '%d'", i, len(hm.Match), len(hm.Regex)) + continue + } + if hm.Regex[0].Key != test.expectedRegex { + t.Errorf("item %d, expected key '%s', but was '%s'", i, test.expectedRegex, hm.Regex[0].Key) + continue + } + } + } +} + func TestBuildFrontendEmpty(t *testing.T) { frontends, _ := BuildRawFrontends([]*Host{}) if len(frontends) > 0 { diff --git a/pkg/haproxy/types/host.go b/pkg/haproxy/types/host.go index 6f984f253..5ade4c535 100644 --- a/pkg/haproxy/types/host.go +++ b/pkg/haproxy/types/host.go @@ -38,6 +38,7 @@ func (h *Host) AddPath(backend *Backend, path string) { Backend: backend, BackendID: backend.ID, }) + // reverse order in order to avoid overlap of sub-paths sort.Slice(h.Paths, func(i, j int) bool { return h.Paths[i].Path > h.Paths[j].Path }) diff --git a/pkg/haproxy/types/types.go b/pkg/haproxy/types/types.go index 9e17700fc..57d528515 100644 --- a/pkg/haproxy/types/types.go +++ b/pkg/haproxy/types/types.go @@ -91,8 +91,10 @@ type HostsMapEntry struct { // HostsMap ... type HostsMap struct { - Entries []*HostsMapEntry - Filename string + Match []*HostsMapEntry + MatchFile string + Regex []*HostsMapEntry + RegexFile string } // HostsMaps ... diff --git a/rootfs/etc/haproxy/template/haproxy.tmpl b/rootfs/etc/haproxy/template/haproxy.tmpl index 6929cc0b1..4d415b471 100644 --- a/rootfs/etc/haproxy/template/haproxy.tmpl +++ b/rootfs/etc/haproxy/template/haproxy.tmpl @@ -244,15 +244,34 @@ listen _front__tls tcp-request content accept if { req.ssl_hello_type 1 } {{- if $fgroup.HasSSLPassthrough }} ## ssl-passthrough - tcp-request content set-var(req.backend) req.ssl_sni,lower,map({{ $fgroup.SSLPassthroughMap }},_nomatch) + tcp-request content set-var(req.backend) + {{- "" }} req.ssl_sni,lower,map({{ $fgroup.SSLPassthroughMap.MatchFile }},_nomatch) use_backend %[var(req.backend)] unless { var(req.backend) _nomatch } {{- end }} {{- range $frontend := $frontends }} - ## {{ $frontend.Name }} {{- range $bind := $frontend.Binds }} - use-server _server{{ $bind.Name }} if { req.ssl_sni -i -f {{ $bind.UseServerList }} } + ## {{ $frontend.Name }}/{{ $bind.Name }} + use-server _server{{ $bind.Name }} if { req.ssl_sni -i -f {{ $bind.UseServerList.MatchFile }} } server _server{{ $bind.Name }} {{ $bind.Socket }} send-proxy-v2 weight 0 {{- end }} +{{- end }} + +{{- if $fgroup.SSLPassthroughMap.HasRegex }} + ## ssl-passthrough wildcard +{{- /*** TODO is map_reg() converter running on every request? ***/}} + tcp-request content set-var(req.backend) + {{- "" }} req.ssl_sni,lower,map_reg({{ $fgroup.SSLPassthroughMap.RegexFile }},_nomatch) + {{- "" }} if { var(req.backend) _nomatch } + use_backend %[var(req.backend)] unless { var(req.backend) _nomatch } +{{- end }} +{{- range $frontend := $frontends }} +{{- range $bind := $frontend.Binds }} +{{- if $bind.UseServerList.HasRegex }} + ## {{ $frontend.Name }}/{{ $bind.Name }} wildcard + use-server _server{{ $bind.Name }}_wildcard if { req.ssl_sni -i -m reg -f {{ $bind.UseServerList.RegexFile }} } + server _server{{ $bind.Name }}_wildcard {{ $bind.Socket }} send-proxy-v2 weight 0 +{{- end }} +{{- end }} {{- end }} # TODO default backend {{- end }} @@ -261,32 +280,39 @@ listen _front__tls # # # HTTP frontend # -frontend _front__http +frontend _front_http mode http bind :80 {{- /*------------------------------------*/}} {{- $hasredirect := $fgroup.HasRedirectHTTPS }} -{{- $hashttp := $fgroup.HasHTTPHost }} -{{- if $hasredirect }} +{{- if or $hasredirect $fgroup.HTTPFrontsMap.HasRegex }} http-request set-var(req.base) base,regsub(:[0-9]+/,/) -{{- if $hashttp }} - http-request set-var(req.backend) var(req.base),map_beg({{ $fgroup.HTTPFrontsMap }},_nomatch) + http-request set-var(req.backend) var(req.base),map_beg({{ $fgroup.HTTPFrontsMap.MatchFile }},_nomatch) +{{- else }} + http-request set-var(req.backend) base,regsub(:[0-9]+/,/),map_beg({{ $fgroup.HTTPFrontsMap.MatchFile }},_nomatch) {{- end }} -{{- else if $hashttp }} - http-request set-var(req.backend) base,regsub(:[0-9]+/,/),map_beg({{ $fgroup.HTTPFrontsMap }},_nomatch) +{{- if $fgroup.HTTPFrontsMap.HasRegex }} + http-request set-var(req.backend) + {{- "" }} var(req.base),map_reg({{ $fgroup.HTTPFrontsMap.RegexFile }},_nomatch) + {{- "" }} if { var(req.backend) _nomatch } {{- end }} {{- /*------------------------------------*/}} {{- if $hasredirect }} - redirect scheme https if - {{- "" }} { var(req.base),map_beg({{ $fgroup.RedirectMap }},_nomatch) yes } +{{- if $fgroup.RedirectMap.HasRegex }} + http-request set-var(req.redir) + {{- "" }} var(req.base),map_beg({{ $fgroup.RedirectMap.MatchFile }},_nomatch) + redirect scheme https if { var(req.redir) yes } + redirect scheme https if { var(req.redir) _nomatch } + {{- "" }} { var(req.base),map_reg({{ $fgroup.RedirectMap.RegexFile }},_nomatch) yes } +{{- else }} + redirect scheme https if { var(req.base),map_beg({{ $fgroup.RedirectMap.MatchFile }},_nomatch) yes } +{{- end }} {{- end }} {{- /*------------------------------------*/}} -{{- if $hashttp }} use_backend %[var(req.backend)] unless { var(req.backend) _nomatch } -{{- end }} {{- template "defaultbackend" map $cfg }} @@ -320,45 +346,91 @@ frontend {{ $frontend.Name }} {{- if $frontend.Timeout.ClientFin }} timeout client-fin {{ $frontend.Timeout.ClientFin }} {{- end }} -{{- if $frontend.HasVarNamespace }} - http-request set-var(txn.namespace) base - {{- if $frontend.ConvertLowercase }},lower{{ end }} - {{- "" }},regsub(:[0-9]+/,/) - {{- "" }},map_beg({{ $frontend.VarNamespaceMap }},-) -{{- end }} {{- /*------------------------------------*/}} +{{- if or $frontend.HostBackendsMap.HasRegex $frontend.HasVarNamespace }} + http-request set-var(req.base) base + {{- if $frontend.ConvertLowercase }},lower{{ end }} + {{- "" }},regsub(:[0-9]+/,/) + http-request set-var(req.hostbackend) + {{- "" }} var(req.base),map_beg({{ $frontend.HostBackendsMap.MatchFile }},_nomatch) +{{- else }} http-request set-var(req.hostbackend) base {{- if $frontend.ConvertLowercase }},lower{{ end }} {{- "" }},regsub(:[0-9]+/,/) - {{- "" }},map_beg({{ $frontend.HostBackendsMap }},_nomatch) + {{- "" }},map_beg({{ $frontend.HostBackendsMap.MatchFile }},_nomatch) +{{- end }} +{{- if $frontend.HostBackendsMap.HasRegex }} + http-request set-var(req.hostbackend) + {{- "" }} var(req.base),map_reg({{ $frontend.HostBackendsMap.RegexFile }},_nomatch) + {{- "" }} if { var(req.hostbackend) _nomatch } +{{- end }} + +{{- /*------------------------------------*/}} +{{- if $frontend.HasVarNamespace }} + http-request set-var(txn.namespace) + {{- "" }} var(req.base),map_beg({{ $frontend.VarNamespaceMap.MatchFile }},-) +{{- if $frontend.VarNamespaceMap.HasRegex }} + http-request set-var(txn.namespace) + {{- "" }} var(req.base),map_reg({{ $frontend.VarNamespaceMap.RegexFile }},-) + {{- "" }} if { var(txn.namespace) - } +{{- end }} +{{- end }} {{- /*------------------------------------*/}} {{- if $frontend.HasTLSAuth }} -{{- /* missing concat converter, fix after 1.9 */}} +{{- /*** TODO missing concat converter, fix after 1.9 ***/}} http-request set-header x-ha-base %[ssl_fc_sni]%[path] +{{- if $frontend.SNIBackendsMap.HasRegex }} + http-request set-var(req.snibase) hdr(x-ha-base) + {{- if $frontend.ConvertLowercase }},lower{{ end }} + {{- "" }},regsub(:[0-9]+/,/) + http-request set-var(req.snibackend) var(req.snibase) + {{- "" }},map_beg({{ $frontend.SNIBackendsMap.MatchFile }},_nomatch) + http-request set-var(req.snibackend) var(req.snibase) + {{- "" }},map_reg({{ $frontend.SNIBackendsMap.RegexFile }},_nomatch) if { var(req.snibackend) _nomatch } +{{- else }} http-request set-var(req.snibackend) hdr(x-ha-base) {{- if $frontend.ConvertLowercase }},lower{{ end }} {{- "" }},regsub(:[0-9]+/,/) - {{- "" }},map_beg({{ $frontend.SNIBackendsMap }},_nomatch) + {{- "" }},map_beg({{ $frontend.SNIBackendsMap.MatchFile }},_nomatch) +{{- end }} {{- $mandatory := $frontend.HasTLSMandatory }} {{- if $mandatory }} acl tls-has-crt ssl_c_used - acl tls-need-crt ssl_fc_sni -i -f {{ $frontend.TLSNoCrtErrorList }} + acl tls-need-crt ssl_fc_sni -i -f {{ $frontend.TLSNoCrtErrorList.MatchFile }} +{{- if $frontend.TLSNoCrtErrorList.HasRegex }} + acl tls-need-crt ssl_fc_sni -i -m reg -f {{ $frontend.TLSNoCrtErrorList.RegexFile }} +{{- end }} {{- end }} acl tls-has-invalid-crt ssl_c_ca_err gt 0 acl tls-has-invalid-crt ssl_c_err gt 0 - acl tls-check-crt ssl_fc_sni -i -f {{ $frontend.TLSInvalidCrtErrorList }} + acl tls-check-crt ssl_fc_sni -i -f {{ $frontend.TLSInvalidCrtErrorList.MatchFile }} +{{- if $frontend.TLSInvalidCrtErrorList.HasRegex }} + acl tls-check-crt ssl_fc_sni -i -m reg -f {{ $frontend.TLSInvalidCrtErrorList.RegexFile }} +{{- end }} {{- if $mandatory }} http-request set-var(req.tls_nocrt_redir) ssl_fc_sni {{- if $frontend.ConvertLowercase }},lower{{ end }} - {{- "" }},map({{ $frontend.TLSNoCrtErrorPagesMap }},_internal) + {{- "" }},map({{ $frontend.TLSNoCrtErrorPagesMap.MatchFile }},_internal) {{- "" }} if !tls-has-crt tls-need-crt +{{- if $frontend.TLSNoCrtErrorPagesMap.HasRegex }} + http-request set-var(req.tls_nocrt_redir) ssl_fc_sni + {{- if $frontend.ConvertLowercase }},lower{{ end }} + {{- "" }},map_reg({{ $frontend.TLSNoCrtErrorPagesMap.RegexFile }},_internal) + {{- "" }} if { var(req.tls_nocrt_redir) _internal } +{{- end }} {{- end }} http-request set-var(req.tls_invalidcrt_redir) ssl_fc_sni {{- if $frontend.ConvertLowercase }},lower{{ end }} - {{- "" }},map({{ $frontend.TLSInvalidCrtErrorPagesMap }},_internal) + {{- "" }},map({{ $frontend.TLSInvalidCrtErrorPagesMap.MatchFile }},_internal) {{- "" }} if tls-has-invalid-crt tls-check-crt +{{- if $frontend.TLSInvalidCrtErrorPagesMap.HasRegex }} + http-request set-var(req.tls_invalidcrt_redir) ssl_fc_sni + {{- if $frontend.ConvertLowercase }},lower{{ end }} + {{- "" }},map_reg({{ $frontend.TLSInvalidCrtErrorPagesMap.RegexFile }},_internal) + {{- "" }} if { var(req.tls_invalidcrt_redir) _internal } +{{- end }} {{- if and $mandatory $frontend.HasNoCrtErrorPage }} http-request redirect location %[var(req.tls_nocrt_redir)] code 303 if {{- "" }} { var(req.tls_nocrt_redir) -m found } !{ var(req.tls_nocrt_redir) _internal } From 76ce31e7c2b8641919c8ff9a3991ecb8ed0f9ace Mon Sep 17 00:00:00 2001 From: Joao Morais Date: Sun, 12 May 2019 08:15:23 -0300 Subject: [PATCH 05/30] Always use case insensitive match --- pkg/haproxy/instance_test.go | 40 ++++++++++++------------ pkg/haproxy/types/frontend.go | 2 ++ pkg/haproxy/types/frontend_test.go | 12 +++---- pkg/haproxy/types/types.go | 3 +- rootfs/etc/haproxy/template/haproxy.tmpl | 28 +++++------------ 5 files changed, 37 insertions(+), 48 deletions(-) diff --git a/pkg/haproxy/instance_test.go b/pkg/haproxy/instance_test.go index 0c2f5d9c7..2d7aeec1e 100644 --- a/pkg/haproxy/instance_test.go +++ b/pkg/haproxy/instance_test.go @@ -84,7 +84,7 @@ frontend _front_http frontend _front001 mode http bind :443 ssl alpn h2,http/1.1 crt /var/haproxy/ssl/certs/default.pem - http-request set-var(req.hostbackend) base,regsub(:[0-9]+/,/),map_beg(/etc/haproxy/maps/_front001_host.map,_nomatch) + http-request set-var(req.hostbackend) base,lower,regsub(:[0-9]+/,/),map_beg(/etc/haproxy/maps/_front001_host.map,_nomatch) use_backend %%[var(req.hostbackend)] unless { var(req.hostbackend) _nomatch } default_backend _error404 ` @@ -149,7 +149,7 @@ frontend _front_http frontend _front001 mode http bind :443 ssl alpn h2,http/1.1 crt /var/haproxy/ssl/certs/default.pem - http-request set-var(req.base) base,regsub(:[0-9]+/,/) + http-request set-var(req.base) base,lower,regsub(:[0-9]+/,/) http-request set-var(req.hostbackend) var(req.base),map_beg(/etc/haproxy/maps/_front001_host.map,_nomatch) http-request set-var(txn.namespace) var(req.base),map_beg(/etc/haproxy/maps/_front001_k8s_ns.map,-) use_backend %[var(req.hostbackend)] unless { var(req.hostbackend) _nomatch } @@ -222,7 +222,7 @@ frontend _front_http frontend _front001 mode http bind :443 ssl alpn h2,http/1.1 crt /var/haproxy/ssl/certs/default.pem crt /var/haproxy/certs/_public - http-request set-var(req.base) base,regsub(:[0-9]+/,/) + http-request set-var(req.base) base,lower,regsub(:[0-9]+/,/) http-request set-var(req.hostbackend) var(req.base),map_beg(/etc/haproxy/maps/_front001_host.map,_nomatch) http-request set-var(txn.namespace) var(req.base),map_beg(/etc/haproxy/maps/_front001_k8s_ns.map,-) use_backend %[var(req.hostbackend)] unless { var(req.hostbackend) _nomatch } @@ -312,16 +312,16 @@ frontend _front001 mode http bind unix@/var/run/_socket001.sock accept-proxy ssl alpn h2,http/1.1 crt /var/haproxy/ssl/certs/default.pem ca-file /var/haproxy/ssl/ca/d1.local.pem verify optional ca-ignore-err all crt-ignore-err all bind unix@/var/run/_socket002.sock accept-proxy ssl alpn h2,http/1.1 crt /var/haproxy/ssl/certs/default.pem ca-file /var/haproxy/ssl/ca/d2.local.pem verify optional ca-ignore-err all crt-ignore-err all - http-request set-var(req.hostbackend) base,regsub(:[0-9]+/,/),map_beg(/etc/haproxy/maps/_front001_host.map,_nomatch) + http-request set-var(req.hostbackend) base,lower,regsub(:[0-9]+/,/),map_beg(/etc/haproxy/maps/_front001_host.map,_nomatch) http-request set-header x-ha-base %[ssl_fc_sni]%[path] - http-request set-var(req.snibackend) hdr(x-ha-base),regsub(:[0-9]+/,/),map_beg(/etc/haproxy/maps/_front001_sni.map,_nomatch) + http-request set-var(req.snibackend) hdr(x-ha-base),lower,regsub(:[0-9]+/,/),map_beg(/etc/haproxy/maps/_front001_sni.map,_nomatch) acl tls-has-crt ssl_c_used acl tls-need-crt ssl_fc_sni -i -f /etc/haproxy/maps/_front001_no_crt.list acl tls-has-invalid-crt ssl_c_ca_err gt 0 acl tls-has-invalid-crt ssl_c_err gt 0 acl tls-check-crt ssl_fc_sni -i -f /etc/haproxy/maps/_front001_inv_crt.list - http-request set-var(req.tls_nocrt_redir) ssl_fc_sni,map(/etc/haproxy/maps/_front001_no_crt_redir.map,_internal) if !tls-has-crt tls-need-crt - http-request set-var(req.tls_invalidcrt_redir) ssl_fc_sni,map(/etc/haproxy/maps/_front001_inv_crt_redir.map,_internal) if tls-has-invalid-crt tls-check-crt + http-request set-var(req.tls_nocrt_redir) ssl_fc_sni,lower,map(/etc/haproxy/maps/_front001_no_crt_redir.map,_internal) if !tls-has-crt tls-need-crt + http-request set-var(req.tls_invalidcrt_redir) ssl_fc_sni,lower,map(/etc/haproxy/maps/_front001_inv_crt_redir.map,_internal) if tls-has-invalid-crt tls-check-crt http-request redirect location %[var(req.tls_nocrt_redir)] code 303 if { var(req.tls_nocrt_redir) -m found } !{ var(req.tls_nocrt_redir) _internal } http-request redirect location %[var(req.tls_invalidcrt_redir)] code 303 if { var(req.tls_invalidcrt_redir) -m found } !{ var(req.tls_invalidcrt_redir) _internal } use_backend _error496 if { var(req.tls_nocrt_redir) _internal } @@ -457,13 +457,13 @@ frontend _front001 mode http bind unix@/var/run/_socket001.sock accept-proxy ssl alpn h2,http/1.1 crt /var/haproxy/ssl/certs/default.pem ca-file /var/haproxy/ssl/ca/d1.local.pem verify optional ca-ignore-err all crt-ignore-err all timeout client 1s - http-request set-var(req.hostbackend) base,regsub(:[0-9]+/,/),map_beg(/etc/haproxy/maps/_front001_host.map,_nomatch) + http-request set-var(req.hostbackend) base,lower,regsub(:[0-9]+/,/),map_beg(/etc/haproxy/maps/_front001_host.map,_nomatch) http-request set-header x-ha-base %[ssl_fc_sni]%[path] - http-request set-var(req.snibackend) hdr(x-ha-base),regsub(:[0-9]+/,/),map_beg(/etc/haproxy/maps/_front001_sni.map,_nomatch) + http-request set-var(req.snibackend) hdr(x-ha-base),lower,regsub(:[0-9]+/,/),map_beg(/etc/haproxy/maps/_front001_sni.map,_nomatch) acl tls-has-invalid-crt ssl_c_ca_err gt 0 acl tls-has-invalid-crt ssl_c_err gt 0 acl tls-check-crt ssl_fc_sni -i -f /etc/haproxy/maps/_front001_inv_crt.list - http-request set-var(req.tls_invalidcrt_redir) ssl_fc_sni,map(/etc/haproxy/maps/_front001_inv_crt_redir.map,_internal) if tls-has-invalid-crt tls-check-crt + http-request set-var(req.tls_invalidcrt_redir) ssl_fc_sni,lower,map(/etc/haproxy/maps/_front001_inv_crt_redir.map,_internal) if tls-has-invalid-crt tls-check-crt http-request redirect location %[var(req.tls_invalidcrt_redir)] code 303 if { var(req.tls_invalidcrt_redir) -m found } !{ var(req.tls_invalidcrt_redir) _internal } use_backend _error495 if { var(req.tls_invalidcrt_redir) _internal } use_backend %[var(req.hostbackend)] unless { var(req.hostbackend) _nomatch } @@ -474,16 +474,16 @@ frontend _front002 bind unix@/var/run/_socket002.sock accept-proxy ssl alpn h2,http/1.1 crt /var/haproxy/ssl/certs/default.pem crt /var/haproxy/certs/_socket002 ca-file /var/haproxy/ssl/ca/d2.local.pem verify optional ca-ignore-err all crt-ignore-err all bind unix@/var/run/_socket003.sock accept-proxy ssl alpn h2,http/1.1 crt /var/haproxy/ssl/certs/default.pem timeout client 2s - http-request set-var(req.hostbackend) base,regsub(:[0-9]+/,/),map_beg(/etc/haproxy/maps/_front002_host.map,_nomatch) + http-request set-var(req.hostbackend) base,lower,regsub(:[0-9]+/,/),map_beg(/etc/haproxy/maps/_front002_host.map,_nomatch) http-request set-header x-ha-base %[ssl_fc_sni]%[path] - http-request set-var(req.snibackend) hdr(x-ha-base),regsub(:[0-9]+/,/),map_beg(/etc/haproxy/maps/_front002_sni.map,_nomatch) + http-request set-var(req.snibackend) hdr(x-ha-base),lower,regsub(:[0-9]+/,/),map_beg(/etc/haproxy/maps/_front002_sni.map,_nomatch) acl tls-has-crt ssl_c_used acl tls-need-crt ssl_fc_sni -i -f /etc/haproxy/maps/_front002_no_crt.list acl tls-has-invalid-crt ssl_c_ca_err gt 0 acl tls-has-invalid-crt ssl_c_err gt 0 acl tls-check-crt ssl_fc_sni -i -f /etc/haproxy/maps/_front002_inv_crt.list - http-request set-var(req.tls_nocrt_redir) ssl_fc_sni,map(/etc/haproxy/maps/_front002_no_crt_redir.map,_internal) if !tls-has-crt tls-need-crt - http-request set-var(req.tls_invalidcrt_redir) ssl_fc_sni,map(/etc/haproxy/maps/_front002_inv_crt_redir.map,_internal) if tls-has-invalid-crt tls-check-crt + http-request set-var(req.tls_nocrt_redir) ssl_fc_sni,lower,map(/etc/haproxy/maps/_front002_no_crt_redir.map,_internal) if !tls-has-crt tls-need-crt + http-request set-var(req.tls_invalidcrt_redir) ssl_fc_sni,lower,map(/etc/haproxy/maps/_front002_inv_crt_redir.map,_internal) if tls-has-invalid-crt tls-check-crt http-request redirect location %[var(req.tls_nocrt_redir)] code 303 if { var(req.tls_nocrt_redir) -m found } !{ var(req.tls_nocrt_redir) _internal } http-request redirect location %[var(req.tls_invalidcrt_redir)] code 303 if { var(req.tls_invalidcrt_redir) -m found } !{ var(req.tls_invalidcrt_redir) _internal } use_backend _error496 if { var(req.tls_nocrt_redir) _internal } @@ -619,7 +619,7 @@ frontend _front_http frontend _front001 mode http bind :443 ssl alpn h2,http/1.1 crt /var/haproxy/ssl/certs/default.pem - http-request set-var(req.hostbackend) base,regsub(:[0-9]+/,/),map_beg(/etc/haproxy/maps/_front001_host.map,_nomatch) + http-request set-var(req.hostbackend) base,lower,regsub(:[0-9]+/,/),map_beg(/etc/haproxy/maps/_front001_host.map,_nomatch) use_backend %[var(req.hostbackend)] unless { var(req.hostbackend) _nomatch } default_backend _default_backend `) @@ -796,19 +796,19 @@ frontend _front001 mode http bind unix@/var/run/_socket001.sock accept-proxy ssl alpn h2,http/1.1 crt /var/haproxy/ssl/certs/default.pem bind unix@/var/run/_socket002.sock accept-proxy ssl alpn h2,http/1.1 crt /var/haproxy/ssl/certs/default.pem ca-file /var/haproxy/ssl/ca/d1.local.pem verify optional ca-ignore-err all crt-ignore-err all - http-request set-var(req.base) base,regsub(:[0-9]+/,/) + http-request set-var(req.base) base,lower,regsub(:[0-9]+/,/) http-request set-var(req.hostbackend) var(req.base),map_beg(/etc/haproxy/maps/_front001_host.map,_nomatch) http-request set-var(req.hostbackend) var(req.base),map_reg(/etc/haproxy/maps/_front001_host_regex.map,_nomatch) if { var(req.hostbackend) _nomatch } http-request set-header x-ha-base %[ssl_fc_sni]%[path] - http-request set-var(req.snibase) hdr(x-ha-base),regsub(:[0-9]+/,/) + http-request set-var(req.snibase) hdr(x-ha-base),lower,regsub(:[0-9]+/,/) http-request set-var(req.snibackend) var(req.snibase),map_beg(/etc/haproxy/maps/_front001_sni.map,_nomatch) http-request set-var(req.snibackend) var(req.snibase),map_reg(/etc/haproxy/maps/_front001_sni_regex.map,_nomatch) if { var(req.snibackend) _nomatch } acl tls-has-invalid-crt ssl_c_ca_err gt 0 acl tls-has-invalid-crt ssl_c_err gt 0 acl tls-check-crt ssl_fc_sni -i -f /etc/haproxy/maps/_front001_inv_crt.list acl tls-check-crt ssl_fc_sni -i -m reg -f /etc/haproxy/maps/_front001_inv_crt_regex.list - http-request set-var(req.tls_invalidcrt_redir) ssl_fc_sni,map(/etc/haproxy/maps/_front001_inv_crt_redir.map,_internal) if tls-has-invalid-crt tls-check-crt - http-request set-var(req.tls_invalidcrt_redir) ssl_fc_sni,map_reg(/etc/haproxy/maps/_front001_inv_crt_redir_regex.map,_internal) if { var(req.tls_invalidcrt_redir) _internal } + http-request set-var(req.tls_invalidcrt_redir) ssl_fc_sni,lower,map(/etc/haproxy/maps/_front001_inv_crt_redir.map,_internal) if tls-has-invalid-crt tls-check-crt + http-request set-var(req.tls_invalidcrt_redir) ssl_fc_sni,lower,map_reg(/etc/haproxy/maps/_front001_inv_crt_redir_regex.map,_internal) if { var(req.tls_invalidcrt_redir) _internal } http-request redirect location %[var(req.tls_invalidcrt_redir)] code 303 if { var(req.tls_invalidcrt_redir) -m found } !{ var(req.tls_invalidcrt_redir) _internal } use_backend _error495 if { var(req.tls_invalidcrt_redir) _internal } use_backend %[var(req.hostbackend)] unless { var(req.hostbackend) _nomatch } @@ -818,7 +818,7 @@ frontend _front002 mode http bind unix@/var/run/_socket003.sock accept-proxy ssl alpn h2,http/1.1 crt /var/haproxy/ssl/certs/default.pem timeout client 10s - http-request set-var(req.base) base,regsub(:[0-9]+/,/) + http-request set-var(req.base) base,lower,regsub(:[0-9]+/,/) http-request set-var(req.hostbackend) var(req.base),map_beg(/etc/haproxy/maps/_front002_host.map,_nomatch) http-request set-var(req.hostbackend) var(req.base),map_reg(/etc/haproxy/maps/_front002_host_regex.map,_nomatch) if { var(req.hostbackend) _nomatch } use_backend %[var(req.hostbackend)] unless { var(req.hostbackend) _nomatch } diff --git a/pkg/haproxy/types/frontend.go b/pkg/haproxy/types/frontend.go index 7ee19be46..7b0632414 100644 --- a/pkg/haproxy/types/frontend.go +++ b/pkg/haproxy/types/frontend.go @@ -25,6 +25,8 @@ import ( // AppendHostname ... func (hm *HostsMap) AppendHostname(base, value string) { + // always use case insensitive match + base = strings.ToLower(base) if strings.HasPrefix(base, "*.") { // *.example.local key := "^" + strings.Replace(base, ".", "\\.", -1) diff --git a/pkg/haproxy/types/frontend_test.go b/pkg/haproxy/types/frontend_test.go index c5f73f4b4..5e957a47a 100644 --- a/pkg/haproxy/types/frontend_test.go +++ b/pkg/haproxy/types/frontend_test.go @@ -26,15 +26,15 @@ import ( func TestAppendHostname(t *testing.T) { testCases := []struct { hostname string - expectedMatch bool + expectedMatch string expectedRegex string }{ // 0 - {hostname: "example.local", expectedMatch: true}, + {hostname: "Example.Local", expectedMatch: "example.local"}, // 1 - {hostname: "example.local/", expectedMatch: true}, + {hostname: "example.local/", expectedMatch: "example.local/"}, // 2 - {hostname: "*.example.local", expectedRegex: "^[^.]+\\.example\\.local$"}, + {hostname: "*.Example.Local", expectedRegex: "^[^.]+\\.example\\.local$"}, // 3 {hostname: "*.example.local/", expectedRegex: "^[^.]+\\.example\\.local/"}, // 4 @@ -43,12 +43,12 @@ func TestAppendHostname(t *testing.T) { for i, test := range testCases { hm := &HostsMap{} hm.AppendHostname(test.hostname, "backend") - if test.expectedMatch { + if test.expectedMatch != "" { if len(hm.Match) != 1 || len(hm.Regex) != 0 { t.Errorf("item %d, expected len(match)==1 and len(regex)==0, but was '%d' and '%d'", i, len(hm.Match), len(hm.Regex)) continue } - if hm.Match[0].Key != test.hostname { + if hm.Match[0].Key != test.expectedMatch { t.Errorf("item %d, expected key '%s', but was '%s'", i, test.hostname, hm.Match[0].Key) continue } diff --git a/pkg/haproxy/types/types.go b/pkg/haproxy/types/types.go index 57d528515..8cbd99667 100644 --- a/pkg/haproxy/types/types.go +++ b/pkg/haproxy/types/types.go @@ -121,8 +121,7 @@ type Frontend struct { Binds []*BindConfig Hosts []*Host // - ConvertLowercase bool - Timeout HostTimeoutConfig + Timeout HostTimeoutConfig // Maps *HostsMaps HostBackendsMap *HostsMap diff --git a/rootfs/etc/haproxy/template/haproxy.tmpl b/rootfs/etc/haproxy/template/haproxy.tmpl index 4d415b471..df33e657d 100644 --- a/rootfs/etc/haproxy/template/haproxy.tmpl +++ b/rootfs/etc/haproxy/template/haproxy.tmpl @@ -349,15 +349,11 @@ frontend {{ $frontend.Name }} {{- /*------------------------------------*/}} {{- if or $frontend.HostBackendsMap.HasRegex $frontend.HasVarNamespace }} - http-request set-var(req.base) base - {{- if $frontend.ConvertLowercase }},lower{{ end }} - {{- "" }},regsub(:[0-9]+/,/) + http-request set-var(req.base) base,lower,regsub(:[0-9]+/,/) http-request set-var(req.hostbackend) {{- "" }} var(req.base),map_beg({{ $frontend.HostBackendsMap.MatchFile }},_nomatch) {{- else }} - http-request set-var(req.hostbackend) base - {{- if $frontend.ConvertLowercase }},lower{{ end }} - {{- "" }},regsub(:[0-9]+/,/) + http-request set-var(req.hostbackend) base,lower,regsub(:[0-9]+/,/) {{- "" }},map_beg({{ $frontend.HostBackendsMap.MatchFile }},_nomatch) {{- end }} {{- if $frontend.HostBackendsMap.HasRegex }} @@ -382,17 +378,13 @@ frontend {{ $frontend.Name }} {{- /*** TODO missing concat converter, fix after 1.9 ***/}} http-request set-header x-ha-base %[ssl_fc_sni]%[path] {{- if $frontend.SNIBackendsMap.HasRegex }} - http-request set-var(req.snibase) hdr(x-ha-base) - {{- if $frontend.ConvertLowercase }},lower{{ end }} - {{- "" }},regsub(:[0-9]+/,/) + http-request set-var(req.snibase) hdr(x-ha-base),lower,regsub(:[0-9]+/,/) http-request set-var(req.snibackend) var(req.snibase) {{- "" }},map_beg({{ $frontend.SNIBackendsMap.MatchFile }},_nomatch) http-request set-var(req.snibackend) var(req.snibase) {{- "" }},map_reg({{ $frontend.SNIBackendsMap.RegexFile }},_nomatch) if { var(req.snibackend) _nomatch } {{- else }} - http-request set-var(req.snibackend) hdr(x-ha-base) - {{- if $frontend.ConvertLowercase }},lower{{ end }} - {{- "" }},regsub(:[0-9]+/,/) + http-request set-var(req.snibackend) hdr(x-ha-base),lower,regsub(:[0-9]+/,/) {{- "" }},map_beg({{ $frontend.SNIBackendsMap.MatchFile }},_nomatch) {{- end }} {{- $mandatory := $frontend.HasTLSMandatory }} @@ -410,24 +402,20 @@ frontend {{ $frontend.Name }} acl tls-check-crt ssl_fc_sni -i -m reg -f {{ $frontend.TLSInvalidCrtErrorList.RegexFile }} {{- end }} {{- if $mandatory }} - http-request set-var(req.tls_nocrt_redir) ssl_fc_sni - {{- if $frontend.ConvertLowercase }},lower{{ end }} + http-request set-var(req.tls_nocrt_redir) ssl_fc_sni,lower {{- "" }},map({{ $frontend.TLSNoCrtErrorPagesMap.MatchFile }},_internal) {{- "" }} if !tls-has-crt tls-need-crt {{- if $frontend.TLSNoCrtErrorPagesMap.HasRegex }} - http-request set-var(req.tls_nocrt_redir) ssl_fc_sni - {{- if $frontend.ConvertLowercase }},lower{{ end }} + http-request set-var(req.tls_nocrt_redir) ssl_fc_sni,lower {{- "" }},map_reg({{ $frontend.TLSNoCrtErrorPagesMap.RegexFile }},_internal) {{- "" }} if { var(req.tls_nocrt_redir) _internal } {{- end }} {{- end }} - http-request set-var(req.tls_invalidcrt_redir) ssl_fc_sni - {{- if $frontend.ConvertLowercase }},lower{{ end }} + http-request set-var(req.tls_invalidcrt_redir) ssl_fc_sni,lower {{- "" }},map({{ $frontend.TLSInvalidCrtErrorPagesMap.MatchFile }},_internal) {{- "" }} if tls-has-invalid-crt tls-check-crt {{- if $frontend.TLSInvalidCrtErrorPagesMap.HasRegex }} - http-request set-var(req.tls_invalidcrt_redir) ssl_fc_sni - {{- if $frontend.ConvertLowercase }},lower{{ end }} + http-request set-var(req.tls_invalidcrt_redir) ssl_fc_sni,lower {{- "" }},map_reg({{ $frontend.TLSInvalidCrtErrorPagesMap.RegexFile }},_internal) {{- "" }} if { var(req.tls_invalidcrt_redir) _internal } {{- end }} From cfab0563c6322355aa2fdd39a394c0c34d255ce4 Mon Sep 17 00:00:00 2001 From: Joao Morais Date: Sun, 12 May 2019 09:42:17 -0300 Subject: [PATCH 06/30] Rename maps to avoid collisions --- pkg/haproxy/config.go | 12 ++--- pkg/haproxy/instance_test.go | 68 ++++++++++++------------ pkg/haproxy/types/types.go | 4 +- rootfs/etc/haproxy/template/haproxy.tmpl | 13 +++-- 4 files changed, 48 insertions(+), 49 deletions(-) diff --git a/pkg/haproxy/config.go b/pkg/haproxy/config.go index 0cbbc1cf8..1594fabff 100644 --- a/pkg/haproxy/config.go +++ b/pkg/haproxy/config.go @@ -197,9 +197,9 @@ func (c *config) BuildFrontendGroup() (*hatypes.FrontendGroup, error) { Frontends: frontends, HasSSLPassthrough: len(sslpassthrough) > 0, Maps: fgroupMaps, + HTTPFrontsMap: fgroupMaps.AddMap(c.mapsDir + "/http_front.map"), + HTTPSRedirMap: fgroupMaps.AddMap(c.mapsDir + "/https_redir.map"), SSLPassthroughMap: fgroupMaps.AddMap(c.mapsDir + "/sslpassthrough.map"), - RedirectMap: fgroupMaps.AddMap(c.mapsDir + "/redirect.map"), - HTTPFrontsMap: fgroupMaps.AddMap(c.mapsDir + "/http-front.map"), } if fgroup.HasTCPProxy() { // More than one HAProxy's frontend or bind, or using ssl-passthrough config, @@ -264,18 +264,18 @@ func (c *config) BuildFrontendGroup() (*hatypes.FrontendGroup, error) { return nil, fmt.Errorf("missing root path on host %s", sslpassHost.Hostname) } fgroup.SSLPassthroughMap.AppendHostname(sslpassHost.Hostname, rootPath.BackendID) - fgroup.RedirectMap.AppendHostname(sslpassHost.Hostname+"/", yesno[sslpassHost.HTTPPassthroughBackend == nil]) + fgroup.HTTPSRedirMap.AppendHostname(sslpassHost.Hostname+"/", yesno[sslpassHost.HTTPPassthroughBackend == nil]) if sslpassHost.HTTPPassthroughBackend != nil { fgroup.HTTPFrontsMap.AppendHostname(sslpassHost.Hostname+"/", sslpassHost.HTTPPassthroughBackend.ID) } else { - fgroup.HasRedirectHTTPS = true + fgroup.HasHTTPSRedir = true } } for _, f := range frontends { for _, host := range f.Hosts { for _, path := range host.Paths { // TODO use only root path if all uri has the same conf - fgroup.RedirectMap.AppendHostname(host.Hostname+path.Path, yesno[path.Backend.SSLRedirect]) + fgroup.HTTPSRedirMap.AppendHostname(host.Hostname+path.Path, yesno[path.Backend.SSLRedirect]) base := host.Hostname + path.Path back := path.BackendID if host.HasTLSAuth() { @@ -284,7 +284,7 @@ func (c *config) BuildFrontendGroup() (*hatypes.FrontendGroup, error) { f.HostBackendsMap.AppendHostname(base, back) } if path.Backend.SSLRedirect { - fgroup.HasRedirectHTTPS = true + fgroup.HasHTTPSRedir = true } else { fgroup.HTTPFrontsMap.AppendHostname(base, back) } diff --git a/pkg/haproxy/instance_test.go b/pkg/haproxy/instance_test.go index 2d7aeec1e..977dcb352 100644 --- a/pkg/haproxy/instance_test.go +++ b/pkg/haproxy/instance_test.go @@ -78,7 +78,7 @@ backend _error496 frontend _front_http mode http bind :80 - http-request set-var(req.backend) base,regsub(:[0-9]+/,/),map_beg(/etc/haproxy/maps/http-front.map,_nomatch) + http-request set-var(req.backend) base,regsub(:[0-9]+/,/),map_beg(/etc/haproxy/maps/http_front.map,_nomatch) use_backend %%[var(req.backend)] unless { var(req.backend) _nomatch } default_backend _error404 frontend _front001 @@ -93,7 +93,7 @@ frontend _front001 c.instance.Update() c.checkConfigFull(fmt.Sprintf(template, "--", "--", "--", "--")) - c.checkMap("http-front.map", ` + c.checkMap("http_front.map", ` empty/ default_empty_8080`) c.checkMap("_front001_host.map", ` empty/ default_empty_8080`) @@ -142,8 +142,8 @@ frontend _front_http mode http bind :80 http-request set-var(req.base) base,regsub(:[0-9]+/,/) - http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/http-front.map,_nomatch) - redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/redirect.map,_nomatch) yes } + http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/http_front.map,_nomatch) + redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/https_redir.map,_nomatch) yes } use_backend %[var(req.backend)] unless { var(req.backend) _nomatch } use_backend d1_app_8080 frontend _front001 @@ -156,9 +156,9 @@ frontend _front001 use_backend d1_app_8080 `) - c.checkMap("http-front.map", ` + c.checkMap("http_front.map", ` `) - c.checkMap("redirect.map", ` + c.checkMap("https_redir.map", ` d2.local/app yes `) c.checkMap("_front001_k8s_ns.map", ` @@ -215,8 +215,8 @@ frontend _front_http mode http bind :80 http-request set-var(req.base) base,regsub(:[0-9]+/,/) - http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/http-front.map,_nomatch) - redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/redirect.map,_nomatch) yes } + http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/http_front.map,_nomatch) + redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/https_redir.map,_nomatch) yes } use_backend %[var(req.backend)] unless { var(req.backend) _nomatch } default_backend _default_backend frontend _front001 @@ -229,9 +229,9 @@ frontend _front001 default_backend _default_backend `) - c.checkMap("http-front.map", ` + c.checkMap("http_front.map", ` `) - c.checkMap("redirect.map", ` + c.checkMap("https_redir.map", ` d1.local/ yes d2.local/app yes `) @@ -304,8 +304,8 @@ frontend _front_http mode http bind :80 http-request set-var(req.base) base,regsub(:[0-9]+/,/) - http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/http-front.map,_nomatch) - redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/redirect.map,_nomatch) yes } + http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/http_front.map,_nomatch) + redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/https_redir.map,_nomatch) yes } use_backend %[var(req.backend)] unless { var(req.backend) _nomatch } default_backend _default_backend frontend _front001 @@ -337,9 +337,9 @@ d1.local c.checkMap("_socket002.list", ` d2.local `) - c.checkMap("http-front.map", ` + c.checkMap("http_front.map", ` `) - c.checkMap("redirect.map", ` + c.checkMap("https_redir.map", ` d1.local/ yes d2.local/ yes `) @@ -449,8 +449,8 @@ frontend _front_http mode http bind :80 http-request set-var(req.base) base,regsub(:[0-9]+/,/) - http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/http-front.map,_nomatch) - redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/redirect.map,_nomatch) yes } + http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/http_front.map,_nomatch) + redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/https_redir.map,_nomatch) yes } use_backend %[var(req.backend)] unless { var(req.backend) _nomatch } default_backend _default_backend frontend _front001 @@ -504,11 +504,11 @@ d22.local d3.local d4.local `) - c.checkMap("http-front.map", ` + c.checkMap("http_front.map", ` d3.local/ d_app_8080 d4.local/ d_app_8080 `) - c.checkMap("redirect.map", ` + c.checkMap("https_redir.map", ` d1.local/ yes d21.local/ yes d22.local/ yes @@ -612,8 +612,8 @@ frontend _front_http mode http bind :80 http-request set-var(req.base) base,regsub(:[0-9]+/,/) - http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/http-front.map,_nomatch) - redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/redirect.map,_nomatch) yes } + http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/http_front.map,_nomatch) + redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/https_redir.map,_nomatch) yes } use_backend %[var(req.backend)] unless { var(req.backend) _nomatch } default_backend _default_backend frontend _front001 @@ -624,11 +624,11 @@ frontend _front001 default_backend _default_backend `) - c.checkMap("http-front.map", ` + c.checkMap("http_front.map", ` d.local/sub d_app3_8080 d.local/app/sub d_app2_8080 `) - c.checkMap("redirect.map", ` + c.checkMap("https_redir.map", ` d.local/sub no d.local/app/sub no d.local/app yes @@ -698,17 +698,17 @@ frontend _front_http mode http bind :80 http-request set-var(req.base) base,regsub(:[0-9]+/,/) - http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/http-front.map,_nomatch) - redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/redirect.map,_nomatch) yes } + http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/http_front.map,_nomatch) + redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/https_redir.map,_nomatch) yes } use_backend %[var(req.backend)] unless { var(req.backend) _nomatch } default_backend _error404`) c.checkMap("sslpassthrough.map", ` d2.local d2_app_8080 d3.local d3_app-ssl_8443`) - c.checkMap("http-front.map", ` + c.checkMap("http_front.map", ` d3.local/ d3_app-http_8080`) - c.checkMap("redirect.map", ` + c.checkMap("https_redir.map", ` d2.local/ yes d3.local/ no`) @@ -785,11 +785,11 @@ frontend _front_http mode http bind :80 http-request set-var(req.base) base,regsub(:[0-9]+/,/) - http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/http-front.map,_nomatch) - http-request set-var(req.backend) var(req.base),map_reg(/etc/haproxy/maps/http-front_regex.map,_nomatch) if { var(req.backend) _nomatch } - http-request set-var(req.redir) var(req.base),map_beg(/etc/haproxy/maps/redirect.map,_nomatch) + http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/http_front.map,_nomatch) + http-request set-var(req.backend) var(req.base),map_reg(/etc/haproxy/maps/http_front_regex.map,_nomatch) if { var(req.backend) _nomatch } + http-request set-var(req.redir) var(req.base),map_beg(/etc/haproxy/maps/https_redir.map,_nomatch) redirect scheme https if { var(req.redir) yes } - redirect scheme https if { var(req.redir) _nomatch } { var(req.base),map_reg(/etc/haproxy/maps/redirect_regex.map,_nomatch) yes } + redirect scheme https if { var(req.redir) _nomatch } { var(req.base),map_reg(/etc/haproxy/maps/https_redir_regex.map,_nomatch) yes } use_backend %[var(req.backend)] unless { var(req.backend) _nomatch } default_backend _error404 frontend _front001 @@ -841,15 +841,15 @@ d1.local c.checkMap("_socket003_regex.list", ` ^[^.]+\.d2\.local$ `) - c.checkMap("http-front.map", ` + c.checkMap("http_front.map", ` `) - c.checkMap("http-front_regex.map", ` + c.checkMap("http_front_regex.map", ` ^[^.]+\.d2\.local/ d2_app_8080 `) - c.checkMap("redirect.map", ` + c.checkMap("https_redir.map", ` d1.local/ yes `) - c.checkMap("redirect_regex.map", ` + c.checkMap("https_redir_regex.map", ` ^[^.]+\.app\.d1\.local/ yes ^[^.]+\.sub\.d1\.local/ yes ^[^.]+\.d2\.local/ no diff --git a/pkg/haproxy/types/types.go b/pkg/haproxy/types/types.go index 8cbd99667..05b09c908 100644 --- a/pkg/haproxy/types/types.go +++ b/pkg/haproxy/types/types.go @@ -106,12 +106,12 @@ type HostsMaps struct { type FrontendGroup struct { Frontends []*Frontend // - HasRedirectHTTPS bool + HasHTTPSRedir bool HasSSLPassthrough bool // Maps *HostsMaps HTTPFrontsMap *HostsMap - RedirectMap *HostsMap + HTTPSRedirMap *HostsMap SSLPassthroughMap *HostsMap } diff --git a/rootfs/etc/haproxy/template/haproxy.tmpl b/rootfs/etc/haproxy/template/haproxy.tmpl index df33e657d..b88929ce5 100644 --- a/rootfs/etc/haproxy/template/haproxy.tmpl +++ b/rootfs/etc/haproxy/template/haproxy.tmpl @@ -285,8 +285,7 @@ frontend _front_http bind :80 {{- /*------------------------------------*/}} -{{- $hasredirect := $fgroup.HasRedirectHTTPS }} -{{- if or $hasredirect $fgroup.HTTPFrontsMap.HasRegex }} +{{- if or $fgroup.HasHTTPSRedir $fgroup.HTTPFrontsMap.HasRegex }} http-request set-var(req.base) base,regsub(:[0-9]+/,/) http-request set-var(req.backend) var(req.base),map_beg({{ $fgroup.HTTPFrontsMap.MatchFile }},_nomatch) {{- else }} @@ -299,15 +298,15 @@ frontend _front_http {{- end }} {{- /*------------------------------------*/}} -{{- if $hasredirect }} -{{- if $fgroup.RedirectMap.HasRegex }} +{{- if $fgroup.HasHTTPSRedir }} +{{- if $fgroup.HTTPSRedirMap.HasRegex }} http-request set-var(req.redir) - {{- "" }} var(req.base),map_beg({{ $fgroup.RedirectMap.MatchFile }},_nomatch) + {{- "" }} var(req.base),map_beg({{ $fgroup.HTTPSRedirMap.MatchFile }},_nomatch) redirect scheme https if { var(req.redir) yes } redirect scheme https if { var(req.redir) _nomatch } - {{- "" }} { var(req.base),map_reg({{ $fgroup.RedirectMap.RegexFile }},_nomatch) yes } + {{- "" }} { var(req.base),map_reg({{ $fgroup.HTTPSRedirMap.RegexFile }},_nomatch) yes } {{- else }} - redirect scheme https if { var(req.base),map_beg({{ $fgroup.RedirectMap.MatchFile }},_nomatch) yes } + redirect scheme https if { var(req.base),map_beg({{ $fgroup.HTTPSRedirMap.MatchFile }},_nomatch) yes } {{- end }} {{- end }} From 79f2f263ff95c786c60400c541d6e19a1fd38dfb Mon Sep 17 00:00:00 2001 From: Joao Morais Date: Sun, 12 May 2019 16:17:41 -0300 Subject: [PATCH 07/30] Add app-root frontend annotation --- pkg/haproxy/config.go | 14 +++ pkg/haproxy/instance_test.go | 119 +++++++++++++++++++++-- pkg/haproxy/types/frontend.go | 5 + pkg/haproxy/types/types.go | 2 + rootfs/etc/haproxy/template/haproxy.tmpl | 32 +++++- 5 files changed, 161 insertions(+), 11 deletions(-) diff --git a/pkg/haproxy/config.go b/pkg/haproxy/config.go index 1594fabff..34be2bd31 100644 --- a/pkg/haproxy/config.go +++ b/pkg/haproxy/config.go @@ -188,6 +188,8 @@ func (c *config) FindUserlist(name string) *hatypes.Userlist { } func (c *config) BuildFrontendGroup() (*hatypes.FrontendGroup, error) { + // tested thanks to instance_test templating tests + // ideas to make a nice test or a nice refactor are welcome if len(c.hosts) == 0 { return nil, fmt.Errorf("cannot create frontends without hosts") } @@ -198,6 +200,7 @@ func (c *config) BuildFrontendGroup() (*hatypes.FrontendGroup, error) { HasSSLPassthrough: len(sslpassthrough) > 0, Maps: fgroupMaps, HTTPFrontsMap: fgroupMaps.AddMap(c.mapsDir + "/http_front.map"), + HTTPRootRedirMap: fgroupMaps.AddMap(c.mapsDir + "/http_root_redir.map"), HTTPSRedirMap: fgroupMaps.AddMap(c.mapsDir + "/https_redir.map"), SSLPassthroughMap: fgroupMaps.AddMap(c.mapsDir + "/sslpassthrough.map"), } @@ -246,6 +249,7 @@ func (c *config) BuildFrontendGroup() (*hatypes.FrontendGroup, error) { mapsPrefix := c.mapsDir + "/" + frontend.Name frontend.Maps = hatypes.CreateMaps() frontend.HostBackendsMap = frontend.Maps.AddMap(mapsPrefix + "_host.map") + frontend.RootRedirMap = frontend.Maps.AddMap(mapsPrefix + "_root_redir.map") frontend.SNIBackendsMap = frontend.Maps.AddMap(mapsPrefix + "_sni.map") frontend.TLSInvalidCrtErrorList = frontend.Maps.AddMap(mapsPrefix + "_inv_crt.list") frontend.TLSInvalidCrtErrorPagesMap = frontend.Maps.AddMap(mapsPrefix + "_inv_crt_redir.map") @@ -257,6 +261,10 @@ func (c *config) BuildFrontendGroup() (*hatypes.FrontendGroup, error) { bind.UseServerList = bind.Maps.AddMap(c.mapsDir + "/" + bind.Name + ".list") } } + // Some maps use yes/no answers instead of a list with found/missing keys + // This approach avoid overlap: + // 1. match with path_beg/map_beg, /path has a feature and a declared /path/sub doesn't have + // 2. *.host.domain wildcard/alias/alias-regex has a feature and a declared sub.host.domain doesn't have yesno := map[bool]string{true: "yes", false: "no"} for _, sslpassHost := range sslpassthrough { rootPath := sslpassHost.FindPath("/") @@ -309,6 +317,12 @@ func (c *config) BuildFrontendGroup() (*hatypes.FrontendGroup, error) { } } } + // TODO wildcard/alias/alias-regex hostname can overlap + // a configured domain which doesn't have rootRedirect + if host.RootRedirect != "" { + fgroup.HTTPRootRedirMap.AppendHostname(host.Hostname, host.RootRedirect) + f.RootRedirMap.AppendHostname(host.Hostname, host.RootRedirect) + } } for _, bind := range f.Binds { for _, host := range bind.Hosts { diff --git a/pkg/haproxy/instance_test.go b/pkg/haproxy/instance_test.go index 977dcb352..6002bc971 100644 --- a/pkg/haproxy/instance_test.go +++ b/pkg/haproxy/instance_test.go @@ -143,7 +143,7 @@ frontend _front_http bind :80 http-request set-var(req.base) base,regsub(:[0-9]+/,/) http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/http_front.map,_nomatch) - redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/https_redir.map,_nomatch) yes } + http-request redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/https_redir.map,_nomatch) yes } use_backend %[var(req.backend)] unless { var(req.backend) _nomatch } use_backend d1_app_8080 frontend _front001 @@ -216,7 +216,7 @@ frontend _front_http bind :80 http-request set-var(req.base) base,regsub(:[0-9]+/,/) http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/http_front.map,_nomatch) - redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/https_redir.map,_nomatch) yes } + http-request redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/https_redir.map,_nomatch) yes } use_backend %[var(req.backend)] unless { var(req.backend) _nomatch } default_backend _default_backend frontend _front001 @@ -305,7 +305,7 @@ frontend _front_http bind :80 http-request set-var(req.base) base,regsub(:[0-9]+/,/) http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/http_front.map,_nomatch) - redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/https_redir.map,_nomatch) yes } + http-request redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/https_redir.map,_nomatch) yes } use_backend %[var(req.backend)] unless { var(req.backend) _nomatch } default_backend _default_backend frontend _front001 @@ -450,7 +450,7 @@ frontend _front_http bind :80 http-request set-var(req.base) base,regsub(:[0-9]+/,/) http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/http_front.map,_nomatch) - redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/https_redir.map,_nomatch) yes } + http-request redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/https_redir.map,_nomatch) yes } use_backend %[var(req.backend)] unless { var(req.backend) _nomatch } default_backend _default_backend frontend _front001 @@ -613,7 +613,7 @@ frontend _front_http bind :80 http-request set-var(req.base) base,regsub(:[0-9]+/,/) http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/http_front.map,_nomatch) - redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/https_redir.map,_nomatch) yes } + http-request redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/https_redir.map,_nomatch) yes } use_backend %[var(req.backend)] unless { var(req.backend) _nomatch } default_backend _default_backend frontend _front001 @@ -699,7 +699,7 @@ frontend _front_http bind :80 http-request set-var(req.base) base,regsub(:[0-9]+/,/) http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/http_front.map,_nomatch) - redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/https_redir.map,_nomatch) yes } + http-request redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/https_redir.map,_nomatch) yes } use_backend %[var(req.backend)] unless { var(req.backend) _nomatch } default_backend _error404`) @@ -715,6 +715,90 @@ d3.local/ no`) c.logger.CompareLogging(defaultLogging) } +func TestInstanceRootRedirect(t *testing.T) { + c := setup(t) + defer c.teardown() + + c.configGlobal() + + var h *hatypes.Host + var b *hatypes.Backend + + b = c.config.AcquireBackend("d1", "app", 8080) + b.Endpoints = []*hatypes.Endpoint{endpointS1} + b.SSLRedirect = false + h = c.config.AcquireHost("d1.local") + h.AddPath(b, "/") + h.RootRedirect = "/app" + + b = c.config.AcquireBackend("d2", "app", 8080) + b.Endpoints = []*hatypes.Endpoint{endpointS21} + b.SSLRedirect = true + h = c.config.AcquireHost("d2.local") + h.AddPath(b, "/app1") + h.AddPath(b, "/app2") + h.RootRedirect = "/app1" + + c.instance.Update() + + c.checkConfig(` +backend d1_app_8080 + mode http + server s1 172.17.0.11:8080 weight 100 +backend d2_app_8080 + mode http + server s21 172.17.0.121:8080 weight 100 +backend _error404 + mode http + errorfile 400 /usr/local/etc/haproxy/errors/404.http + http-request deny deny_status 400`, ` +frontend _front_http + mode http + bind :80 + http-request set-var(req.base) base,regsub(:[0-9]+/,/) + http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/http_front.map,_nomatch) + http-request redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/https_redir.map,_nomatch) yes } + http-request set-var(req.host) hdr(host),lower,regsub(:[0-9]+/,/) + http-request set-var(req.rootredir) var(req.host),map(/etc/haproxy/maps/http_root_redir.map,_nomatch) + http-request redirect location %[var(req.rootredir)] if { path / } !{ var(req.rootredir) _nomatch } + use_backend %[var(req.backend)] unless { var(req.backend) _nomatch } + default_backend _error404 +frontend _front001 + mode http + bind :443 ssl alpn h2,http/1.1 crt /var/haproxy/ssl/certs/default.pem + http-request set-var(req.hostbackend) base,lower,regsub(:[0-9]+/,/),map_beg(/etc/haproxy/maps/_front001_host.map,_nomatch) + http-request set-var(req.host) hdr(host),lower,regsub(:[0-9]+/,/) + http-request set-var(req.rootredir) var(req.host),map(/etc/haproxy/maps/_front001_root_redir.map,_nomatch) + http-request redirect location %[var(req.rootredir)] if { path / } !{ var(req.rootredir) _nomatch } + use_backend %[var(req.hostbackend)] unless { var(req.hostbackend) _nomatch } + default_backend _error404 +`) + + c.checkMap("http_front.map", ` +d1.local/ d1_app_8080 +`) + c.checkMap("https_redir.map", ` +d1.local/ no +d2.local/app2 yes +d2.local/app1 yes +`) + c.checkMap("http_root_redir.map", ` +d1.local /app +d2.local /app1 +`) + c.checkMap("_front001_host.map", ` +d1.local/ d1_app_8080 +d2.local/app2 d2_app_8080 +d2.local/app1 d2_app_8080 +`) + c.checkMap("_front001_root_redir.map", ` +d1.local /app +d2.local /app1 +`) + + c.logger.CompareLogging(defaultLogging) +} + func TestInstanceWildcardHostname(t *testing.T) { c := setup(t) defer c.teardown() @@ -743,6 +827,7 @@ func TestInstanceWildcardHostname(t *testing.T) { b.SSLRedirect = false h = c.config.AcquireHost("*.d2.local") h.AddPath(b, "/") + h.RootRedirect = "/app" h.Timeout.Client = "10s" c.instance.Update() @@ -788,8 +873,12 @@ frontend _front_http http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/http_front.map,_nomatch) http-request set-var(req.backend) var(req.base),map_reg(/etc/haproxy/maps/http_front_regex.map,_nomatch) if { var(req.backend) _nomatch } http-request set-var(req.redir) var(req.base),map_beg(/etc/haproxy/maps/https_redir.map,_nomatch) - redirect scheme https if { var(req.redir) yes } - redirect scheme https if { var(req.redir) _nomatch } { var(req.base),map_reg(/etc/haproxy/maps/https_redir_regex.map,_nomatch) yes } + http-request redirect scheme https if { var(req.redir) yes } + http-request redirect scheme https if { var(req.redir) _nomatch } { var(req.base),map_reg(/etc/haproxy/maps/https_redir_regex.map,_nomatch) yes } + http-request set-var(req.host) hdr(host),lower,regsub(:[0-9]+/,/) + http-request set-var(req.rootredir) var(req.host),map(/etc/haproxy/maps/http_root_redir.map,_nomatch) + http-request set-var(req.rootredir) var(req.host),map_reg(/etc/haproxy/maps/http_root_redir_regex.map,_nomatch) if { var(req.rootredir) _nomatch } + http-request redirect location %[var(req.rootredir)] if { path / } !{ var(req.rootredir) _nomatch } use_backend %[var(req.backend)] unless { var(req.backend) _nomatch } default_backend _error404 frontend _front001 @@ -821,6 +910,10 @@ frontend _front002 http-request set-var(req.base) base,lower,regsub(:[0-9]+/,/) http-request set-var(req.hostbackend) var(req.base),map_beg(/etc/haproxy/maps/_front002_host.map,_nomatch) http-request set-var(req.hostbackend) var(req.base),map_reg(/etc/haproxy/maps/_front002_host_regex.map,_nomatch) if { var(req.hostbackend) _nomatch } + http-request set-var(req.host) hdr(host),lower,regsub(:[0-9]+/,/) + http-request set-var(req.rootredir) var(req.host),map(/etc/haproxy/maps/_front002_root_redir.map,_nomatch) + http-request set-var(req.rootredir) var(req.host),map_reg(/etc/haproxy/maps/_front002_root_redir_regex.map,_nomatch) if { var(req.rootredir) _nomatch } + http-request redirect location %[var(req.rootredir)] if { path / } !{ var(req.rootredir) _nomatch } use_backend %[var(req.hostbackend)] unless { var(req.hostbackend) _nomatch } default_backend _error404 `) @@ -853,6 +946,11 @@ d1.local/ yes ^[^.]+\.app\.d1\.local/ yes ^[^.]+\.sub\.d1\.local/ yes ^[^.]+\.d2\.local/ no +`) + c.checkMap("http_root_redir.map", ` +`) + c.checkMap("http_root_redir_regex.map", ` +^[^.]+\.d2\.local$ /app `) c.checkMap("_front001_host.map", ` d1.local/ d1_app_8080 @@ -879,6 +977,11 @@ d1.local/ d1_app_8080 `) c.checkMap("_front002_host_regex.map", ` ^[^.]+\.d2\.local/ d2_app_8080 +`) + c.checkMap("_front002_root_redir.map", ` +`) + c.checkMap("_front002_root_redir_regex.map", ` +^[^.]+\.d2\.local$ /app `) c.logger.CompareLogging(defaultLogging) diff --git a/pkg/haproxy/types/frontend.go b/pkg/haproxy/types/frontend.go index 7b0632414..b5dd4f14c 100644 --- a/pkg/haproxy/types/frontend.go +++ b/pkg/haproxy/types/frontend.go @@ -54,6 +54,11 @@ func (hm *HostsMap) HasRegex() bool { return len(hm.Regex) > 0 } +// HasHost ... +func (hm *HostsMap) HasHost() bool { + return len(hm.Regex) > 0 || len(hm.Match) > 0 +} + // CreateMaps ... func CreateMaps() *HostsMaps { return &HostsMaps{} diff --git a/pkg/haproxy/types/types.go b/pkg/haproxy/types/types.go index 05b09c908..2c2863767 100644 --- a/pkg/haproxy/types/types.go +++ b/pkg/haproxy/types/types.go @@ -111,6 +111,7 @@ type FrontendGroup struct { // Maps *HostsMaps HTTPFrontsMap *HostsMap + HTTPRootRedirMap *HostsMap HTTPSRedirMap *HostsMap SSLPassthroughMap *HostsMap } @@ -125,6 +126,7 @@ type Frontend struct { // Maps *HostsMaps HostBackendsMap *HostsMap + RootRedirMap *HostsMap SNIBackendsMap *HostsMap TLSInvalidCrtErrorList *HostsMap TLSInvalidCrtErrorPagesMap *HostsMap diff --git a/rootfs/etc/haproxy/template/haproxy.tmpl b/rootfs/etc/haproxy/template/haproxy.tmpl index b88929ce5..03dcb408b 100644 --- a/rootfs/etc/haproxy/template/haproxy.tmpl +++ b/rootfs/etc/haproxy/template/haproxy.tmpl @@ -291,6 +291,8 @@ frontend _front_http {{- else }} http-request set-var(req.backend) base,regsub(:[0-9]+/,/),map_beg({{ $fgroup.HTTPFrontsMap.MatchFile }},_nomatch) {{- end }} + +{{- /*------------------------------------*/}} {{- if $fgroup.HTTPFrontsMap.HasRegex }} http-request set-var(req.backend) {{- "" }} var(req.base),map_reg({{ $fgroup.HTTPFrontsMap.RegexFile }},_nomatch) @@ -302,14 +304,26 @@ frontend _front_http {{- if $fgroup.HTTPSRedirMap.HasRegex }} http-request set-var(req.redir) {{- "" }} var(req.base),map_beg({{ $fgroup.HTTPSRedirMap.MatchFile }},_nomatch) - redirect scheme https if { var(req.redir) yes } - redirect scheme https if { var(req.redir) _nomatch } + http-request redirect scheme https if { var(req.redir) yes } + http-request redirect scheme https if { var(req.redir) _nomatch } {{- "" }} { var(req.base),map_reg({{ $fgroup.HTTPSRedirMap.RegexFile }},_nomatch) yes } {{- else }} - redirect scheme https if { var(req.base),map_beg({{ $fgroup.HTTPSRedirMap.MatchFile }},_nomatch) yes } + http-request redirect scheme https if { var(req.base),map_beg({{ $fgroup.HTTPSRedirMap.MatchFile }},_nomatch) yes } {{- end }} {{- end }} +{{- /*------------------------------------*/}} +{{- if $fgroup.HTTPRootRedirMap.HasHost }} + http-request set-var(req.host) hdr(host),lower,regsub(:[0-9]+/,/) + http-request set-var(req.rootredir) + {{- "" }} var(req.host),map({{ $fgroup.HTTPRootRedirMap.MatchFile }},_nomatch) +{{- if $fgroup.HTTPRootRedirMap.HasRegex }} + http-request set-var(req.rootredir) + {{- "" }} var(req.host),map_reg({{ $fgroup.HTTPRootRedirMap.RegexFile }},_nomatch) if { var(req.rootredir) _nomatch } +{{- end }} + http-request redirect location %[var(req.rootredir)] if { path / } !{ var(req.rootredir) _nomatch } +{{- end }} + {{- /*------------------------------------*/}} use_backend %[var(req.backend)] unless { var(req.backend) _nomatch } @@ -361,6 +375,18 @@ frontend {{ $frontend.Name }} {{- "" }} if { var(req.hostbackend) _nomatch } {{- end }} +{{- /*------------------------------------*/}} +{{- if $frontend.RootRedirMap.HasHost }} + http-request set-var(req.host) hdr(host),lower,regsub(:[0-9]+/,/) + http-request set-var(req.rootredir) + {{- "" }} var(req.host),map({{ $frontend.RootRedirMap.MatchFile }},_nomatch) +{{- if $frontend.RootRedirMap.HasRegex }} + http-request set-var(req.rootredir) + {{- "" }} var(req.host),map_reg({{ $frontend.RootRedirMap.RegexFile }},_nomatch) if { var(req.rootredir) _nomatch } +{{- end }} + http-request redirect location %[var(req.rootredir)] if { path / } !{ var(req.rootredir) _nomatch } +{{- end }} + {{- /*------------------------------------*/}} {{- if $frontend.HasVarNamespace }} http-request set-var(txn.namespace) From d50349a8211619c978d64b9f41cb99c883140b2d Mon Sep 17 00:00:00 2001 From: Joao Morais Date: Sun, 12 May 2019 19:05:34 -0300 Subject: [PATCH 08/30] Remove HTTPSRedir check HTTP redirect is always configured on HTTP frontend, so removing var and template checks --- pkg/haproxy/config.go | 6 +----- pkg/haproxy/instance_test.go | 22 ++++++++++++---------- pkg/haproxy/types/types.go | 1 - rootfs/etc/haproxy/template/haproxy.tmpl | 20 ++++++-------------- 4 files changed, 19 insertions(+), 30 deletions(-) diff --git a/pkg/haproxy/config.go b/pkg/haproxy/config.go index 34be2bd31..31db94575 100644 --- a/pkg/haproxy/config.go +++ b/pkg/haproxy/config.go @@ -275,8 +275,6 @@ func (c *config) BuildFrontendGroup() (*hatypes.FrontendGroup, error) { fgroup.HTTPSRedirMap.AppendHostname(sslpassHost.Hostname+"/", yesno[sslpassHost.HTTPPassthroughBackend == nil]) if sslpassHost.HTTPPassthroughBackend != nil { fgroup.HTTPFrontsMap.AppendHostname(sslpassHost.Hostname+"/", sslpassHost.HTTPPassthroughBackend.ID) - } else { - fgroup.HasHTTPSRedir = true } } for _, f := range frontends { @@ -291,9 +289,7 @@ func (c *config) BuildFrontendGroup() (*hatypes.FrontendGroup, error) { } else { f.HostBackendsMap.AppendHostname(base, back) } - if path.Backend.SSLRedirect { - fgroup.HasHTTPSRedir = true - } else { + if !path.Backend.SSLRedirect { fgroup.HTTPFrontsMap.AppendHostname(base, back) } var ns string diff --git a/pkg/haproxy/instance_test.go b/pkg/haproxy/instance_test.go index 6002bc971..de482bb71 100644 --- a/pkg/haproxy/instance_test.go +++ b/pkg/haproxy/instance_test.go @@ -78,7 +78,9 @@ backend _error496 frontend _front_http mode http bind :80 - http-request set-var(req.backend) base,regsub(:[0-9]+/,/),map_beg(/etc/haproxy/maps/http_front.map,_nomatch) + http-request set-var(req.base) base,regsub(:[0-9]+/,/) + http-request redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/https_redir.map,_nomatch) yes } + http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/http_front.map,_nomatch) use_backend %%[var(req.backend)] unless { var(req.backend) _nomatch } default_backend _error404 frontend _front001 @@ -142,8 +144,8 @@ frontend _front_http mode http bind :80 http-request set-var(req.base) base,regsub(:[0-9]+/,/) - http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/http_front.map,_nomatch) http-request redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/https_redir.map,_nomatch) yes } + http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/http_front.map,_nomatch) use_backend %[var(req.backend)] unless { var(req.backend) _nomatch } use_backend d1_app_8080 frontend _front001 @@ -215,8 +217,8 @@ frontend _front_http mode http bind :80 http-request set-var(req.base) base,regsub(:[0-9]+/,/) - http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/http_front.map,_nomatch) http-request redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/https_redir.map,_nomatch) yes } + http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/http_front.map,_nomatch) use_backend %[var(req.backend)] unless { var(req.backend) _nomatch } default_backend _default_backend frontend _front001 @@ -304,8 +306,8 @@ frontend _front_http mode http bind :80 http-request set-var(req.base) base,regsub(:[0-9]+/,/) - http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/http_front.map,_nomatch) http-request redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/https_redir.map,_nomatch) yes } + http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/http_front.map,_nomatch) use_backend %[var(req.backend)] unless { var(req.backend) _nomatch } default_backend _default_backend frontend _front001 @@ -449,8 +451,8 @@ frontend _front_http mode http bind :80 http-request set-var(req.base) base,regsub(:[0-9]+/,/) - http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/http_front.map,_nomatch) http-request redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/https_redir.map,_nomatch) yes } + http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/http_front.map,_nomatch) use_backend %[var(req.backend)] unless { var(req.backend) _nomatch } default_backend _default_backend frontend _front001 @@ -612,8 +614,8 @@ frontend _front_http mode http bind :80 http-request set-var(req.base) base,regsub(:[0-9]+/,/) - http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/http_front.map,_nomatch) http-request redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/https_redir.map,_nomatch) yes } + http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/http_front.map,_nomatch) use_backend %[var(req.backend)] unless { var(req.backend) _nomatch } default_backend _default_backend frontend _front001 @@ -698,8 +700,8 @@ frontend _front_http mode http bind :80 http-request set-var(req.base) base,regsub(:[0-9]+/,/) - http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/http_front.map,_nomatch) http-request redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/https_redir.map,_nomatch) yes } + http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/http_front.map,_nomatch) use_backend %[var(req.backend)] unless { var(req.backend) _nomatch } default_backend _error404`) @@ -756,11 +758,11 @@ frontend _front_http mode http bind :80 http-request set-var(req.base) base,regsub(:[0-9]+/,/) - http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/http_front.map,_nomatch) http-request redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/https_redir.map,_nomatch) yes } http-request set-var(req.host) hdr(host),lower,regsub(:[0-9]+/,/) http-request set-var(req.rootredir) var(req.host),map(/etc/haproxy/maps/http_root_redir.map,_nomatch) http-request redirect location %[var(req.rootredir)] if { path / } !{ var(req.rootredir) _nomatch } + http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/http_front.map,_nomatch) use_backend %[var(req.backend)] unless { var(req.backend) _nomatch } default_backend _error404 frontend _front001 @@ -870,8 +872,6 @@ frontend _front_http mode http bind :80 http-request set-var(req.base) base,regsub(:[0-9]+/,/) - http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/http_front.map,_nomatch) - http-request set-var(req.backend) var(req.base),map_reg(/etc/haproxy/maps/http_front_regex.map,_nomatch) if { var(req.backend) _nomatch } http-request set-var(req.redir) var(req.base),map_beg(/etc/haproxy/maps/https_redir.map,_nomatch) http-request redirect scheme https if { var(req.redir) yes } http-request redirect scheme https if { var(req.redir) _nomatch } { var(req.base),map_reg(/etc/haproxy/maps/https_redir_regex.map,_nomatch) yes } @@ -879,6 +879,8 @@ frontend _front_http http-request set-var(req.rootredir) var(req.host),map(/etc/haproxy/maps/http_root_redir.map,_nomatch) http-request set-var(req.rootredir) var(req.host),map_reg(/etc/haproxy/maps/http_root_redir_regex.map,_nomatch) if { var(req.rootredir) _nomatch } http-request redirect location %[var(req.rootredir)] if { path / } !{ var(req.rootredir) _nomatch } + http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/http_front.map,_nomatch) + http-request set-var(req.backend) var(req.base),map_reg(/etc/haproxy/maps/http_front_regex.map,_nomatch) if { var(req.backend) _nomatch } use_backend %[var(req.backend)] unless { var(req.backend) _nomatch } default_backend _error404 frontend _front001 diff --git a/pkg/haproxy/types/types.go b/pkg/haproxy/types/types.go index 2c2863767..234b5599c 100644 --- a/pkg/haproxy/types/types.go +++ b/pkg/haproxy/types/types.go @@ -106,7 +106,6 @@ type HostsMaps struct { type FrontendGroup struct { Frontends []*Frontend // - HasHTTPSRedir bool HasSSLPassthrough bool // Maps *HostsMaps diff --git a/rootfs/etc/haproxy/template/haproxy.tmpl b/rootfs/etc/haproxy/template/haproxy.tmpl index 03dcb408b..0e38d29bc 100644 --- a/rootfs/etc/haproxy/template/haproxy.tmpl +++ b/rootfs/etc/haproxy/template/haproxy.tmpl @@ -285,22 +285,9 @@ frontend _front_http bind :80 {{- /*------------------------------------*/}} -{{- if or $fgroup.HasHTTPSRedir $fgroup.HTTPFrontsMap.HasRegex }} http-request set-var(req.base) base,regsub(:[0-9]+/,/) - http-request set-var(req.backend) var(req.base),map_beg({{ $fgroup.HTTPFrontsMap.MatchFile }},_nomatch) -{{- else }} - http-request set-var(req.backend) base,regsub(:[0-9]+/,/),map_beg({{ $fgroup.HTTPFrontsMap.MatchFile }},_nomatch) -{{- end }} {{- /*------------------------------------*/}} -{{- if $fgroup.HTTPFrontsMap.HasRegex }} - http-request set-var(req.backend) - {{- "" }} var(req.base),map_reg({{ $fgroup.HTTPFrontsMap.RegexFile }},_nomatch) - {{- "" }} if { var(req.backend) _nomatch } -{{- end }} - -{{- /*------------------------------------*/}} -{{- if $fgroup.HasHTTPSRedir }} {{- if $fgroup.HTTPSRedirMap.HasRegex }} http-request set-var(req.redir) {{- "" }} var(req.base),map_beg({{ $fgroup.HTTPSRedirMap.MatchFile }},_nomatch) @@ -310,7 +297,6 @@ frontend _front_http {{- else }} http-request redirect scheme https if { var(req.base),map_beg({{ $fgroup.HTTPSRedirMap.MatchFile }},_nomatch) yes } {{- end }} -{{- end }} {{- /*------------------------------------*/}} {{- if $fgroup.HTTPRootRedirMap.HasHost }} @@ -325,6 +311,12 @@ frontend _front_http {{- end }} {{- /*------------------------------------*/}} + http-request set-var(req.backend) var(req.base),map_beg({{ $fgroup.HTTPFrontsMap.MatchFile }},_nomatch) +{{- if $fgroup.HTTPFrontsMap.HasRegex }} + http-request set-var(req.backend) + {{- "" }} var(req.base),map_reg({{ $fgroup.HTTPFrontsMap.RegexFile }},_nomatch) + {{- "" }} if { var(req.backend) _nomatch } +{{- end }} use_backend %[var(req.backend)] unless { var(req.backend) _nomatch } {{- template "defaultbackend" map $cfg }} From d8c6e149261ca3a5f76d5927ab3b04d00e3d9c21 Mon Sep 17 00:00:00 2001 From: Joao Morais Date: Sun, 12 May 2019 19:06:18 -0300 Subject: [PATCH 09/30] Including HAProxy warnings on start --- pkg/haproxy/instance.go | 6 +++--- pkg/haproxy/instance_test.go | 2 -- rootfs/etc/haproxy/template/haproxy.tmpl | 1 - 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/pkg/haproxy/instance.go b/pkg/haproxy/instance.go index 6bd5fde55..6f4f80e46 100644 --- a/pkg/haproxy/instance.go +++ b/pkg/haproxy/instance.go @@ -141,7 +141,6 @@ func (i *instance) Update() { i.logger.Error("error reloading server:\n%v", err) return } - i.clearConfig() i.logger.Info("HAProxy successfully reloaded") } @@ -164,8 +163,9 @@ func (i *instance) reload() error { } out, err := exec.Command(i.options.ReloadCmd, i.options.ReloadStrategy, i.options.HAProxyConfigFile).CombinedOutput() if len(out) > 0 { - return fmt.Errorf(string(out)) - } else if err != nil { + i.logger.Warn("output from haproxy:\n%v", string(out)) + } + if err != nil { return err } return nil diff --git a/pkg/haproxy/instance_test.go b/pkg/haproxy/instance_test.go index de482bb71..cf20d8186 100644 --- a/pkg/haproxy/instance_test.go +++ b/pkg/haproxy/instance_test.go @@ -45,7 +45,6 @@ func TestInstanceEmpty(t *testing.T) { template := ` global daemon - quiet stats socket %s level admin expose-fd listeners maxconn 0 lua-load /usr/local/etc/haproxy/lua/send-response.lua @@ -1080,7 +1079,6 @@ func (c *testConfig) configGlobal() { var globalConfig = ` global daemon - quiet stats socket /var/run/haproxy.sock level admin expose-fd listeners maxconn 2000 hard-stop-after 15m diff --git a/rootfs/etc/haproxy/template/haproxy.tmpl b/rootfs/etc/haproxy/template/haproxy.tmpl index 0e38d29bc..51753e72f 100644 --- a/rootfs/etc/haproxy/template/haproxy.tmpl +++ b/rootfs/etc/haproxy/template/haproxy.tmpl @@ -10,7 +10,6 @@ {{- $global := $cfg.Global }} global daemon - quiet {{- if gt $global.Procs.Nbproc 1 }} nbproc {{ $global.Procs.Nbproc }} {{- end }} From a5e5c46955e570ce5e41da05bcea5e37f8b2b18c Mon Sep 17 00:00:00 2001 From: Joao Morais Date: Sun, 12 May 2019 22:00:16 -0300 Subject: [PATCH 10/30] Add prefix on global maps --- pkg/haproxy/config.go | 8 +-- pkg/haproxy/instance_test.go | 96 ++++++++++++++++++------------------ 2 files changed, 53 insertions(+), 51 deletions(-) diff --git a/pkg/haproxy/config.go b/pkg/haproxy/config.go index 31db94575..e34b7b8c8 100644 --- a/pkg/haproxy/config.go +++ b/pkg/haproxy/config.go @@ -199,10 +199,10 @@ func (c *config) BuildFrontendGroup() (*hatypes.FrontendGroup, error) { Frontends: frontends, HasSSLPassthrough: len(sslpassthrough) > 0, Maps: fgroupMaps, - HTTPFrontsMap: fgroupMaps.AddMap(c.mapsDir + "/http_front.map"), - HTTPRootRedirMap: fgroupMaps.AddMap(c.mapsDir + "/http_root_redir.map"), - HTTPSRedirMap: fgroupMaps.AddMap(c.mapsDir + "/https_redir.map"), - SSLPassthroughMap: fgroupMaps.AddMap(c.mapsDir + "/sslpassthrough.map"), + HTTPFrontsMap: fgroupMaps.AddMap(c.mapsDir + "/_global_http_front.map"), + HTTPRootRedirMap: fgroupMaps.AddMap(c.mapsDir + "/_global_http_root_redir.map"), + HTTPSRedirMap: fgroupMaps.AddMap(c.mapsDir + "/_global_https_redir.map"), + SSLPassthroughMap: fgroupMaps.AddMap(c.mapsDir + "/_global_sslpassthrough.map"), } if fgroup.HasTCPProxy() { // More than one HAProxy's frontend or bind, or using ssl-passthrough config, diff --git a/pkg/haproxy/instance_test.go b/pkg/haproxy/instance_test.go index cf20d8186..089c6f8f8 100644 --- a/pkg/haproxy/instance_test.go +++ b/pkg/haproxy/instance_test.go @@ -78,8 +78,8 @@ frontend _front_http mode http bind :80 http-request set-var(req.base) base,regsub(:[0-9]+/,/) - http-request redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/https_redir.map,_nomatch) yes } - http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/http_front.map,_nomatch) + http-request redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/_global_https_redir.map,_nomatch) yes } + http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/_global_http_front.map,_nomatch) use_backend %%[var(req.backend)] unless { var(req.backend) _nomatch } default_backend _error404 frontend _front001 @@ -94,8 +94,10 @@ frontend _front001 c.instance.Update() c.checkConfigFull(fmt.Sprintf(template, "--", "--", "--", "--")) - c.checkMap("http_front.map", ` + c.checkMap("_global_http_front.map", ` empty/ default_empty_8080`) + c.checkMap("_global_https_redir.map", ` +empty/ no`) c.checkMap("_front001_host.map", ` empty/ default_empty_8080`) @@ -143,8 +145,8 @@ frontend _front_http mode http bind :80 http-request set-var(req.base) base,regsub(:[0-9]+/,/) - http-request redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/https_redir.map,_nomatch) yes } - http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/http_front.map,_nomatch) + http-request redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/_global_https_redir.map,_nomatch) yes } + http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/_global_http_front.map,_nomatch) use_backend %[var(req.backend)] unless { var(req.backend) _nomatch } use_backend d1_app_8080 frontend _front001 @@ -157,9 +159,9 @@ frontend _front001 use_backend d1_app_8080 `) - c.checkMap("http_front.map", ` + c.checkMap("_global_http_front.map", ` `) - c.checkMap("https_redir.map", ` + c.checkMap("_global_https_redir.map", ` d2.local/app yes `) c.checkMap("_front001_k8s_ns.map", ` @@ -216,8 +218,8 @@ frontend _front_http mode http bind :80 http-request set-var(req.base) base,regsub(:[0-9]+/,/) - http-request redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/https_redir.map,_nomatch) yes } - http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/http_front.map,_nomatch) + http-request redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/_global_https_redir.map,_nomatch) yes } + http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/_global_http_front.map,_nomatch) use_backend %[var(req.backend)] unless { var(req.backend) _nomatch } default_backend _default_backend frontend _front001 @@ -230,9 +232,9 @@ frontend _front001 default_backend _default_backend `) - c.checkMap("http_front.map", ` + c.checkMap("_global_http_front.map", ` `) - c.checkMap("https_redir.map", ` + c.checkMap("_global_https_redir.map", ` d1.local/ yes d2.local/app yes `) @@ -305,8 +307,8 @@ frontend _front_http mode http bind :80 http-request set-var(req.base) base,regsub(:[0-9]+/,/) - http-request redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/https_redir.map,_nomatch) yes } - http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/http_front.map,_nomatch) + http-request redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/_global_https_redir.map,_nomatch) yes } + http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/_global_http_front.map,_nomatch) use_backend %[var(req.backend)] unless { var(req.backend) _nomatch } default_backend _default_backend frontend _front001 @@ -338,9 +340,9 @@ d1.local c.checkMap("_socket002.list", ` d2.local `) - c.checkMap("http_front.map", ` + c.checkMap("_global_http_front.map", ` `) - c.checkMap("https_redir.map", ` + c.checkMap("_global_https_redir.map", ` d1.local/ yes d2.local/ yes `) @@ -450,8 +452,8 @@ frontend _front_http mode http bind :80 http-request set-var(req.base) base,regsub(:[0-9]+/,/) - http-request redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/https_redir.map,_nomatch) yes } - http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/http_front.map,_nomatch) + http-request redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/_global_https_redir.map,_nomatch) yes } + http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/_global_http_front.map,_nomatch) use_backend %[var(req.backend)] unless { var(req.backend) _nomatch } default_backend _default_backend frontend _front001 @@ -505,11 +507,11 @@ d22.local d3.local d4.local `) - c.checkMap("http_front.map", ` + c.checkMap("_global_http_front.map", ` d3.local/ d_app_8080 d4.local/ d_app_8080 `) - c.checkMap("https_redir.map", ` + c.checkMap("_global_https_redir.map", ` d1.local/ yes d21.local/ yes d22.local/ yes @@ -613,8 +615,8 @@ frontend _front_http mode http bind :80 http-request set-var(req.base) base,regsub(:[0-9]+/,/) - http-request redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/https_redir.map,_nomatch) yes } - http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/http_front.map,_nomatch) + http-request redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/_global_https_redir.map,_nomatch) yes } + http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/_global_http_front.map,_nomatch) use_backend %[var(req.backend)] unless { var(req.backend) _nomatch } default_backend _default_backend frontend _front001 @@ -625,11 +627,11 @@ frontend _front001 default_backend _default_backend `) - c.checkMap("http_front.map", ` + c.checkMap("_global_http_front.map", ` d.local/sub d_app3_8080 d.local/app/sub d_app2_8080 `) - c.checkMap("https_redir.map", ` + c.checkMap("_global_https_redir.map", ` d.local/sub no d.local/app/sub no d.local/app yes @@ -692,24 +694,24 @@ listen _front__tls tcp-request inspect-delay 5s tcp-request content accept if { req.ssl_hello_type 1 } ## ssl-passthrough - tcp-request content set-var(req.backend) req.ssl_sni,lower,map(/etc/haproxy/maps/sslpassthrough.map,_nomatch) + tcp-request content set-var(req.backend) req.ssl_sni,lower,map(/etc/haproxy/maps/_global_sslpassthrough.map,_nomatch) use_backend %[var(req.backend)] unless { var(req.backend) _nomatch } # TODO default backend frontend _front_http mode http bind :80 http-request set-var(req.base) base,regsub(:[0-9]+/,/) - http-request redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/https_redir.map,_nomatch) yes } - http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/http_front.map,_nomatch) + http-request redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/_global_https_redir.map,_nomatch) yes } + http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/_global_http_front.map,_nomatch) use_backend %[var(req.backend)] unless { var(req.backend) _nomatch } default_backend _error404`) - c.checkMap("sslpassthrough.map", ` + c.checkMap("_global_sslpassthrough.map", ` d2.local d2_app_8080 d3.local d3_app-ssl_8443`) - c.checkMap("http_front.map", ` + c.checkMap("_global_http_front.map", ` d3.local/ d3_app-http_8080`) - c.checkMap("https_redir.map", ` + c.checkMap("_global_https_redir.map", ` d2.local/ yes d3.local/ no`) @@ -757,11 +759,11 @@ frontend _front_http mode http bind :80 http-request set-var(req.base) base,regsub(:[0-9]+/,/) - http-request redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/https_redir.map,_nomatch) yes } + http-request redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/_global_https_redir.map,_nomatch) yes } http-request set-var(req.host) hdr(host),lower,regsub(:[0-9]+/,/) - http-request set-var(req.rootredir) var(req.host),map(/etc/haproxy/maps/http_root_redir.map,_nomatch) + http-request set-var(req.rootredir) var(req.host),map(/etc/haproxy/maps/_global_http_root_redir.map,_nomatch) http-request redirect location %[var(req.rootredir)] if { path / } !{ var(req.rootredir) _nomatch } - http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/http_front.map,_nomatch) + http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/_global_http_front.map,_nomatch) use_backend %[var(req.backend)] unless { var(req.backend) _nomatch } default_backend _error404 frontend _front001 @@ -775,15 +777,15 @@ frontend _front001 default_backend _error404 `) - c.checkMap("http_front.map", ` + c.checkMap("_global_http_front.map", ` d1.local/ d1_app_8080 `) - c.checkMap("https_redir.map", ` + c.checkMap("_global_https_redir.map", ` d1.local/ no d2.local/app2 yes d2.local/app1 yes `) - c.checkMap("http_root_redir.map", ` + c.checkMap("_global_http_root_redir.map", ` d1.local /app d2.local /app1 `) @@ -871,15 +873,15 @@ frontend _front_http mode http bind :80 http-request set-var(req.base) base,regsub(:[0-9]+/,/) - http-request set-var(req.redir) var(req.base),map_beg(/etc/haproxy/maps/https_redir.map,_nomatch) + http-request set-var(req.redir) var(req.base),map_beg(/etc/haproxy/maps/_global_https_redir.map,_nomatch) http-request redirect scheme https if { var(req.redir) yes } - http-request redirect scheme https if { var(req.redir) _nomatch } { var(req.base),map_reg(/etc/haproxy/maps/https_redir_regex.map,_nomatch) yes } + http-request redirect scheme https if { var(req.redir) _nomatch } { var(req.base),map_reg(/etc/haproxy/maps/_global_https_redir_regex.map,_nomatch) yes } http-request set-var(req.host) hdr(host),lower,regsub(:[0-9]+/,/) - http-request set-var(req.rootredir) var(req.host),map(/etc/haproxy/maps/http_root_redir.map,_nomatch) - http-request set-var(req.rootredir) var(req.host),map_reg(/etc/haproxy/maps/http_root_redir_regex.map,_nomatch) if { var(req.rootredir) _nomatch } + http-request set-var(req.rootredir) var(req.host),map(/etc/haproxy/maps/_global_http_root_redir.map,_nomatch) + http-request set-var(req.rootredir) var(req.host),map_reg(/etc/haproxy/maps/_global_http_root_redir_regex.map,_nomatch) if { var(req.rootredir) _nomatch } http-request redirect location %[var(req.rootredir)] if { path / } !{ var(req.rootredir) _nomatch } - http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/http_front.map,_nomatch) - http-request set-var(req.backend) var(req.base),map_reg(/etc/haproxy/maps/http_front_regex.map,_nomatch) if { var(req.backend) _nomatch } + http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/_global_http_front.map,_nomatch) + http-request set-var(req.backend) var(req.base),map_reg(/etc/haproxy/maps/_global_http_front_regex.map,_nomatch) if { var(req.backend) _nomatch } use_backend %[var(req.backend)] unless { var(req.backend) _nomatch } default_backend _error404 frontend _front001 @@ -935,22 +937,22 @@ d1.local c.checkMap("_socket003_regex.list", ` ^[^.]+\.d2\.local$ `) - c.checkMap("http_front.map", ` + c.checkMap("_global_http_front.map", ` `) - c.checkMap("http_front_regex.map", ` + c.checkMap("_global_http_front_regex.map", ` ^[^.]+\.d2\.local/ d2_app_8080 `) - c.checkMap("https_redir.map", ` + c.checkMap("_global_https_redir.map", ` d1.local/ yes `) - c.checkMap("https_redir_regex.map", ` + c.checkMap("_global_https_redir_regex.map", ` ^[^.]+\.app\.d1\.local/ yes ^[^.]+\.sub\.d1\.local/ yes ^[^.]+\.d2\.local/ no `) - c.checkMap("http_root_redir.map", ` + c.checkMap("_global_http_root_redir.map", ` `) - c.checkMap("http_root_redir_regex.map", ` + c.checkMap("_global_http_root_redir_regex.map", ` ^[^.]+\.d2\.local$ /app `) c.checkMap("_front001_host.map", ` From 71495a30c70918efbcac1354eda7a0e6e7604c7f Mon Sep 17 00:00:00 2001 From: Joao Morais Date: Mon, 13 May 2019 19:28:29 -0300 Subject: [PATCH 11/30] Add alias and alias-regex --- pkg/haproxy/config.go | 12 +++++ pkg/haproxy/instance_test.go | 87 +++++++++++++++++++++++++++++++++++ pkg/haproxy/types/frontend.go | 27 ++++++++++- 3 files changed, 125 insertions(+), 1 deletion(-) diff --git a/pkg/haproxy/config.go b/pkg/haproxy/config.go index e34b7b8c8..469a0962c 100644 --- a/pkg/haproxy/config.go +++ b/pkg/haproxy/config.go @@ -283,11 +283,23 @@ func (c *config) BuildFrontendGroup() (*hatypes.FrontendGroup, error) { // TODO use only root path if all uri has the same conf fgroup.HTTPSRedirMap.AppendHostname(host.Hostname+path.Path, yesno[path.Backend.SSLRedirect]) base := host.Hostname + path.Path + var aliasName, aliasRegex string + // TODO warn in logs about ignoring alias name due to hostname colision + if host.Alias.AliasName != "" && c.FindHost(host.Alias.AliasName) == nil { + aliasName = host.Alias.AliasName + path.Path + } + if host.Alias.AliasRegex != "" { + aliasRegex = host.Alias.AliasRegex + path.Path + } back := path.BackendID if host.HasTLSAuth() { f.SNIBackendsMap.AppendHostname(base, back) + f.SNIBackendsMap.AppendAliasName(aliasName, back) + f.SNIBackendsMap.AppendAliasRegex(aliasRegex, back) } else { f.HostBackendsMap.AppendHostname(base, back) + f.HostBackendsMap.AppendAliasName(aliasName, back) + f.HostBackendsMap.AppendAliasRegex(aliasRegex, back) } if !path.Backend.SSLRedirect { fgroup.HTTPFrontsMap.AppendHostname(base, back) diff --git a/pkg/haproxy/instance_test.go b/pkg/haproxy/instance_test.go index 089c6f8f8..f8e7cb35d 100644 --- a/pkg/haproxy/instance_test.go +++ b/pkg/haproxy/instance_test.go @@ -802,6 +802,93 @@ d2.local /app1 c.logger.CompareLogging(defaultLogging) } +func TestInstanceAlias(t *testing.T) { + c := setup(t) + defer c.teardown() + + c.configGlobal() + + var h *hatypes.Host + var b *hatypes.Backend + + b = c.config.AcquireBackend("d1", "app", 8080) + b.Endpoints = []*hatypes.Endpoint{endpointS1} + h = c.config.AcquireHost("d1.local") + h.AddPath(b, "/") + h.Alias.AliasName = "*.d1.local" + + b = c.config.AcquireBackend("d2", "app", 8080) + b.Endpoints = []*hatypes.Endpoint{endpointS21} + h = c.config.AcquireHost("d2.local") + h.AddPath(b, "/") + h.Alias.AliasName = "sub.d2.local" + h.Alias.AliasRegex = "^[a-z]+\\.d2\\.local$" + + b = c.config.AcquireBackend("d3", "app", 8080) + b.Endpoints = []*hatypes.Endpoint{endpointS31} + h = c.config.AcquireHost("d3.local") + h.AddPath(b, "/") + h.Alias.AliasRegex = ".*d3\\.local$" + + c.instance.Update() + c.checkConfig(` +backend d1_app_8080 + mode http + server s1 172.17.0.11:8080 weight 100 +backend d2_app_8080 + mode http + server s21 172.17.0.121:8080 weight 100 +backend d3_app_8080 + mode http + server s31 172.17.0.131:8080 weight 100 +backend _error404 + mode http + errorfile 400 /usr/local/etc/haproxy/errors/404.http + http-request deny deny_status 400`, ` +frontend _front_http + mode http + bind :80 + http-request set-var(req.base) base,regsub(:[0-9]+/,/) + http-request redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/_global_https_redir.map,_nomatch) yes } + http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/_global_http_front.map,_nomatch) + use_backend %[var(req.backend)] unless { var(req.backend) _nomatch } + default_backend _error404 +frontend _front001 + mode http + bind :443 ssl alpn h2,http/1.1 crt /var/haproxy/ssl/certs/default.pem + http-request set-var(req.base) base,lower,regsub(:[0-9]+/,/) + http-request set-var(req.hostbackend) var(req.base),map_beg(/etc/haproxy/maps/_front001_host.map,_nomatch) + http-request set-var(req.hostbackend) var(req.base),map_reg(/etc/haproxy/maps/_front001_host_regex.map,_nomatch) if { var(req.hostbackend) _nomatch } + use_backend %[var(req.hostbackend)] unless { var(req.hostbackend) _nomatch } + default_backend _error404 +`) + + c.checkMap("_global_https_redir.map", ` +d1.local/ no +d2.local/ no +d3.local/ no +`) + c.checkMap("_global_http_front.map", ` +d1.local/ d1_app_8080 +d2.local/ d2_app_8080 +d3.local/ d3_app_8080 +`) + c.checkMap("_front001_host.map", ` +d1.local/ d1_app_8080 +d2.local/ d2_app_8080 +sub.d2.local/ d2_app_8080 +d3.local/ d3_app_8080 +`) + c.checkMap("_front001_host_regex.map", ` +^[^.]+\.d1\.local/ d1_app_8080 +^[a-z]+\.d2\.local$/ d2_app_8080 +.*d3\.local$/ d3_app_8080 +`) + + c.logger.CompareLogging(defaultLogging) + +} + func TestInstanceWildcardHostname(t *testing.T) { c := setup(t) defer c.teardown() diff --git a/pkg/haproxy/types/frontend.go b/pkg/haproxy/types/frontend.go index b5dd4f14c..891b44939 100644 --- a/pkg/haproxy/types/frontend.go +++ b/pkg/haproxy/types/frontend.go @@ -27,11 +27,12 @@ import ( func (hm *HostsMap) AppendHostname(base, value string) { // always use case insensitive match base = strings.ToLower(base) + isHostnameOnly := !strings.Contains(base, "/") if strings.HasPrefix(base, "*.") { // *.example.local key := "^" + strings.Replace(base, ".", "\\.", -1) key = strings.Replace(key, "*", "[^.]+", 1) - if !strings.Contains(base, "/") { + if isHostnameOnly { // match eol if only the hostname is provided // if has /path, need to match the begining of the string, a la map_beg() converter key = key + "$" @@ -46,6 +47,30 @@ func (hm *HostsMap) AppendHostname(base, value string) { Key: base, Value: value, }) + // Hostnames are already in alphabetical order but Alias are not + // Sort only hostname maps which uses ebtree search via map converter + if isHostnameOnly { + sort.Slice(hm.Match, func(i, j int) bool { + return hm.Match[i].Key < hm.Match[j].Key + }) + } + } +} + +// AppendAliasName ... +func (hm *HostsMap) AppendAliasName(base, value string) { + if base != "" { + hm.AppendHostname(base, value) + } +} + +// AppendAliasRegex ... +func (hm *HostsMap) AppendAliasRegex(base, value string) { + if base != "" { + hm.Regex = append(hm.Regex, &HostsMapEntry{ + Key: base, + Value: value, + }) } } From 4bbd8abd7468d717babcfe4636a92718e7aadcc2 Mon Sep 17 00:00:00 2001 From: Joao Morais Date: Sat, 18 May 2019 15:53:15 -0300 Subject: [PATCH 12/30] Add TLS auth headers --- pkg/converters/ingress/annotations/global.go | 1 + pkg/converters/ingress/annotations/host.go | 1 - pkg/converters/ingress/annotations/updater.go | 1 + pkg/converters/ingress/types/annotations.go | 2 +- pkg/haproxy/config.go | 1 + pkg/haproxy/instance_test.go | 106 +++++++++++++++++- pkg/haproxy/types/types.go | 24 ++-- rootfs/etc/haproxy/template/haproxy.tmpl | 24 +++- 8 files changed, 142 insertions(+), 18 deletions(-) diff --git a/pkg/converters/ingress/annotations/global.go b/pkg/converters/ingress/annotations/global.go index 5b5d60828..a2b515d79 100644 --- a/pkg/converters/ingress/annotations/global.go +++ b/pkg/converters/ingress/annotations/global.go @@ -99,6 +99,7 @@ func (c *updater) buildGlobalSSL(d *globalData) { d.global.SSL.DHParam.DefaultMaxSize = d.config.SSLDHDefaultMaxSize d.global.SSL.Engine = d.config.SSLEngine d.global.SSL.ModeAsync = d.config.SSLModeAsync + d.global.SSL.HeadersPrefix = d.config.SSLHeadersPrefix } func (c *updater) buildGlobalModSecurity(d *globalData) { diff --git a/pkg/converters/ingress/annotations/host.go b/pkg/converters/ingress/annotations/host.go index bed6ace48..b0d45eb6c 100644 --- a/pkg/converters/ingress/annotations/host.go +++ b/pkg/converters/ingress/annotations/host.go @@ -29,7 +29,6 @@ func (c *updater) buildHostAuthTLS(d *hostData) { d.host.TLS.CAHash = cafile.SHA1Hash d.host.TLS.CAVerifyOptional = verify == "optional" || verify == "optional_no_ca" d.host.TLS.CAErrorPage = d.ann.AuthTLSErrorPage - d.host.TLS.AddCertHeader = d.ann.AuthTLSCertHeader } else { c.logger.Error("error building TLS auth config: %v", err) } diff --git a/pkg/converters/ingress/annotations/updater.go b/pkg/converters/ingress/annotations/updater.go index d3acd8aca..841044ff6 100644 --- a/pkg/converters/ingress/annotations/updater.go +++ b/pkg/converters/ingress/annotations/updater.go @@ -108,6 +108,7 @@ func (c *updater) UpdateBackendConfig(backend *hatypes.Backend, ann *ingtypes.Ba backend.MaxConnServer = ann.MaxconnServer backend.ProxyBodySize = ann.ProxyBodySize backend.SSLRedirect = ann.SSLRedirect + backend.SSL.AddCertHeader = ann.AuthTLSCertHeader c.buildBackendAffinity(data) c.buildBackendAuthHTTP(data) c.buildBackendBlueGreen(data) diff --git a/pkg/converters/ingress/types/annotations.go b/pkg/converters/ingress/types/annotations.go index 9a4bf0925..6e91de5dc 100644 --- a/pkg/converters/ingress/types/annotations.go +++ b/pkg/converters/ingress/types/annotations.go @@ -20,7 +20,6 @@ package types type HostAnnotations struct { Source Source `json:"-"` AppRoot string `json:"app-root"` - AuthTLSCertHeader bool `json:"auth-tls-cert-header"` AuthTLSErrorPage string `json:"auth-tls-error-page"` AuthTLSVerifyClient string `json:"auth-tls-verify-client"` AuthTLSSecret string `json:"auth-tls-secret"` @@ -38,6 +37,7 @@ type BackendAnnotations struct { Affinity string `json:"affinity"` AuthRealm string `json:"auth-realm"` AuthSecret string `json:"auth-secret"` + AuthTLSCertHeader bool `json:"auth-tls-cert-header"` AuthType string `json:"auth-type"` BalanceAlgorithm string `json:"balance-algorithm"` BlueGreenBalance string `json:"blue-green-balance"` diff --git a/pkg/haproxy/config.go b/pkg/haproxy/config.go index 469a0962c..d55018f74 100644 --- a/pkg/haproxy/config.go +++ b/pkg/haproxy/config.go @@ -296,6 +296,7 @@ func (c *config) BuildFrontendGroup() (*hatypes.FrontendGroup, error) { f.SNIBackendsMap.AppendHostname(base, back) f.SNIBackendsMap.AppendAliasName(aliasName, back) f.SNIBackendsMap.AppendAliasRegex(aliasRegex, back) + path.Backend.SSL.HasTLSAuth = true } else { f.HostBackendsMap.AppendHostname(base, back) f.HostBackendsMap.AppendAliasName(aliasName, back) diff --git a/pkg/haproxy/instance_test.go b/pkg/haproxy/instance_test.go index f8e7cb35d..5c71f736a 100644 --- a/pkg/haproxy/instance_test.go +++ b/pkg/haproxy/instance_test.go @@ -79,6 +79,10 @@ frontend _front_http bind :80 http-request set-var(req.base) base,regsub(:[0-9]+/,/) http-request redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/_global_https_redir.map,_nomatch) yes } + http-request del-header -Client-CN + http-request del-header -Client-DN + http-request del-header -Client-SHA1 + http-request del-header -Client-Cert http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/_global_http_front.map,_nomatch) use_backend %%[var(req.backend)] unless { var(req.backend) _nomatch } default_backend _error404 @@ -86,6 +90,10 @@ frontend _front001 mode http bind :443 ssl alpn h2,http/1.1 crt /var/haproxy/ssl/certs/default.pem http-request set-var(req.hostbackend) base,lower,regsub(:[0-9]+/,/),map_beg(/etc/haproxy/maps/_front001_host.map,_nomatch) + http-request del-header -Client-CN + http-request del-header -Client-DN + http-request del-header -Client-SHA1 + http-request del-header -Client-Cert use_backend %%[var(req.hostbackend)] unless { var(req.hostbackend) _nomatch } default_backend _error404 ` @@ -146,6 +154,10 @@ frontend _front_http bind :80 http-request set-var(req.base) base,regsub(:[0-9]+/,/) http-request redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/_global_https_redir.map,_nomatch) yes } + http-request del-header X-SSL-Client-CN + http-request del-header X-SSL-Client-DN + http-request del-header X-SSL-Client-SHA1 + http-request del-header X-SSL-Client-Cert http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/_global_http_front.map,_nomatch) use_backend %[var(req.backend)] unless { var(req.backend) _nomatch } use_backend d1_app_8080 @@ -155,6 +167,10 @@ frontend _front001 http-request set-var(req.base) base,lower,regsub(:[0-9]+/,/) http-request set-var(req.hostbackend) var(req.base),map_beg(/etc/haproxy/maps/_front001_host.map,_nomatch) http-request set-var(txn.namespace) var(req.base),map_beg(/etc/haproxy/maps/_front001_k8s_ns.map,-) + http-request del-header X-SSL-Client-CN + http-request del-header X-SSL-Client-DN + http-request del-header X-SSL-Client-SHA1 + http-request del-header X-SSL-Client-Cert use_backend %[var(req.hostbackend)] unless { var(req.hostbackend) _nomatch } use_backend d1_app_8080 `) @@ -219,6 +235,10 @@ frontend _front_http bind :80 http-request set-var(req.base) base,regsub(:[0-9]+/,/) http-request redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/_global_https_redir.map,_nomatch) yes } + http-request del-header X-SSL-Client-CN + http-request del-header X-SSL-Client-DN + http-request del-header X-SSL-Client-SHA1 + http-request del-header X-SSL-Client-Cert http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/_global_http_front.map,_nomatch) use_backend %[var(req.backend)] unless { var(req.backend) _nomatch } default_backend _default_backend @@ -228,6 +248,10 @@ frontend _front001 http-request set-var(req.base) base,lower,regsub(:[0-9]+/,/) http-request set-var(req.hostbackend) var(req.base),map_beg(/etc/haproxy/maps/_front001_host.map,_nomatch) http-request set-var(txn.namespace) var(req.base),map_beg(/etc/haproxy/maps/_front001_k8s_ns.map,-) + http-request del-header X-SSL-Client-CN + http-request del-header X-SSL-Client-DN + http-request del-header X-SSL-Client-SHA1 + http-request del-header X-SSL-Client-Cert use_backend %[var(req.hostbackend)] unless { var(req.hostbackend) _nomatch } default_backend _default_backend `) @@ -274,6 +298,7 @@ func TestInstanceSingleFrontendTwoBindsCA(t *testing.T) { h.AddPath(b, "/") b.SSLRedirect = true b.Endpoints = []*hatypes.Endpoint{endpointS1} + b.SSL.AddCertHeader = true h.TLS.CAFilename = "/var/haproxy/ssl/ca/d1.local.pem" h.TLS.CAHash = "1" h.TLS.CAErrorPage = "http://d1.local/error.html" @@ -287,6 +312,10 @@ func TestInstanceSingleFrontendTwoBindsCA(t *testing.T) { c.checkConfig(` backend d_app_8080 mode http + http-request set-header X-SSL-Client-CN %{+Q}[ssl_c_s_dn(cn)] + http-request set-header X-SSL-Client-DN %{+Q}[ssl_c_s_dn] + http-request set-header X-SSL-Client-SHA1 %{+Q}[ssl_c_sha1,hex] + http-request set-header X-SSL-Client-Cert %{+Q}[ssl_c_der,base64] server s1 172.17.0.11:8080 weight 100 backend _default_backend mode http @@ -308,6 +337,10 @@ frontend _front_http bind :80 http-request set-var(req.base) base,regsub(:[0-9]+/,/) http-request redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/_global_https_redir.map,_nomatch) yes } + http-request del-header X-SSL-Client-CN + http-request del-header X-SSL-Client-DN + http-request del-header X-SSL-Client-SHA1 + http-request del-header X-SSL-Client-Cert http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/_global_http_front.map,_nomatch) use_backend %[var(req.backend)] unless { var(req.backend) _nomatch } default_backend _default_backend @@ -316,6 +349,10 @@ frontend _front001 bind unix@/var/run/_socket001.sock accept-proxy ssl alpn h2,http/1.1 crt /var/haproxy/ssl/certs/default.pem ca-file /var/haproxy/ssl/ca/d1.local.pem verify optional ca-ignore-err all crt-ignore-err all bind unix@/var/run/_socket002.sock accept-proxy ssl alpn h2,http/1.1 crt /var/haproxy/ssl/certs/default.pem ca-file /var/haproxy/ssl/ca/d2.local.pem verify optional ca-ignore-err all crt-ignore-err all http-request set-var(req.hostbackend) base,lower,regsub(:[0-9]+/,/),map_beg(/etc/haproxy/maps/_front001_host.map,_nomatch) + http-request del-header X-SSL-Client-CN + http-request del-header X-SSL-Client-DN + http-request del-header X-SSL-Client-SHA1 + http-request del-header X-SSL-Client-Cert http-request set-header x-ha-base %[ssl_fc_sni]%[path] http-request set-var(req.snibackend) hdr(x-ha-base),lower,regsub(:[0-9]+/,/),map_beg(/etc/haproxy/maps/_front001_sni.map,_nomatch) acl tls-has-crt ssl_c_used @@ -385,7 +422,6 @@ func TestInstanceTwoFrontendsThreeBindsCA(t *testing.T) { b = c.config.AcquireBackend("d", "appca", 8080) h = c.config.AcquireHost("d1.local") h.AddPath(b, "/") - b.SSLRedirect = true b.Endpoints = []*hatypes.Endpoint{endpointS1} h.Timeout.Client = "1s" h.TLS.CAFilename = "/var/haproxy/ssl/ca/d1.local.pem" @@ -429,6 +465,9 @@ backend d_app_8080 server s21 172.17.0.121:8080 weight 100 backend d_appca_8080 mode http + http-request set-header X-SSL-Client-CN %{+Q}[ssl_c_s_dn(cn)] if ssl_fc + http-request set-header X-SSL-Client-DN %{+Q}[ssl_c_s_dn] if ssl_fc + http-request set-header X-SSL-Client-SHA1 %{+Q}[ssl_c_sha1,hex] if ssl_fc server s1 172.17.0.11:8080 weight 100 backend _default_backend mode http @@ -453,6 +492,10 @@ frontend _front_http bind :80 http-request set-var(req.base) base,regsub(:[0-9]+/,/) http-request redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/_global_https_redir.map,_nomatch) yes } + http-request del-header X-SSL-Client-CN + http-request del-header X-SSL-Client-DN + http-request del-header X-SSL-Client-SHA1 + http-request del-header X-SSL-Client-Cert http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/_global_http_front.map,_nomatch) use_backend %[var(req.backend)] unless { var(req.backend) _nomatch } default_backend _default_backend @@ -461,6 +504,10 @@ frontend _front001 bind unix@/var/run/_socket001.sock accept-proxy ssl alpn h2,http/1.1 crt /var/haproxy/ssl/certs/default.pem ca-file /var/haproxy/ssl/ca/d1.local.pem verify optional ca-ignore-err all crt-ignore-err all timeout client 1s http-request set-var(req.hostbackend) base,lower,regsub(:[0-9]+/,/),map_beg(/etc/haproxy/maps/_front001_host.map,_nomatch) + http-request del-header X-SSL-Client-CN + http-request del-header X-SSL-Client-DN + http-request del-header X-SSL-Client-SHA1 + http-request del-header X-SSL-Client-Cert http-request set-header x-ha-base %[ssl_fc_sni]%[path] http-request set-var(req.snibackend) hdr(x-ha-base),lower,regsub(:[0-9]+/,/),map_beg(/etc/haproxy/maps/_front001_sni.map,_nomatch) acl tls-has-invalid-crt ssl_c_ca_err gt 0 @@ -478,6 +525,10 @@ frontend _front002 bind unix@/var/run/_socket003.sock accept-proxy ssl alpn h2,http/1.1 crt /var/haproxy/ssl/certs/default.pem timeout client 2s http-request set-var(req.hostbackend) base,lower,regsub(:[0-9]+/,/),map_beg(/etc/haproxy/maps/_front002_host.map,_nomatch) + http-request del-header X-SSL-Client-CN + http-request del-header X-SSL-Client-DN + http-request del-header X-SSL-Client-SHA1 + http-request del-header X-SSL-Client-Cert http-request set-header x-ha-base %[ssl_fc_sni]%[path] http-request set-var(req.snibackend) hdr(x-ha-base),lower,regsub(:[0-9]+/,/),map_beg(/etc/haproxy/maps/_front002_sni.map,_nomatch) acl tls-has-crt ssl_c_used @@ -508,13 +559,16 @@ d3.local d4.local `) c.checkMap("_global_http_front.map", ` +d1.local/ d_appca_8080 +d21.local/ d_appca_8080 +d22.local/ d_appca_8080 d3.local/ d_app_8080 d4.local/ d_app_8080 `) c.checkMap("_global_https_redir.map", ` -d1.local/ yes -d21.local/ yes -d22.local/ yes +d1.local/ no +d21.local/ no +d22.local/ no d3.local/ no d4.local/ no `) @@ -616,6 +670,10 @@ frontend _front_http bind :80 http-request set-var(req.base) base,regsub(:[0-9]+/,/) http-request redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/_global_https_redir.map,_nomatch) yes } + http-request del-header X-SSL-Client-CN + http-request del-header X-SSL-Client-DN + http-request del-header X-SSL-Client-SHA1 + http-request del-header X-SSL-Client-Cert http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/_global_http_front.map,_nomatch) use_backend %[var(req.backend)] unless { var(req.backend) _nomatch } default_backend _default_backend @@ -623,6 +681,10 @@ frontend _front001 mode http bind :443 ssl alpn h2,http/1.1 crt /var/haproxy/ssl/certs/default.pem http-request set-var(req.hostbackend) base,lower,regsub(:[0-9]+/,/),map_beg(/etc/haproxy/maps/_front001_host.map,_nomatch) + http-request del-header X-SSL-Client-CN + http-request del-header X-SSL-Client-DN + http-request del-header X-SSL-Client-SHA1 + http-request del-header X-SSL-Client-Cert use_backend %[var(req.hostbackend)] unless { var(req.hostbackend) _nomatch } default_backend _default_backend `) @@ -702,6 +764,10 @@ frontend _front_http bind :80 http-request set-var(req.base) base,regsub(:[0-9]+/,/) http-request redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/_global_https_redir.map,_nomatch) yes } + http-request del-header X-SSL-Client-CN + http-request del-header X-SSL-Client-DN + http-request del-header X-SSL-Client-SHA1 + http-request del-header X-SSL-Client-Cert http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/_global_http_front.map,_nomatch) use_backend %[var(req.backend)] unless { var(req.backend) _nomatch } default_backend _error404`) @@ -763,6 +829,10 @@ frontend _front_http http-request set-var(req.host) hdr(host),lower,regsub(:[0-9]+/,/) http-request set-var(req.rootredir) var(req.host),map(/etc/haproxy/maps/_global_http_root_redir.map,_nomatch) http-request redirect location %[var(req.rootredir)] if { path / } !{ var(req.rootredir) _nomatch } + http-request del-header X-SSL-Client-CN + http-request del-header X-SSL-Client-DN + http-request del-header X-SSL-Client-SHA1 + http-request del-header X-SSL-Client-Cert http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/_global_http_front.map,_nomatch) use_backend %[var(req.backend)] unless { var(req.backend) _nomatch } default_backend _error404 @@ -773,6 +843,10 @@ frontend _front001 http-request set-var(req.host) hdr(host),lower,regsub(:[0-9]+/,/) http-request set-var(req.rootredir) var(req.host),map(/etc/haproxy/maps/_front001_root_redir.map,_nomatch) http-request redirect location %[var(req.rootredir)] if { path / } !{ var(req.rootredir) _nomatch } + http-request del-header X-SSL-Client-CN + http-request del-header X-SSL-Client-DN + http-request del-header X-SSL-Client-SHA1 + http-request del-header X-SSL-Client-Cert use_backend %[var(req.hostbackend)] unless { var(req.hostbackend) _nomatch } default_backend _error404 `) @@ -850,6 +924,10 @@ frontend _front_http bind :80 http-request set-var(req.base) base,regsub(:[0-9]+/,/) http-request redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/_global_https_redir.map,_nomatch) yes } + http-request del-header X-SSL-Client-CN + http-request del-header X-SSL-Client-DN + http-request del-header X-SSL-Client-SHA1 + http-request del-header X-SSL-Client-Cert http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/_global_http_front.map,_nomatch) use_backend %[var(req.backend)] unless { var(req.backend) _nomatch } default_backend _error404 @@ -859,6 +937,10 @@ frontend _front001 http-request set-var(req.base) base,lower,regsub(:[0-9]+/,/) http-request set-var(req.hostbackend) var(req.base),map_beg(/etc/haproxy/maps/_front001_host.map,_nomatch) http-request set-var(req.hostbackend) var(req.base),map_reg(/etc/haproxy/maps/_front001_host_regex.map,_nomatch) if { var(req.hostbackend) _nomatch } + http-request del-header X-SSL-Client-CN + http-request del-header X-SSL-Client-DN + http-request del-header X-SSL-Client-SHA1 + http-request del-header X-SSL-Client-Cert use_backend %[var(req.hostbackend)] unless { var(req.hostbackend) _nomatch } default_backend _error404 `) @@ -924,6 +1006,9 @@ func TestInstanceWildcardHostname(t *testing.T) { c.checkConfig(` backend d1_app_8080 mode http + http-request set-header X-SSL-Client-CN %{+Q}[ssl_c_s_dn(cn)] + http-request set-header X-SSL-Client-DN %{+Q}[ssl_c_s_dn] + http-request set-header X-SSL-Client-SHA1 %{+Q}[ssl_c_sha1,hex] server s1 172.17.0.11:8080 weight 100 backend d2_app_8080 mode http @@ -967,6 +1052,10 @@ frontend _front_http http-request set-var(req.rootredir) var(req.host),map(/etc/haproxy/maps/_global_http_root_redir.map,_nomatch) http-request set-var(req.rootredir) var(req.host),map_reg(/etc/haproxy/maps/_global_http_root_redir_regex.map,_nomatch) if { var(req.rootredir) _nomatch } http-request redirect location %[var(req.rootredir)] if { path / } !{ var(req.rootredir) _nomatch } + http-request del-header X-SSL-Client-CN + http-request del-header X-SSL-Client-DN + http-request del-header X-SSL-Client-SHA1 + http-request del-header X-SSL-Client-Cert http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/_global_http_front.map,_nomatch) http-request set-var(req.backend) var(req.base),map_reg(/etc/haproxy/maps/_global_http_front_regex.map,_nomatch) if { var(req.backend) _nomatch } use_backend %[var(req.backend)] unless { var(req.backend) _nomatch } @@ -978,6 +1067,10 @@ frontend _front001 http-request set-var(req.base) base,lower,regsub(:[0-9]+/,/) http-request set-var(req.hostbackend) var(req.base),map_beg(/etc/haproxy/maps/_front001_host.map,_nomatch) http-request set-var(req.hostbackend) var(req.base),map_reg(/etc/haproxy/maps/_front001_host_regex.map,_nomatch) if { var(req.hostbackend) _nomatch } + http-request del-header X-SSL-Client-CN + http-request del-header X-SSL-Client-DN + http-request del-header X-SSL-Client-SHA1 + http-request del-header X-SSL-Client-Cert http-request set-header x-ha-base %[ssl_fc_sni]%[path] http-request set-var(req.snibase) hdr(x-ha-base),lower,regsub(:[0-9]+/,/) http-request set-var(req.snibackend) var(req.snibase),map_beg(/etc/haproxy/maps/_front001_sni.map,_nomatch) @@ -1004,6 +1097,10 @@ frontend _front002 http-request set-var(req.rootredir) var(req.host),map(/etc/haproxy/maps/_front002_root_redir.map,_nomatch) http-request set-var(req.rootredir) var(req.host),map_reg(/etc/haproxy/maps/_front002_root_redir_regex.map,_nomatch) if { var(req.rootredir) _nomatch } http-request redirect location %[var(req.rootredir)] if { path / } !{ var(req.rootredir) _nomatch } + http-request del-header X-SSL-Client-CN + http-request del-header X-SSL-Client-DN + http-request del-header X-SSL-Client-SHA1 + http-request del-header X-SSL-Client-Cert use_backend %[var(req.hostbackend)] unless { var(req.hostbackend) _nomatch } default_backend _error404 `) @@ -1151,6 +1248,7 @@ func (c *testConfig) configGlobal() { global.MaxConn = 2000 global.SSL.Ciphers = "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256" global.SSL.DHParam.Filename = "/var/haproxy/tls/dhparam.pem" + global.SSL.HeadersPrefix = "X-SSL" global.SSL.Options = "no-sslv3" global.StatsSocket = "/var/run/haproxy.sock" global.Timeout.Client = "50s" diff --git a/pkg/haproxy/types/types.go b/pkg/haproxy/types/types.go index 234b5599c..a05ba0feb 100644 --- a/pkg/haproxy/types/types.go +++ b/pkg/haproxy/types/types.go @@ -57,11 +57,12 @@ type TimeoutConfig struct { // SSLConfig ... type SSLConfig struct { - DHParam DHParamConfig - Ciphers string - Options string - Engine string - ModeAsync bool + DHParam DHParamConfig + Ciphers string + Options string + Engine string + ModeAsync bool + HeadersPrefix string } // DHParamConfig ... @@ -200,7 +201,6 @@ type HostTimeoutConfig struct { // HostTLSConfig ... type HostTLSConfig struct { - AddCertHeader bool CAErrorPage string CAFilename string CAHash string @@ -262,11 +262,13 @@ type HealthCheck struct { // SSLBackendConfig ... type SSLBackendConfig struct { - IsSecure bool - CertFilename string - CertHash string - CAFilename string - CAHash string + HasTLSAuth bool + AddCertHeader bool + IsSecure bool + CertFilename string + CertHash string + CAFilename string + CAHash string } // BackendTimeoutConfig ... diff --git a/rootfs/etc/haproxy/template/haproxy.tmpl b/rootfs/etc/haproxy/template/haproxy.tmpl index 51753e72f..841a53e77 100644 --- a/rootfs/etc/haproxy/template/haproxy.tmpl +++ b/rootfs/etc/haproxy/template/haproxy.tmpl @@ -7,6 +7,7 @@ # # # {{- $cfg := . }} +{{- $fgroup := $cfg.BuildFrontendGroup }} {{- $global := $cfg.Global }} global daemon @@ -160,6 +161,16 @@ backend {{ $backend.ID }} timeout tunnel {{ $timeout.Tunnel }} {{- end }} +{{- /*------------------------------------*/}} +{{- if $backend.SSL.HasTLSAuth }} + http-request set-header {{ $global.SSL.HeadersPrefix }}-Client-CN %{+Q}[ssl_c_s_dn(cn)]{{ if not $backend.SSLRedirect }} if ssl_fc{{ end }} + http-request set-header {{ $global.SSL.HeadersPrefix }}-Client-DN %{+Q}[ssl_c_s_dn]{{ if not $backend.SSLRedirect }} if ssl_fc{{ end }} + http-request set-header {{ $global.SSL.HeadersPrefix }}-Client-SHA1 %{+Q}[ssl_c_sha1,hex]{{ if not $backend.SSLRedirect }} if ssl_fc{{ end }} +{{- if $backend.SSL.AddCertHeader }} + http-request set-header {{ $global.SSL.HeadersPrefix }}-Client-Cert %{+Q}[ssl_c_der,base64]{{ if not $backend.SSLRedirect }} if ssl_fc{{ end }} +{{- end }} +{{- end }} + {{- /*------------------------------------*/}} {{- range $snippet := $backend.CustomConfig }} {{ $snippet }} @@ -228,7 +239,6 @@ backend _error496 # # FRONTENDS # # # -{{- $fgroup := $cfg.BuildFrontendGroup }} {{- $frontends := $fgroup.Frontends }} {{- if $fgroup.HasTCPProxy }} @@ -309,6 +319,12 @@ frontend _front_http http-request redirect location %[var(req.rootredir)] if { path / } !{ var(req.rootredir) _nomatch } {{- end }} +{{- /*------------------------------------*/}} + http-request del-header {{ $global.SSL.HeadersPrefix }}-Client-CN + http-request del-header {{ $global.SSL.HeadersPrefix }}-Client-DN + http-request del-header {{ $global.SSL.HeadersPrefix }}-Client-SHA1 + http-request del-header {{ $global.SSL.HeadersPrefix }}-Client-Cert + {{- /*------------------------------------*/}} http-request set-var(req.backend) var(req.base),map_beg({{ $fgroup.HTTPFrontsMap.MatchFile }},_nomatch) {{- if $fgroup.HTTPFrontsMap.HasRegex }} @@ -389,6 +405,12 @@ frontend {{ $frontend.Name }} {{- end }} {{- end }} +{{- /*------------------------------------*/}} + http-request del-header {{ $global.SSL.HeadersPrefix }}-Client-CN + http-request del-header {{ $global.SSL.HeadersPrefix }}-Client-DN + http-request del-header {{ $global.SSL.HeadersPrefix }}-Client-SHA1 + http-request del-header {{ $global.SSL.HeadersPrefix }}-Client-Cert + {{- /*------------------------------------*/}} {{- if $frontend.HasTLSAuth }} {{- /*** TODO missing concat converter, fix after 1.9 ***/}} From cfa7f83ac9e5da13f25710aa265a00b37ca5e07d Mon Sep 17 00:00:00 2001 From: Joao Morais Date: Sat, 18 May 2019 17:12:35 -0300 Subject: [PATCH 13/30] Some config reuse on unit tests --- pkg/haproxy/instance_test.go | 293 +++++++++++++++-------------------- 1 file changed, 127 insertions(+), 166 deletions(-) diff --git a/pkg/haproxy/instance_test.go b/pkg/haproxy/instance_test.go index 5c71f736a..97c6a29d6 100644 --- a/pkg/haproxy/instance_test.go +++ b/pkg/haproxy/instance_test.go @@ -17,7 +17,6 @@ limitations under the License. package haproxy import ( - "fmt" "io/ioutil" "os" "regexp" @@ -42,24 +41,36 @@ func TestInstanceEmpty(t *testing.T) { c := setup(t) defer c.teardown() - template := ` + c.config.AcquireHost("empty").AddPath(c.config.AcquireBackend("default", "empty", 8080), "/") + c.instance.Update() + + c.checkConfig(` global daemon - stats socket %s level admin expose-fd listeners - maxconn 0 + stats socket /var/run/haproxy.sock level admin expose-fd listeners + maxconn 2000 + hard-stop-after 15m lua-load /usr/local/etc/haproxy/lua/send-response.lua lua-load /usr/local/etc/haproxy/lua/auth-request.lua - tune.ssl.default-dh-param 0 + ssl-dh-param-file /var/haproxy/tls/dhparam.pem + ssl-default-bind-ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256 + ssl-default-bind-options no-sslv3 defaults log global - maxconn 0 + maxconn 2000 option redispatch option dontlognull option http-server-close option http-keep-alive - timeout client %s - timeout connect %s - timeout server %s + timeout client 50s + timeout client-fin 50s + timeout connect 5s + timeout http-keep-alive 1m + timeout http-request 5s + timeout queue 5s + timeout server 50s + timeout server-fin 50s + timeout tunnel 1h backend default_empty_8080 mode http backend _error404 @@ -79,28 +90,18 @@ frontend _front_http bind :80 http-request set-var(req.base) base,regsub(:[0-9]+/,/) http-request redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/_global_https_redir.map,_nomatch) yes } - http-request del-header -Client-CN - http-request del-header -Client-DN - http-request del-header -Client-SHA1 - http-request del-header -Client-Cert + <> http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/_global_http_front.map,_nomatch) - use_backend %%[var(req.backend)] unless { var(req.backend) _nomatch } + use_backend %[var(req.backend)] unless { var(req.backend) _nomatch } default_backend _error404 frontend _front001 mode http bind :443 ssl alpn h2,http/1.1 crt /var/haproxy/ssl/certs/default.pem http-request set-var(req.hostbackend) base,lower,regsub(:[0-9]+/,/),map_beg(/etc/haproxy/maps/_front001_host.map,_nomatch) - http-request del-header -Client-CN - http-request del-header -Client-DN - http-request del-header -Client-SHA1 - http-request del-header -Client-Cert - use_backend %%[var(req.hostbackend)] unless { var(req.hostbackend) _nomatch } + <> + use_backend %[var(req.hostbackend)] unless { var(req.hostbackend) _nomatch } default_backend _error404 -` - - c.config.AcquireHost("empty").AddPath(c.config.AcquireBackend("default", "empty", 8080), "/") - c.instance.Update() - c.checkConfigFull(fmt.Sprintf(template, "--", "--", "--", "--")) +`) c.checkMap("_global_http_front.map", ` empty/ default_empty_8080`) @@ -116,7 +117,6 @@ func TestInstanceDefaultHost(t *testing.T) { c := setup(t) defer c.teardown() - c.configGlobal() def := c.config.AcquireBackend("default", "default-backend", 8080) def.Endpoints = []*hatypes.Endpoint{endpointS0} c.config.ConfigDefaultBackend(def) @@ -140,6 +140,8 @@ func TestInstanceDefaultHost(t *testing.T) { c.instance.Update() c.checkConfig(` +<> +<> backend d1_app_8080 mode http server s1 172.17.0.11:8080 weight 100 @@ -148,16 +150,14 @@ backend d2_app_8080 server s1 172.17.0.11:8080 weight 100 backend _default_backend mode http - server s0 172.17.0.99:8080 weight 100`, ` + server s0 172.17.0.99:8080 weight 100 +<> frontend _front_http mode http bind :80 http-request set-var(req.base) base,regsub(:[0-9]+/,/) http-request redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/_global_https_redir.map,_nomatch) yes } - http-request del-header X-SSL-Client-CN - http-request del-header X-SSL-Client-DN - http-request del-header X-SSL-Client-SHA1 - http-request del-header X-SSL-Client-Cert + <> http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/_global_http_front.map,_nomatch) use_backend %[var(req.backend)] unless { var(req.backend) _nomatch } use_backend d1_app_8080 @@ -167,10 +167,7 @@ frontend _front001 http-request set-var(req.base) base,lower,regsub(:[0-9]+/,/) http-request set-var(req.hostbackend) var(req.base),map_beg(/etc/haproxy/maps/_front001_host.map,_nomatch) http-request set-var(txn.namespace) var(req.base),map_beg(/etc/haproxy/maps/_front001_k8s_ns.map,-) - http-request del-header X-SSL-Client-CN - http-request del-header X-SSL-Client-DN - http-request del-header X-SSL-Client-SHA1 - http-request del-header X-SSL-Client-Cert + <> use_backend %[var(req.hostbackend)] unless { var(req.hostbackend) _nomatch } use_backend d1_app_8080 `) @@ -194,7 +191,6 @@ func TestInstanceSingleFrontendSingleBind(t *testing.T) { c := setup(t) defer c.teardown() - c.configGlobal() def := c.config.AcquireBackend("default", "default-backend", 8080) def.Endpoints = []*hatypes.Endpoint{endpointS0} c.config.ConfigDefaultBackend(def) @@ -221,6 +217,8 @@ func TestInstanceSingleFrontendSingleBind(t *testing.T) { c.instance.Update() c.checkConfig(` +<> +<> backend d1_app_8080 mode http server s1 172.17.0.11:8080 weight 100 @@ -229,16 +227,14 @@ backend d2_app_8080 server s1 172.17.0.11:8080 weight 100 backend _default_backend mode http - server s0 172.17.0.99:8080 weight 100`, ` + server s0 172.17.0.99:8080 weight 100 +<> frontend _front_http mode http bind :80 http-request set-var(req.base) base,regsub(:[0-9]+/,/) http-request redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/_global_https_redir.map,_nomatch) yes } - http-request del-header X-SSL-Client-CN - http-request del-header X-SSL-Client-DN - http-request del-header X-SSL-Client-SHA1 - http-request del-header X-SSL-Client-Cert + <> http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/_global_http_front.map,_nomatch) use_backend %[var(req.backend)] unless { var(req.backend) _nomatch } default_backend _default_backend @@ -248,10 +244,7 @@ frontend _front001 http-request set-var(req.base) base,lower,regsub(:[0-9]+/,/) http-request set-var(req.hostbackend) var(req.base),map_beg(/etc/haproxy/maps/_front001_host.map,_nomatch) http-request set-var(txn.namespace) var(req.base),map_beg(/etc/haproxy/maps/_front001_k8s_ns.map,-) - http-request del-header X-SSL-Client-CN - http-request del-header X-SSL-Client-DN - http-request del-header X-SSL-Client-SHA1 - http-request del-header X-SSL-Client-Cert + <> use_backend %[var(req.hostbackend)] unless { var(req.hostbackend) _nomatch } default_backend _default_backend `) @@ -285,7 +278,6 @@ func TestInstanceSingleFrontendTwoBindsCA(t *testing.T) { c := setup(t) defer c.teardown() - c.configGlobal() def := c.config.AcquireBackend("default", "default-backend", 8080) def.Endpoints = []*hatypes.Endpoint{endpointS0} c.config.ConfigDefaultBackend(def) @@ -310,6 +302,8 @@ func TestInstanceSingleFrontendTwoBindsCA(t *testing.T) { c.instance.Update() c.checkConfig(` +<> +<> backend d_app_8080 mode http http-request set-header X-SSL-Client-CN %{+Q}[ssl_c_s_dn(cn)] @@ -319,7 +313,8 @@ backend d_app_8080 server s1 172.17.0.11:8080 weight 100 backend _default_backend mode http - server s0 172.17.0.99:8080 weight 100`, ` + server s0 172.17.0.99:8080 weight 100 +<> listen _front__tls mode tcp bind :443 @@ -337,10 +332,7 @@ frontend _front_http bind :80 http-request set-var(req.base) base,regsub(:[0-9]+/,/) http-request redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/_global_https_redir.map,_nomatch) yes } - http-request del-header X-SSL-Client-CN - http-request del-header X-SSL-Client-DN - http-request del-header X-SSL-Client-SHA1 - http-request del-header X-SSL-Client-Cert + <> http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/_global_http_front.map,_nomatch) use_backend %[var(req.backend)] unless { var(req.backend) _nomatch } default_backend _default_backend @@ -349,10 +341,7 @@ frontend _front001 bind unix@/var/run/_socket001.sock accept-proxy ssl alpn h2,http/1.1 crt /var/haproxy/ssl/certs/default.pem ca-file /var/haproxy/ssl/ca/d1.local.pem verify optional ca-ignore-err all crt-ignore-err all bind unix@/var/run/_socket002.sock accept-proxy ssl alpn h2,http/1.1 crt /var/haproxy/ssl/certs/default.pem ca-file /var/haproxy/ssl/ca/d2.local.pem verify optional ca-ignore-err all crt-ignore-err all http-request set-var(req.hostbackend) base,lower,regsub(:[0-9]+/,/),map_beg(/etc/haproxy/maps/_front001_host.map,_nomatch) - http-request del-header X-SSL-Client-CN - http-request del-header X-SSL-Client-DN - http-request del-header X-SSL-Client-SHA1 - http-request del-header X-SSL-Client-Cert + <> http-request set-header x-ha-base %[ssl_fc_sni]%[path] http-request set-var(req.snibackend) hdr(x-ha-base),lower,regsub(:[0-9]+/,/),map_beg(/etc/haproxy/maps/_front001_sni.map,_nomatch) acl tls-has-crt ssl_c_used @@ -411,7 +400,6 @@ func TestInstanceTwoFrontendsThreeBindsCA(t *testing.T) { c := setup(t) defer c.teardown() - c.configGlobal() def := c.config.AcquireBackend("default", "default-backend", 8080) def.Endpoints = []*hatypes.Endpoint{endpointS0} c.config.ConfigDefaultBackend(def) @@ -460,6 +448,8 @@ func TestInstanceTwoFrontendsThreeBindsCA(t *testing.T) { c.instance.Update() c.checkConfig(` +<> +<> backend d_app_8080 mode http server s21 172.17.0.121:8080 weight 100 @@ -471,7 +461,8 @@ backend d_appca_8080 server s1 172.17.0.11:8080 weight 100 backend _default_backend mode http - server s0 172.17.0.99:8080 weight 100`, ` + server s0 172.17.0.99:8080 weight 100 +<> listen _front__tls mode tcp bind :443 @@ -492,10 +483,7 @@ frontend _front_http bind :80 http-request set-var(req.base) base,regsub(:[0-9]+/,/) http-request redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/_global_https_redir.map,_nomatch) yes } - http-request del-header X-SSL-Client-CN - http-request del-header X-SSL-Client-DN - http-request del-header X-SSL-Client-SHA1 - http-request del-header X-SSL-Client-Cert + <> http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/_global_http_front.map,_nomatch) use_backend %[var(req.backend)] unless { var(req.backend) _nomatch } default_backend _default_backend @@ -504,10 +492,7 @@ frontend _front001 bind unix@/var/run/_socket001.sock accept-proxy ssl alpn h2,http/1.1 crt /var/haproxy/ssl/certs/default.pem ca-file /var/haproxy/ssl/ca/d1.local.pem verify optional ca-ignore-err all crt-ignore-err all timeout client 1s http-request set-var(req.hostbackend) base,lower,regsub(:[0-9]+/,/),map_beg(/etc/haproxy/maps/_front001_host.map,_nomatch) - http-request del-header X-SSL-Client-CN - http-request del-header X-SSL-Client-DN - http-request del-header X-SSL-Client-SHA1 - http-request del-header X-SSL-Client-Cert + <> http-request set-header x-ha-base %[ssl_fc_sni]%[path] http-request set-var(req.snibackend) hdr(x-ha-base),lower,regsub(:[0-9]+/,/),map_beg(/etc/haproxy/maps/_front001_sni.map,_nomatch) acl tls-has-invalid-crt ssl_c_ca_err gt 0 @@ -525,10 +510,7 @@ frontend _front002 bind unix@/var/run/_socket003.sock accept-proxy ssl alpn h2,http/1.1 crt /var/haproxy/ssl/certs/default.pem timeout client 2s http-request set-var(req.hostbackend) base,lower,regsub(:[0-9]+/,/),map_beg(/etc/haproxy/maps/_front002_host.map,_nomatch) - http-request del-header X-SSL-Client-CN - http-request del-header X-SSL-Client-DN - http-request del-header X-SSL-Client-SHA1 - http-request del-header X-SSL-Client-Cert + <> http-request set-header x-ha-base %[ssl_fc_sni]%[path] http-request set-var(req.snibackend) hdr(x-ha-base),lower,regsub(:[0-9]+/,/),map_beg(/etc/haproxy/maps/_front002_sni.map,_nomatch) acl tls-has-crt ssl_c_used @@ -618,7 +600,6 @@ func TestInstanceSomePaths(t *testing.T) { c := setup(t) defer c.teardown() - c.configGlobal() def := c.config.AcquireBackend("default", "default-backend", 8080) def.Endpoints = []*hatypes.Endpoint{endpointS0} c.config.ConfigDefaultBackend(def) @@ -647,6 +628,8 @@ func TestInstanceSomePaths(t *testing.T) { c.instance.Update() c.checkConfig(` +<> +<> backend d_app0_8080 mode http server s1 172.17.0.11:8080 weight 100 @@ -664,16 +647,14 @@ backend d_app3_8080 server s33 172.17.0.133:8080 weight 100 backend _default_backend mode http - server s0 172.17.0.99:8080 weight 100`, ` + server s0 172.17.0.99:8080 weight 100 +<> frontend _front_http mode http bind :80 http-request set-var(req.base) base,regsub(:[0-9]+/,/) http-request redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/_global_https_redir.map,_nomatch) yes } - http-request del-header X-SSL-Client-CN - http-request del-header X-SSL-Client-DN - http-request del-header X-SSL-Client-SHA1 - http-request del-header X-SSL-Client-Cert + <> http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/_global_http_front.map,_nomatch) use_backend %[var(req.backend)] unless { var(req.backend) _nomatch } default_backend _default_backend @@ -681,10 +662,7 @@ frontend _front001 mode http bind :443 ssl alpn h2,http/1.1 crt /var/haproxy/ssl/certs/default.pem http-request set-var(req.hostbackend) base,lower,regsub(:[0-9]+/,/),map_beg(/etc/haproxy/maps/_front001_host.map,_nomatch) - http-request del-header X-SSL-Client-CN - http-request del-header X-SSL-Client-DN - http-request del-header X-SSL-Client-SHA1 - http-request del-header X-SSL-Client-Cert + <> use_backend %[var(req.hostbackend)] unless { var(req.hostbackend) _nomatch } default_backend _default_backend `) @@ -713,8 +691,6 @@ func TestInstanceSSLPassthrough(t *testing.T) { c := setup(t) defer c.teardown() - c.configGlobal() - var h *hatypes.Host var b *hatypes.Backend @@ -737,6 +713,8 @@ func TestInstanceSSLPassthrough(t *testing.T) { c.instance.Update() c.checkConfig(` +<> +<> backend d2_app_8080 mode http server s31 172.17.0.131:8080 weight 100 @@ -749,7 +727,8 @@ backend d3_app-ssl_8443 backend _error404 mode http errorfile 400 /usr/local/etc/haproxy/errors/404.http - http-request deny deny_status 400`, ` + http-request deny deny_status 400 +<> listen _front__tls mode tcp bind :443 @@ -764,10 +743,7 @@ frontend _front_http bind :80 http-request set-var(req.base) base,regsub(:[0-9]+/,/) http-request redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/_global_https_redir.map,_nomatch) yes } - http-request del-header X-SSL-Client-CN - http-request del-header X-SSL-Client-DN - http-request del-header X-SSL-Client-SHA1 - http-request del-header X-SSL-Client-Cert + <> http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/_global_http_front.map,_nomatch) use_backend %[var(req.backend)] unless { var(req.backend) _nomatch } default_backend _error404`) @@ -788,8 +764,6 @@ func TestInstanceRootRedirect(t *testing.T) { c := setup(t) defer c.teardown() - c.configGlobal() - var h *hatypes.Host var b *hatypes.Backend @@ -811,6 +785,8 @@ func TestInstanceRootRedirect(t *testing.T) { c.instance.Update() c.checkConfig(` +<> +<> backend d1_app_8080 mode http server s1 172.17.0.11:8080 weight 100 @@ -820,7 +796,8 @@ backend d2_app_8080 backend _error404 mode http errorfile 400 /usr/local/etc/haproxy/errors/404.http - http-request deny deny_status 400`, ` + http-request deny deny_status 400 +<> frontend _front_http mode http bind :80 @@ -829,10 +806,7 @@ frontend _front_http http-request set-var(req.host) hdr(host),lower,regsub(:[0-9]+/,/) http-request set-var(req.rootredir) var(req.host),map(/etc/haproxy/maps/_global_http_root_redir.map,_nomatch) http-request redirect location %[var(req.rootredir)] if { path / } !{ var(req.rootredir) _nomatch } - http-request del-header X-SSL-Client-CN - http-request del-header X-SSL-Client-DN - http-request del-header X-SSL-Client-SHA1 - http-request del-header X-SSL-Client-Cert + <> http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/_global_http_front.map,_nomatch) use_backend %[var(req.backend)] unless { var(req.backend) _nomatch } default_backend _error404 @@ -843,10 +817,7 @@ frontend _front001 http-request set-var(req.host) hdr(host),lower,regsub(:[0-9]+/,/) http-request set-var(req.rootredir) var(req.host),map(/etc/haproxy/maps/_front001_root_redir.map,_nomatch) http-request redirect location %[var(req.rootredir)] if { path / } !{ var(req.rootredir) _nomatch } - http-request del-header X-SSL-Client-CN - http-request del-header X-SSL-Client-DN - http-request del-header X-SSL-Client-SHA1 - http-request del-header X-SSL-Client-Cert + <> use_backend %[var(req.hostbackend)] unless { var(req.hostbackend) _nomatch } default_backend _error404 `) @@ -880,8 +851,6 @@ func TestInstanceAlias(t *testing.T) { c := setup(t) defer c.teardown() - c.configGlobal() - var h *hatypes.Host var b *hatypes.Backend @@ -906,6 +875,8 @@ func TestInstanceAlias(t *testing.T) { c.instance.Update() c.checkConfig(` +<> +<> backend d1_app_8080 mode http server s1 172.17.0.11:8080 weight 100 @@ -918,16 +889,14 @@ backend d3_app_8080 backend _error404 mode http errorfile 400 /usr/local/etc/haproxy/errors/404.http - http-request deny deny_status 400`, ` + http-request deny deny_status 400 +<> frontend _front_http mode http bind :80 http-request set-var(req.base) base,regsub(:[0-9]+/,/) http-request redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/_global_https_redir.map,_nomatch) yes } - http-request del-header X-SSL-Client-CN - http-request del-header X-SSL-Client-DN - http-request del-header X-SSL-Client-SHA1 - http-request del-header X-SSL-Client-Cert + <> http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/_global_http_front.map,_nomatch) use_backend %[var(req.backend)] unless { var(req.backend) _nomatch } default_backend _error404 @@ -937,10 +906,7 @@ frontend _front001 http-request set-var(req.base) base,lower,regsub(:[0-9]+/,/) http-request set-var(req.hostbackend) var(req.base),map_beg(/etc/haproxy/maps/_front001_host.map,_nomatch) http-request set-var(req.hostbackend) var(req.base),map_reg(/etc/haproxy/maps/_front001_host_regex.map,_nomatch) if { var(req.hostbackend) _nomatch } - http-request del-header X-SSL-Client-CN - http-request del-header X-SSL-Client-DN - http-request del-header X-SSL-Client-SHA1 - http-request del-header X-SSL-Client-Cert + <> use_backend %[var(req.hostbackend)] unless { var(req.hostbackend) _nomatch } default_backend _error404 `) @@ -975,8 +941,6 @@ func TestInstanceWildcardHostname(t *testing.T) { c := setup(t) defer c.teardown() - c.configGlobal() - var h *hatypes.Host var b *hatypes.Backend @@ -1004,6 +968,8 @@ func TestInstanceWildcardHostname(t *testing.T) { c.instance.Update() c.checkConfig(` +<> +<> backend d1_app_8080 mode http http-request set-header X-SSL-Client-CN %{+Q}[ssl_c_s_dn(cn)] @@ -1016,7 +982,8 @@ backend d2_app_8080 backend _error404 mode http errorfile 400 /usr/local/etc/haproxy/errors/404.http - http-request deny deny_status 400`, ` + http-request deny deny_status 400 +<> listen _front__tls mode tcp bind :443 @@ -1052,10 +1019,7 @@ frontend _front_http http-request set-var(req.rootredir) var(req.host),map(/etc/haproxy/maps/_global_http_root_redir.map,_nomatch) http-request set-var(req.rootredir) var(req.host),map_reg(/etc/haproxy/maps/_global_http_root_redir_regex.map,_nomatch) if { var(req.rootredir) _nomatch } http-request redirect location %[var(req.rootredir)] if { path / } !{ var(req.rootredir) _nomatch } - http-request del-header X-SSL-Client-CN - http-request del-header X-SSL-Client-DN - http-request del-header X-SSL-Client-SHA1 - http-request del-header X-SSL-Client-Cert + <> http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/_global_http_front.map,_nomatch) http-request set-var(req.backend) var(req.base),map_reg(/etc/haproxy/maps/_global_http_front_regex.map,_nomatch) if { var(req.backend) _nomatch } use_backend %[var(req.backend)] unless { var(req.backend) _nomatch } @@ -1067,10 +1031,7 @@ frontend _front001 http-request set-var(req.base) base,lower,regsub(:[0-9]+/,/) http-request set-var(req.hostbackend) var(req.base),map_beg(/etc/haproxy/maps/_front001_host.map,_nomatch) http-request set-var(req.hostbackend) var(req.base),map_reg(/etc/haproxy/maps/_front001_host_regex.map,_nomatch) if { var(req.hostbackend) _nomatch } - http-request del-header X-SSL-Client-CN - http-request del-header X-SSL-Client-DN - http-request del-header X-SSL-Client-SHA1 - http-request del-header X-SSL-Client-Cert + <> http-request set-header x-ha-base %[ssl_fc_sni]%[path] http-request set-var(req.snibase) hdr(x-ha-base),lower,regsub(:[0-9]+/,/) http-request set-var(req.snibackend) var(req.snibase),map_beg(/etc/haproxy/maps/_front001_sni.map,_nomatch) @@ -1097,10 +1058,7 @@ frontend _front002 http-request set-var(req.rootredir) var(req.host),map(/etc/haproxy/maps/_front002_root_redir.map,_nomatch) http-request set-var(req.rootredir) var(req.host),map_reg(/etc/haproxy/maps/_front002_root_redir_regex.map,_nomatch) if { var(req.rootredir) _nomatch } http-request redirect location %[var(req.rootredir)] if { path / } !{ var(req.rootredir) _nomatch } - http-request del-header X-SSL-Client-CN - http-request del-header X-SSL-Client-DN - http-request del-header X-SSL-Client-SHA1 - http-request del-header X-SSL-Client-Cert + <> use_backend %[var(req.hostbackend)] unless { var(req.hostbackend) _nomatch } default_backend _error404 `) @@ -1225,7 +1183,7 @@ func setup(t *testing.T) *testConfig { }) instance.curConfig = config config.ConfigDefaultX509Cert("/var/haproxy/ssl/certs/default.pem") - return &testConfig{ + c := &testConfig{ t: t, logger: logger, bindUtils: bindUtils, @@ -1234,6 +1192,8 @@ func setup(t *testing.T) *testConfig { tempdir: tempdir, configfile: configfile, } + c.configGlobal() + return c } func (c *testConfig) teardown() { @@ -1263,44 +1223,6 @@ func (c *testConfig) configGlobal() { global.Timeout.Tunnel = "1h" } -var globalConfig = ` -global - daemon - stats socket /var/run/haproxy.sock level admin expose-fd listeners - maxconn 2000 - hard-stop-after 15m - lua-load /usr/local/etc/haproxy/lua/send-response.lua - lua-load /usr/local/etc/haproxy/lua/auth-request.lua - ssl-dh-param-file /var/haproxy/tls/dhparam.pem - ssl-default-bind-ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256 - ssl-default-bind-options no-sslv3 -defaults - log global - maxconn 2000 - option redispatch - option dontlognull - option http-server-close - option http-keep-alive - timeout client 50s - timeout client-fin 50s - timeout connect 5s - timeout http-keep-alive 1m - timeout http-request 5s - timeout queue 5s - timeout server 50s - timeout server-fin 50s - timeout tunnel 1h` - -var errorPages = ` -backend _error495 - mode http - errorfile 400 /usr/local/etc/haproxy/errors/495.http - http-request deny deny_status 400 -backend _error496 - mode http - errorfile 400 /usr/local/etc/haproxy/errors/496.http - http-request deny deny_status 400` - var endpointS0 = &hatypes.Endpoint{ Name: "s0", IP: "172.17.0.99", @@ -1366,12 +1288,51 @@ func _yamlMarshal(in interface{}) string { return string(out) } -func (c *testConfig) checkConfig(backend, frontend string) { - c.checkConfigFull(globalConfig + backend + errorPages + frontend) -} - -func (c *testConfig) checkConfigFull(expected string) { +func (c *testConfig) checkConfig(expected string) { actual := strings.Replace(c.readConfig(c.configfile), c.tempdir, "/etc/haproxy/maps", -1) + replace := map[string]string{ + "<>": `global + daemon + stats socket /var/run/haproxy.sock level admin expose-fd listeners + maxconn 2000 + hard-stop-after 15m + lua-load /usr/local/etc/haproxy/lua/send-response.lua + lua-load /usr/local/etc/haproxy/lua/auth-request.lua + ssl-dh-param-file /var/haproxy/tls/dhparam.pem + ssl-default-bind-ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256 + ssl-default-bind-options no-sslv3`, + "<>": `defaults + log global + maxconn 2000 + option redispatch + option dontlognull + option http-server-close + option http-keep-alive + timeout client 50s + timeout client-fin 50s + timeout connect 5s + timeout http-keep-alive 1m + timeout http-request 5s + timeout queue 5s + timeout server 50s + timeout server-fin 50s + timeout tunnel 1h`, + "<>": `backend _error495 + mode http + errorfile 400 /usr/local/etc/haproxy/errors/495.http + http-request deny deny_status 400 +backend _error496 + mode http + errorfile 400 /usr/local/etc/haproxy/errors/496.http + http-request deny deny_status 400`, + " <>": ` http-request del-header X-SSL-Client-CN + http-request del-header X-SSL-Client-DN + http-request del-header X-SSL-Client-SHA1 + http-request del-header X-SSL-Client-Cert`, + } + for old, new := range replace { + expected = strings.Replace(expected, old, new, -1) + } c.compareText("haproxy.cfg", actual, expected) } From a8e448bac54e8d768b50b9bff686484d9693dad2 Mon Sep 17 00:00:00 2001 From: Joao Morais Date: Sun, 9 Jun 2019 11:03:17 -0300 Subject: [PATCH 14/30] Improve configuration changing check --- pkg/haproxy/config.go | 27 +++++++++++++++--------- pkg/haproxy/config_test.go | 8 +++---- pkg/haproxy/instance.go | 5 +++++ rootfs/etc/haproxy/template/haproxy.tmpl | 6 +++--- 4 files changed, 29 insertions(+), 17 deletions(-) diff --git a/pkg/haproxy/config.go b/pkg/haproxy/config.go index d55018f74..8d0ec449f 100644 --- a/pkg/haproxy/config.go +++ b/pkg/haproxy/config.go @@ -35,7 +35,8 @@ type Config interface { ConfigDefaultX509Cert(filename string) AddUserlist(name string, users []hatypes.User) *hatypes.Userlist FindUserlist(name string) *hatypes.Userlist - BuildFrontendGroup() (*hatypes.FrontendGroup, error) + FrontendGroup() *hatypes.FrontendGroup + BuildFrontendGroup() error DefaultHost() *hatypes.Host DefaultBackend() *hatypes.Backend Global() *hatypes.Global @@ -46,6 +47,7 @@ type Config interface { } type config struct { + fgroup *hatypes.FrontendGroup bindUtils hatypes.BindUtils mapsTemplate *template.Config mapsDir string @@ -187,11 +189,15 @@ func (c *config) FindUserlist(name string) *hatypes.Userlist { return nil } -func (c *config) BuildFrontendGroup() (*hatypes.FrontendGroup, error) { +func (c *config) FrontendGroup() *hatypes.FrontendGroup { + return c.fgroup +} + +func (c *config) BuildFrontendGroup() error { // tested thanks to instance_test templating tests // ideas to make a nice test or a nice refactor are welcome if len(c.hosts) == 0 { - return nil, fmt.Errorf("cannot create frontends without hosts") + return fmt.Errorf("cannot create frontends without hosts") } frontends, sslpassthrough := hatypes.BuildRawFrontends(c.hosts) fgroupMaps := hatypes.CreateMaps() @@ -218,7 +224,7 @@ func (c *config) BuildFrontendGroup() (*hatypes.FrontendGroup, error) { } else { x509dir, err := c.createCertsDir(bindName, bind.Hosts) if err != nil { - return nil, err + return err } bind.TLS.TLSCert = c.defaultX509Cert bind.TLS.TLSCertDir = x509dir @@ -239,7 +245,7 @@ func (c *config) BuildFrontendGroup() (*hatypes.FrontendGroup, error) { } else { x509dir, err := c.createCertsDir(bind.Name, bind.Hosts) if err != nil { - return nil, err + return err } frontends[0].Binds[0].TLS.TLSCert = c.defaultX509Cert frontends[0].Binds[0].TLS.TLSCertDir = x509dir @@ -269,7 +275,7 @@ func (c *config) BuildFrontendGroup() (*hatypes.FrontendGroup, error) { for _, sslpassHost := range sslpassthrough { rootPath := sslpassHost.FindPath("/") if rootPath == nil { - return nil, fmt.Errorf("missing root path on host %s", sslpassHost.Hostname) + return fmt.Errorf("missing root path on host %s", sslpassHost.Hostname) } fgroup.SSLPassthroughMap.AppendHostname(sslpassHost.Hostname, rootPath.BackendID) fgroup.HTTPSRedirMap.AppendHostname(sslpassHost.Hostname+"/", yesno[sslpassHost.HTTPPassthroughBackend == nil]) @@ -340,19 +346,20 @@ func (c *config) BuildFrontendGroup() (*hatypes.FrontendGroup, error) { } } if err := writeMaps(fgroup.Maps, c.mapsTemplate); err != nil { - return nil, err + return err } for _, f := range frontends { if err := writeMaps(f.Maps, c.mapsTemplate); err != nil { - return nil, err + return err } for _, bind := range f.Binds { if err := writeMaps(bind.Maps, c.mapsTemplate); err != nil { - return nil, err + return err } } } - return fgroup, nil + c.fgroup = fgroup + return nil } func writeMaps(maps *hatypes.HostsMaps, template *template.Config) error { diff --git a/pkg/haproxy/config_test.go b/pkg/haproxy/config_test.go index dba68dcfe..9e786e75b 100644 --- a/pkg/haproxy/config_test.go +++ b/pkg/haproxy/config_test.go @@ -24,11 +24,11 @@ import ( func TestEmptyFrontend(t *testing.T) { c := createConfig(&ha_helper.BindUtilsMock{}, options{}) - if _, err := c.BuildFrontendGroup(); err == nil { + if err := c.BuildFrontendGroup(); err == nil { t.Error("expected error creating empty frontend") } c.AcquireHost("empty") - if _, err := c.BuildFrontendGroup(); err != nil { + if err := c.BuildFrontendGroup(); err != nil { t.Errorf("error creating frontends: %v", err) } } @@ -106,8 +106,8 @@ func TestEqual(t *testing.T) { if !c1.Equals(c2) { t.Error("c1 and c2 should be equals (with hosts)") } - _, err1 := c1.BuildFrontendGroup() - _, err2 := c2.BuildFrontendGroup() + err1 := c1.BuildFrontendGroup() + err2 := c2.BuildFrontendGroup() if err1 != nil { t.Errorf("error building c1: %v", err1) } diff --git a/pkg/haproxy/instance.go b/pkg/haproxy/instance.go index 6f4f80e46..d0201d278 100644 --- a/pkg/haproxy/instance.go +++ b/pkg/haproxy/instance.go @@ -117,6 +117,11 @@ func (i *instance) Update() { i.logger.InfoV(2, "new configuration is empty") return } + if err := i.curConfig.BuildFrontendGroup(); err != nil { + i.logger.Error("error building configuration group: %v", err) + i.clearConfig() + return + } if i.curConfig.Equals(i.oldConfig) { i.logger.InfoV(2, "old and new configurations match, skipping reload") i.clearConfig() diff --git a/rootfs/etc/haproxy/template/haproxy.tmpl b/rootfs/etc/haproxy/template/haproxy.tmpl index 7a59b1e11..8df4bb221 100644 --- a/rootfs/etc/haproxy/template/haproxy.tmpl +++ b/rootfs/etc/haproxy/template/haproxy.tmpl @@ -7,7 +7,7 @@ # # # {{- $cfg := . }} -{{- $fgroup := $cfg.BuildFrontendGroup }} +{{- $fgroup := $cfg.FrontendGroup }} {{- $global := $cfg.Global }} global daemon @@ -65,9 +65,9 @@ defaults maxconn {{ $global.MaxConn }} {{- if $global.DrainSupport }} option persist - {{- if $global.DrainSupportRedispatch }} +{{- if $global.DrainSupportRedispatch }} option redispatch - {{- end }} +{{- end }} {{- else }} option redispatch {{- end }} From 47323cdf54179e42cd187081c5fd9b2bd891ce74 Mon Sep 17 00:00:00 2001 From: Joao Morais Date: Sun, 9 Jun 2019 15:40:13 -0300 Subject: [PATCH 15/30] Cookie affinity annotations --- pkg/converters/ingress/annotations/backend.go | 2 +- .../ingress/annotations/backend_test.go | 8 +-- pkg/converters/ingress/annotations/updater.go | 1 + pkg/converters/ingress/defaults.go | 7 ++- pkg/converters/ingress/types/annotations.go | 3 +- pkg/converters/ingress/types/config.go | 1 + pkg/haproxy/instance_test.go | 63 +++++++++++++++++++ pkg/haproxy/types/types.go | 8 ++- rootfs/etc/haproxy/template/haproxy.tmpl | 12 ++++ 9 files changed, 94 insertions(+), 11 deletions(-) diff --git a/pkg/converters/ingress/annotations/backend.go b/pkg/converters/ingress/annotations/backend.go index f0c8c9791..6340ccc9c 100644 --- a/pkg/converters/ingress/annotations/backend.go +++ b/pkg/converters/ingress/annotations/backend.go @@ -47,7 +47,7 @@ func (c *updater) buildBackendAffinity(d *backData) { } d.backend.Cookie.Name = name d.backend.Cookie.Strategy = strategy - d.backend.Cookie.Key = d.ann.CookieKey + d.backend.Cookie.Dynamic = d.ann.SessionCookieDynamic } func (c *updater) buildBackendAuthHTTP(d *backData) { diff --git a/pkg/converters/ingress/annotations/backend_test.go b/pkg/converters/ingress/annotations/backend_test.go index 681e33679..4319d5fea 100644 --- a/pkg/converters/ingress/annotations/backend_test.go +++ b/pkg/converters/ingress/annotations/backend_test.go @@ -71,14 +71,14 @@ func TestAffinity(t *testing.T) { }, // 6 { - ann: types.BackendAnnotations{Affinity: "cookie", SessionCookieStrategy: "prefix"}, - expCookie: hatypes.Cookie{Name: "INGRESSCOOKIE", Strategy: "prefix"}, + ann: types.BackendAnnotations{Affinity: "cookie", SessionCookieStrategy: "prefix", SessionCookieDynamic: true}, + expCookie: hatypes.Cookie{Name: "INGRESSCOOKIE", Strategy: "prefix", Dynamic: true}, expLogging: "", }, // 7 { - ann: types.BackendAnnotations{Affinity: "cookie", CookieKey: "ha"}, - expCookie: hatypes.Cookie{Name: "INGRESSCOOKIE", Strategy: "insert", Key: "ha"}, + ann: types.BackendAnnotations{Affinity: "cookie", SessionCookieDynamic: false}, + expCookie: hatypes.Cookie{Name: "INGRESSCOOKIE", Strategy: "insert", Dynamic: false}, expLogging: "", }, } diff --git a/pkg/converters/ingress/annotations/updater.go b/pkg/converters/ingress/annotations/updater.go index c793a0a1a..c6b57664b 100644 --- a/pkg/converters/ingress/annotations/updater.go +++ b/pkg/converters/ingress/annotations/updater.go @@ -76,6 +76,7 @@ func (c *updater) UpdateGlobalConfig(global *hatypes.Global, config *ingtypes.Co global.MaxConn = config.MaxConnections global.DrainSupport = config.DrainSupport global.DrainSupportRedispatch = config.DrainSupportRedispatch + global.Cookie.Key = config.CookieKey global.LoadServerState = config.LoadServerState global.StatsSocket = "/var/run/haproxy-stats.sock" c.buildGlobalProc(data) diff --git a/pkg/converters/ingress/defaults.go b/pkg/converters/ingress/defaults.go index bf80b06e6..f3565af7a 100644 --- a/pkg/converters/ingress/defaults.go +++ b/pkg/converters/ingress/defaults.go @@ -28,13 +28,14 @@ const ( func createDefaults() *types.Config { return &types.Config{ ConfigDefaults: types.ConfigDefaults{ - BalanceAlgorithm: "roundrobin", - CookieKey: "Ingress", - HSTS: true, + BalanceAlgorithm: "roundrobin", + CookieKey: "Ingress", + HSTS: true, HSTSIncludeSubdomains: false, HSTSMaxAge: "15768000", HSTSPreload: false, ProxyBodySize: "", + SessionCookieDynamic: true, SSLRedirect: true, TimeoutClient: "50s", TimeoutClientFin: "50s", diff --git a/pkg/converters/ingress/types/annotations.go b/pkg/converters/ingress/types/annotations.go index 6e91de5dc..4df242678 100644 --- a/pkg/converters/ingress/types/annotations.go +++ b/pkg/converters/ingress/types/annotations.go @@ -44,7 +44,6 @@ type BackendAnnotations struct { BlueGreenDeploy string `json:"blue-green-deploy"` BlueGreenMode string `json:"blue-green-mode"` ConfigBackend string `json:"config-backend"` - CookieKey string `json:"cookie-key"` CorsAllowCredentials bool `json:"cors-allow-credentials"` CorsAllowHeaders string `json:"cors-allow-headers"` CorsAllowMethods string `json:"cors-allow-methods"` @@ -70,7 +69,7 @@ type BackendAnnotations struct { SecureBackends bool `json:"secure-backends"` SecureCrtSecret string `json:"secure-crt-secret"` SecureVerifyCASecret string `json:"secure-verify-ca-secret"` - SessionCookieDynamic string `json:"session-cookie-dynamic"` + SessionCookieDynamic bool `json:"session-cookie-dynamic"` SessionCookieName string `json:"session-cookie-name"` SessionCookieStrategy string `json:"session-cookie-strategy"` SSLRedirect bool `json:"ssl-redirect"` diff --git a/pkg/converters/ingress/types/config.go b/pkg/converters/ingress/types/config.go index ab938f92d..a4338a523 100644 --- a/pkg/converters/ingress/types/config.go +++ b/pkg/converters/ingress/types/config.go @@ -25,6 +25,7 @@ type ConfigDefaults struct { HSTSMaxAge string `json:"hsts-max-age"` HSTSPreload bool `json:"hsts-preload"` ProxyBodySize string `json:"proxy-body-size"` + SessionCookieDynamic bool `json:"session-cookie-dynamic"` SSLRedirect bool `json:"ssl-redirect"` TimeoutClient string `json:"timeout-client"` TimeoutClientFin string `json:"timeout-client-fin"` diff --git a/pkg/haproxy/instance_test.go b/pkg/haproxy/instance_test.go index 97c6a29d6..0007581c3 100644 --- a/pkg/haproxy/instance_test.go +++ b/pkg/haproxy/instance_test.go @@ -1132,6 +1132,68 @@ d1.local/ d1_app_8080 c.logger.CompareLogging(defaultLogging) } +func TestAffinity(t *testing.T) { + c := setup(t) + defer c.teardown() + + var h *hatypes.Host + var b *hatypes.Backend + + b = c.config.AcquireBackend("d1", "app", 8080) + b.Endpoints = []*hatypes.Endpoint{endpointS1} + b.Cookie.Name = "ingress-controller" + b.Cookie.Strategy = "insert" + h = c.config.AcquireHost("d1.local") + h.AddPath(b, "/") + + b = c.config.AcquireBackend("d2", "app", 8080) + b.Endpoints = []*hatypes.Endpoint{endpointS21} + b.Cookie.Name = "Ingress" + b.Cookie.Strategy = "prefix" + b.Cookie.Dynamic = true + + h = c.config.AcquireHost("d2.local") + h.AddPath(b, "/") + + c.instance.Update() + c.checkConfig(` +<> +<> +backend d1_app_8080 + mode http + cookie ingress-controller insert indirect nocache httponly + server s1 172.17.0.11:8080 weight 100 cookie s1 +backend d2_app_8080 + mode http + cookie Ingress prefix dynamic + dynamic-cookie-key "Ingress" + server s21 172.17.0.121:8080 weight 100 +backend _error404 + mode http + errorfile 400 /usr/local/etc/haproxy/errors/404.http + http-request deny deny_status 400 +<> +frontend _front_http + mode http + bind :80 + http-request set-var(req.base) base,regsub(:[0-9]+/,/) + http-request redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/_global_https_redir.map,_nomatch) yes } + <> + http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/_global_http_front.map,_nomatch) + use_backend %[var(req.backend)] unless { var(req.backend) _nomatch } + default_backend _error404 +frontend _front001 + mode http + bind :443 ssl alpn h2,http/1.1 crt /var/haproxy/ssl/certs/default.pem + http-request set-var(req.hostbackend) base,lower,regsub(:[0-9]+/,/),map_beg(/etc/haproxy/maps/_front001_host.map,_nomatch) + <> + use_backend %[var(req.hostbackend)] unless { var(req.hostbackend) _nomatch } + default_backend _error404 +`) + + c.logger.CompareLogging(defaultLogging) +} + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * BUILDERS @@ -1205,6 +1267,7 @@ func (c *testConfig) teardown() { func (c *testConfig) configGlobal() { global := c.config.Global() + global.Cookie.Key = "Ingress" global.MaxConn = 2000 global.SSL.Ciphers = "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256" global.SSL.DHParam.Filename = "/var/haproxy/tls/dhparam.pem" diff --git a/pkg/haproxy/types/types.go b/pkg/haproxy/types/types.go index 8388cb6b0..5a283a762 100644 --- a/pkg/haproxy/types/types.go +++ b/pkg/haproxy/types/types.go @@ -24,6 +24,7 @@ type Global struct { Timeout TimeoutConfig SSL SSLConfig ModSecurity ModSecurityConfig + Cookie CookieConfig DrainSupport bool DrainSupportRedispatch bool LoadServerState bool @@ -79,6 +80,11 @@ type ModSecurityConfig struct { Timeout ModSecurityTimeoutConfig } +// CookieConfig ... +type CookieConfig struct { + Key string +} + // ModSecurityTimeoutConfig ... type ModSecurityTimeoutConfig struct { Hello string @@ -288,7 +294,7 @@ type BackendTimeoutConfig struct { type Cookie struct { Name string Strategy string - Key string + Dynamic bool } // HTTPRequest ... diff --git a/rootfs/etc/haproxy/template/haproxy.tmpl b/rootfs/etc/haproxy/template/haproxy.tmpl index 8df4bb221..f68dfe6bd 100644 --- a/rootfs/etc/haproxy/template/haproxy.tmpl +++ b/rootfs/etc/haproxy/template/haproxy.tmpl @@ -177,6 +177,17 @@ backend {{ $backend.ID }} {{- end }} {{- end }} +{{- /*------------------------------------*/}} +{{- $cookie := $backend.Cookie }} +{{- if $cookie.Name }} + cookie {{ $cookie.Name }} {{ $cookie.Strategy }} + {{- if eq $cookie.Strategy "insert" }} indirect nocache httponly{{ end }} + {{- if $cookie.Dynamic }} dynamic{{ end }} +{{- if $cookie.Dynamic }} + dynamic-cookie-key "{{ $global.Cookie.Key }}" +{{- end }} +{{- end }} + {{- /*------------------------------------*/}} {{- range $snippet := $backend.CustomConfig }} {{ $snippet }} @@ -187,6 +198,7 @@ backend {{ $backend.ID }} server {{ $ep.Name }} {{ $ep.IP }}:{{ $ep.Port }} {{- if $ep.Disabled }} disabled{{ end }} {{- "" }} weight {{ $ep.Weight }} + {{- if and ($cookie.Name) (not $cookie.Dynamic) }} cookie {{ $ep.Name }}{{ end }} {{- template "backend" map $backend }} {{- end }} {{- end }} From 9e2c6fa0b3db4fa2be60cbe3a13c30ba6688f0d2 Mon Sep 17 00:00:00 2001 From: Joao Morais Date: Sun, 9 Jun 2019 18:21:37 -0300 Subject: [PATCH 16/30] Add DrainSupport --- pkg/controller/cache.go | 12 ++++ pkg/converters/ingress/annotations/backend.go | 4 ++ .../ingress/annotations/backend_test.go | 21 +++++- pkg/converters/ingress/annotations/updater.go | 4 +- .../ingress/helper_test/cachemock.go | 10 +++ pkg/converters/ingress/ingress.go | 17 ++++- pkg/converters/ingress/ingress_test.go | 72 +++++++++++++++++-- pkg/converters/ingress/types/interfaces.go | 1 + pkg/haproxy/types/types.go | 31 ++++---- rootfs/etc/haproxy/template/haproxy.tmpl | 4 +- 10 files changed, 151 insertions(+), 25 deletions(-) diff --git a/pkg/controller/cache.go b/pkg/controller/cache.go index 2c676cfdb..e55714ad6 100644 --- a/pkg/controller/cache.go +++ b/pkg/controller/cache.go @@ -50,6 +50,18 @@ func (c *cache) GetEndpoints(service *api.Service) (*api.Endpoints, error) { return &ep, err } +func (c *cache) GetTerminatingPods(service *api.Service) ([]*api.Pod, error) { + pods, err := c.listers.Pod.GetTerminatingServicePods(service) + if err != nil { + return []*api.Pod{}, err + } + podRef := make([]*api.Pod, len(pods)) + for i := range pods { + podRef[i] = &pods[i] + } + return podRef, err +} + func (c *cache) GetPod(podName string) (*api.Pod, error) { sname := strings.Split(podName, "/") if len(sname) != 2 { diff --git a/pkg/converters/ingress/annotations/backend.go b/pkg/converters/ingress/annotations/backend.go index 6340ccc9c..1cfbaa65a 100644 --- a/pkg/converters/ingress/annotations/backend.go +++ b/pkg/converters/ingress/annotations/backend.go @@ -167,6 +167,10 @@ func (c *updater) buildBackendBlueGreen(d *backData) { deployWeights = append(deployWeights, dw) } for _, ep := range d.backend.Endpoints { + if ep.Weight == 0 { + // Draining endpoint, remove from blue/green calc + continue + } hasLabel := false if pod, err := c.cache.GetPod(ep.TargetRef); err == nil { for _, dw := range deployWeights { diff --git a/pkg/converters/ingress/annotations/backend_test.go b/pkg/converters/ingress/annotations/backend_test.go index 4319d5fea..87154f3bc 100644 --- a/pkg/converters/ingress/annotations/backend_test.go +++ b/pkg/converters/ingress/annotations/backend_test.go @@ -18,6 +18,7 @@ package annotations import ( "reflect" + "strconv" "strings" "testing" @@ -244,11 +245,20 @@ func TestBlueGreen(t *testing.T) { ep := []*hatypes.Endpoint{} if targets != "" { for _, targetRef := range strings.Split(targets, ",") { + targetWeight := strings.Split(targetRef, "=") + target := targetRef + weight := 1 + if len(targetWeight) == 2 { + target = targetWeight[0] + if w, err := strconv.ParseInt(targetWeight[1], 10, 0); err == nil { + weight = int(w) + } + } ep = append(ep, &hatypes.Endpoint{ IP: "172.17.0.11", Port: 8080, - Weight: 1, - TargetRef: targetRef, + Weight: weight, + TargetRef: target, }) } } @@ -481,6 +491,13 @@ INFO-V(3) blue/green balance label 'v=3' on ingress 'default/ing1' does not refe expWeights: []int{256, 1, 1, 1, 1}, expLogging: "", }, + // 29 + { + ann: buildAnn("v=1=50,v=2=50", "deploy"), + endpoints: buildEndpoints("pod0101-01,pod0101-02,pod0102-01,pod0102-02=0"), + expWeights: []int{1, 1, 2, 0}, + expLogging: "", + }, } for i, test := range testCase { diff --git a/pkg/converters/ingress/annotations/updater.go b/pkg/converters/ingress/annotations/updater.go index c6b57664b..4ff5749d1 100644 --- a/pkg/converters/ingress/annotations/updater.go +++ b/pkg/converters/ingress/annotations/updater.go @@ -74,8 +74,8 @@ func (c *updater) UpdateGlobalConfig(global *hatypes.Global, config *ingtypes.Co global.Syslog.Format = config.SyslogFormat global.Syslog.Tag = config.SyslogTag global.MaxConn = config.MaxConnections - global.DrainSupport = config.DrainSupport - global.DrainSupportRedispatch = config.DrainSupportRedispatch + global.DrainSupport.Drain = config.DrainSupport + global.DrainSupport.Redispatch = config.DrainSupportRedispatch global.Cookie.Key = config.CookieKey global.LoadServerState = config.LoadServerState global.StatsSocket = "/var/run/haproxy-stats.sock" diff --git a/pkg/converters/ingress/helper_test/cachemock.go b/pkg/converters/ingress/helper_test/cachemock.go index b14139355..11dcc82d9 100644 --- a/pkg/converters/ingress/helper_test/cachemock.go +++ b/pkg/converters/ingress/helper_test/cachemock.go @@ -33,6 +33,7 @@ type SecretContent map[string]map[string][]byte type CacheMock struct { SvcList []*api.Service EpList map[string]*api.Endpoints + TermPodList map[string][]*api.Pod PodList map[string]*api.Pod SecretTLSPath map[string]string SecretCAPath map[string]string @@ -62,6 +63,15 @@ func (c *CacheMock) GetEndpoints(service *api.Service) (*api.Endpoints, error) { return nil, fmt.Errorf("could not find endpoints for service '%s'", serviceName) } +// GetTerminatingPods ... +func (c *CacheMock) GetTerminatingPods(service *api.Service) ([]*api.Pod, error) { + serviceName := service.Namespace + "/" + service.Name + if pods, found := c.TermPodList[serviceName]; found { + return pods, nil + } + return []*api.Pod{}, nil +} + // GetPod ... func (c *CacheMock) GetPod(podName string) (*api.Pod, error) { if pod, found := c.PodList[podName]; found { diff --git a/pkg/converters/ingress/ingress.go b/pkg/converters/ingress/ingress.go index 460d4de4b..2f4474a69 100644 --- a/pkg/converters/ingress/ingress.go +++ b/pkg/converters/ingress/ingress.go @@ -247,16 +247,31 @@ func (c *converter) addEndpoints(svc *api.Service, servicePort int, backend *hat } // TODO ServiceTypeExternalName // TODO ServiceUpstream - annotation nao documentada - // TODO DrainSupport for _, subset := range endpoints.Subsets { for _, port := range subset.Ports { if int(port.Port) == servicePort && port.Protocol == api.ProtocolTCP { for _, addr := range subset.Addresses { backend.NewEndpoint(addr.IP, servicePort, addr.TargetRef.Namespace+"/"+addr.TargetRef.Name) } + if c.globalConfig.DrainSupport { + for _, addr := range subset.NotReadyAddresses { + ep := backend.NewEndpoint(addr.IP, servicePort, addr.TargetRef.Namespace+"/"+addr.TargetRef.Name) + ep.Weight = 0 + } + } } } } + if c.globalConfig.DrainSupport { + pods, err := c.cache.GetTerminatingPods(svc) + if err != nil { + return err + } + for _, pod := range pods { + ep := backend.NewEndpoint(pod.Status.PodIP, servicePort, pod.Namespace+"/"+pod.Name) + ep.Weight = 0 + } + } return nil } diff --git a/pkg/converters/ingress/ingress_test.go b/pkg/converters/ingress/ingress_test.go index d366b58ce..dd60d6f5c 100644 --- a/pkg/converters/ingress/ingress_test.go +++ b/pkg/converters/ingress/ingress_test.go @@ -181,6 +181,51 @@ func TestSyncInvalidEndpoint(t *testing.T) { ERROR error adding endpoints of service 'default/echo': could not find endpoints for service 'default/echo'`) } +func TestSyncDrainSupport(t *testing.T) { + c := setup(t) + defer c.teardown() + + svc := c.createSvc1("default/echo", "8080", "172.17.1.101,172.17.1.102") + svcName := svc.Namespace + "/" + svc.Name + ep := c.cache.EpList[svcName] + ss := &ep.Subsets[0] + addr := ss.Addresses + ss.Addresses = []api.EndpointAddress{addr[0]} + ss.NotReadyAddresses = []api.EndpointAddress{addr[1]} + pod := c.createPod1("default/echo-xxxxx", "172.17.1.103") + c.cache.TermPodList[svcName] = []*api.Pod{pod} + + c.SyncDef( + map[string]string{"drain-support": "true"}, + c.createIng1("default/echo", "echo.example.com", "/", "echo:8080"), + ) + + c.compareConfigFront(` +- hostname: echo.example.com + paths: + - path: / + backend: default_echo_8080 +`) + c.compareConfigBack(` +- id: default_echo_8080 + endpoints: + - ip: 172.17.1.101 + port: 8080 + - ip: 172.17.1.102 + port: 8080 + drain: true + - ip: 172.17.1.103 + port: 8080 + drain: true +- id: _default_backend + endpoints: + - ip: 172.17.0.99 + port: 8080 +`) + + c.compareLogging(``) +} + func TestSyncRootPathLast(t *testing.T) { c := setup(t) defer c.teardown() @@ -932,8 +977,9 @@ func setup(t *testing.T) *testConfig { decode: scheme.Codecs.UniversalDeserializer().Decode, hconfig: haproxy.CreateInstance(logger, &ha_helper.BindUtilsMock{}, haproxy.InstanceOptions{}).Config(), cache: &ing_helper.CacheMock{ - SvcList: []*api.Service{}, - EpList: map[string]*api.Endpoints{}, + SvcList: []*api.Service{}, + EpList: map[string]*api.Endpoints{}, + TermPodList: map[string][]*api.Pod{}, SecretTLSPath: map[string]string{ "system/ingress-default": "/tls/tls-default.pem", }, @@ -1039,6 +1085,21 @@ subsets: return svc } +func (c *testConfig) createPod1(name, ip string) *api.Pod { + pname := strings.Split(name, "/") + + pod := c.createObject(` +apiVersion: v1 +kind: Pod +metadata: + name: ` + pname[1] + ` + namespace: ` + pname[0] + ` +status: + podIP: ` + ip).(*api.Pod) + + return pod +} + func (c *testConfig) createSecretTLS1(secretName string) { c.cache.SecretTLSPath[secretName] = "/tls/" + secretName + ".pem" } @@ -1195,8 +1256,9 @@ func (c *testConfig) compareConfigDefaultFront(expected string) { type ( endpointMock struct { - IP string - Port int + IP string + Port int + Drain bool `yaml:",omitempty"` } backendMock struct { ID string @@ -1211,7 +1273,7 @@ func convertBackend(habackends ...*hatypes.Backend) []backendMock { for _, b := range habackends { endpoints := []endpointMock{} for _, e := range b.Endpoints { - endpoints = append(endpoints, endpointMock{IP: e.IP, Port: e.Port}) + endpoints = append(endpoints, endpointMock{IP: e.IP, Port: e.Port, Drain: e.Weight == 0}) } backends = append(backends, backendMock{ ID: b.ID, diff --git a/pkg/converters/ingress/types/interfaces.go b/pkg/converters/ingress/types/interfaces.go index db60ab13f..f2ec1ae90 100644 --- a/pkg/converters/ingress/types/interfaces.go +++ b/pkg/converters/ingress/types/interfaces.go @@ -24,6 +24,7 @@ import ( type Cache interface { GetService(serviceName string) (*api.Service, error) GetEndpoints(service *api.Service) (*api.Endpoints, error) + GetTerminatingPods(service *api.Service) ([]*api.Pod, error) GetPod(podName string) (*api.Pod, error) GetTLSSecretPath(secretName string) (File, error) GetCASecretPath(secretName string) (File, error) diff --git a/pkg/haproxy/types/types.go b/pkg/haproxy/types/types.go index 5a283a762..6efefee76 100644 --- a/pkg/haproxy/types/types.go +++ b/pkg/haproxy/types/types.go @@ -18,19 +18,18 @@ package types // Global ... type Global struct { - Procs ProcsConfig - Syslog SyslogConfig - MaxConn int - Timeout TimeoutConfig - SSL SSLConfig - ModSecurity ModSecurityConfig - Cookie CookieConfig - DrainSupport bool - DrainSupportRedispatch bool - LoadServerState bool - StatsSocket string - CustomConfig []string - CustomDefaults []string + Procs ProcsConfig + Syslog SyslogConfig + MaxConn int + Timeout TimeoutConfig + SSL SSLConfig + ModSecurity ModSecurityConfig + Cookie CookieConfig + DrainSupport DrainConfig + LoadServerState bool + StatsSocket string + CustomConfig []string + CustomDefaults []string } // ProcsConfig ... @@ -85,6 +84,12 @@ type CookieConfig struct { Key string } +// DrainConfig ... +type DrainConfig struct { + Drain bool + Redispatch bool +} + // ModSecurityTimeoutConfig ... type ModSecurityTimeoutConfig struct { Hello string diff --git a/rootfs/etc/haproxy/template/haproxy.tmpl b/rootfs/etc/haproxy/template/haproxy.tmpl index f68dfe6bd..b3eb08c14 100644 --- a/rootfs/etc/haproxy/template/haproxy.tmpl +++ b/rootfs/etc/haproxy/template/haproxy.tmpl @@ -63,9 +63,9 @@ defaults load-server-state-from-file global {{- end }} maxconn {{ $global.MaxConn }} -{{- if $global.DrainSupport }} +{{- if $global.DrainSupport.Drain }} option persist -{{- if $global.DrainSupportRedispatch }} +{{- if $global.DrainSupport.Redispatch }} option redispatch {{- end }} {{- else }} From 8ff9a31a96e9859751313fe8b5bed837643680b7 Mon Sep 17 00:00:00 2001 From: Joao Morais Date: Sun, 9 Jun 2019 21:58:39 -0300 Subject: [PATCH 17/30] Add Cors --- pkg/converters/ingress/annotations/backend.go | 39 ++++++++++++ pkg/converters/ingress/annotations/updater.go | 1 + pkg/converters/ingress/types/annotations.go | 1 + pkg/haproxy/instance_test.go | 62 +++++++++++++++++++ pkg/haproxy/types/types.go | 13 ++++ rootfs/etc/haproxy/template/haproxy.tmpl | 29 +++++++++ 6 files changed, 145 insertions(+) diff --git a/pkg/converters/ingress/annotations/backend.go b/pkg/converters/ingress/annotations/backend.go index 1cfbaa65a..d6a0109e6 100644 --- a/pkg/converters/ingress/annotations/backend.go +++ b/pkg/converters/ingress/annotations/backend.go @@ -18,6 +18,7 @@ package annotations import ( "fmt" + "regexp" "strconv" "strings" @@ -265,3 +266,41 @@ func (c *updater) buildBackendBlueGreen(d *backData) { } } } + +var ( + corsOriginRegex = regexp.MustCompile(`^(https?://[A-Za-z0-9\-\.]*(:[0-9]+)?|\*)?$`) + corsMethodsRegex = regexp.MustCompile(`^([A-Za-z]+,?\s?)+$`) + corsHeadersRegex = regexp.MustCompile(`^([A-Za-z0-9\-\_]+,?\s?)+$`) +) + +func (c *updater) buildBackendCors(d *backData) { + if !d.ann.CorsEnable { + return + } + d.backend.Cors.Enabled = true + if d.ann.CorsAllowOrigin != "" && corsOriginRegex.MatchString(d.ann.CorsAllowOrigin) { + d.backend.Cors.AllowOrigin = d.ann.CorsAllowOrigin + } else { + d.backend.Cors.AllowOrigin = "*" + } + if corsHeadersRegex.MatchString(d.ann.CorsAllowHeaders) { + d.backend.Cors.AllowHeaders = d.ann.CorsAllowHeaders + } else { + d.backend.Cors.AllowHeaders = + "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization" + } + if corsMethodsRegex.MatchString(d.ann.CorsAllowMethods) { + d.backend.Cors.AllowMethods = d.ann.CorsAllowMethods + } else { + d.backend.Cors.AllowMethods = "GET, PUT, POST, DELETE, PATCH, OPTIONS" + } + d.backend.Cors.AllowCredentials = d.ann.CorsAllowCredentials + if d.ann.CorsMaxAge > 0 { + d.backend.Cors.MaxAge = d.ann.CorsMaxAge + } else { + d.backend.Cors.MaxAge = 86400 + } + if corsHeadersRegex.MatchString(d.ann.CorsExposeHeaders) { + d.backend.Cors.ExposeHeaders = d.ann.CorsExposeHeaders + } +} diff --git a/pkg/converters/ingress/annotations/updater.go b/pkg/converters/ingress/annotations/updater.go index 4ff5749d1..e34379abe 100644 --- a/pkg/converters/ingress/annotations/updater.go +++ b/pkg/converters/ingress/annotations/updater.go @@ -114,4 +114,5 @@ func (c *updater) UpdateBackendConfig(backend *hatypes.Backend, ann *ingtypes.Ba c.buildBackendAffinity(data) c.buildBackendAuthHTTP(data) c.buildBackendBlueGreen(data) + c.buildBackendCors(data) } diff --git a/pkg/converters/ingress/types/annotations.go b/pkg/converters/ingress/types/annotations.go index 4df242678..741b2aaa8 100644 --- a/pkg/converters/ingress/types/annotations.go +++ b/pkg/converters/ingress/types/annotations.go @@ -49,6 +49,7 @@ type BackendAnnotations struct { CorsAllowMethods string `json:"cors-allow-methods"` CorsAllowOrigin string `json:"cors-allow-origin"` CorsEnable bool `json:"cors-enable"` + CorsExposeHeaders string `json:"cors-expose-headers"` CorsMaxAge int `json:"cors-max-age"` HSTS bool `json:"hsts"` HSTSIncludeSubdomains bool `json:"hsts-include-subdomains"` diff --git a/pkg/haproxy/instance_test.go b/pkg/haproxy/instance_test.go index 0007581c3..db3d5fdbe 100644 --- a/pkg/haproxy/instance_test.go +++ b/pkg/haproxy/instance_test.go @@ -1194,6 +1194,68 @@ frontend _front001 c.logger.CompareLogging(defaultLogging) } +func TestCors(t *testing.T) { + c := setup(t) + defer c.teardown() + + var h *hatypes.Host + var b *hatypes.Backend + + b = c.config.AcquireBackend("d1", "app", 8080) + b.Endpoints = []*hatypes.Endpoint{endpointS1} + b.Cors.Enabled = true + b.Cors.AllowOrigin = "*" + b.Cors.AllowHeaders = + "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization" + b.Cors.AllowMethods = "GET, PUT, POST, DELETE, PATCH, OPTIONS" + b.Cors.MaxAge = 86400 + h = c.config.AcquireHost("d1.local") + h.AddPath(b, "/") + + c.instance.Update() + c.checkConfig(` +<> +<> +backend d1_app_8080 + mode http + http-request use-service lua.send-response if METH_OPTIONS + http-response set-status 204 reason "No Content" if METH_OPTIONS + http-response set-header Content-Type "text/plain" if METH_OPTIONS + http-response set-header Content-Length "0" if METH_OPTIONS + http-response set-header Access-Control-Allow-Origin "*" if METH_OPTIONS + http-response set-header Access-Control-Allow-Methods "GET, PUT, POST, DELETE, PATCH, OPTIONS" if METH_OPTIONS + http-response set-header Access-Control-Allow-Headers "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization" if METH_OPTIONS + http-response set-header Access-Control-Max-Age "86400" if METH_OPTIONS + http-response set-header Access-Control-Allow-Origin "*" + http-response set-header Access-Control-Allow-Methods "GET, PUT, POST, DELETE, PATCH, OPTIONS" + http-response set-header Access-Control-Allow-Headers "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization" + server s1 172.17.0.11:8080 weight 100 +backend _error404 + mode http + errorfile 400 /usr/local/etc/haproxy/errors/404.http + http-request deny deny_status 400 +<> +frontend _front_http + mode http + bind :80 + http-request set-var(req.base) base,regsub(:[0-9]+/,/) + http-request redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/_global_https_redir.map,_nomatch) yes } + <> + http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/_global_http_front.map,_nomatch) + use_backend %[var(req.backend)] unless { var(req.backend) _nomatch } + default_backend _error404 +frontend _front001 + mode http + bind :443 ssl alpn h2,http/1.1 crt /var/haproxy/ssl/certs/default.pem + http-request set-var(req.hostbackend) base,lower,regsub(:[0-9]+/,/),map_beg(/etc/haproxy/maps/_front001_host.map,_nomatch) + <> + use_backend %[var(req.hostbackend)] unless { var(req.hostbackend) _nomatch } + default_backend _error404 +`) + + c.logger.CompareLogging(defaultLogging) +} + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * BUILDERS diff --git a/pkg/haproxy/types/types.go b/pkg/haproxy/types/types.go index 6efefee76..6c3a55db0 100644 --- a/pkg/haproxy/types/types.go +++ b/pkg/haproxy/types/types.go @@ -233,6 +233,7 @@ type Backend struct { AgentCheck AgentCheck BalanceAlgorithm string Cookie Cookie + Cors Cors CustomConfig []string HealthCheck HealthCheck HTTPRequests []*HTTPRequest @@ -302,6 +303,18 @@ type Cookie struct { Dynamic bool } +// Cors ... +type Cors struct { + Enabled bool + // + AllowCredentials bool + AllowHeaders string + AllowMethods string + AllowOrigin string + ExposeHeaders string + MaxAge int +} + // HTTPRequest ... type HTTPRequest struct { } diff --git a/rootfs/etc/haproxy/template/haproxy.tmpl b/rootfs/etc/haproxy/template/haproxy.tmpl index b3eb08c14..c5e412d61 100644 --- a/rootfs/etc/haproxy/template/haproxy.tmpl +++ b/rootfs/etc/haproxy/template/haproxy.tmpl @@ -177,6 +177,11 @@ backend {{ $backend.ID }} {{- end }} {{- end }} +{{- /*------------------------------------*/}} +{{- if $backend.Cors.Enabled }} + http-request use-service lua.send-response if METH_OPTIONS +{{- end }} + {{- /*------------------------------------*/}} {{- $cookie := $backend.Cookie }} {{- if $cookie.Name }} @@ -193,6 +198,30 @@ backend {{ $backend.ID }} {{ $snippet }} {{- end }} +{{- /*------------------------------------*/}} +{{- if $backend.Cors.Enabled }} +{{- $cors := $backend.Cors }} + http-response set-status 204 reason "No Content" if METH_OPTIONS + http-response set-header Content-Type "text/plain" if METH_OPTIONS + http-response set-header Content-Length "0" if METH_OPTIONS + http-response set-header Access-Control-Allow-Origin "{{ $cors.AllowOrigin }}" if METH_OPTIONS + http-response set-header Access-Control-Allow-Methods "{{ $cors.AllowMethods }}" if METH_OPTIONS + http-response set-header Access-Control-Allow-Headers "{{ $cors.AllowHeaders }}" if METH_OPTIONS +{{- if $cors.AllowCredentials }} + http-response set-header Access-Control-Allow-Credentials "{{ $cors.AllowCredentials }}" if METH_OPTIONS +{{- end }} + http-response set-header Access-Control-Max-Age "{{ $cors.MaxAge }}" if METH_OPTIONS + http-response set-header Access-Control-Allow-Origin "{{ $cors.AllowOrigin }}" + http-response set-header Access-Control-Allow-Methods "{{ $cors.AllowMethods }}" + http-response set-header Access-Control-Allow-Headers "{{ $cors.AllowHeaders }}" +{{- if $cors.AllowCredentials }} + http-response set-header Access-Control-Allow-Credentials "{{ $cors.AllowCredentials }}" +{{- end }} +{{- if $cors.ExposeHeaders }} + http-response set-header Access-Control-Expose-Headers "{{ $cors.ExposeHeaders }}" +{{- end }} +{{- end }} + {{- /*------------------------------------*/}} {{- range $ep := $backend.Endpoints }} server {{ $ep.Name }} {{ $ep.IP }}:{{ $ep.Port }} From ab7cbbc385db386c7c39483525cc8124d02d489e Mon Sep 17 00:00:00 2001 From: Joao Morais Date: Sun, 9 Jun 2019 22:33:36 -0300 Subject: [PATCH 18/30] Reuse config on template tests --- pkg/haproxy/instance_test.go | 105 ++++++++++++++--------------------- 1 file changed, 41 insertions(+), 64 deletions(-) diff --git a/pkg/haproxy/instance_test.go b/pkg/haproxy/instance_test.go index db3d5fdbe..59180cabe 100644 --- a/pkg/haproxy/instance_test.go +++ b/pkg/haproxy/instance_test.go @@ -724,11 +724,7 @@ backend d3_app-http_8080 backend d3_app-ssl_8443 mode http server s41s 172.17.0.141:8443 weight 100 -backend _error404 - mode http - errorfile 400 /usr/local/etc/haproxy/errors/404.http - http-request deny deny_status 400 -<> +<> listen _front__tls mode tcp bind :443 @@ -793,11 +789,7 @@ backend d1_app_8080 backend d2_app_8080 mode http server s21 172.17.0.121:8080 weight 100 -backend _error404 - mode http - errorfile 400 /usr/local/etc/haproxy/errors/404.http - http-request deny deny_status 400 -<> +<> frontend _front_http mode http bind :80 @@ -886,11 +878,7 @@ backend d2_app_8080 backend d3_app_8080 mode http server s31 172.17.0.131:8080 weight 100 -backend _error404 - mode http - errorfile 400 /usr/local/etc/haproxy/errors/404.http - http-request deny deny_status 400 -<> +<> frontend _front_http mode http bind :80 @@ -979,11 +967,7 @@ backend d1_app_8080 backend d2_app_8080 mode http server s21 172.17.0.121:8080 weight 100 -backend _error404 - mode http - errorfile 400 /usr/local/etc/haproxy/errors/404.http - http-request deny deny_status 400 -<> +<> listen _front__tls mode tcp bind :443 @@ -1168,27 +1152,8 @@ backend d2_app_8080 cookie Ingress prefix dynamic dynamic-cookie-key "Ingress" server s21 172.17.0.121:8080 weight 100 -backend _error404 - mode http - errorfile 400 /usr/local/etc/haproxy/errors/404.http - http-request deny deny_status 400 -<> -frontend _front_http - mode http - bind :80 - http-request set-var(req.base) base,regsub(:[0-9]+/,/) - http-request redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/_global_https_redir.map,_nomatch) yes } - <> - http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/_global_http_front.map,_nomatch) - use_backend %[var(req.backend)] unless { var(req.backend) _nomatch } - default_backend _error404 -frontend _front001 - mode http - bind :443 ssl alpn h2,http/1.1 crt /var/haproxy/ssl/certs/default.pem - http-request set-var(req.hostbackend) base,lower,regsub(:[0-9]+/,/),map_beg(/etc/haproxy/maps/_front001_host.map,_nomatch) - <> - use_backend %[var(req.hostbackend)] unless { var(req.hostbackend) _nomatch } - default_backend _error404 +<> +<> `) c.logger.CompareLogging(defaultLogging) @@ -1230,27 +1195,8 @@ backend d1_app_8080 http-response set-header Access-Control-Allow-Methods "GET, PUT, POST, DELETE, PATCH, OPTIONS" http-response set-header Access-Control-Allow-Headers "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization" server s1 172.17.0.11:8080 weight 100 -backend _error404 - mode http - errorfile 400 /usr/local/etc/haproxy/errors/404.http - http-request deny deny_status 400 -<> -frontend _front_http - mode http - bind :80 - http-request set-var(req.base) base,regsub(:[0-9]+/,/) - http-request redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/_global_https_redir.map,_nomatch) yes } - <> - http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/_global_http_front.map,_nomatch) - use_backend %[var(req.backend)] unless { var(req.backend) _nomatch } - default_backend _error404 -frontend _front001 - mode http - bind :443 ssl alpn h2,http/1.1 crt /var/haproxy/ssl/certs/default.pem - http-request set-var(req.hostbackend) base,lower,regsub(:[0-9]+/,/),map_beg(/etc/haproxy/maps/_front001_host.map,_nomatch) - <> - use_backend %[var(req.hostbackend)] unless { var(req.hostbackend) _nomatch } - default_backend _error404 +<> +<> `) c.logger.CompareLogging(defaultLogging) @@ -1450,13 +1396,44 @@ backend _error496 mode http errorfile 400 /usr/local/etc/haproxy/errors/496.http http-request deny deny_status 400`, + "<>": `backend _error404 + mode http + errorfile 400 /usr/local/etc/haproxy/errors/404.http + http-request deny deny_status 400 +<>`, " <>": ` http-request del-header X-SSL-Client-CN http-request del-header X-SSL-Client-DN http-request del-header X-SSL-Client-SHA1 http-request del-header X-SSL-Client-Cert`, + "<>": `frontend _front_http + mode http + bind :80 + http-request set-var(req.base) base,regsub(:[0-9]+/,/) + http-request redirect scheme https if { var(req.base),map_beg(/etc/haproxy/maps/_global_https_redir.map,_nomatch) yes } + <> + http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/_global_http_front.map,_nomatch) + use_backend %[var(req.backend)] unless { var(req.backend) _nomatch } + default_backend _error404 +frontend _front001 + mode http + bind :443 ssl alpn h2,http/1.1 crt /var/haproxy/ssl/certs/default.pem + http-request set-var(req.hostbackend) base,lower,regsub(:[0-9]+/,/),map_beg(/etc/haproxy/maps/_front001_host.map,_nomatch) + <> + use_backend %[var(req.hostbackend)] unless { var(req.hostbackend) _nomatch } + default_backend _error404`, } - for old, new := range replace { - expected = strings.Replace(expected, old, new, -1) + for { + changed := false + for old, new := range replace { + after := strings.Replace(expected, old, new, -1) + if after != expected { + changed = true + } + expected = after + } + if !changed { + break + } } c.compareText("haproxy.cfg", actual, expected) } From 96b7d09aa81ea0d93b5dd341735f6491b689aa33 Mon Sep 17 00:00:00 2001 From: Joao Morais Date: Mon, 10 Jun 2019 11:26:37 -0300 Subject: [PATCH 19/30] Add HSTS --- pkg/converters/ingress/annotations/updater.go | 4 ++ pkg/haproxy/instance_test.go | 37 +++++++++++++++++-- pkg/haproxy/types/types.go | 9 +++++ rootfs/etc/haproxy/template/haproxy.tmpl | 24 +++++++++--- 4 files changed, 65 insertions(+), 9 deletions(-) diff --git a/pkg/converters/ingress/annotations/updater.go b/pkg/converters/ingress/annotations/updater.go index e34379abe..16c3b5135 100644 --- a/pkg/converters/ingress/annotations/updater.go +++ b/pkg/converters/ingress/annotations/updater.go @@ -107,6 +107,10 @@ func (c *updater) UpdateBackendConfig(backend *hatypes.Backend, ann *ingtypes.Ba } // TODO check ModeTCP with HTTP annotations backend.BalanceAlgorithm = ann.BalanceAlgorithm + backend.HSTS.Enabled = ann.HSTS + backend.HSTS.MaxAge = ann.HSTSMaxAge + backend.HSTS.Preload = ann.HSTSPreload + backend.HSTS.Subdomains = ann.HSTSIncludeSubdomains backend.MaxConnServer = ann.MaxconnServer backend.ProxyBodySize = ann.ProxyBodySize backend.SSLRedirect = ann.SSLRedirect diff --git a/pkg/haproxy/instance_test.go b/pkg/haproxy/instance_test.go index 59180cabe..bc6ee42e0 100644 --- a/pkg/haproxy/instance_test.go +++ b/pkg/haproxy/instance_test.go @@ -455,9 +455,9 @@ backend d_app_8080 server s21 172.17.0.121:8080 weight 100 backend d_appca_8080 mode http - http-request set-header X-SSL-Client-CN %{+Q}[ssl_c_s_dn(cn)] if ssl_fc - http-request set-header X-SSL-Client-DN %{+Q}[ssl_c_s_dn] if ssl_fc - http-request set-header X-SSL-Client-SHA1 %{+Q}[ssl_c_sha1,hex] if ssl_fc + http-request set-header X-SSL-Client-CN %{+Q}[ssl_c_s_dn(cn)] if { ssl_fc } + http-request set-header X-SSL-Client-DN %{+Q}[ssl_c_s_dn] if { ssl_fc } + http-request set-header X-SSL-Client-SHA1 %{+Q}[ssl_c_sha1,hex] if { ssl_fc } server s1 172.17.0.11:8080 weight 100 backend _default_backend mode http @@ -1202,6 +1202,37 @@ backend d1_app_8080 c.logger.CompareLogging(defaultLogging) } +func TestHSTS(t *testing.T) { + c := setup(t) + defer c.teardown() + + var h *hatypes.Host + var b *hatypes.Backend + + b = c.config.AcquireBackend("d1", "app", 8080) + b.Endpoints = []*hatypes.Endpoint{endpointS1} + b.HSTS.Enabled = true + b.HSTS.MaxAge = 15768000 + b.HSTS.Preload = true + b.HSTS.Subdomains = true + h = c.config.AcquireHost("d1.local") + h.AddPath(b, "/") + + c.instance.Update() + c.checkConfig(` +<> +<> +backend d1_app_8080 + mode http + http-response set-header Strict-Transport-Security "max-age=15768000; includeSubDomains; preload" if { ssl_fc } + server s1 172.17.0.11:8080 weight 100 +<> +<> +`) + + c.logger.CompareLogging(defaultLogging) +} + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * BUILDERS diff --git a/pkg/haproxy/types/types.go b/pkg/haproxy/types/types.go index 6c3a55db0..d89df9fa5 100644 --- a/pkg/haproxy/types/types.go +++ b/pkg/haproxy/types/types.go @@ -237,6 +237,7 @@ type Backend struct { CustomConfig []string HealthCheck HealthCheck HTTPRequests []*HTTPRequest + HSTS HSTS MaxConnServer int MaxQueueServer int ModeTCP bool @@ -319,6 +320,14 @@ type Cors struct { type HTTPRequest struct { } +// HSTS ... +type HSTS struct { + Enabled bool + MaxAge int + Subdomains bool + Preload bool +} + // Userlist ... type Userlist struct { Name string diff --git a/rootfs/etc/haproxy/template/haproxy.tmpl b/rootfs/etc/haproxy/template/haproxy.tmpl index c5e412d61..53ff941d5 100644 --- a/rootfs/etc/haproxy/template/haproxy.tmpl +++ b/rootfs/etc/haproxy/template/haproxy.tmpl @@ -167,13 +167,15 @@ backend {{ $backend.ID }} timeout tunnel {{ $timeout.Tunnel }} {{- end }} +{{- if not $backend.ModeTCP }} + {{- /*------------------------------------*/}} {{- if $backend.SSL.HasTLSAuth }} - http-request set-header {{ $global.SSL.HeadersPrefix }}-Client-CN %{+Q}[ssl_c_s_dn(cn)]{{ if not $backend.SSLRedirect }} if ssl_fc{{ end }} - http-request set-header {{ $global.SSL.HeadersPrefix }}-Client-DN %{+Q}[ssl_c_s_dn]{{ if not $backend.SSLRedirect }} if ssl_fc{{ end }} - http-request set-header {{ $global.SSL.HeadersPrefix }}-Client-SHA1 %{+Q}[ssl_c_sha1,hex]{{ if not $backend.SSLRedirect }} if ssl_fc{{ end }} + http-request set-header {{ $global.SSL.HeadersPrefix }}-Client-CN %{+Q}[ssl_c_s_dn(cn)]{{ if not $backend.SSLRedirect }} if { ssl_fc }{{ end }} + http-request set-header {{ $global.SSL.HeadersPrefix }}-Client-DN %{+Q}[ssl_c_s_dn]{{ if not $backend.SSLRedirect }} if { ssl_fc }{{ end }} + http-request set-header {{ $global.SSL.HeadersPrefix }}-Client-SHA1 %{+Q}[ssl_c_sha1,hex]{{ if not $backend.SSLRedirect }} if { ssl_fc }{{ end }} {{- if $backend.SSL.AddCertHeader }} - http-request set-header {{ $global.SSL.HeadersPrefix }}-Client-Cert %{+Q}[ssl_c_der,base64]{{ if not $backend.SSLRedirect }} if ssl_fc{{ end }} + http-request set-header {{ $global.SSL.HeadersPrefix }}-Client-Cert %{+Q}[ssl_c_der,base64]{{ if not $backend.SSLRedirect }} if { ssl_fc }{{ end }} {{- end }} {{- end }} @@ -183,8 +185,8 @@ backend {{ $backend.ID }} {{- end }} {{- /*------------------------------------*/}} +{{- if $backend.Cookie.Name }} {{- $cookie := $backend.Cookie }} -{{- if $cookie.Name }} cookie {{ $cookie.Name }} {{ $cookie.Strategy }} {{- if eq $cookie.Strategy "insert" }} indirect nocache httponly{{ end }} {{- if $cookie.Dynamic }} dynamic{{ end }} @@ -198,6 +200,14 @@ backend {{ $backend.ID }} {{ $snippet }} {{- end }} +{{- /*------------------------------------*/}} +{{- if $backend.HSTS.Enabled }} +{{- $hsts := $backend.HSTS }} + http-response set-header Strict-Transport-Security "max-age={{ $hsts.MaxAge }} + {{- if $hsts.Subdomains }}; includeSubDomains{{ end }} + {{- if $hsts.Preload }}; preload{{ end }}"{{ if not $backend.SSLRedirect }} if { ssl_fc }{{ end }} +{{- end }} + {{- /*------------------------------------*/}} {{- if $backend.Cors.Enabled }} {{- $cors := $backend.Cors }} @@ -222,12 +232,14 @@ backend {{ $backend.ID }} {{- end }} {{- end }} +{{- end }}{{/*** $backend.ModeTCP ***/}} + {{- /*------------------------------------*/}} {{- range $ep := $backend.Endpoints }} server {{ $ep.Name }} {{ $ep.IP }}:{{ $ep.Port }} {{- if $ep.Disabled }} disabled{{ end }} {{- "" }} weight {{ $ep.Weight }} - {{- if and ($cookie.Name) (not $cookie.Dynamic) }} cookie {{ $ep.Name }}{{ end }} + {{- if and (not $backend.ModeTCP) ($backend.Cookie.Name) (not $backend.Cookie.Dynamic) }} cookie {{ $ep.Name }}{{ end }} {{- template "backend" map $backend }} {{- end }} {{- end }} From fc56ff978bc1b8bf29b9374faa55a7ae045fd27b Mon Sep 17 00:00:00 2001 From: Joao Morais Date: Mon, 10 Jun 2019 22:27:45 -0300 Subject: [PATCH 20/30] Add named service port --- pkg/converters/ingress/annotations/host.go | 2 +- pkg/converters/ingress/ingress.go | 64 +++++++++++----- pkg/converters/ingress/ingress_test.go | 83 ++++++++++++++++++++- pkg/converters/ingress/types/annotations.go | 2 +- pkg/haproxy/config.go | 14 ++-- pkg/haproxy/config_test.go | 12 +-- pkg/haproxy/instance_test.go | 62 +++++++-------- pkg/haproxy/types/types.go | 2 +- 8 files changed, 174 insertions(+), 67 deletions(-) diff --git a/pkg/converters/ingress/annotations/host.go b/pkg/converters/ingress/annotations/host.go index b0d45eb6c..086adc5e1 100644 --- a/pkg/converters/ingress/annotations/host.go +++ b/pkg/converters/ingress/annotations/host.go @@ -48,7 +48,7 @@ func (c *updater) buildHostSSLPassthrough(d *hostData) { c.logger.Warn("ignoring path '%s' from '%s': ssl-passthrough only support root path", path.Path, d.ann.Source) } } - if d.ann.SSLPassthroughHTTPPort != 0 { + if d.ann.SSLPassthroughHTTPPort != "" { httpBackend := c.haproxy.FindBackend(rootPath.Backend.Namespace, rootPath.Backend.Name, d.ann.SSLPassthroughHTTPPort) d.host.HTTPPassthroughBackend = httpBackend } diff --git a/pkg/converters/ingress/ingress.go b/pkg/converters/ingress/ingress.go index 2f4474a69..4c9ad9ef2 100644 --- a/pkg/converters/ingress/ingress.go +++ b/pkg/converters/ingress/ingress.go @@ -18,10 +18,12 @@ package ingress import ( "fmt" + "strconv" "strings" api "k8s.io/api/core/v1" extensions "k8s.io/api/extensions/v1beta1" + "k8s.io/apimachinery/pkg/util/intstr" "github.com/jcmoraisjr/haproxy-ingress/pkg/converters/ingress/annotations" ingtypes "github.com/jcmoraisjr/haproxy-ingress/pkg/converters/ingress/types" @@ -50,7 +52,7 @@ func NewIngressConverter(options *ingtypes.ConverterOptions, haproxy haproxy.Con } haproxy.ConfigDefaultX509Cert(options.DefaultSSLFile.Filename) if options.DefaultBackend != "" { - if backend, err := c.addBackend(options.DefaultBackend, 0, &ingtypes.BackendAnnotations{}); err == nil { + if backend, err := c.addBackend(options.DefaultBackend, "", &ingtypes.BackendAnnotations{}); err == nil { haproxy.ConfigDefaultBackend(backend) } else { c.logger.Error("error reading default service: %v", err) @@ -154,7 +156,7 @@ func (c *converter) syncAnnotations() { } } -func (c *converter) addDefaultHostBackend(fullSvcName string, svcPort int, ingFrontAnn *ingtypes.HostAnnotations, ingBackAnn *ingtypes.BackendAnnotations) error { +func (c *converter) addDefaultHostBackend(fullSvcName, svcPort string, ingFrontAnn *ingtypes.HostAnnotations, ingBackAnn *ingtypes.BackendAnnotations) error { if fr := c.haproxy.FindHost("*"); fr != nil { if fr.FindPath("/") != nil { return fmt.Errorf("path / was already defined on default host") @@ -182,7 +184,7 @@ func (c *converter) addHost(hostname string, ingAnn *ingtypes.HostAnnotations) * return host } -func (c *converter) addBackend(fullSvcName string, svcPort int, ingAnn *ingtypes.BackendAnnotations) (*hatypes.Backend, error) { +func (c *converter) addBackend(fullSvcName, svcPort string, ingAnn *ingtypes.BackendAnnotations) (*hatypes.Backend, error) { svc, err := c.cache.GetService(fullSvcName) if err != nil { return nil, err @@ -190,17 +192,20 @@ func (c *converter) addBackend(fullSvcName string, svcPort int, ingAnn *ingtypes ssvcName := strings.Split(fullSvcName, "/") namespace := ssvcName[0] svcName := ssvcName[1] - if svcPort == 0 { + if svcPort == "" { // if the port wasn't specified, take the first one // from the api.Service object - // TODO named port - svcPort = svc.Spec.Ports[0].TargetPort.IntValue() + svcPort = svc.Spec.Ports[0].TargetPort.String() } - backend := c.haproxy.AcquireBackend(namespace, svcName, svcPort) + epport := findServicePort(svc, svcPort) + if epport.String() == "" { + return nil, fmt.Errorf("port not found: '%s'", svcPort) + } + backend := c.haproxy.AcquireBackend(namespace, svcName, epport.String()) ann, found := c.backendAnnotations[backend] if !found { // New backend, configure endpoints and svc annotations - if err := c.addEndpoints(svc, svcPort, backend); err != nil { + if err := c.addEndpoints(svc, epport, backend); err != nil { c.logger.Error("error adding endpoints of service '%s': %v", fullSvcName, err) } // Initialize with service annotations, giving precedence @@ -214,16 +219,39 @@ func (c *converter) addBackend(fullSvcName string, svcPort int, ingAnn *ingtypes // Merging Ingress annotations skipped, _ := utils.UpdateStruct(c.globalConfig.ConfigDefaults, ingAnn, ann) if len(skipped) > 0 { - c.logger.Info("skipping backend '%s/%s:%d' annotation(s) from %v due to conflict: %v", + c.logger.Info("skipping backend '%s/%s:%s' annotation(s) from %v due to conflict: %v", backend.Namespace, backend.Name, backend.Port, ingAnn.Source, skipped) } return backend, nil } +func findServicePort(svc *api.Service, servicePort string) intstr.IntOrString { + for _, port := range svc.Spec.Ports { + if port.Name == servicePort { + return port.TargetPort + } + } + for _, port := range svc.Spec.Ports { + if port.TargetPort.String() == servicePort { + return port.TargetPort + } + } + svcPortNumber, err := strconv.ParseInt(servicePort, 10, 0) + if err != nil { + return intstr.FromString("") + } + for _, port := range svc.Spec.Ports { + if port.Port == int32(svcPortNumber) { + return port.TargetPort + } + } + return intstr.FromString("") +} + func (c *converter) addHTTPPassthrough(fullSvcName string, ingFrontAnn *ingtypes.HostAnnotations, ingBackAnn *ingtypes.BackendAnnotations) { // a very specific use case of pre-parsing annotations: // need to add a backend if ssl-passthrough-http-port assigned - if ingFrontAnn.SSLPassthrough && ingFrontAnn.SSLPassthroughHTTPPort != 0 { + if ingFrontAnn.SSLPassthrough && ingFrontAnn.SSLPassthroughHTTPPort != "" { c.addBackend(fullSvcName, ingFrontAnn.SSLPassthroughHTTPPort, ingBackAnn) } } @@ -240,22 +268,24 @@ func (c *converter) addTLS(namespace, secretName string) ingtypes.File { return c.options.DefaultSSLFile } -func (c *converter) addEndpoints(svc *api.Service, servicePort int, backend *hatypes.Backend) error { +func (c *converter) addEndpoints(svc *api.Service, svcPort intstr.IntOrString, backend *hatypes.Backend) error { endpoints, err := c.cache.GetEndpoints(svc) if err != nil { return err } // TODO ServiceTypeExternalName // TODO ServiceUpstream - annotation nao documentada + // TODO svcPort.IntValue() doesn't work if svc.targetPort is a pod's named port for _, subset := range endpoints.Subsets { for _, port := range subset.Ports { - if int(port.Port) == servicePort && port.Protocol == api.ProtocolTCP { + ssport := int(port.Port) + if ssport == svcPort.IntValue() && port.Protocol == api.ProtocolTCP { for _, addr := range subset.Addresses { - backend.NewEndpoint(addr.IP, servicePort, addr.TargetRef.Namespace+"/"+addr.TargetRef.Name) + backend.NewEndpoint(addr.IP, ssport, addr.TargetRef.Namespace+"/"+addr.TargetRef.Name) } if c.globalConfig.DrainSupport { for _, addr := range subset.NotReadyAddresses { - ep := backend.NewEndpoint(addr.IP, servicePort, addr.TargetRef.Namespace+"/"+addr.TargetRef.Name) + ep := backend.NewEndpoint(addr.IP, ssport, addr.TargetRef.Namespace+"/"+addr.TargetRef.Name) ep.Weight = 0 } } @@ -268,7 +298,7 @@ func (c *converter) addEndpoints(svc *api.Service, servicePort int, backend *hat return err } for _, pod := range pods { - ep := backend.NewEndpoint(pod.Status.PodIP, servicePort, pod.Namespace+"/"+pod.Name) + ep := backend.NewEndpoint(pod.Status.PodIP, svcPort.IntValue(), pod.Namespace+"/"+pod.Name) ep.Weight = 0 } } @@ -297,8 +327,8 @@ func (c *converter) readAnnotations(source *ingtypes.Source, annotations map[str return frontAnn, backAnn } -func readServiceNamePort(backend *extensions.IngressBackend) (string, int) { +func readServiceNamePort(backend *extensions.IngressBackend) (string, string) { serviceName := backend.ServiceName - servicePort := backend.ServicePort.IntValue() + servicePort := backend.ServicePort.String() return serviceName, servicePort } diff --git a/pkg/converters/ingress/ingress_test.go b/pkg/converters/ingress/ingress_test.go index dd60d6f5c..81e852c46 100644 --- a/pkg/converters/ingress/ingress_test.go +++ b/pkg/converters/ingress/ingress_test.go @@ -82,6 +82,75 @@ func TestSyncDefaultSvcNotFound(t *testing.T) { ERROR error reading default service: service not found: 'system/default'`) } +func TestSyncSvcPortNotFound(t *testing.T) { + c := setup(t) + defer c.teardown() + + c.createSvc1Auto() + c.Sync(c.createIng1("default/echo", "echo.example.com", "/", "echo:non")) + + c.compareConfigFront(` +- hostname: echo.example.com + paths: [] +`) + + c.compareConfigBack(` +- id: _default_backend + endpoints: + - ip: 172.17.0.99 + port: 8080 +`) + + c.compareLogging(` +WARN skipping backend config of ingress 'default/echo': port not found: 'non' +`) +} + +func TestSyncSvcNamedPort(t *testing.T) { + c := setup(t) + defer c.teardown() + + c.createSvc1("default/echo", "httpsvc:1001:8080", "172.17.1.101") + c.Sync( + c.createIng1("default/echo1", "echo1.example.com", "/", "echo:httpsvc"), + c.createIng1("default/echo2", "echo2.example.com", "/", "echo:1001"), + c.createIng1("default/echo3", "echo3.example.com", "/", "echo:8080"), + c.createIng1("default/echo4", "echo4.example.com", "/", "echo:9000"), + ) + + c.compareConfigFront(` +- hostname: echo1.example.com + paths: + - path: / + backend: default_echo_8080 +- hostname: echo2.example.com + paths: + - path: / + backend: default_echo_8080 +- hostname: echo3.example.com + paths: + - path: / + backend: default_echo_8080 +- hostname: echo4.example.com + paths: [] +`) + + c.compareConfigBack(` +- id: default_echo_8080 + endpoints: + - ip: 172.17.1.101 + port: 8080 +- id: _default_backend + endpoints: + - ip: 172.17.0.99 + port: 8080 +`) + + c.compareLogging(` +WARN skipping backend config of ingress 'default/echo4': port not found: '9000' +`) +} + func TestSyncSingle(t *testing.T) { c := setup(t) defer c.teardown() @@ -1042,6 +1111,12 @@ func (c *testConfig) createSvc1Ann(name, port, endpoints string, ann map[string] func (c *testConfig) createSvc1(name, port, endpoints string) *api.Service { sname := strings.Split(name, "/") + sport := strings.Split(port, ":") + if len(sport) < 2 { + sport = []string{"", port, port} + } else if len(sport) < 3 { + sport = []string{sport[0], sport[1], sport[1]} + } svc := c.createObject(` apiVersion: v1 @@ -1051,8 +1126,9 @@ metadata: namespace: ` + sname[0] + ` spec: ports: - - port: ` + port + ` - targetPort: ` + port).(*api.Service) + - name: ` + sport[0] + ` + port: ` + sport[1] + ` + targetPort: ` + sport[2]).(*api.Service) c.cache.SvcList = append(c.cache.SvcList, svc) @@ -1065,7 +1141,8 @@ metadata: subsets: - addresses: [] ports: - - port: ` + port + ` + - name: ` + sport[0] + ` + port: ` + sport[2] + ` protocol: TCP`).(*api.Endpoints) addr := []api.EndpointAddress{} diff --git a/pkg/converters/ingress/types/annotations.go b/pkg/converters/ingress/types/annotations.go index 741b2aaa8..2628a0373 100644 --- a/pkg/converters/ingress/types/annotations.go +++ b/pkg/converters/ingress/types/annotations.go @@ -26,7 +26,7 @@ type HostAnnotations struct { ServerAlias string `json:"server-alias"` ServerAliasRegex string `json:"server-alias-regex"` SSLPassthrough bool `json:"ssl-passthrough"` - SSLPassthroughHTTPPort int `json:"ssl-passthrough-http-port"` + SSLPassthroughHTTPPort string `json:"ssl-passthrough-http-port"` TimeoutClient string `json:"timeout-client"` TimeoutClientFin string `json:"timeout-client-fin"` } diff --git a/pkg/haproxy/config.go b/pkg/haproxy/config.go index 8d0ec449f..7911303c2 100644 --- a/pkg/haproxy/config.go +++ b/pkg/haproxy/config.go @@ -29,8 +29,8 @@ import ( type Config interface { AcquireHost(hostname string) *hatypes.Host FindHost(hostname string) *hatypes.Host - AcquireBackend(namespace, name string, port int) *hatypes.Backend - FindBackend(namespace, name string, port int) *hatypes.Backend + AcquireBackend(namespace, name, port string) *hatypes.Backend + FindBackend(namespace, name, port string) *hatypes.Backend ConfigDefaultBackend(defaultBackend *hatypes.Backend) ConfigDefaultX509Cert(filename string) AddUserlist(name string, users []hatypes.User) *hatypes.Userlist @@ -124,7 +124,7 @@ func (c *config) sortBackends() { }) } -func (c *config) AcquireBackend(namespace, name string, port int) *hatypes.Backend { +func (c *config) AcquireBackend(namespace, name, port string) *hatypes.Backend { if backend := c.FindBackend(namespace, name, port); backend != nil { return backend } @@ -134,7 +134,7 @@ func (c *config) AcquireBackend(namespace, name string, port int) *hatypes.Backe return backend } -func (c *config) FindBackend(namespace, name string, port int) *hatypes.Backend { +func (c *config) FindBackend(namespace, name, port string) *hatypes.Backend { for _, b := range c.backends { if b.Namespace == namespace && b.Name == name && b.Port == port { return b @@ -143,7 +143,7 @@ func (c *config) FindBackend(namespace, name string, port int) *hatypes.Backend return nil } -func createBackend(namespace, name string, port int) *hatypes.Backend { +func createBackend(namespace, name, port string) *hatypes.Backend { return &hatypes.Backend{ ID: buildID(namespace, name, port), Namespace: namespace, @@ -153,8 +153,8 @@ func createBackend(namespace, name string, port int) *hatypes.Backend { } } -func buildID(namespace, name string, port int) string { - return fmt.Sprintf("%s_%s_%d", namespace, name, port) +func buildID(namespace, name, port string) string { + return fmt.Sprintf("%s_%s_%s", namespace, name, port) } func (c *config) ConfigDefaultBackend(defaultBackend *hatypes.Backend) { diff --git a/pkg/haproxy/config_test.go b/pkg/haproxy/config_test.go index 9e786e75b..3beddb4ec 100644 --- a/pkg/haproxy/config_test.go +++ b/pkg/haproxy/config_test.go @@ -58,11 +58,11 @@ func TestBuildID(t *testing.T) { testCases := []struct { namespace string name string - port int + port string expected string }{ { - "default", "echo", 8080, "default_echo_8080", + "default", "echo", "8080", "default_echo_8080", }, } for _, test := range testCases { @@ -86,13 +86,13 @@ func TestEqual(t *testing.T) { if !c1.Equals(c2) { t.Error("c1 and c2 should be equals (default cert)") } - b1 := c1.AcquireBackend("d", "app1", 8080) - c1.AcquireBackend("d", "app2", 8080) + b1 := c1.AcquireBackend("d", "app1", "8080") + c1.AcquireBackend("d", "app2", "8080") if c1.Equals(c2) { t.Error("c1 and c2 should not be equals (backends on one side)") } - c2.AcquireBackend("d", "app2", 8080) - b2 := c2.AcquireBackend("d", "app1", 8080) + c2.AcquireBackend("d", "app2", "8080") + b2 := c2.AcquireBackend("d", "app1", "8080") if !c1.Equals(c2) { t.Error("c1 and c2 should be equals (with backends)") } diff --git a/pkg/haproxy/instance_test.go b/pkg/haproxy/instance_test.go index bc6ee42e0..b8d904f5d 100644 --- a/pkg/haproxy/instance_test.go +++ b/pkg/haproxy/instance_test.go @@ -41,7 +41,7 @@ func TestInstanceEmpty(t *testing.T) { c := setup(t) defer c.teardown() - c.config.AcquireHost("empty").AddPath(c.config.AcquireBackend("default", "empty", 8080), "/") + c.config.AcquireHost("empty").AddPath(c.config.AcquireBackend("default", "empty", "8080"), "/") c.instance.Update() c.checkConfig(` @@ -117,21 +117,21 @@ func TestInstanceDefaultHost(t *testing.T) { c := setup(t) defer c.teardown() - def := c.config.AcquireBackend("default", "default-backend", 8080) + def := c.config.AcquireBackend("default", "default-backend", "8080") def.Endpoints = []*hatypes.Endpoint{endpointS0} c.config.ConfigDefaultBackend(def) var h *hatypes.Host var b *hatypes.Backend - b = c.config.AcquireBackend("d1", "app", 8080) + b = c.config.AcquireBackend("d1", "app", "8080") h = c.config.AcquireHost("*") h.AddPath(b, "/") b.SSLRedirect = true b.Endpoints = []*hatypes.Endpoint{endpointS1} h.VarNamespace = true - b = c.config.AcquireBackend("d2", "app", 8080) + b = c.config.AcquireBackend("d2", "app", "8080") h = c.config.AcquireHost("d2.local") h.AddPath(b, "/app") b.SSLRedirect = true @@ -191,14 +191,14 @@ func TestInstanceSingleFrontendSingleBind(t *testing.T) { c := setup(t) defer c.teardown() - def := c.config.AcquireBackend("default", "default-backend", 8080) + def := c.config.AcquireBackend("default", "default-backend", "8080") def.Endpoints = []*hatypes.Endpoint{endpointS0} c.config.ConfigDefaultBackend(def) var h *hatypes.Host var b *hatypes.Backend - b = c.config.AcquireBackend("d1", "app", 8080) + b = c.config.AcquireBackend("d1", "app", "8080") h = c.config.AcquireHost("d1.local") h.AddPath(b, "/") b.SSLRedirect = true @@ -207,7 +207,7 @@ func TestInstanceSingleFrontendSingleBind(t *testing.T) { h.TLS.TLSFilename = "/var/haproxy/ssl/certs/d1.pem" h.TLS.TLSHash = "1" - b = c.config.AcquireBackend("d2", "app", 8080) + b = c.config.AcquireBackend("d2", "app", "8080") h = c.config.AcquireHost("d2.local") h.AddPath(b, "/app") b.SSLRedirect = true @@ -278,14 +278,14 @@ func TestInstanceSingleFrontendTwoBindsCA(t *testing.T) { c := setup(t) defer c.teardown() - def := c.config.AcquireBackend("default", "default-backend", 8080) + def := c.config.AcquireBackend("default", "default-backend", "8080") def.Endpoints = []*hatypes.Endpoint{endpointS0} c.config.ConfigDefaultBackend(def) var h *hatypes.Host var b *hatypes.Backend - b = c.config.AcquireBackend("d", "app", 8080) + b = c.config.AcquireBackend("d", "app", "8080") h = c.config.AcquireHost("d1.local") h.AddPath(b, "/") b.SSLRedirect = true @@ -400,14 +400,14 @@ func TestInstanceTwoFrontendsThreeBindsCA(t *testing.T) { c := setup(t) defer c.teardown() - def := c.config.AcquireBackend("default", "default-backend", 8080) + def := c.config.AcquireBackend("default", "default-backend", "8080") def.Endpoints = []*hatypes.Endpoint{endpointS0} c.config.ConfigDefaultBackend(def) var h *hatypes.Host var b *hatypes.Backend - b = c.config.AcquireBackend("d", "appca", 8080) + b = c.config.AcquireBackend("d", "appca", "8080") h = c.config.AcquireHost("d1.local") h.AddPath(b, "/") b.Endpoints = []*hatypes.Endpoint{endpointS1} @@ -436,7 +436,7 @@ func TestInstanceTwoFrontendsThreeBindsCA(t *testing.T) { h.TLS.CAHash = "1" h.TLS.CAErrorPage = "http://d22.local/error.html" - b = c.config.AcquireBackend("d", "app", 8080) + b = c.config.AcquireBackend("d", "app", "8080") h = c.config.AcquireHost("d3.local") h.AddPath(b, "/") b.Endpoints = []*hatypes.Endpoint{endpointS21} @@ -600,29 +600,29 @@ func TestInstanceSomePaths(t *testing.T) { c := setup(t) defer c.teardown() - def := c.config.AcquireBackend("default", "default-backend", 8080) + def := c.config.AcquireBackend("default", "default-backend", "8080") def.Endpoints = []*hatypes.Endpoint{endpointS0} c.config.ConfigDefaultBackend(def) var h *hatypes.Host var b *hatypes.Backend - b = c.config.AcquireBackend("d", "app0", 8080) + b = c.config.AcquireBackend("d", "app0", "8080") h = c.config.AcquireHost("d.local") h.AddPath(b, "/") b.SSLRedirect = true b.Endpoints = []*hatypes.Endpoint{endpointS1} - b = c.config.AcquireBackend("d", "app1", 8080) + b = c.config.AcquireBackend("d", "app1", "8080") h.AddPath(b, "/app") b.SSLRedirect = true b.Endpoints = []*hatypes.Endpoint{endpointS1} - b = c.config.AcquireBackend("d", "app2", 8080) + b = c.config.AcquireBackend("d", "app2", "8080") h.AddPath(b, "/app/sub") b.Endpoints = []*hatypes.Endpoint{endpointS21, endpointS22} - b = c.config.AcquireBackend("d", "app3", 8080) + b = c.config.AcquireBackend("d", "app3", "8080") h.AddPath(b, "/sub") b.Endpoints = []*hatypes.Endpoint{endpointS31, endpointS32, endpointS33} @@ -694,20 +694,20 @@ func TestInstanceSSLPassthrough(t *testing.T) { var h *hatypes.Host var b *hatypes.Backend - b = c.config.AcquireBackend("d2", "app", 8080) + b = c.config.AcquireBackend("d2", "app", "8080") h = c.config.AcquireHost("d2.local") h.AddPath(b, "/") b.SSLRedirect = true b.Endpoints = []*hatypes.Endpoint{endpointS31} h.SSLPassthrough = true - b = c.config.AcquireBackend("d3", "app-ssl", 8443) + b = c.config.AcquireBackend("d3", "app-ssl", "8443") h = c.config.AcquireHost("d3.local") h.AddPath(b, "/") b.Endpoints = []*hatypes.Endpoint{endpointS41s} h.SSLPassthrough = true - b = c.config.AcquireBackend("d3", "app-http", 8080) + b = c.config.AcquireBackend("d3", "app-http", "8080") b.Endpoints = []*hatypes.Endpoint{endpointS41h} h.HTTPPassthroughBackend = b @@ -763,14 +763,14 @@ func TestInstanceRootRedirect(t *testing.T) { var h *hatypes.Host var b *hatypes.Backend - b = c.config.AcquireBackend("d1", "app", 8080) + b = c.config.AcquireBackend("d1", "app", "8080") b.Endpoints = []*hatypes.Endpoint{endpointS1} b.SSLRedirect = false h = c.config.AcquireHost("d1.local") h.AddPath(b, "/") h.RootRedirect = "/app" - b = c.config.AcquireBackend("d2", "app", 8080) + b = c.config.AcquireBackend("d2", "app", "8080") b.Endpoints = []*hatypes.Endpoint{endpointS21} b.SSLRedirect = true h = c.config.AcquireHost("d2.local") @@ -846,20 +846,20 @@ func TestInstanceAlias(t *testing.T) { var h *hatypes.Host var b *hatypes.Backend - b = c.config.AcquireBackend("d1", "app", 8080) + b = c.config.AcquireBackend("d1", "app", "8080") b.Endpoints = []*hatypes.Endpoint{endpointS1} h = c.config.AcquireHost("d1.local") h.AddPath(b, "/") h.Alias.AliasName = "*.d1.local" - b = c.config.AcquireBackend("d2", "app", 8080) + b = c.config.AcquireBackend("d2", "app", "8080") b.Endpoints = []*hatypes.Endpoint{endpointS21} h = c.config.AcquireHost("d2.local") h.AddPath(b, "/") h.Alias.AliasName = "sub.d2.local" h.Alias.AliasRegex = "^[a-z]+\\.d2\\.local$" - b = c.config.AcquireBackend("d3", "app", 8080) + b = c.config.AcquireBackend("d3", "app", "8080") b.Endpoints = []*hatypes.Endpoint{endpointS31} h = c.config.AcquireHost("d3.local") h.AddPath(b, "/") @@ -932,7 +932,7 @@ func TestInstanceWildcardHostname(t *testing.T) { var h *hatypes.Host var b *hatypes.Backend - b = c.config.AcquireBackend("d1", "app", 8080) + b = c.config.AcquireBackend("d1", "app", "8080") b.Endpoints = []*hatypes.Endpoint{endpointS1} b.SSLRedirect = true h = c.config.AcquireHost("d1.local") @@ -946,7 +946,7 @@ func TestInstanceWildcardHostname(t *testing.T) { h.TLS.CAVerifyOptional = true h.TLS.CAErrorPage = "http://sub.d1.local/error.html" - b = c.config.AcquireBackend("d2", "app", 8080) + b = c.config.AcquireBackend("d2", "app", "8080") b.Endpoints = []*hatypes.Endpoint{endpointS21} b.SSLRedirect = false h = c.config.AcquireHost("*.d2.local") @@ -1123,14 +1123,14 @@ func TestAffinity(t *testing.T) { var h *hatypes.Host var b *hatypes.Backend - b = c.config.AcquireBackend("d1", "app", 8080) + b = c.config.AcquireBackend("d1", "app", "8080") b.Endpoints = []*hatypes.Endpoint{endpointS1} b.Cookie.Name = "ingress-controller" b.Cookie.Strategy = "insert" h = c.config.AcquireHost("d1.local") h.AddPath(b, "/") - b = c.config.AcquireBackend("d2", "app", 8080) + b = c.config.AcquireBackend("d2", "app", "8080") b.Endpoints = []*hatypes.Endpoint{endpointS21} b.Cookie.Name = "Ingress" b.Cookie.Strategy = "prefix" @@ -1166,7 +1166,7 @@ func TestCors(t *testing.T) { var h *hatypes.Host var b *hatypes.Backend - b = c.config.AcquireBackend("d1", "app", 8080) + b = c.config.AcquireBackend("d1", "app", "8080") b.Endpoints = []*hatypes.Endpoint{endpointS1} b.Cors.Enabled = true b.Cors.AllowOrigin = "*" @@ -1209,7 +1209,7 @@ func TestHSTS(t *testing.T) { var h *hatypes.Host var b *hatypes.Backend - b = c.config.AcquireBackend("d1", "app", 8080) + b = c.config.AcquireBackend("d1", "app", "8080") b.Endpoints = []*hatypes.Endpoint{endpointS1} b.HSTS.Enabled = true b.HSTS.MaxAge = 15768000 diff --git a/pkg/haproxy/types/types.go b/pkg/haproxy/types/types.go index d89df9fa5..d788e5720 100644 --- a/pkg/haproxy/types/types.go +++ b/pkg/haproxy/types/types.go @@ -227,7 +227,7 @@ type Backend struct { ID string Namespace string Name string - Port int + Port string Endpoints []*Endpoint // AgentCheck AgentCheck From 514e5e63549eac35f748bb64d1275928848b3ba5 Mon Sep 17 00:00:00 2001 From: Joao Morais Date: Tue, 11 Jun 2019 21:29:34 -0300 Subject: [PATCH 21/30] Add svc port check on ssl-passthrough http port --- pkg/converters/ingress/ingress.go | 4 +- pkg/converters/ingress/ingress_test.go | 83 ++++++++++++++++++++++---- 2 files changed, 75 insertions(+), 12 deletions(-) diff --git a/pkg/converters/ingress/ingress.go b/pkg/converters/ingress/ingress.go index 4c9ad9ef2..d7f1ec152 100644 --- a/pkg/converters/ingress/ingress.go +++ b/pkg/converters/ingress/ingress.go @@ -252,7 +252,9 @@ func (c *converter) addHTTPPassthrough(fullSvcName string, ingFrontAnn *ingtypes // a very specific use case of pre-parsing annotations: // need to add a backend if ssl-passthrough-http-port assigned if ingFrontAnn.SSLPassthrough && ingFrontAnn.SSLPassthroughHTTPPort != "" { - c.addBackend(fullSvcName, ingFrontAnn.SSLPassthroughHTTPPort, ingBackAnn) + if _, err := c.addBackend(fullSvcName, ingFrontAnn.SSLPassthroughHTTPPort, ingBackAnn); err != nil { + c.logger.Warn("skipping http port config of ssl-passthrough: %v", err) + } } } diff --git a/pkg/converters/ingress/ingress_test.go b/pkg/converters/ingress/ingress_test.go index 81e852c46..db1fd5f6e 100644 --- a/pkg/converters/ingress/ingress_test.go +++ b/pkg/converters/ingress/ingress_test.go @@ -26,6 +26,7 @@ import ( extensions "k8s.io/api/extensions/v1beta1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/client-go/kubernetes/scheme" ing_helper "github.com/jcmoraisjr/haproxy-ingress/pkg/converters/ingress/helper_test" @@ -254,9 +255,8 @@ func TestSyncDrainSupport(t *testing.T) { c := setup(t) defer c.teardown() - svc := c.createSvc1("default/echo", "8080", "172.17.1.101,172.17.1.102") + svc, ep := c.createSvc1("default/echo", "8080", "172.17.1.101,172.17.1.102") svcName := svc.Namespace + "/" + svc.Name - ep := c.cache.EpList[svcName] ss := &ep.Subsets[0] addr := ss.Addresses ss.Addresses = []api.EndpointAddress{addr[0]} @@ -1021,6 +1021,67 @@ func TestSyncAnnBackDefault(t *testing.T) { INFO skipping backend 'default/echo5:8080' annotation(s) from ingress 'default/echo5' due to conflict: [balance-algorithm]`) } +func TestSyncAnnPassthrough(t *testing.T) { + c := setup(t) + defer c.teardown() + + svc, ep := c.createSvc1("default/echo", "http:8080", "172.17.1.101") + svcPort := api.ServicePort{ + Name: "https", + Port: 8443, + TargetPort: intstr.FromInt(8443), + } + epPort := api.EndpointPort{ + Name: "https", + Port: 8443, + Protocol: api.ProtocolTCP, + } + svc.Spec.Ports = append(svc.Spec.Ports, svcPort) + ep.Subsets[0].Ports = append(ep.Subsets[0].Ports, epPort) + c.Sync( + c.createIng1Ann("default/echo1", "echo1.example.com", "/", "echo:8443", + map[string]string{ + "ingress.kubernetes.io/ssl-passthrough": "true", + "ingress.kubernetes.io/ssl-passthrough-http-port": "8080", + }), + c.createIng1Ann("default/echo2", "echo2.example.com", "/", "echo:8443", + map[string]string{ + "ingress.kubernetes.io/ssl-passthrough": "true", + "ingress.kubernetes.io/ssl-passthrough-http-port": "9000", + }), + ) + + c.compareConfigFront(` +- hostname: echo1.example.com + paths: + - path: / + backend: default_echo_8443 +- hostname: echo2.example.com + paths: + - path: / + backend: default_echo_8443 +`) + + c.compareConfigBack(` +- id: default_echo_8080 + endpoints: + - ip: 172.17.1.101 + port: 8080 +- id: default_echo_8443 + endpoints: + - ip: 172.17.1.101 + port: 8443 +- id: _default_backend + endpoints: + - ip: 172.17.0.99 + port: 8080 +`) + + c.compareLogging(` +WARN skipping http port config of ssl-passthrough: port not found: '9000' +`) +} + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * BUILDERS @@ -1093,23 +1154,23 @@ func (c *testConfig) SyncDef(config map[string]string, ing ...*extensions.Ingres conv.Sync(ing) } -func (c *testConfig) createSvc1Auto() *api.Service { +func (c *testConfig) createSvc1Auto() (*api.Service, *api.Endpoints) { return c.createSvc1("default/echo", "8080", "172.17.0.11") } -func (c *testConfig) createSvc1AutoAnn(ann map[string]string) *api.Service { - svc := c.createSvc1Auto() +func (c *testConfig) createSvc1AutoAnn(ann map[string]string) (*api.Service, *api.Endpoints) { + svc, ep := c.createSvc1Auto() svc.SetAnnotations(ann) - return svc + return svc, ep } -func (c *testConfig) createSvc1Ann(name, port, endpoints string, ann map[string]string) *api.Service { - svc := c.createSvc1(name, port, endpoints) +func (c *testConfig) createSvc1Ann(name, port, endpoints string, ann map[string]string) (*api.Service, *api.Endpoints) { + svc, ep := c.createSvc1(name, port, endpoints) svc.SetAnnotations(ann) - return svc + return svc, ep } -func (c *testConfig) createSvc1(name, port, endpoints string) *api.Service { +func (c *testConfig) createSvc1(name, port, endpoints string) (*api.Service, *api.Endpoints) { sname := strings.Split(name, "/") sport := strings.Split(port, ":") if len(sport) < 2 { @@ -1159,7 +1220,7 @@ subsets: ep.Subsets[0].Addresses = addr c.cache.EpList[name] = ep - return svc + return svc, ep } func (c *testConfig) createPod1(name, ip string) *api.Pod { From 8a72a3dd991aef2679b62b4851d4ada596ea8787 Mon Sep 17 00:00:00 2001 From: Joao Morais Date: Fri, 14 Jun 2019 08:02:56 -0300 Subject: [PATCH 22/30] Add forwarded-for configmap option --- pkg/converters/ingress/annotations/global.go | 18 +++++ .../ingress/annotations/global_test.go | 77 +++++++++++++++++++ pkg/converters/ingress/annotations/updater.go | 1 + .../ingress/annotations/updater_test.go | 7 ++ pkg/converters/ingress/defaults.go | 2 +- pkg/haproxy/instance_test.go | 30 ++++++++ pkg/haproxy/types/types.go | 1 + rootfs/etc/haproxy/template/haproxy.tmpl | 11 ++- 8 files changed, 145 insertions(+), 2 deletions(-) create mode 100644 pkg/converters/ingress/annotations/global_test.go diff --git a/pkg/converters/ingress/annotations/global.go b/pkg/converters/ingress/annotations/global.go index 2371aaa69..38f6e80e0 100644 --- a/pkg/converters/ingress/annotations/global.go +++ b/pkg/converters/ingress/annotations/global.go @@ -18,6 +18,7 @@ package annotations import ( "fmt" + "regexp" "strings" ) @@ -109,9 +110,26 @@ func (c *updater) buildGlobalModSecurity(d *globalData) { d.global.ModSecurity.Timeout.Processing = d.config.ModsecurityTimeoutProcessing } +var ( + forwardRegex = regexp.MustCompile(`^(add|ignore|ifmissing)$`) +) + +func (c *updater) buildGlobalForwardFor(d *globalData) { + if forwardRegex.MatchString(d.config.Forwardfor) { + d.global.ForwardFor = d.config.Forwardfor + } else { + if d.config.Forwardfor != "" { + c.logger.Warn("Invalid forwardfor value option on configmap: '%s'. Using 'add' instead", d.config.Forwardfor) + } + d.global.ForwardFor = "add" + } +} + func (c *updater) buildGlobalCustomConfig(d *globalData) { if d.config.ConfigGlobal != "" { d.global.CustomConfig = strings.Split(strings.TrimRight(d.config.ConfigGlobal, "\n"), "\n") + } + if d.config.ConfigGlobals.ConfigDefaults != "" { d.global.CustomDefaults = strings.Split(strings.TrimRight(d.config.ConfigGlobals.ConfigDefaults, "\n"), "\n") } } diff --git a/pkg/converters/ingress/annotations/global_test.go b/pkg/converters/ingress/annotations/global_test.go new file mode 100644 index 000000000..a298e14a9 --- /dev/null +++ b/pkg/converters/ingress/annotations/global_test.go @@ -0,0 +1,77 @@ +/* +Copyright 2019 The HAProxy Ingress Controller 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 annotations + +import ( + "testing" + + "github.com/jcmoraisjr/haproxy-ingress/pkg/converters/ingress/types" +) + +func TestForwardFor(t *testing.T) { + testCases := []struct { + conf string + expected string + logging string + }{ + // 0 + { + conf: "", + expected: "add", + logging: "", + }, + // 1 + { + conf: "non", + expected: "add", + logging: "WARN Invalid forwardfor value option on configmap: 'non'. Using 'add' instead", + }, + // 2 + { + conf: "add", + expected: "add", + logging: "", + }, + // 3 + { + conf: "ignore", + expected: "ignore", + logging: "", + }, + // 4 + { + conf: "ifmissing", + expected: "ifmissing", + logging: "", + }, + } + for i, test := range testCases { + c := setup(t) + u := c.createUpdater() + d := c.createGlobalData(&types.Config{ + ConfigGlobals: types.ConfigGlobals{ + Forwardfor: test.conf, + }, + }) + u.buildGlobalForwardFor(d) + if d.global.ForwardFor != test.expected { + t.Errorf("ForwardFor differs on %d: expected '%s' but was '%s'", i, test.expected, d.global.ForwardFor) + } + c.logger.CompareLogging(test.logging) + c.teardown() + } +} diff --git a/pkg/converters/ingress/annotations/updater.go b/pkg/converters/ingress/annotations/updater.go index 16c3b5135..3c0221575 100644 --- a/pkg/converters/ingress/annotations/updater.go +++ b/pkg/converters/ingress/annotations/updater.go @@ -83,6 +83,7 @@ func (c *updater) UpdateGlobalConfig(global *hatypes.Global, config *ingtypes.Co c.buildGlobalTimeout(data) c.buildGlobalSSL(data) c.buildGlobalModSecurity(data) + c.buildGlobalForwardFor(data) c.buildGlobalCustomConfig(data) } diff --git a/pkg/converters/ingress/annotations/updater_test.go b/pkg/converters/ingress/annotations/updater_test.go index 6cde98ace..5ee484716 100644 --- a/pkg/converters/ingress/annotations/updater_test.go +++ b/pkg/converters/ingress/annotations/updater_test.go @@ -73,3 +73,10 @@ func (c *testConfig) createBackendData(namespace, name string, ann *types.Backen ann: ann, } } + +func (c *testConfig) createGlobalData(config *types.Config) *globalData { + return &globalData{ + global: &hatypes.Global{}, + config: config, + } +} diff --git a/pkg/converters/ingress/defaults.go b/pkg/converters/ingress/defaults.go index f3565af7a..f7b3e4a1a 100644 --- a/pkg/converters/ingress/defaults.go +++ b/pkg/converters/ingress/defaults.go @@ -54,9 +54,9 @@ func createDefaults() *types.Config { BindIPAddrHTTP: "*", BindIPAddrStats: "*", BindIPAddrTCP: "*", + ConfigDefaults: "", ConfigFrontend: "", ConfigGlobal: "", - ConfigDefaults: "", DNSAcceptedPayloadSize: 8192, DNSClusterDomain: "cluster.local", DNSHoldObsolete: "0s", diff --git a/pkg/haproxy/instance_test.go b/pkg/haproxy/instance_test.go index b8d904f5d..1a53447ce 100644 --- a/pkg/haproxy/instance_test.go +++ b/pkg/haproxy/instance_test.go @@ -1233,6 +1233,36 @@ backend d1_app_8080 c.logger.CompareLogging(defaultLogging) } +func TestForwardFor(t *testing.T) { + c := setup(t) + defer c.teardown() + + var h *hatypes.Host + var b *hatypes.Backend + + b = c.config.AcquireBackend("d1", "app", "8080") + b.Endpoints = []*hatypes.Endpoint{endpointS1} + h = c.config.AcquireHost("d1.local") + h.AddPath(b, "/") + c.config.Global().ForwardFor = "add" + + c.instance.Update() + c.checkConfig(` +<> +<> +backend d1_app_8080 + mode http + http-request set-header X-Original-Forwarded-For %[hdr(x-forwarded-for)] if { hdr(x-forwarded-for) -m found } + http-request del-header x-forwarded-for + option forwardfor + server s1 172.17.0.11:8080 weight 100 +<> +<> +`) + + c.logger.CompareLogging(defaultLogging) +} + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * BUILDERS diff --git a/pkg/haproxy/types/types.go b/pkg/haproxy/types/types.go index d788e5720..de0fa1c27 100644 --- a/pkg/haproxy/types/types.go +++ b/pkg/haproxy/types/types.go @@ -26,6 +26,7 @@ type Global struct { ModSecurity ModSecurityConfig Cookie CookieConfig DrainSupport DrainConfig + ForwardFor string LoadServerState bool StatsSocket string CustomConfig []string diff --git a/rootfs/etc/haproxy/template/haproxy.tmpl b/rootfs/etc/haproxy/template/haproxy.tmpl index 53ff941d5..5dfbe7951 100644 --- a/rootfs/etc/haproxy/template/haproxy.tmpl +++ b/rootfs/etc/haproxy/template/haproxy.tmpl @@ -184,6 +184,15 @@ backend {{ $backend.ID }} http-request use-service lua.send-response if METH_OPTIONS {{- end }} +{{- /*------------------------------------*/}} +{{- if eq $global.ForwardFor "add" }} + http-request set-header X-Original-Forwarded-For %[hdr(x-forwarded-for)] if { hdr(x-forwarded-for) -m found } + http-request del-header x-forwarded-for + option forwardfor +{{- else if eq $global.ForwardFor "ifmissing" }} + option forwardfor if-none +{{- end }} + {{- /*------------------------------------*/}} {{- if $backend.Cookie.Name }} {{- $cookie := $backend.Cookie }} @@ -232,7 +241,7 @@ backend {{ $backend.ID }} {{- end }} {{- end }} -{{- end }}{{/*** $backend.ModeTCP ***/}} +{{- end }}{{/*** if not $backend.ModeTCP ***/}} {{- /*------------------------------------*/}} {{- range $ep := $backend.Endpoints }} From e2b9035d99eafe06a23ce59d7c7f9511e1f686ab Mon Sep 17 00:00:00 2001 From: Joao Morais Date: Sun, 16 Jun 2019 10:15:55 -0300 Subject: [PATCH 23/30] Reuse of backend tests --- pkg/haproxy/instance_test.go | 252 +++++++++++++++-------------------- 1 file changed, 105 insertions(+), 147 deletions(-) diff --git a/pkg/haproxy/instance_test.go b/pkg/haproxy/instance_test.go index 1a53447ce..57542e5bb 100644 --- a/pkg/haproxy/instance_test.go +++ b/pkg/haproxy/instance_test.go @@ -31,6 +31,111 @@ import ( "github.com/jcmoraisjr/haproxy-ingress/pkg/types/helper_test" ) +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * + * BACKEND TESTCASES + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +func TestBackends(t *testing.T) { + testCases := []struct { + doconfig func(g *hatypes.Global, b *hatypes.Backend) + srvsuffix string + expected string + }{ + { + doconfig: func(g *hatypes.Global, b *hatypes.Backend) { + b.Cookie.Name = "ingress-controller" + b.Cookie.Strategy = "insert" + }, + srvsuffix: "cookie s1", + expected: ` + cookie ingress-controller insert indirect nocache httponly`, + }, + { + doconfig: func(g *hatypes.Global, b *hatypes.Backend) { + b.Cookie.Name = "Ingress" + b.Cookie.Strategy = "prefix" + b.Cookie.Dynamic = true + }, + expected: ` + cookie Ingress prefix dynamic + dynamic-cookie-key "Ingress"`, + }, + { + doconfig: func(g *hatypes.Global, b *hatypes.Backend) { + b.Cors.Enabled = true + b.Cors.AllowOrigin = "*" + b.Cors.AllowHeaders = + "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization" + b.Cors.AllowMethods = "GET, PUT, POST, DELETE, PATCH, OPTIONS" + b.Cors.MaxAge = 86400 + }, + expected: ` + http-request use-service lua.send-response if METH_OPTIONS + http-response set-status 204 reason "No Content" if METH_OPTIONS + http-response set-header Content-Type "text/plain" if METH_OPTIONS + http-response set-header Content-Length "0" if METH_OPTIONS + http-response set-header Access-Control-Allow-Origin "*" if METH_OPTIONS + http-response set-header Access-Control-Allow-Methods "GET, PUT, POST, DELETE, PATCH, OPTIONS" if METH_OPTIONS + http-response set-header Access-Control-Allow-Headers "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization" if METH_OPTIONS + http-response set-header Access-Control-Max-Age "86400" if METH_OPTIONS + http-response set-header Access-Control-Allow-Origin "*" + http-response set-header Access-Control-Allow-Methods "GET, PUT, POST, DELETE, PATCH, OPTIONS" + http-response set-header Access-Control-Allow-Headers "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization"`, + }, + { + doconfig: func(g *hatypes.Global, b *hatypes.Backend) { + b.HSTS.Enabled = true + b.HSTS.MaxAge = 15768000 + b.HSTS.Preload = true + b.HSTS.Subdomains = true + }, + expected: ` + http-response set-header Strict-Transport-Security "max-age=15768000; includeSubDomains; preload" if { ssl_fc }`, + }, + { + doconfig: func(g *hatypes.Global, b *hatypes.Backend) { + g.ForwardFor = "add" + }, + expected: ` + http-request set-header X-Original-Forwarded-For %[hdr(x-forwarded-for)] if { hdr(x-forwarded-for) -m found } + http-request del-header x-forwarded-for + option forwardfor`, + }, + } + + for _, test := range testCases { + c := setup(t) + + var h *hatypes.Host + var b *hatypes.Backend + + b = c.config.AcquireBackend("d1", "app", "8080") + b.Endpoints = []*hatypes.Endpoint{endpointS1} + h = c.config.AcquireHost("d1.local") + h.AddPath(b, "/") + test.doconfig(c.config.Global(), b) + if test.srvsuffix != "" { + test.srvsuffix = " " + test.srvsuffix + } + + c.instance.Update() + c.checkConfig(` +<> +<> +backend d1_app_8080 + mode http` + test.expected + ` + server s1 172.17.0.11:8080 weight 100` + test.srvsuffix + ` +<> +<> +`) + + c.logger.CompareLogging(defaultLogging) + c.teardown() + } +} + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * TEMPLATES @@ -1116,153 +1221,6 @@ d1.local/ d1_app_8080 c.logger.CompareLogging(defaultLogging) } -func TestAffinity(t *testing.T) { - c := setup(t) - defer c.teardown() - - var h *hatypes.Host - var b *hatypes.Backend - - b = c.config.AcquireBackend("d1", "app", "8080") - b.Endpoints = []*hatypes.Endpoint{endpointS1} - b.Cookie.Name = "ingress-controller" - b.Cookie.Strategy = "insert" - h = c.config.AcquireHost("d1.local") - h.AddPath(b, "/") - - b = c.config.AcquireBackend("d2", "app", "8080") - b.Endpoints = []*hatypes.Endpoint{endpointS21} - b.Cookie.Name = "Ingress" - b.Cookie.Strategy = "prefix" - b.Cookie.Dynamic = true - - h = c.config.AcquireHost("d2.local") - h.AddPath(b, "/") - - c.instance.Update() - c.checkConfig(` -<> -<> -backend d1_app_8080 - mode http - cookie ingress-controller insert indirect nocache httponly - server s1 172.17.0.11:8080 weight 100 cookie s1 -backend d2_app_8080 - mode http - cookie Ingress prefix dynamic - dynamic-cookie-key "Ingress" - server s21 172.17.0.121:8080 weight 100 -<> -<> -`) - - c.logger.CompareLogging(defaultLogging) -} - -func TestCors(t *testing.T) { - c := setup(t) - defer c.teardown() - - var h *hatypes.Host - var b *hatypes.Backend - - b = c.config.AcquireBackend("d1", "app", "8080") - b.Endpoints = []*hatypes.Endpoint{endpointS1} - b.Cors.Enabled = true - b.Cors.AllowOrigin = "*" - b.Cors.AllowHeaders = - "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization" - b.Cors.AllowMethods = "GET, PUT, POST, DELETE, PATCH, OPTIONS" - b.Cors.MaxAge = 86400 - h = c.config.AcquireHost("d1.local") - h.AddPath(b, "/") - - c.instance.Update() - c.checkConfig(` -<> -<> -backend d1_app_8080 - mode http - http-request use-service lua.send-response if METH_OPTIONS - http-response set-status 204 reason "No Content" if METH_OPTIONS - http-response set-header Content-Type "text/plain" if METH_OPTIONS - http-response set-header Content-Length "0" if METH_OPTIONS - http-response set-header Access-Control-Allow-Origin "*" if METH_OPTIONS - http-response set-header Access-Control-Allow-Methods "GET, PUT, POST, DELETE, PATCH, OPTIONS" if METH_OPTIONS - http-response set-header Access-Control-Allow-Headers "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization" if METH_OPTIONS - http-response set-header Access-Control-Max-Age "86400" if METH_OPTIONS - http-response set-header Access-Control-Allow-Origin "*" - http-response set-header Access-Control-Allow-Methods "GET, PUT, POST, DELETE, PATCH, OPTIONS" - http-response set-header Access-Control-Allow-Headers "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization" - server s1 172.17.0.11:8080 weight 100 -<> -<> -`) - - c.logger.CompareLogging(defaultLogging) -} - -func TestHSTS(t *testing.T) { - c := setup(t) - defer c.teardown() - - var h *hatypes.Host - var b *hatypes.Backend - - b = c.config.AcquireBackend("d1", "app", "8080") - b.Endpoints = []*hatypes.Endpoint{endpointS1} - b.HSTS.Enabled = true - b.HSTS.MaxAge = 15768000 - b.HSTS.Preload = true - b.HSTS.Subdomains = true - h = c.config.AcquireHost("d1.local") - h.AddPath(b, "/") - - c.instance.Update() - c.checkConfig(` -<> -<> -backend d1_app_8080 - mode http - http-response set-header Strict-Transport-Security "max-age=15768000; includeSubDomains; preload" if { ssl_fc } - server s1 172.17.0.11:8080 weight 100 -<> -<> -`) - - c.logger.CompareLogging(defaultLogging) -} - -func TestForwardFor(t *testing.T) { - c := setup(t) - defer c.teardown() - - var h *hatypes.Host - var b *hatypes.Backend - - b = c.config.AcquireBackend("d1", "app", "8080") - b.Endpoints = []*hatypes.Endpoint{endpointS1} - h = c.config.AcquireHost("d1.local") - h.AddPath(b, "/") - c.config.Global().ForwardFor = "add" - - c.instance.Update() - c.checkConfig(` -<> -<> -backend d1_app_8080 - mode http - http-request set-header X-Original-Forwarded-For %[hdr(x-forwarded-for)] if { hdr(x-forwarded-for) -m found } - http-request del-header x-forwarded-for - option forwardfor - server s1 172.17.0.11:8080 weight 100 -<> -<> -`) - - c.logger.CompareLogging(defaultLogging) -} - /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * BUILDERS From 4105d8005386e42ed8cac918dd320d511fecc53e Mon Sep 17 00:00:00 2001 From: Joao Morais Date: Sun, 16 Jun 2019 17:10:50 -0300 Subject: [PATCH 24/30] Add rewrite-target Rewrite now happens in a different way when compared with v0.7. reqrep was moved to the backend and the same backend can be the target of more than one host/path_beg combination. The problem happens when different path_beg (ingress->spec->rules[]->http->paths[]->path) uses rewrite-target - and rewrite-target needs the configured path. The current approach is to sort all used paths in reverse order in order to avoid overlaps - eg an /app path before an /app/sub would match an /app/sub/dir request. All reqrep will happen in that order and should avoid that a wrong rewrite happens. --- pkg/converters/ingress/annotations/backend.go | 15 +++++ .../ingress/annotations/backend_test.go | 36 +++++++++++ pkg/converters/ingress/annotations/updater.go | 1 + pkg/haproxy/instance_test.go | 40 ++++++++++-- pkg/haproxy/types/backend.go | 17 +++++ pkg/haproxy/types/backend_test.go | 63 +++++++++++++++++++ pkg/haproxy/types/host.go | 1 + pkg/haproxy/types/types.go | 2 + rootfs/etc/haproxy/template/haproxy.tmpl | 11 ++++ 9 files changed, 182 insertions(+), 4 deletions(-) create mode 100644 pkg/haproxy/types/backend_test.go diff --git a/pkg/converters/ingress/annotations/backend.go b/pkg/converters/ingress/annotations/backend.go index d6a0109e6..5094e36a7 100644 --- a/pkg/converters/ingress/annotations/backend.go +++ b/pkg/converters/ingress/annotations/backend.go @@ -304,3 +304,18 @@ func (c *updater) buildBackendCors(d *backData) { d.backend.Cors.ExposeHeaders = d.ann.CorsExposeHeaders } } + +var ( + rewriteURLRegex = regexp.MustCompile(`^[^"' ]+$`) +) + +func (c *updater) buildRewriteURL(d *backData) { + if d.ann.RewriteTarget == "" { + return + } + if !rewriteURLRegex.MatchString(d.ann.RewriteTarget) { + c.logger.Warn("rewrite-target does not allow white spaces or single/double quotes on %v", d.ann.Source) + return + } + d.backend.RewriteURL = d.ann.RewriteTarget +} diff --git a/pkg/converters/ingress/annotations/backend_test.go b/pkg/converters/ingress/annotations/backend_test.go index 87154f3bc..072e14a30 100644 --- a/pkg/converters/ingress/annotations/backend_test.go +++ b/pkg/converters/ingress/annotations/backend_test.go @@ -518,3 +518,39 @@ INFO-V(3) blue/green balance label 'v=3' on ingress 'default/ing1' does not refe c.teardown() } } + +func TestRewriteURL(t *testing.T) { + testCases := []struct { + input string + expected string + logging string + }{ + // 0 + { + input: ``, + expected: ``, + }, + // 1 + { + input: `/"/`, + expected: ``, + logging: `WARN rewrite-target does not allow white spaces or single/double quotes on ingress 'default/app'`, + }, + // 2 + { + input: `/app`, + expected: `/app`, + }, + } + + for i, test := range testCases { + c := setup(t) + d := c.createBackendData("default", "app", &types.BackendAnnotations{RewriteTarget: test.input}) + c.createUpdater().buildRewriteURL(d) + if d.backend.RewriteURL != test.expected { + t.Errorf("rewrite on %d differs - expected: %v - actual: %v", i, test.expected, d.backend.RewriteURL) + } + c.logger.CompareLogging(test.logging) + c.teardown() + } +} diff --git a/pkg/converters/ingress/annotations/updater.go b/pkg/converters/ingress/annotations/updater.go index 3c0221575..f87373a67 100644 --- a/pkg/converters/ingress/annotations/updater.go +++ b/pkg/converters/ingress/annotations/updater.go @@ -120,4 +120,5 @@ func (c *updater) UpdateBackendConfig(backend *hatypes.Backend, ann *ingtypes.Ba c.buildBackendAuthHTTP(data) c.buildBackendBlueGreen(data) c.buildBackendCors(data) + c.buildRewriteURL(data) } diff --git a/pkg/haproxy/instance_test.go b/pkg/haproxy/instance_test.go index 57542e5bb..652abb4c6 100644 --- a/pkg/haproxy/instance_test.go +++ b/pkg/haproxy/instance_test.go @@ -40,6 +40,7 @@ import ( func TestBackends(t *testing.T) { testCases := []struct { doconfig func(g *hatypes.Global, b *hatypes.Backend) + path []string srvsuffix string expected string }{ @@ -103,22 +104,53 @@ func TestBackends(t *testing.T) { http-request del-header x-forwarded-for option forwardfor`, }, + { + doconfig: func(g *hatypes.Global, b *hatypes.Backend) { + b.RewriteURL = "/" + }, + path: []string{"/app"}, + expected: ` + reqrep ^([^:\ ]*)\ /app/?(.*)$ \1\ /\2`, + }, + { + doconfig: func(g *hatypes.Global, b *hatypes.Backend) { + b.RewriteURL = "/other" + }, + path: []string{"/app"}, + expected: ` + reqrep ^([^:\ ]*)\ /app(.*)$ \1\ /other\2`, + }, + { + doconfig: func(g *hatypes.Global, b *hatypes.Backend) { + b.RewriteURL = "/other/" + }, + path: []string{"/app", "/app/sub"}, + expected: ` + reqrep ^([^:\ ]*)\ /app/sub(.*)$ \1\ /other/\2 + reqrep ^([^:\ ]*)\ /app(.*)$ \1\ /other/\2`, + }, } for _, test := range testCases { c := setup(t) + if len(test.path) == 0 { + test.path = []string{"/"} + } + if test.srvsuffix != "" { + test.srvsuffix = " " + test.srvsuffix + } + var h *hatypes.Host var b *hatypes.Backend b = c.config.AcquireBackend("d1", "app", "8080") b.Endpoints = []*hatypes.Endpoint{endpointS1} h = c.config.AcquireHost("d1.local") - h.AddPath(b, "/") - test.doconfig(c.config.Global(), b) - if test.srvsuffix != "" { - test.srvsuffix = " " + test.srvsuffix + for _, p := range test.path { + h.AddPath(b, p) } + test.doconfig(c.config.Global(), b) c.instance.Update() c.checkConfig(` diff --git a/pkg/haproxy/types/backend.go b/pkg/haproxy/types/backend.go index 6c43850a5..e00d29677 100644 --- a/pkg/haproxy/types/backend.go +++ b/pkg/haproxy/types/backend.go @@ -37,6 +37,23 @@ func (b *Backend) NewEndpoint(ip string, port int, targetRef string) *Endpoint { return endpoint } +// AddPath ... +func (b *Backend) AddPath(path string) { + for _, p := range b.Paths { + if p == path { + // add only unique paths + return + } + } + // host's paths that references this backend + // used on RewriteURL config + b.Paths = append(b.Paths, path) + // reverse order in order to avoid overlap of sub-paths + sort.Slice(b.Paths, func(i, j int) bool { + return b.Paths[i] > b.Paths[j] + }) +} + // HreqValidateUserlist ... func (b *Backend) HreqValidateUserlist(userlist *Userlist) { // TODO implement diff --git a/pkg/haproxy/types/backend_test.go b/pkg/haproxy/types/backend_test.go new file mode 100644 index 000000000..d4bb3063a --- /dev/null +++ b/pkg/haproxy/types/backend_test.go @@ -0,0 +1,63 @@ +/* +Copyright 2019 The HAProxy Ingress Controller 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 types + +import ( + "reflect" + "testing" +) + +func TestAddPath(t *testing.T) { + testCases := []struct { + input []string + expected []string + }{ + { + input: []string{"/"}, + expected: []string{"/"}, + }, + { + input: []string{"/app", "/app"}, + expected: []string{"/app"}, + }, + { + input: []string{"/app", "/root"}, + expected: []string{"/root", "/app"}, + }, + { + input: []string{"/app", "/root", "/root"}, + expected: []string{"/root", "/app"}, + }, + { + input: []string{"/app", "/root", "/app"}, + expected: []string{"/root", "/app"}, + }, + { + input: []string{"/", "/app", "/root"}, + expected: []string{"/root", "/app", "/"}, + }, + } + for _, test := range testCases { + b := &Backend{} + for _, p := range test.input { + b.AddPath(p) + } + if !reflect.DeepEqual(b.Paths, test.expected) { + t.Errorf("backend.Paths differs - actual: %v - expected: %v", b.Paths, test.expected) + } + } +} diff --git a/pkg/haproxy/types/host.go b/pkg/haproxy/types/host.go index 5ade4c535..5cf7b2ac6 100644 --- a/pkg/haproxy/types/host.go +++ b/pkg/haproxy/types/host.go @@ -38,6 +38,7 @@ func (h *Host) AddPath(backend *Backend, path string) { Backend: backend, BackendID: backend.ID, }) + backend.AddPath(path) // reverse order in order to avoid overlap of sub-paths sort.Slice(h.Paths, func(i, j int) bool { return h.Paths[i].Path > h.Paths[j].Path diff --git a/pkg/haproxy/types/types.go b/pkg/haproxy/types/types.go index de0fa1c27..aca606e30 100644 --- a/pkg/haproxy/types/types.go +++ b/pkg/haproxy/types/types.go @@ -242,7 +242,9 @@ type Backend struct { MaxConnServer int MaxQueueServer int ModeTCP bool + Paths []string ProxyBodySize string + RewriteURL string SendProxyProtocol string SSL SSLBackendConfig SSLRedirect bool diff --git a/rootfs/etc/haproxy/template/haproxy.tmpl b/rootfs/etc/haproxy/template/haproxy.tmpl index 5dfbe7951..b0092b7f6 100644 --- a/rootfs/etc/haproxy/template/haproxy.tmpl +++ b/rootfs/etc/haproxy/template/haproxy.tmpl @@ -209,6 +209,17 @@ backend {{ $backend.ID }} {{ $snippet }} {{- end }} +{{- /*------------------------------------*/}} +{{- if $backend.RewriteURL }} +{{- range $path := $backend.Paths }} +{{- if eq $backend.RewriteURL "/" }} + reqrep ^([^:\ ]*)\ {{ $path }}/?(.*)$ \1\ {{ $backend.RewriteURL }}\2 +{{- else }} + reqrep ^([^:\ ]*)\ {{ $path }}(.*)$ \1\ {{ $backend.RewriteURL }}{{ if hasSuffix $path "/" }}/{{ end }}\2 +{{- end }} +{{- end }} +{{- end }} + {{- /*------------------------------------*/}} {{- if $backend.HSTS.Enabled }} {{- $hsts := $backend.HSTS }} From 3b9722ad5afe123891189df1730b9a1b54f3e40e Mon Sep 17 00:00:00 2001 From: Joao Morais Date: Sun, 16 Jun 2019 17:31:13 -0300 Subject: [PATCH 25/30] Add http/s log options --- pkg/converters/ingress/annotations/updater.go | 3 ++ pkg/haproxy/types/types.go | 9 +++-- rootfs/etc/haproxy/template/haproxy.tmpl | 40 +++++++++++++++++++ 3 files changed, 49 insertions(+), 3 deletions(-) diff --git a/pkg/converters/ingress/annotations/updater.go b/pkg/converters/ingress/annotations/updater.go index f87373a67..dd2935204 100644 --- a/pkg/converters/ingress/annotations/updater.go +++ b/pkg/converters/ingress/annotations/updater.go @@ -73,6 +73,9 @@ func (c *updater) UpdateGlobalConfig(global *hatypes.Global, config *ingtypes.Co global.Syslog.Endpoint = config.SyslogEndpoint global.Syslog.Format = config.SyslogFormat global.Syslog.Tag = config.SyslogTag + global.Syslog.HTTPLogFormat = config.HTTPLogFormat + global.Syslog.HTTPSLogFormat = config.HTTPSLogFormat + global.Syslog.TCPLogFormat = config.TCPLogFormat global.MaxConn = config.MaxConnections global.DrainSupport.Drain = config.DrainSupport global.DrainSupport.Redispatch = config.DrainSupportRedispatch diff --git a/pkg/haproxy/types/types.go b/pkg/haproxy/types/types.go index aca606e30..8d5fd1030 100644 --- a/pkg/haproxy/types/types.go +++ b/pkg/haproxy/types/types.go @@ -46,9 +46,12 @@ type ProcsConfig struct { // SyslogConfig ... type SyslogConfig struct { - Endpoint string - Format string - Tag string + Endpoint string + Format string + HTTPLogFormat string + HTTPSLogFormat string + Tag string + TCPLogFormat string } // TimeoutConfig ... diff --git a/rootfs/etc/haproxy/template/haproxy.tmpl b/rootfs/etc/haproxy/template/haproxy.tmpl index b0092b7f6..a28a5ca55 100644 --- a/rootfs/etc/haproxy/template/haproxy.tmpl +++ b/rootfs/etc/haproxy/template/haproxy.tmpl @@ -328,14 +328,31 @@ backend _error496 listen _front__tls mode tcp bind :443 + +{{- /*------------------------------------*/}} +{{- if $global.Syslog.Endpoint }} +{{- if eq $global.Syslog.HTTPSLogFormat "default" }} + option tcplog +{{- else if $global.Syslog.HTTPSLogFormat }} + log-format {{ $global.Syslog.HTTPSLogFormat }} +{{- else }} + no log +{{- end }} +{{- end }} + +{{- /*------------------------------------*/}} tcp-request inspect-delay 5s tcp-request content accept if { req.ssl_hello_type 1 } + +{{- /*------------------------------------*/}} {{- if $fgroup.HasSSLPassthrough }} ## ssl-passthrough tcp-request content set-var(req.backend) {{- "" }} req.ssl_sni,lower,map({{ $fgroup.SSLPassthroughMap.MatchFile }},_nomatch) use_backend %[var(req.backend)] unless { var(req.backend) _nomatch } {{- end }} + +{{- /*------------------------------------*/}} {{- range $frontend := $frontends }} {{- range $bind := $frontend.Binds }} ## {{ $frontend.Name }}/{{ $bind.Name }} @@ -344,12 +361,15 @@ listen _front__tls {{- end }} {{- end }} +{{- /*------------------------------------*/}} {{- if $fgroup.SSLPassthroughMap.HasRegex }} ## ssl-passthrough wildcard {{- /*** TODO is map_reg() converter running on every request? ***/}} tcp-request content set-var(req.backend) {{- "" }} req.ssl_sni,lower,map_reg({{ $fgroup.SSLPassthroughMap.RegexFile }},_nomatch) {{- "" }} if { var(req.backend) _nomatch } + +{{- /*------------------------------------*/}} use_backend %[var(req.backend)] unless { var(req.backend) _nomatch } {{- end }} {{- range $frontend := $frontends }} @@ -361,6 +381,8 @@ listen _front__tls {{- end }} {{- end }} {{- end }} + +{{- /*------------------------------------*/}} # TODO default backend {{- end }} @@ -372,6 +394,15 @@ frontend _front_http mode http bind :80 +{{- /*------------------------------------*/}} +{{- if $global.Syslog.Endpoint }} +{{- if $global.Syslog.HTTPLogFormat }} + log-format {{ $global.Syslog.HTTPLogFormat }} +{{- else }} + option httplog +{{- end }} +{{- end }} + {{- /*------------------------------------*/}} http-request set-var(req.base) base,regsub(:[0-9]+/,/) @@ -446,6 +477,15 @@ frontend {{ $frontend.Name }} timeout client-fin {{ $frontend.Timeout.ClientFin }} {{- end }} +{{- /*------------------------------------*/}} +{{- if $global.Syslog.Endpoint }} +{{- if $global.Syslog.HTTPLogFormat }} + log-format {{ $global.Syslog.HTTPLogFormat }} +{{- else }} + option httplog +{{- end }} +{{- end }} + {{- /*------------------------------------*/}} {{- if or $frontend.HostBackendsMap.HasRegex $frontend.HasVarNamespace }} http-request set-var(req.base) base,lower,regsub(:[0-9]+/,/) From 4d0a620df78b25973a49b88647485bba0d326ac3 Mon Sep 17 00:00:00 2001 From: Joao Morais Date: Sun, 16 Jun 2019 19:19:28 -0300 Subject: [PATCH 26/30] Add whitelist-source-range --- pkg/converters/ingress/annotations/backend.go | 16 ++++++++ .../ingress/annotations/backend_test.go | 40 +++++++++++++++++++ pkg/converters/ingress/annotations/updater.go | 1 + pkg/haproxy/instance_test.go | 25 +++++++++++- pkg/haproxy/types/types.go | 1 + rootfs/etc/haproxy/template/haproxy.tmpl | 10 +++++ 6 files changed, 91 insertions(+), 2 deletions(-) diff --git a/pkg/converters/ingress/annotations/backend.go b/pkg/converters/ingress/annotations/backend.go index 5094e36a7..704bab93f 100644 --- a/pkg/converters/ingress/annotations/backend.go +++ b/pkg/converters/ingress/annotations/backend.go @@ -18,6 +18,7 @@ package annotations import ( "fmt" + "net" "regexp" "strconv" "strings" @@ -319,3 +320,18 @@ func (c *updater) buildRewriteURL(d *backData) { } d.backend.RewriteURL = d.ann.RewriteTarget } + +func (c *updater) buildWhitelist(d *backData) { + if d.ann.WhitelistSourceRange == "" { + return + } + var cidrlist []string + for _, cidr := range strings.Split(d.ann.WhitelistSourceRange, ",") { + if _, _, err := net.ParseCIDR(cidr); err != nil { + c.logger.Warn("skipping invalid cidr '%s' in whitelist config on %v", cidr, d.ann.Source) + } else { + cidrlist = append(cidrlist, cidr) + } + } + d.backend.Whitelist = cidrlist +} diff --git a/pkg/converters/ingress/annotations/backend_test.go b/pkg/converters/ingress/annotations/backend_test.go index 072e14a30..93389cdc0 100644 --- a/pkg/converters/ingress/annotations/backend_test.go +++ b/pkg/converters/ingress/annotations/backend_test.go @@ -554,3 +554,43 @@ func TestRewriteURL(t *testing.T) { c.teardown() } } + +func TestWhitelist(t *testing.T) { + testCase := []struct { + cidrlist string + expected []string + logging string + }{ + // 0 + { + cidrlist: "10.0.0.0/8,192.168.0.0/16", + expected: []string{"10.0.0.0/8", "192.168.0.0/16"}, + }, + // 1 + { + cidrlist: "10.0.0.0/8,192.168.0/16", + expected: []string{"10.0.0.0/8"}, + logging: `WARN skipping invalid cidr '192.168.0/16' in whitelist config on ingress 'default/app'`, + }, + // 2 + { + cidrlist: "10.0.0/8,192.168.0/16", + expected: []string{}, + logging: ` +WARN skipping invalid cidr '10.0.0/8' in whitelist config on ingress 'default/app' +WARN skipping invalid cidr '192.168.0/16' in whitelist config on ingress 'default/app'`, + }, + } + for i, test := range testCase { + c := setup(t) + d := c.createBackendData("default", "app", &types.BackendAnnotations{WhitelistSourceRange: test.cidrlist}) + c.createUpdater().buildWhitelist(d) + if !reflect.DeepEqual(d.backend.Whitelist, test.expected) { + if len(d.backend.Whitelist) > 0 || len(test.expected) > 0 { + t.Errorf("whitelist on %d differs - expected: %v - actual: %v", i, test.expected, d.backend.Whitelist) + } + } + c.logger.CompareLogging(test.logging) + c.teardown() + } +} diff --git a/pkg/converters/ingress/annotations/updater.go b/pkg/converters/ingress/annotations/updater.go index dd2935204..47d5ec1bf 100644 --- a/pkg/converters/ingress/annotations/updater.go +++ b/pkg/converters/ingress/annotations/updater.go @@ -124,4 +124,5 @@ func (c *updater) UpdateBackendConfig(backend *hatypes.Backend, ann *ingtypes.Ba c.buildBackendBlueGreen(data) c.buildBackendCors(data) c.buildRewriteURL(data) + c.buildWhitelist(data) } diff --git a/pkg/haproxy/instance_test.go b/pkg/haproxy/instance_test.go index 652abb4c6..028dfd855 100644 --- a/pkg/haproxy/instance_test.go +++ b/pkg/haproxy/instance_test.go @@ -129,8 +129,22 @@ func TestBackends(t *testing.T) { reqrep ^([^:\ ]*)\ /app/sub(.*)$ \1\ /other/\2 reqrep ^([^:\ ]*)\ /app(.*)$ \1\ /other/\2`, }, + { + doconfig: func(g *hatypes.Global, b *hatypes.Backend) { + b.Whitelist = []string{"10.0.0.0/8", "192.168.0.0/16"} + }, + expected: ` + http-request deny if !{ src 10.0.0.0/8 192.168.0.0/16 }`, + }, + { + doconfig: func(g *hatypes.Global, b *hatypes.Backend) { + b.Whitelist = []string{"10.0.0.0/8", "192.168.0.0/16"} + b.ModeTCP = true + }, + expected: ` + tcp-request content reject if !{ src 10.0.0.0/8 192.168.0.0/16 }`, + }, } - for _, test := range testCases { c := setup(t) @@ -152,12 +166,19 @@ func TestBackends(t *testing.T) { } test.doconfig(c.config.Global(), b) + var mode string + if b.ModeTCP { + mode = "tcp" + } else { + mode = "http" + } + c.instance.Update() c.checkConfig(` <> <> backend d1_app_8080 - mode http` + test.expected + ` + mode ` + mode + test.expected + ` server s1 172.17.0.11:8080 weight 100` + test.srvsuffix + ` <> <> diff --git a/pkg/haproxy/types/types.go b/pkg/haproxy/types/types.go index 8d5fd1030..344f343f8 100644 --- a/pkg/haproxy/types/types.go +++ b/pkg/haproxy/types/types.go @@ -252,6 +252,7 @@ type Backend struct { SSL SSLBackendConfig SSLRedirect bool Timeout BackendTimeoutConfig + Whitelist []string } // Endpoint ... diff --git a/rootfs/etc/haproxy/template/haproxy.tmpl b/rootfs/etc/haproxy/template/haproxy.tmpl index a28a5ca55..62a30128d 100644 --- a/rootfs/etc/haproxy/template/haproxy.tmpl +++ b/rootfs/etc/haproxy/template/haproxy.tmpl @@ -167,8 +167,18 @@ backend {{ $backend.ID }} timeout tunnel {{ $timeout.Tunnel }} {{- end }} +{{- /*------------------------------------*/}} +{{- if and $backend.ModeTCP $backend.Whitelist }} + tcp-request content reject if !{ src{{ range $cidr := $backend.Whitelist }} {{ $cidr }}{{ end }} } +{{- end }} + {{- if not $backend.ModeTCP }} +{{- /*------------------------------------*/}} +{{- if $backend.Whitelist }} + http-request deny if !{ src{{ range $cidr := $backend.Whitelist }} {{ $cidr }}{{ end }} } +{{- end }} + {{- /*------------------------------------*/}} {{- if $backend.SSL.HasTLSAuth }} http-request set-header {{ $global.SSL.HeadersPrefix }}-Client-CN %{+Q}[ssl_c_s_dn(cn)]{{ if not $backend.SSLRedirect }} if { ssl_fc }{{ end }} From 8263763e2b79c46530e4df684535fc237a6d19fa Mon Sep 17 00:00:00 2001 From: Joao Morais Date: Sun, 16 Jun 2019 21:58:49 -0300 Subject: [PATCH 27/30] Add auth basic --- pkg/converters/ingress/annotations/backend.go | 9 +- .../ingress/annotations/backend_test.go | 61 +++++----- pkg/haproxy/instance_test.go | 105 ++++++++++++++++++ pkg/haproxy/types/backend.go | 10 -- pkg/haproxy/types/types.go | 11 +- rootfs/etc/haproxy/template/haproxy.tmpl | 7 ++ 6 files changed, 156 insertions(+), 47 deletions(-) diff --git a/pkg/converters/ingress/annotations/backend.go b/pkg/converters/ingress/annotations/backend.go index 704bab93f..fed19ad42 100644 --- a/pkg/converters/ingress/annotations/backend.go +++ b/pkg/converters/ingress/annotations/backend.go @@ -82,7 +82,14 @@ func (c *updater) buildBackendAuthHTTP(d *backData) { c.logger.Warn("userlist on %v for basic authentication is empty", d.ann.Source) } } - d.backend.HreqValidateUserlist(userlist) + d.backend.Userlist.Name = userlist.Name + realm := "localhost" // HAProxy's backend name would be used if missing + if strings.Index(d.ann.AuthRealm, `"`) >= 0 { + c.logger.Warn("ignoring auth-realm with quotes on %v", d.ann.Source) + } else if d.ann.AuthRealm != "" { + realm = d.ann.AuthRealm + } + d.backend.Userlist.Realm = realm } func (c *updater) buildBackendAuthHTTPExtractUserlist(source, secret, users string) ([]hatypes.User, []error) { diff --git a/pkg/converters/ingress/annotations/backend_test.go b/pkg/converters/ingress/annotations/backend_test.go index 93389cdc0..25a3c91fb 100644 --- a/pkg/converters/ingress/annotations/backend_test.go +++ b/pkg/converters/ingress/annotations/backend_test.go @@ -99,13 +99,12 @@ func TestAffinity(t *testing.T) { func TestAuthHTTP(t *testing.T) { testCase := []struct { - namespace string - ingname string - ann types.BackendAnnotations - secrets ing_helper.SecretContent - expUserlists []*hatypes.Userlist - expHTTPRequests []*hatypes.HTTPRequest - expLogging string + namespace string + ingname string + ann types.BackendAnnotations + secrets ing_helper.SecretContent + expUserlists []*hatypes.Userlist + expLogging string }{ // 0 { @@ -135,25 +134,32 @@ func TestAuthHTTP(t *testing.T) { }, // 5 { - namespace: "ns1", - ingname: "i1", - ann: types.BackendAnnotations{AuthType: "basic", AuthSecret: "mypwd"}, - secrets: ing_helper.SecretContent{"ns1/mypwd": {"auth": []byte{}}}, - expUserlists: []*hatypes.Userlist{&hatypes.Userlist{Name: "ns1_mypwd"}}, - expHTTPRequests: []*hatypes.HTTPRequest{{}}, - expLogging: "WARN userlist on ingress 'ns1/i1' for basic authentication is empty", + ann: types.BackendAnnotations{AuthType: "basic", AuthSecret: "mypwd", AuthRealm: `"a name"`}, + secrets: ing_helper.SecretContent{"default/mypwd": {"auth": []byte("usr1::clear1")}}, + expUserlists: []*hatypes.Userlist{&hatypes.Userlist{Name: "default_mypwd", Users: []hatypes.User{ + {Name: "usr1", Passwd: "clear1", Encrypted: false}, + }}}, + expLogging: "WARN ignoring auth-realm with quotes on ingress 'default/ing1'", }, // 6 { - ann: types.BackendAnnotations{AuthType: "basic", AuthSecret: "basicpwd"}, - secrets: ing_helper.SecretContent{"default/basicpwd": {"auth": []byte("fail")}}, - expUserlists: []*hatypes.Userlist{&hatypes.Userlist{Name: "default_basicpwd"}}, - expHTTPRequests: []*hatypes.HTTPRequest{{}}, + namespace: "ns1", + ingname: "i1", + ann: types.BackendAnnotations{AuthType: "basic", AuthSecret: "mypwd"}, + secrets: ing_helper.SecretContent{"ns1/mypwd": {"auth": []byte{}}}, + expUserlists: []*hatypes.Userlist{&hatypes.Userlist{Name: "ns1_mypwd"}}, + expLogging: "WARN userlist on ingress 'ns1/i1' for basic authentication is empty", + }, + // 7 + { + ann: types.BackendAnnotations{AuthType: "basic", AuthSecret: "basicpwd"}, + secrets: ing_helper.SecretContent{"default/basicpwd": {"auth": []byte("fail")}}, + expUserlists: []*hatypes.Userlist{&hatypes.Userlist{Name: "default_basicpwd"}}, expLogging: ` WARN ignoring malformed usr/passwd on secret 'default/basicpwd', declared on ingress 'default/ing1': missing password of user 'fail' line 1 WARN userlist on ingress 'default/ing1' for basic authentication is empty`, }, - // 7 + // 8 { ann: types.BackendAnnotations{AuthType: "basic", AuthSecret: "basicpwd"}, secrets: ing_helper.SecretContent{"default/basicpwd": {"auth": []byte(` @@ -162,10 +168,9 @@ nopwd`)}}, expUserlists: []*hatypes.Userlist{&hatypes.Userlist{Name: "default_basicpwd", Users: []hatypes.User{ {Name: "usr1", Passwd: "clearpwd1", Encrypted: false}, }}}, - expHTTPRequests: []*hatypes.HTTPRequest{{}}, - expLogging: "WARN ignoring malformed usr/passwd on secret 'default/basicpwd', declared on ingress 'default/ing1': missing password of user 'nopwd' line 3", + expLogging: "WARN ignoring malformed usr/passwd on secret 'default/basicpwd', declared on ingress 'default/ing1': missing password of user 'nopwd' line 3", }, - // 8 + // 9 { ann: types.BackendAnnotations{AuthType: "basic", AuthSecret: "basicpwd"}, secrets: ing_helper.SecretContent{"default/basicpwd": {"auth": []byte(` @@ -173,8 +178,7 @@ usrnopwd1: usrnopwd2:: :encpwd3 ::clearpwd4`)}}, - expUserlists: []*hatypes.Userlist{&hatypes.Userlist{Name: "default_basicpwd"}}, - expHTTPRequests: []*hatypes.HTTPRequest{{}}, + expUserlists: []*hatypes.Userlist{&hatypes.Userlist{Name: "default_basicpwd"}}, expLogging: ` WARN ignoring malformed usr/passwd on secret 'default/basicpwd', declared on ingress 'default/ing1': missing password of user 'usrnopwd1' line 2 WARN ignoring malformed usr/passwd on secret 'default/basicpwd', declared on ingress 'default/ing1': missing password of user 'usrnopwd2' line 3 @@ -182,7 +186,7 @@ WARN ignoring malformed usr/passwd on secret 'default/basicpwd', declared on ing WARN ignoring malformed usr/passwd on secret 'default/basicpwd', declared on ingress 'default/ing1': missing username line 5 WARN userlist on ingress 'default/ing1' for basic authentication is empty`, }, - // 9 + // 10 { ann: types.BackendAnnotations{AuthType: "basic", AuthSecret: "basicpwd"}, secrets: ing_helper.SecretContent{"default/basicpwd": {"auth": []byte(` @@ -192,8 +196,7 @@ usr2::clearpwd2`)}}, {Name: "usr1", Passwd: "encpwd1", Encrypted: true}, {Name: "usr2", Passwd: "clearpwd2", Encrypted: false}, }}}, - expHTTPRequests: []*hatypes.HTTPRequest{{}}, - expLogging: "", + expLogging: "", }, } @@ -211,13 +214,9 @@ usr2::clearpwd2`)}}, d := c.createBackendData(test.namespace, test.ingname, &test.ann) u.buildBackendAuthHTTP(d) userlists := u.haproxy.Userlists() - httpRequests := d.backend.HTTPRequests if len(userlists)+len(test.expUserlists) > 0 && !reflect.DeepEqual(test.expUserlists, userlists) { t.Errorf("userlists config %d differs - expected: %+v - actual: %+v", i, test.expUserlists, userlists) } - if len(httpRequests)+len(test.expHTTPRequests) > 0 && !reflect.DeepEqual(test.expHTTPRequests, httpRequests) { - t.Errorf("httprequest config %d differs - expected: %+v - actual: %+v", i, test.expHTTPRequests, httpRequests) - } c.logger.CompareLogging(test.expLogging) c.teardown() } diff --git a/pkg/haproxy/instance_test.go b/pkg/haproxy/instance_test.go index 028dfd855..9d09441e0 100644 --- a/pkg/haproxy/instance_test.go +++ b/pkg/haproxy/instance_test.go @@ -17,6 +17,7 @@ limitations under the License. package haproxy import ( + "fmt" "io/ioutil" "os" "regexp" @@ -1083,6 +1084,110 @@ d3.local/ d3_app_8080 } +func TestUserlist(t *testing.T) { + type list struct { + name string + users []hatypes.User + } + testCase := []struct { + lists []list + listname string + realm string + config string + }{ + { + lists: []list{ + { + name: "default_usr", + users: []hatypes.User{ + {Name: "usr1", Passwd: "clear1", Encrypted: false}, + }, + }, + }, + listname: "default_usr", + config: ` +userlist default_usr + user usr1 insecure-password clear1`, + }, + { + lists: []list{ + { + name: "default_auth", + users: []hatypes.User{ + {Name: "usr1", Passwd: "clear1", Encrypted: false}, + {Name: "usr2", Passwd: "xxxx", Encrypted: true}, + }, + }, + }, + listname: "default_auth", + realm: "usrlist", + config: ` +userlist default_auth + user usr1 insecure-password clear1 + user usr2 password xxxx`, + }, + { + lists: []list{ + { + name: "default_auth1", + users: []hatypes.User{ + {Name: "usr1", Passwd: "clear1", Encrypted: false}, + }, + }, + { + name: "default_auth2", + users: []hatypes.User{ + {Name: "usr2", Passwd: "xxxx", Encrypted: true}, + }, + }, + }, + listname: "default_auth1", + realm: "multi list", + config: ` +userlist default_auth1 + user usr1 insecure-password clear1 +userlist default_auth2 + user usr2 password xxxx`, + }, + } + for _, test := range testCase { + c := setup(t) + + var h *hatypes.Host + var b *hatypes.Backend + + b = c.config.AcquireBackend("d1", "app", "8080") + b.Endpoints = []*hatypes.Endpoint{endpointS1} + h = c.config.AcquireHost("d1.local") + h.AddPath(b, "/") + + for _, list := range test.lists { + c.config.AddUserlist(list.name, list.users) + } + b.Userlist.Name = test.listname + b.Userlist.Realm = test.realm + + var realm string + if test.realm != "" { + realm = fmt.Sprintf(` realm "%s"`, test.realm) + } + + c.instance.Update() + c.checkConfig(` +<> +<>` + test.config + ` +backend d1_app_8080 + mode http + http-request auth` + realm + ` if !{ http_auth(` + test.listname + `) } + server s1 172.17.0.11:8080 weight 100 +<> +<> +`) + c.logger.CompareLogging(defaultLogging) + c.teardown() + } +} + func TestInstanceWildcardHostname(t *testing.T) { c := setup(t) defer c.teardown() diff --git a/pkg/haproxy/types/backend.go b/pkg/haproxy/types/backend.go index e00d29677..47efa9bc0 100644 --- a/pkg/haproxy/types/backend.go +++ b/pkg/haproxy/types/backend.go @@ -53,13 +53,3 @@ func (b *Backend) AddPath(path string) { return b.Paths[i] > b.Paths[j] }) } - -// HreqValidateUserlist ... -func (b *Backend) HreqValidateUserlist(userlist *Userlist) { - // TODO implement - b.HTTPRequests = append(b.HTTPRequests, &HTTPRequest{}) -} - -func (h *HTTPRequest) String() string { - return fmt.Sprintf("%+v", *h) -} diff --git a/pkg/haproxy/types/types.go b/pkg/haproxy/types/types.go index 344f343f8..42299a4c0 100644 --- a/pkg/haproxy/types/types.go +++ b/pkg/haproxy/types/types.go @@ -240,7 +240,6 @@ type Backend struct { Cors Cors CustomConfig []string HealthCheck HealthCheck - HTTPRequests []*HTTPRequest HSTS HSTS MaxConnServer int MaxQueueServer int @@ -252,6 +251,7 @@ type Backend struct { SSL SSLBackendConfig SSLRedirect bool Timeout BackendTimeoutConfig + Userlist UserlistConfig Whitelist []string } @@ -304,6 +304,11 @@ type BackendTimeoutConfig struct { Tunnel string } +type UserlistConfig struct { + Name string + Realm string +} + // Cookie ... type Cookie struct { Name string @@ -323,10 +328,6 @@ type Cors struct { MaxAge int } -// HTTPRequest ... -type HTTPRequest struct { -} - // HSTS ... type HSTS struct { Enabled bool diff --git a/rootfs/etc/haproxy/template/haproxy.tmpl b/rootfs/etc/haproxy/template/haproxy.tmpl index 62a30128d..f63a0dd95 100644 --- a/rootfs/etc/haproxy/template/haproxy.tmpl +++ b/rootfs/etc/haproxy/template/haproxy.tmpl @@ -179,6 +179,13 @@ backend {{ $backend.ID }} http-request deny if !{ src{{ range $cidr := $backend.Whitelist }} {{ $cidr }}{{ end }} } {{- end }} +{{- /*------------------------------------*/}} +{{- if $backend.Userlist.Name }} + http-request auth + {{- if $backend.Userlist.Realm }} realm "{{ $backend.Userlist.Realm }}"{{ end }} + {{- "" }} if !{ http_auth({{ $backend.Userlist.Name }}) } +{{- end }} + {{- /*------------------------------------*/}} {{- if $backend.SSL.HasTLSAuth }} http-request set-header {{ $global.SSL.HeadersPrefix }}-Client-CN %{+Q}[ssl_c_s_dn(cn)]{{ if not $backend.SSLRedirect }} if { ssl_fc }{{ end }} From f6830e18e8e947e72ed175aea63ca6ac89990a65 Mon Sep 17 00:00:00 2001 From: Joao Morais Date: Sat, 22 Jun 2019 08:54:29 -0300 Subject: [PATCH 28/30] backend http / tcp segmentation --- rootfs/etc/haproxy/template/haproxy.tmpl | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/rootfs/etc/haproxy/template/haproxy.tmpl b/rootfs/etc/haproxy/template/haproxy.tmpl index f63a0dd95..8de888145 100644 --- a/rootfs/etc/haproxy/template/haproxy.tmpl +++ b/rootfs/etc/haproxy/template/haproxy.tmpl @@ -168,11 +168,19 @@ backend {{ $backend.ID }} {{- end }} {{- /*------------------------------------*/}} -{{- if and $backend.ModeTCP $backend.Whitelist }} +{{- /* MODE TCP */}} +{{- /*------------------------------------*/}} +{{- if $backend.ModeTCP }} + +{{- /*------------------------------------*/}} +{{- if $backend.Whitelist }} tcp-request content reject if !{ src{{ range $cidr := $backend.Whitelist }} {{ $cidr }}{{ end }} } {{- end }} -{{- if not $backend.ModeTCP }} +{{- /*------------------------------------*/}} +{{- /* MODE HTTP */}} +{{- /*------------------------------------*/}} +{{- else }}{{/*** if $backend.ModeTCP ***/}} {{- /*------------------------------------*/}} {{- if $backend.Whitelist }} @@ -242,7 +250,8 @@ backend {{ $backend.ID }} {{- $hsts := $backend.HSTS }} http-response set-header Strict-Transport-Security "max-age={{ $hsts.MaxAge }} {{- if $hsts.Subdomains }}; includeSubDomains{{ end }} - {{- if $hsts.Preload }}; preload{{ end }}"{{ if not $backend.SSLRedirect }} if { ssl_fc }{{ end }} + {{- if $hsts.Preload }}; preload{{ end }}" + {{- if not $backend.SSLRedirect }} if { ssl_fc }{{ end }} {{- end }} {{- /*------------------------------------*/}} @@ -269,7 +278,7 @@ backend {{ $backend.ID }} {{- end }} {{- end }} -{{- end }}{{/*** if not $backend.ModeTCP ***/}} +{{- end }}{{/*** if $backend.ModeTCP ***/}} {{- /*------------------------------------*/}} {{- range $ep := $backend.Endpoints }} From 1a148b0387a19029db212f2c906b62f7054b9915 Mon Sep 17 00:00:00 2001 From: Joao Morais Date: Sat, 22 Jun 2019 09:49:09 -0300 Subject: [PATCH 29/30] Add ModSecurity --- pkg/converters/ingress/annotations/backend.go | 11 +++ .../ingress/annotations/backend_test.go | 34 ++++++++ pkg/converters/ingress/annotations/updater.go | 1 + pkg/haproxy/instance_test.go | 77 +++++++++++++++++++ pkg/haproxy/types/types.go | 1 + .../haproxy/modsecurity/spoe-modsecurity.tmpl | 4 +- rootfs/etc/haproxy/template/haproxy.tmpl | 30 ++++++++ 7 files changed, 156 insertions(+), 2 deletions(-) diff --git a/pkg/converters/ingress/annotations/backend.go b/pkg/converters/ingress/annotations/backend.go index fed19ad42..8eb999abf 100644 --- a/pkg/converters/ingress/annotations/backend.go +++ b/pkg/converters/ingress/annotations/backend.go @@ -328,6 +328,17 @@ func (c *updater) buildRewriteURL(d *backData) { d.backend.RewriteURL = d.ann.RewriteTarget } +func (c *updater) buildWAF(d *backData) { + if d.ann.WAF == "" { + return + } + if d.ann.WAF != "modsecurity" { + c.logger.Warn("ignoring invalid WAF mode: %s", d.ann.WAF) + return + } + d.backend.WAF = d.ann.WAF +} + func (c *updater) buildWhitelist(d *backData) { if d.ann.WhitelistSourceRange == "" { return diff --git a/pkg/converters/ingress/annotations/backend_test.go b/pkg/converters/ingress/annotations/backend_test.go index 25a3c91fb..59c890c34 100644 --- a/pkg/converters/ingress/annotations/backend_test.go +++ b/pkg/converters/ingress/annotations/backend_test.go @@ -554,6 +554,40 @@ func TestRewriteURL(t *testing.T) { } } +func TestWAF(t *testing.T) { + testCase := []struct { + waf string + expected string + logging string + }{ + { + waf: "", + expected: "", + logging: "", + }, + { + waf: "none", + expected: "", + logging: "WARN ignoring invalid WAF mode: none", + }, + { + waf: "modsecurity", + expected: "modsecurity", + logging: "", + }, + } + for i, test := range testCase { + c := setup(t) + d := c.createBackendData("default", "app", &types.BackendAnnotations{WAF: test.waf}) + c.createUpdater().buildWAF(d) + if d.backend.WAF != test.expected { + t.Errorf("WAF on %d differs - expected: %v - actual: %v", i, test.expected, d.backend.WAF) + } + c.logger.CompareLogging(test.logging) + c.teardown() + } +} + func TestWhitelist(t *testing.T) { testCase := []struct { cidrlist string diff --git a/pkg/converters/ingress/annotations/updater.go b/pkg/converters/ingress/annotations/updater.go index 47d5ec1bf..cde857ed7 100644 --- a/pkg/converters/ingress/annotations/updater.go +++ b/pkg/converters/ingress/annotations/updater.go @@ -124,5 +124,6 @@ func (c *updater) UpdateBackendConfig(backend *hatypes.Backend, ann *ingtypes.Ba c.buildBackendBlueGreen(data) c.buildBackendCors(data) c.buildRewriteURL(data) + c.buildWAF(data) c.buildWhitelist(data) } diff --git a/pkg/haproxy/instance_test.go b/pkg/haproxy/instance_test.go index 9d09441e0..f80c0f431 100644 --- a/pkg/haproxy/instance_test.go +++ b/pkg/haproxy/instance_test.go @@ -1188,6 +1188,83 @@ backend d1_app_8080 } } +func TestModSecurity(t *testing.T) { + testCases := []struct { + waf string + endpoints []string + backendExp string + modsecExp string + }{ + { + waf: "modsecurity", + endpoints: []string{}, + backendExp: ``, + modsecExp: ``, + }, + { + waf: "", + endpoints: []string{"10.0.0.101:12345"}, + backendExp: ``, + modsecExp: ` + server modsec-spoa0 10.0.0.101:12345`, + }, + { + waf: "modsecurity", + endpoints: []string{"10.0.0.101:12345"}, + backendExp: ` + filter spoe engine modsecurity config /etc/haproxy/spoe-modsecurity.conf + http-request deny if { var(txn.modsec.code) -m int gt 0 }`, + modsecExp: ` + server modsec-spoa0 10.0.0.101:12345`, + }, + { + waf: "modsecurity", + endpoints: []string{"10.0.0.101:12345", "10.0.0.102:12345"}, + backendExp: ` + filter spoe engine modsecurity config /etc/haproxy/spoe-modsecurity.conf + http-request deny if { var(txn.modsec.code) -m int gt 0 }`, + modsecExp: ` + server modsec-spoa0 10.0.0.101:12345 + server modsec-spoa1 10.0.0.102:12345`, + }, + } + for _, test := range testCases { + c := setup(t) + + var h *hatypes.Host + var b *hatypes.Backend + + b = c.config.AcquireBackend("d1", "app", "8080") + b.Endpoints = []*hatypes.Endpoint{endpointS1} + b.WAF = test.waf + h = c.config.AcquireHost("d1.local") + h.AddPath(b, "/") + c.config.Global().ModSecurity.Endpoints = test.endpoints + + c.instance.Update() + + var modsec string + if test.modsecExp != "" { + modsec = ` +backend spoe-modsecurity + mode tcp + timeout connect 5s + timeout server 5s` + test.modsecExp + } + c.checkConfig(` +<> +<> +backend d1_app_8080 + mode http` + test.backendExp + ` + server s1 172.17.0.11:8080 weight 100 +<> +<>` + modsec) + + c.logger.CompareLogging(defaultLogging) + c.teardown() + } +} + func TestInstanceWildcardHostname(t *testing.T) { c := setup(t) defer c.teardown() diff --git a/pkg/haproxy/types/types.go b/pkg/haproxy/types/types.go index 42299a4c0..f08a0df1d 100644 --- a/pkg/haproxy/types/types.go +++ b/pkg/haproxy/types/types.go @@ -252,6 +252,7 @@ type Backend struct { SSLRedirect bool Timeout BackendTimeoutConfig Userlist UserlistConfig + WAF string Whitelist []string } diff --git a/rootfs/etc/haproxy/modsecurity/spoe-modsecurity.tmpl b/rootfs/etc/haproxy/modsecurity/spoe-modsecurity.tmpl index 08a1f98e4..2f0fdd405 100644 --- a/rootfs/etc/haproxy/modsecurity/spoe-modsecurity.tmpl +++ b/rootfs/etc/haproxy/modsecurity/spoe-modsecurity.tmpl @@ -6,7 +6,7 @@ # # This file is automatically updated, do not edit # # # -{{- $modsec := .Global.ModSecurity -}} +{{- $modsec := .Global.ModSecurity }} [modsecurity] spoe-agent modsecurity-agent messages check-request @@ -17,4 +17,4 @@ spoe-agent modsecurity-agent use-backend spoe-modsecurity spoe-message check-request args unique-id method path query req.ver req.hdrs_bin req.body_size req.body - event on-frontend-http-request + event on-backend-http-request diff --git a/rootfs/etc/haproxy/template/haproxy.tmpl b/rootfs/etc/haproxy/template/haproxy.tmpl index 8de888145..6e2f31e38 100644 --- a/rootfs/etc/haproxy/template/haproxy.tmpl +++ b/rootfs/etc/haproxy/template/haproxy.tmpl @@ -194,6 +194,12 @@ backend {{ $backend.ID }} {{- "" }} if !{ http_auth({{ $backend.Userlist.Name }}) } {{- end }} +{{- /*------------------------------------*/}} +{{- if and (eq $backend.WAF "modsecurity") $global.ModSecurity.Endpoints }} + filter spoe engine modsecurity config /etc/haproxy/spoe-modsecurity.conf + http-request deny if { var(txn.modsec.code) -m int gt 0 } +{{- end }} + {{- /*------------------------------------*/}} {{- if $backend.SSL.HasTLSAuth }} http-request set-header {{ $global.SSL.HeadersPrefix }}-Client-CN %{+Q}[ssl_c_s_dn(cn)]{{ if not $backend.SSLRedirect }} if { ssl_fc }{{ end }} @@ -640,4 +646,28 @@ frontend {{ $frontend.Name }} {{- else }} default_backend _error404 {{- end }} +{{- end }} + + + # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # +# # SUPPORT +# # +# + +{{- if $global.ModSecurity.Endpoints }} + + # # # # # # # # # # # # # # # # # # # +# # +# ModSecurity Agent +# +backend spoe-modsecurity + mode tcp + timeout connect 5s + timeout server 5s +{{- range $i, $endpoint := $global.ModSecurity.Endpoints }} + server modsec-spoa{{ $i }} {{ $endpoint }} +{{- end }} + {{- end }} From 874e9c46e810d094a487c5ef702d78b4bc09a1ca Mon Sep 17 00:00:00 2001 From: Joao Morais Date: Sat, 22 Jun 2019 23:04:10 -0300 Subject: [PATCH 30/30] Add oauth --- pkg/converters/ingress/annotations/backend.go | 56 ++++++++ .../ingress/annotations/backend_test.go | 126 ++++++++++++++++++ pkg/converters/ingress/annotations/updater.go | 1 + pkg/haproxy/instance_test.go | 13 ++ pkg/haproxy/types/types.go | 9 ++ rootfs/etc/haproxy/template/haproxy.tmpl | 13 ++ 6 files changed, 218 insertions(+) diff --git a/pkg/converters/ingress/annotations/backend.go b/pkg/converters/ingress/annotations/backend.go index 8eb999abf..951f5238a 100644 --- a/pkg/converters/ingress/annotations/backend.go +++ b/pkg/converters/ingress/annotations/backend.go @@ -313,6 +313,62 @@ func (c *updater) buildBackendCors(d *backData) { } } +var ( + oauthHeaderRegex = regexp.MustCompile(`^[A-Za-z0-9-]+:[A-Za-z0-9-_]+$`) +) + +func (c *updater) buildOAuth(d *backData) { + if d.ann.OAuth == "" { + return + } + if d.ann.OAuth != "oauth2_proxy" { + c.logger.Warn("ignoring invalid oauth implementation '%s' on %v", d.ann.OAuth, d.ann.Source) + return + } + uriPrefix := "/oauth2" + headers := []string{"X-Auth-Request-Email:auth_response_email"} + if d.ann.OAuthURIPrefix != "" { + uriPrefix = d.ann.OAuthURIPrefix + } + if d.ann.OAuthHeaders != "" { + headers = strings.Split(d.ann.OAuthHeaders, ",") + } + uriPrefix = strings.TrimRight(uriPrefix, "/") + namespace := d.ann.Source.Namespace + backend := c.findBackend(namespace, uriPrefix) + if backend == nil { + c.logger.Error("path '%s' was not found on namespace '%s'", uriPrefix, namespace) + return + } + headersMap := make(map[string]string, len(headers)) + for _, header := range headers { + if len(header) == 0 { + continue + } + if !oauthHeaderRegex.MatchString(header) { + c.logger.Warn("invalid header format '%s' on %v", header, d.ann.Source) + continue + } + h := strings.Split(header, ":") + headersMap[h[0]] = h[1] + } + d.backend.OAuth.Impl = d.ann.OAuth + d.backend.OAuth.BackendName = backend.ID + d.backend.OAuth.URIPrefix = uriPrefix + d.backend.OAuth.Headers = headersMap +} + +func (c *updater) findBackend(namespace, uriPrefix string) *hatypes.Backend { + for _, host := range c.haproxy.Hosts() { + for _, path := range host.Paths { + if strings.TrimRight(path.Path, "/") == uriPrefix && path.Backend.Namespace == namespace { + return path.Backend + } + } + } + return nil +} + var ( rewriteURLRegex = regexp.MustCompile(`^[^"' ]+$`) ) diff --git a/pkg/converters/ingress/annotations/backend_test.go b/pkg/converters/ingress/annotations/backend_test.go index 59c890c34..121617543 100644 --- a/pkg/converters/ingress/annotations/backend_test.go +++ b/pkg/converters/ingress/annotations/backend_test.go @@ -518,6 +518,132 @@ INFO-V(3) blue/green balance label 'v=3' on ingress 'default/ing1' does not refe } } +func TestOAuth(t *testing.T) { + testCases := []struct { + ann types.BackendAnnotations + backend string + oauthExp hatypes.OAuthConfig + logging string + }{ + // 0 + { + ann: types.BackendAnnotations{}, + oauthExp: hatypes.OAuthConfig{}, + }, + // 1 + { + ann: types.BackendAnnotations{OAuth: "none"}, + logging: "WARN ignoring invalid oauth implementation 'none' on ingress 'default/app'", + }, + // 2 + { + ann: types.BackendAnnotations{OAuth: "oauth2_proxy"}, + logging: "ERROR path '/oauth2' was not found on namespace 'default'", + }, + // 3 + { + ann: types.BackendAnnotations{OAuth: "oauth2_proxy"}, + backend: "default:back:/oauth2", + oauthExp: hatypes.OAuthConfig{ + Impl: "oauth2_proxy", + BackendName: "default_back_8080", + URIPrefix: "/oauth2", + Headers: map[string]string{"X-Auth-Request-Email": "auth_response_email"}, + }, + }, + // 4 + { + ann: types.BackendAnnotations{OAuth: "oauth2_proxy", OAuthURIPrefix: "/auth"}, + backend: "default:back:/auth", + oauthExp: hatypes.OAuthConfig{ + Impl: "oauth2_proxy", + BackendName: "default_back_8080", + URIPrefix: "/auth", + Headers: map[string]string{"X-Auth-Request-Email": "auth_response_email"}, + }, + }, + // 5 + { + ann: types.BackendAnnotations{OAuth: "oauth2_proxy", OAuthHeaders: "X-Auth-New:attr_from_lua"}, + backend: "default:back:/oauth2", + oauthExp: hatypes.OAuthConfig{ + Impl: "oauth2_proxy", + BackendName: "default_back_8080", + URIPrefix: "/oauth2", + Headers: map[string]string{"X-Auth-New": "attr_from_lua"}, + }, + }, + // 6 + { + ann: types.BackendAnnotations{OAuth: "oauth2_proxy", OAuthHeaders: "space before:attr"}, + backend: "default:back:/oauth2", + oauthExp: hatypes.OAuthConfig{ + Impl: "oauth2_proxy", + BackendName: "default_back_8080", + URIPrefix: "/oauth2", + Headers: map[string]string{}, + }, + logging: "WARN invalid header format 'space before:attr' on ingress 'default/app'", + }, + // 7 + { + ann: types.BackendAnnotations{OAuth: "oauth2_proxy", OAuthHeaders: "no-colon"}, + backend: "default:back:/oauth2", + oauthExp: hatypes.OAuthConfig{ + Impl: "oauth2_proxy", + BackendName: "default_back_8080", + URIPrefix: "/oauth2", + Headers: map[string]string{}, + }, + logging: "WARN invalid header format 'no-colon' on ingress 'default/app'", + }, + // 8 + { + ann: types.BackendAnnotations{OAuth: "oauth2_proxy", OAuthHeaders: "more:colons:unsupported"}, + backend: "default:back:/oauth2", + oauthExp: hatypes.OAuthConfig{ + Impl: "oauth2_proxy", + BackendName: "default_back_8080", + URIPrefix: "/oauth2", + Headers: map[string]string{}, + }, + logging: "WARN invalid header format 'more:colons:unsupported' on ingress 'default/app'", + }, + // 9 + { + ann: types.BackendAnnotations{ + OAuth: "oauth2_proxy", + OAuthHeaders: ",,X-Auth-Request-Email:auth_response_email,,X-Auth-New:attr_from_lua,", + }, + backend: "default:back:/oauth2", + oauthExp: hatypes.OAuthConfig{ + Impl: "oauth2_proxy", + BackendName: "default_back_8080", + URIPrefix: "/oauth2", + Headers: map[string]string{ + "X-Auth-Request-Email": "auth_response_email", + "X-Auth-New": "attr_from_lua", + }, + }, + }, + } + for i, test := range testCases { + c := setup(t) + d := c.createBackendData("default", "app", &test.ann) + if test.backend != "" { + b := strings.Split(test.backend, ":") + backend := c.haproxy.AcquireBackend(b[0], b[1], "8080") + c.haproxy.AcquireHost("app.local").AddPath(backend, b[2]) + } + c.createUpdater().buildOAuth(d) + if !reflect.DeepEqual(test.oauthExp, d.backend.OAuth) { + t.Errorf("oauth on %d differs - expected: %+v - actual: %+v", i, test.oauthExp, d.backend.OAuth) + } + c.logger.CompareLogging(test.logging) + c.teardown() + } +} + func TestRewriteURL(t *testing.T) { testCases := []struct { input string diff --git a/pkg/converters/ingress/annotations/updater.go b/pkg/converters/ingress/annotations/updater.go index cde857ed7..fe17be33d 100644 --- a/pkg/converters/ingress/annotations/updater.go +++ b/pkg/converters/ingress/annotations/updater.go @@ -123,6 +123,7 @@ func (c *updater) UpdateBackendConfig(backend *hatypes.Backend, ann *ingtypes.Ba c.buildBackendAuthHTTP(data) c.buildBackendBlueGreen(data) c.buildBackendCors(data) + c.buildOAuth(data) c.buildRewriteURL(data) c.buildWAF(data) c.buildWhitelist(data) diff --git a/pkg/haproxy/instance_test.go b/pkg/haproxy/instance_test.go index f80c0f431..cf11d2dd6 100644 --- a/pkg/haproxy/instance_test.go +++ b/pkg/haproxy/instance_test.go @@ -145,6 +145,19 @@ func TestBackends(t *testing.T) { expected: ` tcp-request content reject if !{ src 10.0.0.0/8 192.168.0.0/16 }`, }, + { + doconfig: func(g *hatypes.Global, b *hatypes.Backend) { + b.OAuth.Impl = "oauth2_proxy" + b.OAuth.BackendName = "system_oauth_4180" + b.OAuth.URIPrefix = "/oauth2" + b.OAuth.Headers = map[string]string{"X-Auth-Request-Email": "auth_response_email"} + }, + expected: ` + http-request set-header X-Real-IP %[src] + http-request lua.auth-request system_oauth_4180 /oauth2/auth + http-request redirect location /oauth2/start?rd=%[path] if !{ path_beg /oauth2/ } !{ var(txn.auth_response_successful) -m bool } + http-request set-header X-Auth-Request-Email %[var(txn.auth_response_email)] if { var(txn.auth_response_email) -m found }`, + }, } for _, test := range testCases { c := setup(t) diff --git a/pkg/haproxy/types/types.go b/pkg/haproxy/types/types.go index f08a0df1d..7690ed667 100644 --- a/pkg/haproxy/types/types.go +++ b/pkg/haproxy/types/types.go @@ -244,6 +244,7 @@ type Backend struct { MaxConnServer int MaxQueueServer int ModeTCP bool + OAuth OAuthConfig Paths []string ProxyBodySize string RewriteURL string @@ -283,6 +284,14 @@ type HealthCheck struct { RiseCount string } +// OAuthConfig ... +type OAuthConfig struct { + Impl string + BackendName string + URIPrefix string + Headers map[string]string +} + // SSLBackendConfig ... type SSLBackendConfig struct { HasTLSAuth bool diff --git a/rootfs/etc/haproxy/template/haproxy.tmpl b/rootfs/etc/haproxy/template/haproxy.tmpl index 6e2f31e38..6b0a49c53 100644 --- a/rootfs/etc/haproxy/template/haproxy.tmpl +++ b/rootfs/etc/haproxy/template/haproxy.tmpl @@ -224,6 +224,19 @@ backend {{ $backend.ID }} option forwardfor if-none {{- end }} +{{- /*------------------------------------*/}} +{{- if $backend.OAuth.Impl }} +{{- $oauth := $backend.OAuth }} +{{- if eq $oauth.Impl "oauth2_proxy" }} + http-request set-header X-Real-IP %[src] + http-request lua.auth-request {{ $oauth.BackendName }} {{ $oauth.URIPrefix }}/auth + http-request redirect location {{ $oauth.URIPrefix }}/start?rd=%[path] if !{ path_beg {{ $oauth.URIPrefix }}/ } !{ var(txn.auth_response_successful) -m bool } +{{- range $header, $attr := $oauth.Headers }} + http-request set-header {{ $header }} %[var(txn.{{ $attr }})] if { var(txn.{{ $attr }}) -m found } +{{- end }} +{{- end }} +{{- end }} + {{- /*------------------------------------*/}} {{- if $backend.Cookie.Name }} {{- $cookie := $backend.Cookie }}