Skip to content

Commit

Permalink
Add test for presence of X-Forwarded-For and X-Forwarded-Proto HTTP h…
Browse files Browse the repository at this point in the history
…eaders (#299)

* add test for presence of X-Forwarded-For and X-Forwarded-Proto HTTP headers

Presence and content of `X-Forwarded-For` and `X-Forwarded-Proto` is checked in requests made to the origin server.

* add test for X-Forwarded-Proto for HTTP requests

* set X-Forwarded-Proto for requests forwarded to the origin server

* use insecure HTTP client for performing test requests

Generated cert is not trusted by the test runner.

* add comment for parseDumpedHTTPHeadersFromBody

* accept ::1 as valid X-Forwarded-For value in tests
  • Loading branch information
vojtad committed Feb 3, 2022
1 parent 6a7372b commit eca3979
Show file tree
Hide file tree
Showing 5 changed files with 63 additions and 3 deletions.
44 changes: 43 additions & 1 deletion cmd/puma-dev/main_test.go
Expand Up @@ -2,6 +2,7 @@ package main

import (
"crypto/sha1"
"crypto/tls"
"encoding/json"
"errors"
"fmt"
Expand Down Expand Up @@ -144,7 +145,14 @@ func getURLWithHost(t *testing.T, url string, host string) string {
req, _ := http.NewRequest("GET", url, nil)
req.Host = host

resp, err := http.DefaultClient.Do(req)
// we don't care about checking certs in tests
// the generated cert will not be trusted by the test runner
insecureTransport := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
insecureClient := &http.Client{Transport: insecureTransport}

resp, err := insecureClient.Do(req)

if err != nil {
assert.FailNow(t, err.Error())
Expand All @@ -157,6 +165,20 @@ func getURLWithHost(t *testing.T, url string, host string) string {
return strings.TrimSpace(string(bodyBytes))
}

// function to parse dumped request headers sent by puma-dev to the origin server
// for details see etc/rack-request-headers-dump/config.ru
func parseDumpedHTTPHeadersFromBody(body string) map[string]string {
dumpedHeadersLines := strings.Split(body, "\n")

dumpedHeaders := make(map[string]string)
for _, pairString := range dumpedHeadersLines {
pair := strings.SplitN(pairString, " ", 2)
dumpedHeaders[pair[0]] = pair[1]
}

return dumpedHeaders
}

func pollForEvent(t *testing.T, app string, event string, reason string) error {
return retry.Do(
func() error {
Expand Down Expand Up @@ -293,4 +315,24 @@ func runPlatformAgnosticTestScenarios(t *testing.T) {

assert.Equal(t, "rack wuz here", getURLWithHost(t, reqURL, statusHost))
})

t.Run("request-headers-dump contains X-Forwarded-* headers with http", func(t *testing.T) {
reqURL := fmt.Sprintf("http://localhost:%d/", *fHTTPPort)
statusHost := "request-headers-dump"

dumpedHeaders := parseDumpedHTTPHeadersFromBody(getURLWithHost(t, reqURL, statusHost))

assert.Regexp(t, `127\.0\.0\.1|::1`, dumpedHeaders["HTTP_X_FORWARDED_FOR"])
assert.Equal(t, "http", dumpedHeaders["HTTP_X_FORWARDED_PROTO"])
})

t.Run("request-headers-dump contains X-Forwarded-* headers with https", func(t *testing.T) {
reqURL := fmt.Sprintf("https://localhost:%d/", *fTLSPort)
statusHost := "request-headers-dump"

dumpedHeaders := parseDumpedHTTPHeadersFromBody(getURLWithHost(t, reqURL, statusHost))

assert.Regexp(t, `^127\.0\.0\.1|::1`, dumpedHeaders["HTTP_X_FORWARDED_FOR"])
assert.Equal(t, "https", dumpedHeaders["HTTP_X_FORWARDED_PROTO"])
})
}
5 changes: 3 additions & 2 deletions dev/devtest/testutils.go
Expand Up @@ -220,8 +220,9 @@ func LinkTestApps(t *testing.T, workingDirPath string, testAppsToLink map[string

func LinkAllTestApps(t *testing.T, workingDirPath string) func() {
testAppsToLink := map[string]string{
"hipuma": "rack-hi-puma",
"static-site": "static-hi-puma",
"hipuma": "rack-hi-puma",
"static-site": "static-hi-puma",
"request-headers-dump": "rack-request-headers-dump",
}

return LinkTestApps(t, workingDirPath, testAppsToLink)
Expand Down
6 changes: 6 additions & 0 deletions dev/http.go
Expand Up @@ -144,6 +144,12 @@ func (h *HTTPServer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
}
}

if req.TLS == nil {
req.Header.Set("X-Forwarded-Proto", "http")
} else {
req.Header.Set("X-Forwarded-Proto", "https")
}

req.URL.Scheme, req.URL.Host = app.Scheme, app.Address()
h.proxy.ServeHTTP(w, req)
}
Expand Down
11 changes: 11 additions & 0 deletions etc/rack-request-headers-dump/config.ru
@@ -0,0 +1,11 @@
class Application
def call(env)
status = 200
headers = { "Content-Type" => "application/json" }
body = [ env.select { |k, v| k.start_with?('HTTP_') }.map { |(k, v)| [k, v].join(' ') }.join("\n") ]

[status, headers, body]
end
end

run Application.new
Empty file.

0 comments on commit eca3979

Please sign in to comment.