Skip to content

Commit

Permalink
feat: (WIP) Proxy Protocol Check for Traffic Endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
a18e authored and peanball committed Apr 19, 2024
1 parent c2b816b commit b29433f
Show file tree
Hide file tree
Showing 7 changed files with 142 additions and 3 deletions.
5 changes: 4 additions & 1 deletion acceptance-tests/go.mod
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
module github.com/cloudfoundry/haproxy-boshrelease/acceptance-tests

go 1.18
go 1.21

toolchain go1.22.2

require (
github.com/bramvdbogaerde/go-scp v1.4.0
github.com/gorilla/websocket v1.5.1
github.com/onsi/ginkgo/v2 v2.17.1
github.com/onsi/gomega v1.33.0
github.com/pires/go-proxyproto v0.7.0
golang.org/x/crypto v0.22.0
golang.org/x/net v0.24.0
gopkg.in/yaml.v2 v2.4.0
Expand Down
4 changes: 4 additions & 0 deletions acceptance-tests/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ github.com/onsi/ginkgo/v2 v2.17.1 h1:V++EzdbhI4ZV4ev0UTIj0PzhzOcReJFyJaLjtSF55M8
github.com/onsi/ginkgo/v2 v2.17.1/go.mod h1:llBI3WDLL9Z6taip6f33H76YcWtJv+7R3HigUjbIBOs=
github.com/onsi/gomega v1.33.0 h1:snPCflnZrpMsy94p4lXVEkHo12lmPnc3vY5XBbreexE=
github.com/onsi/gomega v1.33.0/go.mod h1:+925n5YtiFsLzzafLUHzVMBpvvRAzrydIBiSIxjX3wY=
github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs=
github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
Expand All @@ -34,11 +36,13 @@ golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc=
golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
Expand Down
109 changes: 109 additions & 0 deletions acceptance-tests/proxy_protocol_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package acceptance_tests

import (
"fmt"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
proxyproto "github.com/pires/go-proxyproto"
"net"
"net/http"
)

var _ = Describe("Proxy Protocol", func() {
opsfileProxyProtocol := `---
# Enable Proxy Protocol
- type: replace
path: /instance_groups/name=haproxy/jobs/name=haproxy/properties/ha_proxy/accept_proxy?
value: true
- type: replace
path: /instance_groups/name=haproxy/jobs/name=haproxy/properties/ha_proxy/enable_health_check_http?
value: true
- type: replace
path: /instance_groups/name=haproxy/jobs/name=haproxy/properties/ha_proxy/disable_health_check_proxy?
value: false
`

It("Correctly proxies Proxy Protocol requests", func() {
haproxyBackendPort := 12000
haproxyInfo, _ := deployHAProxy(baseManifestVars{
haproxyBackendPort: haproxyBackendPort,
haproxyBackendServers: []string{"127.0.0.1"},
deploymentName: deploymentNameForTestNode(),
}, []string{opsfileProxyProtocol}, map[string]interface{}{}, true)

closeLocalServer, localPort := startDefaultTestServer()
defer closeLocalServer()

closeTunnel := setupTunnelFromHaproxyToTestServer(haproxyInfo, haproxyBackendPort, localPort)
defer closeTunnel()

By("Sending a request with Proxy Protocol Header to HAProxy traffic port")
err := performProxyProtocolRequest(haproxyInfo.PublicIP, 80, "/")
Expect(err).NotTo(HaveOccurred())

By("Sending a request without Proxy Protocol Header to HAProxy")
expect400(http.Get(fmt.Sprintf("http://%s", haproxyInfo.PublicIP)))

By("Sending a request with Proxy Protocol Header to HAProxy healthcheck port")
err = performProxyProtocolRequest(haproxyInfo.PublicIP, 8080, "/health")
Expect(err).NotTo(HaveOccurred())

By("Sending a request without Proxy Protocol Header to HAProxy healthcheck port")
expect400(http.Get(fmt.Sprintf("http://%s:8080/health", haproxyInfo.PublicIP)))
})
})

func performProxyProtocolRequest(ip string, port int, endpoint string) error {
// Create a connection to the HAProxy instance
conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", ip, port))
if err != nil {
return err
}

defer conn.Close()

// Create proxy protocol header
header := &proxyproto.Header{
Version: 1,
Command: proxyproto.PROXY,
TransportProtocol: proxyproto.TCPv4,
SourceAddr: &net.TCPAddr{
IP: net.ParseIP("10.1.1.1"),
Port: 1000,
},
DestinationAddr: &net.TCPAddr{
IP: net.ParseIP(ip),
Port: port,
},
}

// Write header to the connection
_, err = header.WriteTo(conn)
if err != nil {
return err
}

// Send HTTP Request
request := fmt.Sprintf("GET %s HTTP/1.1\r\n"+
"Host: %s\r\n"+
"Content-Length: 0\r\n"+
"Content-Type: text/plain\r\n"+
"\r\n", endpoint, ip)
_, err = conn.Write([]byte(request))
if err != nil {
return err
}

// Read the response
buf := make([]byte, 1024)
_, err = conn.Read(buf)
if err != nil {
return err
}

// Get HTTP status code
if string(buf[9:12]) != "200" {
return fmt.Errorf("expected HTTP status code 200, got %s", string(buf))
}
return nil
}
7 changes: 6 additions & 1 deletion ci/scripts/acceptance-tests
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@ if [ -n "$FOCUS" ]; then
ADDITIONAL_ARGS=("--focus" "$FOCUS")
fi

cd "${REPO_ROOT:?required}"
cd ${REPO_ROOT:?required}

# TODO TEMPORARY: REMOVE BEFORE MERGE
git config --global --add safe.directory /repo
git config --global --add safe.directory /repo/src/ttar

echo "----- Pulling in any git submodules..."
git submodule update --init --recursive --force

Expand Down
3 changes: 3 additions & 0 deletions jobs/haproxy/spec
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,9 @@ properties:
ha_proxy.disable_tcp_accept_proxy:
description: "Disables the PROXY protocol on tcp backends. Only applies if `ha_proxy.accept_proxy` is enabled."
default: false
ha_proxy.disable_health_check_proxy:
description: "Disables the use of the PROXY protocol for health checks. Only applies if `ha_proxy.accept_proxy` is enabled."
default: false
ha_proxy.binding_ip:
description: "If there are multiple ethernet interfaces, specify which one to bind. Set to `::` to bind to all IPv6 interfaces (no IPv4). IPv6 must be enabled on the HAProxy VM in the deployment manifest."
default: ""
Expand Down
2 changes: 1 addition & 1 deletion jobs/haproxy/templates/haproxy.config.erb
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,7 @@ listen health_check_http_url
mode http
option httpclose
monitor-uri /health
<%- if p("ha_proxy.accept_proxy") -%>
<% if p("ha_proxy.accept_proxy") && !p("ha_proxy.disable_health_check_proxy") -%>
tcp-request connection expect-proxy layer4 unless LOCALHOST
<%- end -%>
acl http-routers_down nbsrv(<%= backends.first[:name] %>) eq 0
Expand Down
15 changes: 15 additions & 0 deletions spec/haproxy/templates/haproxy_config/healthcheck_listener_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,21 @@
it 'sets expect-proxy with exception for LOCALHOST' do
expect(healthcheck_listener).to include('tcp-request connection expect-proxy layer4 unless LOCALHOST')
end

context 'when ha_proxy.disable_health_check_proxy is also true' do
let(:properties) do
{
'enable_health_check_http' => true,
'accept_proxy' => true,
'disable_health_check_proxy' => true,
}
end

it 'does not set expect-proxy for the healthcheck' do
expect(healthcheck_listener).not_to include('tcp-request connection expect-proxy layer4 unless LOCALHOST')
end
end

end
end
end

0 comments on commit b29433f

Please sign in to comment.