Skip to content

Commit 4ee3a0a

Browse files
add new property backend_match_http_protocol
1 parent c9061d5 commit 4ee3a0a

File tree

12 files changed

+561
-174
lines changed

12 files changed

+561
-174
lines changed

acceptance-tests/acceptance_tests_suite_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ func startDefaultTestServer() (func(), int) {
4040
var upgrader = websocket.Upgrader{}
4141

4242
By("Starting a local websocket server to act as a backend")
43-
closeLocalServer, localPort, err := startLocalHTTPServer(func(w http.ResponseWriter, r *http.Request) {
43+
closeLocalServer, localPort, err := startLocalHTTPServer(nil, func(w http.ResponseWriter, r *http.Request) {
4444
// if no upgrade requested, act like a normal HTTP server
4545
if strings.ToLower(r.Header.Get("Upgrade")) != "websocket" {
4646
fmt.Fprintln(w, "Hello cloud foundry")
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
package acceptance_tests
2+
3+
import (
4+
"crypto/tls"
5+
"fmt"
6+
"net/http"
7+
8+
. "github.com/onsi/ginkgo"
9+
. "github.com/onsi/gomega"
10+
)
11+
12+
var _ = Describe("Backend match HTTP protocol", func() {
13+
var haproxyInfo haproxyInfo
14+
var closeTunnel func()
15+
var closeLocalServer func()
16+
var http1Client *http.Client
17+
var http2Client *http.Client
18+
19+
haproxyBackendPort := 12000
20+
opsfileHTTPS := `---
21+
- type: replace
22+
path: /instance_groups/name=haproxy/jobs/name=haproxy/properties/ha_proxy/backend_ssl?
23+
value: verify
24+
- type: replace
25+
path: /instance_groups/name=haproxy/jobs/name=haproxy/properties/ha_proxy/backend_ca_file?
26+
value: ((https_backend.ca))
27+
- type: replace
28+
path: /instance_groups/name=haproxy/jobs/name=haproxy/properties/ha_proxy/backend_match_http_protocol?
29+
value: true
30+
# Configure CA and cert chain
31+
- type: replace
32+
path: /instance_groups/name=haproxy/jobs/name=haproxy/properties/ha_proxy/crt_list?/-
33+
value:
34+
snifilter:
35+
- haproxy.internal
36+
ssl_pem:
37+
cert_chain: ((https_frontend.certificate))((default_ca.certificate))
38+
private_key: ((https_frontend.private_key))
39+
alpn: ['h2', 'http/1.1']
40+
# Declare certs
41+
- type: replace
42+
path: /variables?/-
43+
value:
44+
name: default_ca
45+
type: certificate
46+
options:
47+
is_ca: true
48+
common_name: bosh
49+
- type: replace
50+
path: /variables?/-
51+
value:
52+
name: https_frontend
53+
type: certificate
54+
options:
55+
ca: default_ca
56+
common_name: haproxy.internal
57+
alternative_names: [haproxy.internal]
58+
- type: replace
59+
path: /variables?/-
60+
value:
61+
name: https_backend
62+
type: certificate
63+
options:
64+
ca: default_ca
65+
common_name: 127.0.0.1
66+
alternative_names: [127.0.0.1]
67+
`
68+
69+
var creds struct {
70+
HTTPSFrontend struct {
71+
Certificate string `yaml:"certificate"`
72+
PrivateKey string `yaml:"private_key"`
73+
CA string `yaml:"ca"`
74+
} `yaml:"https_frontend"`
75+
HTTPSBackend struct {
76+
Certificate string `yaml:"certificate"`
77+
PrivateKey string `yaml:"private_key"`
78+
CA string `yaml:"ca"`
79+
} `yaml:"https_backend"`
80+
}
81+
82+
JustBeforeEach(func() {
83+
var varsStoreReader varsStoreReader
84+
haproxyInfo, varsStoreReader = deployHAProxy(baseManifestVars{
85+
haproxyBackendPort: haproxyBackendPort,
86+
haproxyBackendServers: []string{"127.0.0.1"},
87+
deploymentName: defaultDeploymentName,
88+
}, []string{opsfileHTTPS}, map[string]interface{}{}, true)
89+
90+
err := varsStoreReader(&creds)
91+
Expect(err).NotTo(HaveOccurred())
92+
93+
// Build backend server that supports HTTP2 and HTTP1.1
94+
backendTLSCert, err := tls.X509KeyPair([]byte(creds.HTTPSBackend.Certificate), []byte(creds.HTTPSBackend.PrivateKey))
95+
Expect(err).NotTo(HaveOccurred())
96+
97+
backendTLSConfig := &tls.Config{
98+
Certificates: []tls.Certificate{backendTLSCert},
99+
MinVersion: tls.VersionTLS12,
100+
MaxVersion: tls.VersionTLS12,
101+
NextProtos: []string{"h2", "http/1.1"},
102+
}
103+
104+
var localPort int
105+
closeLocalServer, localPort, err = startLocalHTTPServer(backendTLSConfig, func(w http.ResponseWriter, r *http.Request) {
106+
fmt.Println("Backend server handling incoming request")
107+
protocolHeaderValue := "none"
108+
if r.TLS != nil {
109+
protocolHeaderValue = r.TLS.NegotiatedProtocol
110+
}
111+
w.Header().Add("X-BACKEND-ALPN-PROTOCOL", protocolHeaderValue)
112+
w.Header().Add("X-BACKEND-PROTO", r.Proto)
113+
_, _ = w.Write([]byte("OK"))
114+
})
115+
Expect(err).NotTo(HaveOccurred())
116+
closeTunnel = setupTunnelFromHaproxyToTestServer(haproxyInfo, haproxyBackendPort, localPort)
117+
118+
addresses := map[string]string{
119+
"haproxy.internal:443": fmt.Sprintf("%s:443", haproxyInfo.PublicIP),
120+
}
121+
122+
http1Client = buildHTTPClient([]string{creds.HTTPSFrontend.CA}, addresses, []tls.Certificate{}, "")
123+
http2Client = buildHTTP2Client([]string{creds.HTTPSFrontend.CA}, addresses, []tls.Certificate{})
124+
})
125+
126+
Context("When backend_match_http_protocol is true", func() {
127+
It("uses the same backend protocol as was used for the frontend connection", func() {
128+
resp, err := http1Client.Get("https://haproxy.internal:443")
129+
Expect(err).NotTo(HaveOccurred())
130+
Expect(resp.StatusCode).To(Equal(200))
131+
132+
// Frontend request HTTP1.1
133+
Expect(resp.Proto).To(Equal("HTTP/1.1"))
134+
Expect(resp.TLS.NegotiatedProtocol).To(Equal(""))
135+
136+
// Backend request HTTP1.1
137+
Expect(resp.Header.Get("X-BACKEND-PROTO")).To((Equal("HTTP/1.1")))
138+
Expect(resp.Header.Get("X-BACKEND-ALPN-PROTOCOL")).To((Equal("http/1.1")))
139+
140+
resp, err = http2Client.Get("https://haproxy.internal:443")
141+
Expect(err).NotTo(HaveOccurred())
142+
Expect(resp.StatusCode).To(Equal(200))
143+
144+
// Frontend request HTTP2
145+
Expect(resp.Proto).To(Equal("HTTP/2.0"))
146+
Expect(resp.TLS.NegotiatedProtocol).To(Equal("h2"))
147+
148+
// Backend request HTTP2
149+
Expect(resp.Header.Get("X-BACKEND-PROTO")).To((Equal("HTTP/2.0")))
150+
Expect(resp.Header.Get("X-BACKEND-ALPN-PROTOCOL")).To(Equal("h2"))
151+
})
152+
})
153+
154+
AfterEach(func() {
155+
if closeLocalServer != nil {
156+
defer closeLocalServer()
157+
}
158+
if closeTunnel != nil {
159+
defer closeTunnel()
160+
}
161+
})
162+
})

acceptance-tests/http.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,15 @@ import (
1818

1919
// starts a local http server handling the provided handler
2020
// returns a close function to stop the server and the port the server is listening on
21-
func startLocalHTTPServer(handler func(http.ResponseWriter, *http.Request)) (func(), int, error) {
22-
server := httptest.NewServer(http.HandlerFunc(handler))
21+
func startLocalHTTPServer(tlsConfig *tls.Config, handler func(http.ResponseWriter, *http.Request)) (func(), int, error) {
22+
server := httptest.NewUnstartedServer(http.HandlerFunc(handler))
23+
if tlsConfig != nil {
24+
server.TLS = tlsConfig
25+
server.StartTLS()
26+
} else {
27+
server.Start()
28+
}
29+
2330
serverURL, err := url.Parse(server.URL)
2431
if err != nil {
2532
return nil, 0, err
@@ -78,6 +85,7 @@ func buildHTTP2Client(caCerts []string, addressMap map[string]string, clientCert
7885

7986
httpClient := buildHTTPClient(caCerts, addressMap, clientCerts, "")
8087
transport := httpClient.Transport.(*http.Transport)
88+
8189
http2.ConfigureTransport(transport)
8290

8391
// force HTTP2-only

acceptance-tests/xfcc_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ var _ = Describe("forwarded_client_cert", func() {
149149

150150
By("Starting a local http server to act as a backend")
151151
var localPort int
152-
closeLocalServer, localPort, err = startLocalHTTPServer(func(w http.ResponseWriter, r *http.Request) {
152+
closeLocalServer, localPort, err = startLocalHTTPServer(nil, func(w http.ResponseWriter, r *http.Request) {
153153
fmt.Println("Backend server handling incoming request")
154154
recordedHeaders = r.Header
155155
_, _ = w.Write([]byte("OK"))

jobs/haproxy/spec

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ properties:
146146
ssl_ciphersuites: TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
147147
ssl_min_version: TLSv1.2
148148
ssl_max_version: TLSv1.3
149-
alpn:
149+
alpn:
150150
- h2
151151
- http/1.1
152152
client_revocation_list: |
@@ -248,9 +248,12 @@ properties:
248248
ha_proxy.disable_tls_13:
249249
default: false
250250
description: "Disable TLS 1.3 in HA Proxy"
251+
ha_proxy.backend_match_http_protocol:
252+
default: false
253+
description: Uses the same version of HTTP for backend connections that was used for frontend connections (ie HTTP 1.1 or HTTP 2). Ignores the value of enable_http2. HTTP2 backend connections require that `ha_proxy.backend_ssl` is not `off`.
251254
ha_proxy.disable_backend_http2_websockets:
252255
default: false
253-
description: "Forward websockets to the backend servers using HTTP/1.1, never HTTP/2. Does not apply to custom routed_backend_servers. Works around https://github.com/cloudfoundry/routing-release/issues/230"
256+
description: "Forward websockets to the backend servers using HTTP/1.1, never HTTP/2. Does not apply to custom routed_backend_servers. Works around https://github.com/cloudfoundry/routing-release/issues/230. Overrides backend_match_http_protocol for websockets."
254257

255258
ha_proxy.connect_timeout:
256259
description: "Timeout (in floating point seconds) used on connections from haproxy to a backend, while waiting for the TCP handshake to complete + connection to establish"
@@ -302,7 +305,7 @@ properties:
302305
description: "Array of the router IPs acting as the HTTP/TCP backends (should include servers all Availability Zones being used)"
303306
default: []
304307
ha_proxy.backend_ssl:
305-
description: "Optionally enable SSL verification for backend servers, one of `verify`, `noverify`, any other value assumes no ssl backend. Setting `verify` requires `ha_proxy.backend_ca_file` key to be set."
308+
description: "Optionally enable SSL verification for backend servers, one of `verify`, `noverify`, any other value assumes no ssl backend. Setting `verify` requires `ha_proxy.backend_ca_file` key to be set. Note that `off` will disable all backend HTTP2 support regardless of other properties."
306309
default: "off"
307310
ha_proxy.backend_ssl_verifyhost:
308311
description: "Optional hostname to verify in the x509 certificate subject for SSL-enabled backend servers. Requires `ha_proxy.backend_ssl` is set to `verify` when using this."
@@ -663,5 +666,5 @@ properties:
663666
Prefer backend servers which are located on the same availability zone. Note that this only affects servers provided via the http_backend link property. Servers provided via the tcp backend_link will automatically prefer the local AZ.
664667
default: false
665668
ha_proxy.enable_http2:
666-
description: Enables ingress and egress HTTP/2 ALPN negotiation
669+
description: Enables ingress (frontend) and egress (backend) HTTP/2 ALPN negotiation. Egress (backend) HTTP protocol version may be overriden by `ha_proxy.backend_ssl`, `ha_proxy.disable_backend_http2_websockets` and `ha_proxy.backend_match_http_protocol`.
667670
default: false

0 commit comments

Comments
 (0)