debian-mirror-gitlab/workhorse/internal/upstream/upstream_test.go

336 lines
13 KiB
Go
Raw Normal View History

2021-03-05 16:19:46 +05:30
package upstream
import (
2021-10-27 15:23:28 +05:30
"fmt"
2021-03-05 16:19:46 +05:30
"io"
"io/ioutil"
"net/http"
"net/http/httptest"
2022-01-26 12:08:38 +05:30
"os"
2021-03-05 16:19:46 +05:30
"testing"
2021-11-18 22:05:49 +05:30
"time"
2021-03-05 16:19:46 +05:30
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/require"
2021-10-27 15:23:28 +05:30
"gitlab.com/gitlab-org/gitlab/workhorse/internal/config"
"gitlab.com/gitlab-org/gitlab/workhorse/internal/helper"
"gitlab.com/gitlab-org/gitlab/workhorse/internal/testhelper"
2021-03-05 16:19:46 +05:30
)
2021-10-27 15:23:28 +05:30
const (
geoProxyEndpoint = "/api/v4/geo/proxy"
testDocumentRoot = "testdata/public"
)
type testCase struct {
desc string
path string
expectedResponse string
}
2022-01-26 12:08:38 +05:30
func TestMain(m *testing.M) {
// Secret should be configured before any Geo API poll happens to prevent
// race conditions where the first API call happens without a secret path
testhelper.ConfigureSecret()
os.Exit(m.Run())
}
2021-03-05 16:19:46 +05:30
func TestRouting(t *testing.T) {
handle := func(u *upstream, regex string) routeEntry {
handler := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
io.WriteString(w, regex)
})
return u.route("", regex, handler)
}
const (
foobar = `\A/foobar\z`
quxbaz = `\A/quxbaz\z`
main = ""
)
u := newUpstream(config.Config{}, logrus.StandardLogger(), func(u *upstream) {
u.Routes = []routeEntry{
handle(u, foobar),
handle(u, quxbaz),
handle(u, main),
}
})
ts := httptest.NewServer(u)
defer ts.Close()
2021-10-27 15:23:28 +05:30
testCases := []testCase{
2021-03-05 16:19:46 +05:30
{"main route works", "/", main},
{"foobar route works", "/foobar", foobar},
{"quxbaz route works", "/quxbaz", quxbaz},
{"path traversal works, ends up in quxbaz", "/foobar/../quxbaz", quxbaz},
{"escaped path traversal does not match any route", "/foobar%2f%2e%2e%2fquxbaz", main},
{"double escaped path traversal does not match any route", "/foobar%252f%252e%252e%252fquxbaz", main},
}
2021-10-27 15:23:28 +05:30
runTestCases(t, ts, testCases)
}
// This test can be removed when the environment variable `GEO_SECONDARY_PROXY` is removed
func TestGeoProxyFeatureDisabledOnGeoSecondarySite(t *testing.T) {
// We could just not set up the primary, but then we'd have to assert
// that the internal API call isn't made. This is easier.
remoteServer, rsDeferredClose := startRemoteServer("Geo primary")
defer rsDeferredClose()
geoProxyEndpointResponseBody := fmt.Sprintf(`{"geo_proxy_url":"%v"}`, remoteServer.URL)
2021-11-18 22:05:49 +05:30
railsServer, deferredClose := startRailsServer("Local Rails server", &geoProxyEndpointResponseBody)
2021-10-27 15:23:28 +05:30
defer deferredClose()
2021-11-18 22:05:49 +05:30
ws, wsDeferredClose, _ := startWorkhorseServer(railsServer.URL, false)
2021-10-27 15:23:28 +05:30
defer wsDeferredClose()
testCases := []testCase{
{"jobs request is served locally", "/api/v4/jobs/request", "Local Rails server received request to path /api/v4/jobs/request"},
{"health check is served locally", "/-/health", "Local Rails server received request to path /-/health"},
{"unknown route is served locally", "/anything", "Local Rails server received request to path /anything"},
}
runTestCases(t, ws, testCases)
}
func TestGeoProxyFeatureEnabledOnGeoSecondarySite(t *testing.T) {
testCases := []testCase{
2021-11-18 22:05:49 +05:30
{"push from secondary is forwarded", "/-/push_from_secondary/foo/bar.git/info/refs", "Geo primary received request to path /-/push_from_secondary/foo/bar.git/info/refs"},
{"LFS files are served locally", "/group/project.git/gitlab-lfs/objects/37446575700829a11278ad3a550f244f45d5ae4fe1552778fa4f041f9eaeecf6", "Local Rails server received request to path /group/project.git/gitlab-lfs/objects/37446575700829a11278ad3a550f244f45d5ae4fe1552778fa4f041f9eaeecf6"},
2021-10-27 15:23:28 +05:30
{"jobs request is forwarded", "/api/v4/jobs/request", "Geo primary received request to path /api/v4/jobs/request"},
{"health check is served locally", "/-/health", "Local Rails server received request to path /-/health"},
{"unknown route is forwarded", "/anything", "Geo primary received request to path /anything"},
}
2021-12-11 22:18:48 +05:30
runTestCasesWithGeoProxyEnabled(t, testCases)
2021-10-27 15:23:28 +05:30
}
// This test can be removed when the environment variable `GEO_SECONDARY_PROXY` is removed
func TestGeoProxyFeatureDisabledOnNonGeoSecondarySite(t *testing.T) {
geoProxyEndpointResponseBody := "{}"
2021-11-18 22:05:49 +05:30
railsServer, deferredClose := startRailsServer("Local Rails server", &geoProxyEndpointResponseBody)
2021-10-27 15:23:28 +05:30
defer deferredClose()
2021-11-18 22:05:49 +05:30
ws, wsDeferredClose, _ := startWorkhorseServer(railsServer.URL, false)
2021-10-27 15:23:28 +05:30
defer wsDeferredClose()
testCases := []testCase{
2021-11-18 22:05:49 +05:30
{"LFS files are served locally", "/group/project.git/gitlab-lfs/objects/37446575700829a11278ad3a550f244f45d5ae4fe1552778fa4f041f9eaeecf6", "Local Rails server received request to path /group/project.git/gitlab-lfs/objects/37446575700829a11278ad3a550f244f45d5ae4fe1552778fa4f041f9eaeecf6"},
2021-10-27 15:23:28 +05:30
{"jobs request is served locally", "/api/v4/jobs/request", "Local Rails server received request to path /api/v4/jobs/request"},
{"health check is served locally", "/-/health", "Local Rails server received request to path /-/health"},
{"unknown route is served locally", "/anything", "Local Rails server received request to path /anything"},
}
runTestCases(t, ws, testCases)
}
func TestGeoProxyFeatureEnabledOnNonGeoSecondarySite(t *testing.T) {
geoProxyEndpointResponseBody := "{}"
2021-11-18 22:05:49 +05:30
railsServer, deferredClose := startRailsServer("Local Rails server", &geoProxyEndpointResponseBody)
2021-10-27 15:23:28 +05:30
defer deferredClose()
2021-11-18 22:05:49 +05:30
ws, wsDeferredClose, _ := startWorkhorseServer(railsServer.URL, true)
2021-10-27 15:23:28 +05:30
defer wsDeferredClose()
testCases := []testCase{
2021-11-18 22:05:49 +05:30
{"LFS files are served locally", "/group/project.git/gitlab-lfs/objects/37446575700829a11278ad3a550f244f45d5ae4fe1552778fa4f041f9eaeecf6", "Local Rails server received request to path /group/project.git/gitlab-lfs/objects/37446575700829a11278ad3a550f244f45d5ae4fe1552778fa4f041f9eaeecf6"},
2021-10-27 15:23:28 +05:30
{"jobs request is served locally", "/api/v4/jobs/request", "Local Rails server received request to path /api/v4/jobs/request"},
{"health check is served locally", "/-/health", "Local Rails server received request to path /-/health"},
{"unknown route is served locally", "/anything", "Local Rails server received request to path /anything"},
}
runTestCases(t, ws, testCases)
}
func TestGeoProxyFeatureEnabledButWithAPIError(t *testing.T) {
geoProxyEndpointResponseBody := "Invalid response"
2021-11-18 22:05:49 +05:30
railsServer, deferredClose := startRailsServer("Local Rails server", &geoProxyEndpointResponseBody)
2021-10-27 15:23:28 +05:30
defer deferredClose()
2021-11-18 22:05:49 +05:30
ws, wsDeferredClose, _ := startWorkhorseServer(railsServer.URL, true)
2021-10-27 15:23:28 +05:30
defer wsDeferredClose()
testCases := []testCase{
2021-11-18 22:05:49 +05:30
{"LFS files are served locally", "/group/project.git/gitlab-lfs/objects/37446575700829a11278ad3a550f244f45d5ae4fe1552778fa4f041f9eaeecf6", "Local Rails server received request to path /group/project.git/gitlab-lfs/objects/37446575700829a11278ad3a550f244f45d5ae4fe1552778fa4f041f9eaeecf6"},
2021-10-27 15:23:28 +05:30
{"jobs request is served locally", "/api/v4/jobs/request", "Local Rails server received request to path /api/v4/jobs/request"},
{"health check is served locally", "/-/health", "Local Rails server received request to path /-/health"},
{"unknown route is served locally", "/anything", "Local Rails server received request to path /anything"},
}
runTestCases(t, ws, testCases)
}
2021-11-18 22:05:49 +05:30
func TestGeoProxyFeatureEnablingAndDisabling(t *testing.T) {
remoteServer, rsDeferredClose := startRemoteServer("Geo primary")
defer rsDeferredClose()
geoProxyEndpointEnabledResponseBody := fmt.Sprintf(`{"geo_proxy_url":"%v"}`, remoteServer.URL)
geoProxyEndpointDisabledResponseBody := "{}"
geoProxyEndpointResponseBody := geoProxyEndpointEnabledResponseBody
railsServer, deferredClose := startRailsServer("Local Rails server", &geoProxyEndpointResponseBody)
defer deferredClose()
ws, wsDeferredClose, waitForNextApiPoll := startWorkhorseServer(railsServer.URL, true)
defer wsDeferredClose()
testCasesLocal := []testCase{
{"LFS files are served locally", "/group/project.git/gitlab-lfs/objects/37446575700829a11278ad3a550f244f45d5ae4fe1552778fa4f041f9eaeecf6", "Local Rails server received request to path /group/project.git/gitlab-lfs/objects/37446575700829a11278ad3a550f244f45d5ae4fe1552778fa4f041f9eaeecf6"},
{"jobs request is served locally", "/api/v4/jobs/request", "Local Rails server received request to path /api/v4/jobs/request"},
{"health check is served locally", "/-/health", "Local Rails server received request to path /-/health"},
{"unknown route is served locally", "/anything", "Local Rails server received request to path /anything"},
}
testCasesProxied := []testCase{
{"push from secondary is forwarded", "/-/push_from_secondary/foo/bar.git/info/refs", "Geo primary received request to path /-/push_from_secondary/foo/bar.git/info/refs"},
{"LFS files are served locally", "/group/project.git/gitlab-lfs/objects/37446575700829a11278ad3a550f244f45d5ae4fe1552778fa4f041f9eaeecf6", "Local Rails server received request to path /group/project.git/gitlab-lfs/objects/37446575700829a11278ad3a550f244f45d5ae4fe1552778fa4f041f9eaeecf6"},
{"jobs request is forwarded", "/api/v4/jobs/request", "Geo primary received request to path /api/v4/jobs/request"},
{"health check is served locally", "/-/health", "Local Rails server received request to path /-/health"},
{"unknown route is forwarded", "/anything", "Geo primary received request to path /anything"},
}
// Enabled initially, run tests
runTestCases(t, ws, testCasesProxied)
// Disable proxying and run tests. It's safe to write to
// geoProxyEndpointResponseBody because the polling goroutine is blocked.
geoProxyEndpointResponseBody = geoProxyEndpointDisabledResponseBody
waitForNextApiPoll()
runTestCases(t, ws, testCasesLocal)
// Re-enable proxying and run tests
geoProxyEndpointResponseBody = geoProxyEndpointEnabledResponseBody
waitForNextApiPoll()
runTestCases(t, ws, testCasesProxied)
}
2022-03-02 08:16:31 +05:30
func TestGeoProxySetsCustomHeader(t *testing.T) {
remoteServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
require.Equal(t, "1", r.Header.Get("Gitlab-Workhorse-Geo-Proxy"), "custom proxy header")
w.WriteHeader(http.StatusOK)
}))
defer remoteServer.Close()
geoProxyEndpointResponseBody := fmt.Sprintf(`{"geo_proxy_url":"%v"}`, remoteServer.URL)
railsServer, deferredClose := startRailsServer("Local Rails server", &geoProxyEndpointResponseBody)
defer deferredClose()
ws, wsDeferredClose, _ := startWorkhorseServer(railsServer.URL, true)
defer wsDeferredClose()
http.Get(ws.URL)
}
2021-10-27 15:23:28 +05:30
func runTestCases(t *testing.T, ws *httptest.Server, testCases []testCase) {
t.Helper()
2021-03-05 16:19:46 +05:30
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
2021-10-27 15:23:28 +05:30
resp, err := http.Get(ws.URL + tc.path)
2021-03-05 16:19:46 +05:30
require.NoError(t, err)
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
require.NoError(t, err)
require.Equal(t, 200, resp.StatusCode, "response code")
2021-10-27 15:23:28 +05:30
require.Equal(t, tc.expectedResponse, string(body))
2021-03-05 16:19:46 +05:30
})
}
}
2021-10-27 15:23:28 +05:30
2021-12-11 22:18:48 +05:30
func runTestCasesWithGeoProxyEnabled(t *testing.T, testCases []testCase) {
remoteServer, rsDeferredClose := startRemoteServer("Geo primary")
defer rsDeferredClose()
geoProxyEndpointResponseBody := fmt.Sprintf(`{"geo_proxy_url":"%v"}`, remoteServer.URL)
railsServer, deferredClose := startRailsServer("Local Rails server", &geoProxyEndpointResponseBody)
defer deferredClose()
ws, wsDeferredClose, _ := startWorkhorseServer(railsServer.URL, true)
defer wsDeferredClose()
runTestCases(t, ws, testCases)
}
2021-10-27 15:23:28 +05:30
func newUpstreamConfig(authBackend string) *config.Config {
return &config.Config{
Version: "123",
DocumentRoot: testDocumentRoot,
Backend: helper.URLMustParse(authBackend),
ImageResizerConfig: config.DefaultImageResizerConfig,
}
}
func startRemoteServer(serverName string) (*httptest.Server, func()) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
body := serverName + " received request to path " + r.URL.Path
w.WriteHeader(200)
fmt.Fprint(w, body)
}))
return ts, ts.Close
}
2021-11-18 22:05:49 +05:30
func startRailsServer(railsServerName string, geoProxyEndpointResponseBody *string) (*httptest.Server, func()) {
2021-10-27 15:23:28 +05:30
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var body string
if r.URL.Path == geoProxyEndpoint {
w.Header().Set("Content-Type", "application/vnd.gitlab-workhorse+json")
2021-11-18 22:05:49 +05:30
body = *geoProxyEndpointResponseBody
2021-10-27 15:23:28 +05:30
} else {
body = railsServerName + " received request to path " + r.URL.Path
}
w.WriteHeader(200)
fmt.Fprint(w, body)
}))
return ts, ts.Close
}
2021-11-18 22:05:49 +05:30
func startWorkhorseServer(railsServerURL string, enableGeoProxyFeature bool) (*httptest.Server, func(), func()) {
geoProxySleepC := make(chan struct{})
geoProxySleep := func(time.Duration) {
geoProxySleepC <- struct{}{}
<-geoProxySleepC
}
2021-10-27 15:23:28 +05:30
myConfigureRoutes := func(u *upstream) {
// Enable environment variable "feature flag"
u.enableGeoProxyFeature = enableGeoProxyFeature
2021-11-18 22:05:49 +05:30
// Replace the time.Sleep function with geoProxySleep
u.geoProxyPollSleep = geoProxySleep
2021-10-27 15:23:28 +05:30
// call original
configureRoutes(u)
}
cfg := newUpstreamConfig(railsServerURL)
upstreamHandler := newUpstream(*cfg, logrus.StandardLogger(), myConfigureRoutes)
2021-12-11 22:18:48 +05:30
ws := httptest.NewServer(upstreamHandler)
2021-11-18 22:05:49 +05:30
waitForNextApiPoll := func() {}
2021-10-27 15:23:28 +05:30
if enableGeoProxyFeature {
2021-11-18 22:05:49 +05:30
// Wait for geoProxySleep to be entered for the first time
<-geoProxySleepC
waitForNextApiPoll = func() {
// Cause geoProxySleep to return
geoProxySleepC <- struct{}{}
// Wait for geoProxySleep to be entered again
<-geoProxySleepC
}
2021-10-27 15:23:28 +05:30
}
2021-11-18 22:05:49 +05:30
return ws, ws.Close, waitForNextApiPoll
2021-10-27 15:23:28 +05:30
}