diff options
author | Igor Wiedler <iwiedler@gitlab.com> | 2020-05-04 15:30:39 +0300 |
---|---|---|
committer | Igor Wiedler <iwiedler@gitlab.com> | 2020-11-18 12:48:36 +0300 |
commit | 481a5bf0c8fbac9c18889341757ee9806d4ebd63 (patch) | |
tree | 12e653b8d164ea1a126eaaf189c9609e667b846a /test | |
parent | 12fa24ee96cb9d971a75df2cacfcbb1e014125e9 (diff) |
Support for HTTPS over PROXYv2 protocol
Diffstat (limited to 'test')
-rw-r--r-- | test/acceptance/acceptance_test.go | 11 | ||||
-rw-r--r-- | test/acceptance/helpers_test.go | 143 | ||||
-rw-r--r-- | test/acceptance/proxyv2_test.go | 52 | ||||
-rw-r--r-- | test/acceptance/serving_test.go | 10 |
4 files changed, 189 insertions, 27 deletions
diff --git a/test/acceptance/acceptance_test.go b/test/acceptance/acceptance_test.go index e155ce8b..9921076e 100644 --- a/test/acceptance/acceptance_test.go +++ b/test/acceptance/acceptance_test.go @@ -11,7 +11,7 @@ import ( ) const ( - objectStorageMockServer = "127.0.0.1:37003" + objectStorageMockServer = "127.0.0.1:38001" ) var ( @@ -27,11 +27,14 @@ var ( {"https", "::1", "37001"}, {"proxy", "127.0.0.1", "37002"}, {"proxy", "::1", "37002"}, + {"https-proxyv2", "127.0.0.1", "37003"}, + {"https-proxyv2", "::1", "37003"}, } - httpListener = listeners[0] - httpsListener = listeners[2] - proxyListener = listeners[4] + httpListener = listeners[0] + httpsListener = listeners[2] + proxyListener = listeners[4] + httpsProxyv2Listener = listeners[6] ) func TestMain(m *testing.M) { diff --git a/test/acceptance/helpers_test.go b/test/acceptance/helpers_test.go index 5412f6d5..3506e236 100644 --- a/test/acceptance/helpers_test.go +++ b/test/acceptance/helpers_test.go @@ -2,6 +2,7 @@ package acceptance_test import ( "bytes" + "context" "crypto/tls" "crypto/x509" "fmt" @@ -14,14 +15,18 @@ import ( "os/exec" "path" "strings" + "sync" "testing" "time" + proxyproto "github.com/pires/go-proxyproto" "github.com/stretchr/testify/require" "gitlab.com/gitlab-org/gitlab-pages/internal/request" ) +// The HTTPS certificate isn't signed by anyone. This http client is set up +// so it can talk to servers using it. var ( // The HTTPS certificate isn't signed by anyone. This http client is set up // so it can talk to servers using it. @@ -40,8 +45,49 @@ var ( }, } + // Proxyv2 client + TestProxyv2Client = &http.Client{ + Transport: &http.Transport{ + DialContext: Proxyv2DialContext, + TLSClientConfig: &tls.Config{RootCAs: TestCertPool}, + }, + } + + QuickTimeoutProxyv2Client = &http.Client{ + Transport: &http.Transport{ + DialContext: Proxyv2DialContext, + TLSClientConfig: &tls.Config{RootCAs: TestCertPool}, + ResponseHeaderTimeout: 100 * time.Millisecond, + }, + } + TestCertPool = x509.NewCertPool() + // Proxyv2 will create a dummy request with src 10.1.1.1:1000 + // and dst 20.2.2.2:2000 + Proxyv2DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) { + var d net.Dialer + + conn, err := d.DialContext(ctx, network, addr) + if err != nil { + return nil, err + } + + header := &proxyproto.Header{ + Version: 2, + Command: proxyproto.PROXY, + TransportProtocol: proxyproto.TCPv4, + SourceAddress: net.ParseIP("10.1.1.1"), + SourcePort: 1000, + DestinationAddress: net.ParseIP("20.2.2.2"), + DestinationPort: 2000, + } + + _, err = header.WriteTo(conn) + + return conn, err + } + existingAcmeTokenPath = "/.well-known/acme-challenge/existingtoken" notExistingAcmeTokenPath = "/.well-known/acme-challenge/notexistingtoken" ) @@ -56,6 +102,36 @@ func (t *tWriter) Write(b []byte) (int, error) { return len(b), nil } +type LogCaptureBuffer struct { + b bytes.Buffer + m sync.Mutex +} + +func (b *LogCaptureBuffer) Read(p []byte) (n int, err error) { + b.m.Lock() + defer b.m.Unlock() + + return b.b.Read(p) +} +func (b *LogCaptureBuffer) Write(p []byte) (n int, err error) { + b.m.Lock() + defer b.m.Unlock() + + return b.b.Write(p) +} +func (b *LogCaptureBuffer) String() string { + b.m.Lock() + defer b.m.Unlock() + + return b.b.String() +} +func (b *LogCaptureBuffer) Reset() { + b.m.Lock() + defer b.m.Unlock() + + b.b.Reset() +} + // ListenSpec is used to point at a gitlab-pages http server, preserving the // type of port it is (http, https, proxy) type ListenSpec struct { @@ -66,7 +142,7 @@ type ListenSpec struct { func (l ListenSpec) URL(suffix string) string { scheme := request.SchemeHTTP - if l.Type == request.SchemeHTTPS { + if l.Type == request.SchemeHTTPS || l.Type == "https-proxyv2" { scheme = request.SchemeHTTPS } @@ -90,7 +166,12 @@ func (l ListenSpec) WaitUntilRequestSucceeds(done chan struct{}) error { return err } - response, err := QuickTimeoutHTTPSClient.Transport.RoundTrip(req) + client := QuickTimeoutHTTPSClient + if l.Type == "https-proxyv2" { + client = QuickTimeoutProxyv2Client + } + + response, err := client.Transport.RoundTrip(req) if err != nil { time.Sleep(100 * time.Millisecond) continue @@ -117,19 +198,27 @@ func (l ListenSpec) JoinHostPort() string { // // If run as root via sudo, the gitlab-pages process will drop privileges func RunPagesProcess(t *testing.T, pagesBinary string, listeners []ListenSpec, promPort string, extraArgs ...string) (teardown func()) { - return runPagesProcess(t, true, pagesBinary, listeners, promPort, nil, extraArgs...) + _, cleanup := runPagesProcess(t, true, pagesBinary, listeners, promPort, nil, extraArgs...) + return cleanup } func RunPagesProcessWithoutWait(t *testing.T, pagesBinary string, listeners []ListenSpec, promPort string, extraArgs ...string) (teardown func()) { - return runPagesProcess(t, false, pagesBinary, listeners, promPort, nil, extraArgs...) + _, cleanup := runPagesProcess(t, false, pagesBinary, listeners, promPort, nil, extraArgs...) + return cleanup } func RunPagesProcessWithSSLCertFile(t *testing.T, pagesBinary string, listeners []ListenSpec, promPort string, sslCertFile string, extraArgs ...string) (teardown func()) { - return runPagesProcess(t, true, pagesBinary, listeners, promPort, []string{"SSL_CERT_FILE=" + sslCertFile}, extraArgs...) + _, cleanup := runPagesProcess(t, true, pagesBinary, listeners, promPort, []string{"SSL_CERT_FILE=" + sslCertFile}, extraArgs...) + return cleanup } func RunPagesProcessWithEnvs(t *testing.T, wait bool, pagesBinary string, listeners []ListenSpec, promPort string, envs []string, extraArgs ...string) (teardown func()) { - return runPagesProcess(t, wait, pagesBinary, listeners, promPort, envs, extraArgs...) + _, cleanup := runPagesProcess(t, wait, pagesBinary, listeners, promPort, envs, extraArgs...) + return cleanup +} + +func RunPagesProcessWithOutput(t *testing.T, pagesBinary string, listeners []ListenSpec, promPort string, extraArgs ...string) (out *LogCaptureBuffer, teardown func()) { + return runPagesProcess(t, true, pagesBinary, listeners, promPort, nil, extraArgs...) } func RunPagesProcessWithStubGitLabServer(t *testing.T, wait bool, pagesBinary string, listeners []ListenSpec, promPort string, envs []string, extraArgs ...string) (teardown func()) { @@ -139,7 +228,7 @@ func RunPagesProcessWithStubGitLabServer(t *testing.T, wait bool, pagesBinary st gitLabAPISecretKey := CreateGitLabAPISecretKeyFixtureFile(t) pagesArgs := append([]string{"-gitlab-server", source.URL, "-api-secret-key", gitLabAPISecretKey, "-domain-config-source", "gitlab"}, extraArgs...) - cleanup := runPagesProcess(t, wait, pagesBinary, listeners, promPort, envs, pagesArgs...) + _, cleanup := runPagesProcess(t, wait, pagesBinary, listeners, promPort, envs, pagesArgs...) return func() { source.Close() @@ -153,9 +242,10 @@ func RunPagesProcessWithAuth(t *testing.T, pagesBinary string, listeners []Liste "auth-redirect-uri=https://projects.gitlab-example.com/auth") defer cleanup() - return runPagesProcess(t, true, pagesBinary, listeners, promPort, nil, + _, cleanup2 := runPagesProcess(t, true, pagesBinary, listeners, promPort, nil, "-config="+configFile, ) + return cleanup2 } func RunPagesProcessWithAuthServer(t *testing.T, pagesBinary string, listeners []ListenSpec, promPort string, authServer string) func() { @@ -191,21 +281,25 @@ func runPagesProcessWithAuthServer(t *testing.T, pagesBinary string, listeners [ "auth-redirect-uri=https://projects.gitlab-example.com/auth") defer cleanup() - return runPagesProcess(t, true, pagesBinary, listeners, promPort, extraEnv, + _, cleanup2 := runPagesProcess(t, true, pagesBinary, listeners, promPort, extraEnv, "-config="+configFile) + return cleanup2 } -func runPagesProcess(t *testing.T, wait bool, pagesBinary string, listeners []ListenSpec, promPort string, extraEnv []string, extraArgs ...string) (teardown func()) { +func runPagesProcess(t *testing.T, wait bool, pagesBinary string, listeners []ListenSpec, promPort string, extraEnv []string, extraArgs ...string) (*LogCaptureBuffer, func()) { t.Helper() _, err := os.Stat(pagesBinary) require.NoError(t, err) + logBuf := &LogCaptureBuffer{} + out := io.MultiWriter(&tWriter{t}, logBuf) + args, tempfiles := getPagesArgs(t, listeners, promPort, extraArgs) cmd := exec.Command(pagesBinary, args...) cmd.Env = append(os.Environ(), extraEnv...) - cmd.Stdout = &tWriter{t} - cmd.Stderr = &tWriter{t} + cmd.Stdout = out + cmd.Stderr = out require.NoError(t, cmd.Start()) t.Logf("Running %s %v", pagesBinary, args) @@ -232,7 +326,7 @@ func runPagesProcess(t *testing.T, wait bool, pagesBinary string, listeners []Li } } - return cleanup + return logBuf, cleanup } func getPagesArgs(t *testing.T, listeners []ListenSpec, promPort string, extraArgs []string) (args, tempfiles []string) { @@ -329,7 +423,7 @@ func GetPageFromListenerWithCookie(t *testing.T, spec ListenSpec, host, urlsuffi req.Host = host - return DoPagesRequest(t, req) + return DoPagesRequest(t, spec, req) } func GetCompressedPageFromListener(t *testing.T, spec ListenSpec, host, urlsuffix string, encoding string) (*http.Response, error) { @@ -341,7 +435,7 @@ func GetCompressedPageFromListener(t *testing.T, spec ListenSpec, host, urlsuffi req.Host = host req.Header.Set("Accept-Encoding", encoding) - return DoPagesRequest(t, req) + return DoPagesRequest(t, spec, req) } func GetProxiedPageFromListener(t *testing.T, spec ListenSpec, host, xForwardedHost, urlsuffix string) (*http.Response, error) { @@ -354,12 +448,16 @@ func GetProxiedPageFromListener(t *testing.T, spec ListenSpec, host, xForwardedH req.Host = host req.Header.Set("X-Forwarded-Host", xForwardedHost) - return DoPagesRequest(t, req) + return DoPagesRequest(t, spec, req) } -func DoPagesRequest(t *testing.T, req *http.Request) (*http.Response, error) { +func DoPagesRequest(t *testing.T, spec ListenSpec, req *http.Request) (*http.Response, error) { t.Logf("curl -X %s -H'Host: %s' %s", req.Method, req.Host, req.URL) + if spec.Type == "https-proxyv2" { + return TestProxyv2Client.Do(req) + } + return TestHTTPSClient.Do(req) } @@ -395,6 +493,10 @@ func GetRedirectPageWithHeaders(t *testing.T, spec ListenSpec, host, urlsuffix s req.Host = host + if spec.Type == "https-proxyv2" { + return TestProxyv2Client.Transport.RoundTrip(req) + } + return TestHTTPSClient.Transport.RoundTrip(req) } @@ -416,7 +518,12 @@ func waitForRoundtrips(t *testing.T, listeners []ListenSpec, timeout time.Durati t.Fatal(err) } - if response, err := QuickTimeoutHTTPSClient.Transport.RoundTrip(req); err == nil { + client := QuickTimeoutHTTPSClient + if spec.Type == "https-proxyv2" { + client = QuickTimeoutProxyv2Client + } + + if response, err := client.Transport.RoundTrip(req); err == nil { nListening++ response.Body.Close() break diff --git a/test/acceptance/proxyv2_test.go b/test/acceptance/proxyv2_test.go new file mode 100644 index 00000000..c407ea19 --- /dev/null +++ b/test/acceptance/proxyv2_test.go @@ -0,0 +1,52 @@ +package acceptance_test + +import ( + "io/ioutil" + "net/http" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestProxyv2(t *testing.T) { + skipUnlessEnabled(t) + + logBuf, teardown := RunPagesProcessWithOutput(t, *pagesBinary, listeners, "") + defer teardown() + + // the dummy client IP 10.1.1.1 is set by TestProxyv2Client + tests := map[string]struct { + host string + urlSuffix string + expectedStatusCode int + expectedContent string + expectedLog string + }{ + "basic_proxyv2_request": { + host: "group.gitlab-example.com", + urlSuffix: "project/", + expectedStatusCode: http.StatusOK, + expectedContent: "project-subdir\n", + expectedLog: "group.gitlab-example.com 10.1.1.1", + }, + } + + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + logBuf.Reset() + + response, err := GetPageFromListener(t, httpsProxyv2Listener, tt.host, tt.urlSuffix) + require.NoError(t, err) + defer response.Body.Close() + + require.Equal(t, tt.expectedStatusCode, response.StatusCode) + + body, err := ioutil.ReadAll(response.Body) + require.NoError(t, err) + + require.Contains(t, string(body), tt.expectedContent, "content mismatch") + + require.Contains(t, logBuf.String(), tt.expectedLog, "log mismatch") + }) + } +} diff --git a/test/acceptance/serving_test.go b/test/acceptance/serving_test.go index 01935946..4ccdd8f4 100644 --- a/test/acceptance/serving_test.go +++ b/test/acceptance/serving_test.go @@ -213,7 +213,7 @@ func TestCORSWhenDisabled(t *testing.T) { for _, spec := range listeners { for _, method := range []string{"GET", "OPTIONS"} { - rsp := doCrossOriginRequest(t, method, method, spec.URL("project/")) + rsp := doCrossOriginRequest(t, spec, method, method, spec.URL("project/")) require.Equal(t, http.StatusOK, rsp.StatusCode) require.Equal(t, "", rsp.Header.Get("Access-Control-Allow-Origin")) @@ -229,7 +229,7 @@ func TestCORSAllowsGET(t *testing.T) { for _, spec := range listeners { for _, method := range []string{"GET", "OPTIONS"} { - rsp := doCrossOriginRequest(t, method, method, spec.URL("project/")) + rsp := doCrossOriginRequest(t, spec, method, method, spec.URL("project/")) require.Equal(t, http.StatusOK, rsp.StatusCode) require.Equal(t, "*", rsp.Header.Get("Access-Control-Allow-Origin")) @@ -245,7 +245,7 @@ func TestCORSForbidsPOST(t *testing.T) { defer teardown() for _, spec := range listeners { - rsp := doCrossOriginRequest(t, "OPTIONS", "POST", spec.URL("project/")) + rsp := doCrossOriginRequest(t, spec, "OPTIONS", "POST", spec.URL("project/")) require.Equal(t, http.StatusOK, rsp.StatusCode) require.Equal(t, "", rsp.Header.Get("Access-Control-Allow-Origin")) @@ -502,7 +502,7 @@ func TestKnownHostInReverseProxySetupReturns200(t *testing.T) { } } -func doCrossOriginRequest(t *testing.T, method, reqMethod, url string) *http.Response { +func doCrossOriginRequest(t *testing.T, spec ListenSpec, method, reqMethod, url string) *http.Response { req, err := http.NewRequest(method, url, nil) require.NoError(t, err) @@ -513,7 +513,7 @@ func doCrossOriginRequest(t *testing.T, method, reqMethod, url string) *http.Res var rsp *http.Response err = fmt.Errorf("no request was made") for start := time.Now(); time.Since(start) < 1*time.Second; { - rsp, err = DoPagesRequest(t, req) + rsp, err = DoPagesRequest(t, spec, req) if err == nil { break } |