diff --git a/acceptance-tests/go.mod b/acceptance-tests/go.mod index 083c16e9..02271073 100644 --- a/acceptance-tests/go.mod +++ b/acceptance-tests/go.mod @@ -7,6 +7,7 @@ require ( github.com/gorilla/websocket v1.5.1 github.com/onsi/ginkgo/v2 v2.16.0 github.com/onsi/gomega v1.31.1 + github.com/pires/go-proxyproto v0.7.0 golang.org/x/crypto v0.21.0 golang.org/x/net v0.22.0 gopkg.in/yaml.v2 v2.4.0 diff --git a/acceptance-tests/go.sum b/acceptance-tests/go.sum index 69c67d45..21cc070b 100644 --- a/acceptance-tests/go.sum +++ b/acceptance-tests/go.sum @@ -22,6 +22,8 @@ github.com/onsi/ginkgo/v2 v2.16.0 h1:7q1w9frJDzninhXxjZd+Y/x54XNjG/UlRLIYPZafsPM github.com/onsi/ginkgo/v2 v2.16.0/go.mod h1:llBI3WDLL9Z6taip6f33H76YcWtJv+7R3HigUjbIBOs= github.com/onsi/gomega v1.31.1 h1:KYppCUK+bUgAZwHOu7EXVBKyQA6ILvOESHkn/tgoqvo= github.com/onsi/gomega v1.31.1/go.mod h1:y40C95dwAD1Nz36SsEnxvfFe8FFfNxzI5eJ0EYGyAy0= +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= diff --git a/acceptance-tests/proxy_protocol_test.go b/acceptance-tests/proxy_protocol_test.go new file mode 100644 index 00000000..cefc7428 --- /dev/null +++ b/acceptance-tests/proxy_protocol_test.go @@ -0,0 +1,96 @@ +package acceptance_tests + +import ( + "fmt" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + proxyproto "github.com/pires/go-proxyproto" + "io" + "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 +` + It("Correctly proxies Proxy Protocol requests", func() { + haproxyBackendPort := 12000 + haproxyInfo, _ := deployHAProxy(baseManifestVars{ + haproxyBackendPort: haproxyBackendPort, + haproxyBackendServers: []string{"127.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") + err := performProxyProtocolRequest(haproxyInfo.PublicIP, 443) + Expect(err).NotTo(HaveOccurred()) + + By("Sending a request without Proxy Protocol Header to HAProxy") + expect400(http.Get(fmt.Sprintf("http://%s", haproxyInfo.PublicIP))) + }) +}) + +func performProxyProtocolRequest(ip string, port int) error { + // Dial some proxy listener e.g. https://github.com/mailgun/proxyproto + target, err := net.ResolveTCPAddr("tcp", fmt.Sprintf("%s:%d", ip, port)) + if err != nil { + return err + } + + conn, err := net.DialTCP("tcp", nil, target) + if err != nil { + return err + } + + defer func(conn *net.TCPConn) { + _ = conn.Close() + }(conn) + + // Create a proxyprotocol header or use HeaderProxyFromAddrs() if you + // have two conn's + 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, + }, + } + // After the connection was created write the proxy headers first + _, err = header.WriteTo(conn) + if err != nil { + return err + } + + _, err = io.WriteString(conn, "GET / HTTP/1.1\r\n\r\n") + 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 +} diff --git a/ci/Dockerfile b/ci/Dockerfile index 5147c629..9aa293c0 100644 --- a/ci/Dockerfile +++ b/ci/Dockerfile @@ -16,4 +16,5 @@ RUN /usr/bin/python3 -m pip install -r /requirements.txt # Install go dependencies ENV GOBIN=/usr/local/bin RUN go install github.com/onsi/ginkgo/v2/ginkgo@latest && \ - go install github.com/geofffranks/spruce/cmd/spruce@latest + go install github.com/geofffranks/spruce/cmd/spruce@latest && \ + go install github.com/pires/go-proxyproto@latest diff --git a/ci/scripts/acceptance-tests b/ci/scripts/acceptance-tests index fe9a1184..4955de23 100755 --- a/ci/scripts/acceptance-tests +++ b/ci/scripts/acceptance-tests @@ -15,6 +15,11 @@ if [ -n "$FOCUS" ]; then fi 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