Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add use-forwarded-proto config key #577

Merged
merged 1 commit into from
May 11, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 15 additions & 6 deletions docs/content/en/docs/configuration/keys.md
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ The table below describes all supported configuration keys.
| [`timeout-tunnel`](#timeout) | time with suffix | Backend | `1h` |
| [`tls-alpn`](#tls-alpn) | TLS ALPN advertisement | Global | `h2,http/1.1` |
| [`use-chroot`](#security) | [true\|false] | Global | `false` |
| [`use-forwarded-proto`](#fronting-proxy-port) | [true\|false] | Global | `true` |
| [`use-haproxy-user`](#security) | [true\|false] | Global | `false` |
| [`use-htx`](#use-htx) | [true\|false] | Global | `false` |
| [`use-proxy-protocol`](#proxy-protocol) | [true\|false] | Global | `false` |
Expand Down Expand Up @@ -925,10 +926,11 @@ See also:

## Fronting proxy port

| Configuration key | Scope | Default | Since |
|-----------------------|----------|---------|--------|
| `fronting-proxy-port` | `Global` | | `v0.8` |
| `https-to-http-port` | `Global` | | |
| Configuration key | Scope | Default | Since |
|-----------------------|----------|---------|---------|
| `fronting-proxy-port` | `Global` | | `v0.8` |
| `https-to-http-port` | `Global` | | |
| `use-forwarded-proto` | `Global` | `true` | `v0.10` |

A port number to listen to http requests from a fronting proxy that does the ssl
offload, eg haproxy ingress behind a cloud load balancers that manages the TLS
Expand All @@ -937,8 +939,15 @@ certificates. `https-to-http-port` is an alias to `fronting-proxy-port`.
`fronting-proxy-port` and [`http-port`](#bind-port) can share the same port number, see below
what changes in the behaviour.

Requests made to `fronting-proxy-port` port number evaluate the `X-Forwarded-Proto`
header to decide how to handle the request:
`use-forwarded-proto` defines if haproxy should use `X-Forwarded-Proto` header to decide
how to handle requests made to `fronting-proxy-port` port number.

If `use-forwarded-proto` is `false`, the request takes the `https` route and is handled as if
`X-Forwarded-Proto` header is `https`, see below. The actual header content is ignored by
haproxy and forwarded to the backend if provided.

If `use-forwarded-proto` is `true`, the default value, requests made to `fronting-proxy-port`
port number evaluate the `X-Forwarded-Proto` header to decide how to handle the request:

* If `X-Forwarded-Proto` header is `https`:
* HAProxy will handle the request just like the ssl-offload was made by HAProxy itself - HSTS header is provided if configured and
Expand Down
1 change: 1 addition & 0 deletions pkg/converters/ingress/annotations/global.go
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ func (c *updater) buildGlobalHTTPStoHTTP(d *globalData) {
}
// TODO Change all `ToHTTP` naming to `FrontingProxy`
d.global.Bind.FrontingBind = bind
d.global.Bind.FrontingUseProto = d.mapper.Get(ingtypes.GlobalUseForwardedProto).Bool()
// Socket ID should be a high number to avoid colision
// between the same socket ID from distinct frontends
// TODO match socket and frontend ID in the backend
Expand Down
1 change: 1 addition & 0 deletions pkg/converters/ingress/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ func createDefaults() map[string]string {
types.GlobalTimeoutClientFin: "50s",
types.GlobalTimeoutStop: "10m",
types.GlobalTLSALPN: "h2,http/1.1",
types.GlobalUseForwardedProto: "true",
types.GlobalUseHTX: "true",
}
}
1 change: 1 addition & 0 deletions pkg/converters/ingress/types/global.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ const (
GlobalTimeoutStop = "timeout-stop"
GlobalTLSALPN = "tls-alpn"
GlobalUseChroot = "use-chroot"
GlobalUseForwardedProto = "use-forwarded-proto"
GlobalUseHAProxyUser = "use-haproxy-user"
GlobalUseHTX = "use-htx"
GlobalUseProxyProtocol = "use-proxy-protocol"
Expand Down
187 changes: 178 additions & 9 deletions pkg/haproxy/instance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -741,9 +741,9 @@ empty/ default_empty_8080`)
c.logger.CompareLogging(defaultLogging)
}

func TestInstanceToHTTPSocket(t *testing.T) {
func TestInstanceFrontingProxyUseProto(t *testing.T) {
testCases := []struct {
toHTTPBind string
frontingBind string
domain string
expectedFront string
expectedMap string
Expand All @@ -753,8 +753,8 @@ func TestInstanceToHTTPSocket(t *testing.T) {
}{
// 0
{
toHTTPBind: ":8000",
domain: "d1.local",
frontingBind: ":8000",
domain: "d1.local",
expectedFront: `
mode http
bind :80
Expand Down Expand Up @@ -788,8 +788,8 @@ func TestInstanceToHTTPSocket(t *testing.T) {
},
// 1
{
toHTTPBind: ":8000",
domain: "*.d1.local",
frontingBind: ":8000",
domain: "*.d1.local",
expectedFront: `
mode http
bind :80
Expand Down Expand Up @@ -831,8 +831,8 @@ func TestInstanceToHTTPSocket(t *testing.T) {
},
// 2
{
toHTTPBind: ":80",
domain: "d1.local",
frontingBind: ":80",
domain: "d1.local",
expectedFront: `
mode http
bind :80
Expand Down Expand Up @@ -887,8 +887,9 @@ func TestInstanceToHTTPSocket(t *testing.T) {
}
h.TLS.CAHash = "1"
h.TLS.CAFilename = "/var/haproxy/ssl/ca.pem"
c.config.Global().Bind.FrontingBind = test.toHTTPBind
c.config.Global().Bind.FrontingBind = test.frontingBind
c.config.Global().Bind.FrontingSockID = 11
c.config.Global().Bind.FrontingUseProto = true

c.Update()
c.checkConfig(`
Expand Down Expand Up @@ -937,6 +938,174 @@ frontend _front001
}
}

func TestInstanceFrontingProxyIgnoreProto(t *testing.T) {
testCases := []struct {
frontingBind string
domain string
expectedFront string
expectedMap string
expectedRegexMap string
expectedACL string
expectedSetvar string
}{
// 0
{
frontingBind: ":8000",
domain: "d1.local",
expectedFront: `
mode http
bind :80
bind :8000 id 11
http-request set-var(req.base) base,lower,regsub(:[0-9]+/,/)
http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/_global_http_front.map)
use_backend %[var(req.backend)] if { var(req.backend) -m found }`,
expectedMap: "d1.local/ d1_app_8080",
expectedACL: `
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-host-need-crt var(req.host) -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`,
expectedSetvar: `
http-request set-var(req.path) path
http-request set-var(req.snibase) ssl_fc_sni,concat(,req.path),lower
http-request set-var(req.snibackend) var(req.snibase),map_beg(/etc/haproxy/maps/_front001_sni.map)
http-request set-var(req.snibackend) var(req.base),map_beg(/etc/haproxy/maps/_front001_sni.map) if !{ var(req.snibackend) -m found } !tls-has-crt !tls-host-need-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`,
},
// 1
{
frontingBind: ":8000",
domain: "*.d1.local",
expectedFront: `
mode http
bind :80
bind :8000 id 11
http-request set-var(req.base) base,lower,regsub(:[0-9]+/,/)
http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/_global_http_front.map)
http-request set-var(req.backend) var(req.base),map_reg(/etc/haproxy/maps/_global_http_front_regex.map) if !{ var(req.backend) -m found }
use_backend %[var(req.backend)] if { var(req.backend) -m found }`,
expectedRegexMap: `^[^.]+\.d1\.local/ d1_app_8080`,
expectedACL: `
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-need-crt ssl_fc_sni -i -m reg -f /etc/haproxy/maps/_front001_no_crt_regex.list
acl tls-host-need-crt var(req.host) -i -f /etc/haproxy/maps/_front001_no_crt.list
acl tls-host-need-crt var(req.host) -i -m reg -f /etc/haproxy/maps/_front001_no_crt_regex.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
acl tls-check-crt ssl_fc_sni -i -m reg -f /etc/haproxy/maps/_front001_inv_crt_regex.list`,
expectedSetvar: `
http-request set-var(req.path) path
http-request set-var(req.snibase) ssl_fc_sni,concat(,req.path),lower
http-request set-var(req.snibackend) var(req.snibase),map_beg(/etc/haproxy/maps/_front001_sni.map)
http-request set-var(req.snibackend) var(req.snibase),map_reg(/etc/haproxy/maps/_front001_sni_regex.map) if !{ var(req.snibackend) -m found }
http-request set-var(req.snibackend) var(req.base),map_beg(/etc/haproxy/maps/_front001_sni.map) if !{ var(req.snibackend) -m found } !tls-has-crt !tls-host-need-crt
http-request set-var(req.snibackend) var(req.base),map_reg(/etc/haproxy/maps/_front001_sni_regex.map) if !{ var(req.snibackend) -m found } !tls-has-crt !tls-host-need-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`,
},
// 2
{
frontingBind: ":80",
domain: "d1.local",
expectedFront: `
mode http
bind :80
http-request set-var(req.base) base,lower,regsub(:[0-9]+/,/)
http-request set-var(req.backend) var(req.base),map_beg(/etc/haproxy/maps/_global_http_front.map)
use_backend %[var(req.backend)] if { var(req.backend) -m found }`,
expectedMap: "d1.local/ d1_app_8080",
expectedACL: `
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-host-need-crt var(req.host) -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`,
expectedSetvar: `
http-request set-var(req.path) path
http-request set-var(req.snibase) ssl_fc_sni,concat(,req.path),lower
http-request set-var(req.snibackend) var(req.snibase),map_beg(/etc/haproxy/maps/_front001_sni.map)
http-request set-var(req.snibackend) var(req.base),map_beg(/etc/haproxy/maps/_front001_sni.map) if !{ var(req.snibackend) -m found } !tls-has-crt !tls-host-need-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`,
},
}
for _, test := range testCases {
c := setup(t)

var h *hatypes.Host
var b *hatypes.Backend

b = c.config.Backends().AcquireBackend("d1", "app", "8080")
h = c.config.Hosts().AcquireHost(test.domain)
h.AddPath(b, "/")
b.Endpoints = []*hatypes.Endpoint{endpointS1}
b.HSTS = []*hatypes.BackendConfigHSTS{
{
Paths: hatypes.NewBackendPaths(b.FindHostPath(test.domain + "/")),
Config: hatypes.HSTS{
Enabled: true,
MaxAge: 15768000,
Subdomains: true,
Preload: true,
},
},
}
h.TLS.CAHash = "1"
h.TLS.CAFilename = "/var/haproxy/ssl/ca.pem"
c.config.Global().Bind.FrontingBind = test.frontingBind
c.config.Global().Bind.FrontingSockID = 11
c.config.Global().Bind.FrontingUseProto = false

c.Update()
c.checkConfig(`
<<global>>
<<defaults>>
backend d1_app_8080
mode http
acl local-offload ssl_fc
http-request set-header X-SSL-Client-CN %{+Q}[ssl_c_s_dn(cn)] if local-offload
http-request set-header X-SSL-Client-DN %{+Q}[ssl_c_s_dn] if local-offload
http-request set-header X-SSL-Client-SHA1 %{+Q}[ssl_c_sha1,hex] if local-offload
http-response set-header Strict-Transport-Security "max-age=15768000; includeSubDomains; preload"
server s1 172.17.0.11:8080 weight 100
<<backends-default>>
frontend _front_http` + test.expectedFront + `
default_backend _error404
frontend _front001
mode http
bind :443 ssl alpn h2,http/1.1 crt-list /etc/haproxy/maps/_front001_bind_crt.list ca-ignore-err all crt-ignore-err all
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)
http-request set-var(req.host) hdr(host),lower,regsub(:[0-9]+$,)
http-request set-header X-Forwarded-Proto https
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` + test.expectedACL + test.expectedSetvar + `
http-request use-service lua.send-421 if tls-has-crt { ssl_fc_has_sni } !{ ssl_fc_sni,strcmp(req.host) eq 0 }
http-request use-service lua.send-496 if { var(req.tls_nocrt_redir) _internal }
http-request use-service lua.send-421 if !tls-has-crt tls-host-need-crt
http-request use-service lua.send-495 if { var(req.tls_invalidcrt_redir) _internal }
use_backend %[var(req.hostbackend)] if { var(req.hostbackend) -m found }
use_backend %[var(req.snibackend)] if { var(req.snibackend) -m found }
default_backend _error404
<<support>>
`)
c.checkMap("_global_http_front.map", test.expectedMap)
if test.expectedRegexMap != "" {
c.checkMap("_global_http_front_regex.map", test.expectedRegexMap)
}
c.logger.CompareLogging(defaultLogging)
c.teardown()
}
}

func TestInstanceTCPBackend(t *testing.T) {
testCases := []struct {
doconfig func(c *testConfig)
Expand Down
13 changes: 7 additions & 6 deletions pkg/haproxy/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,13 @@ type Global struct {

// GlobalBindConfig ...
type GlobalBindConfig struct {
AcceptProxy bool
HTTPBind string
HTTPSBind string
TCPBindIP string
FrontingBind string
FrontingSockID int
AcceptProxy bool
HTTPBind string
HTTPSBind string
TCPBindIP string
FrontingBind string
FrontingSockID int
FrontingUseProto bool
}

// ProcsConfig ...
Expand Down
Loading