diff --git a/CHANGELOG b/CHANGELOG index 3051043b97e292428dd290a41ffb945ad8e3ca26..fb05607204f972aef59c5e33870d7b0f1f3eef6d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,6 +2,10 @@ Formerly known as 'gitlab-git-http-server'. +0.5.1 + +Deprecate -relativeURLRoot option, use -authBackend instead. + 0.5.0 Send ALL GitLab requests through gitlab-workhorse. diff --git a/README.md b/README.md index f5158750a176774782e3d5ab1dd04fb392c752db..f28ffec8e72854a5cb371756396f45a905e86990 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,16 @@ Gitlab-workhorse can listen on either a TCP or a Unix domain socket. It can also open a second listening TCP listening socket with the Go [net/http/pprof profiler server](http://golang.org/pkg/net/http/pprof/). +### Relative URL support + +If you are mounting GitLab at a relative URL, e.g. +`example.com/gitlab`, then you should also use this relative URL in +the `authBackend` setting: + +``` +gitlab-workhorse -authBackend http://localhost:8080/gitlab +``` + ## Installation To install into `/usr/local/bin` run `make install`. diff --git a/VERSION b/VERSION index 8f0916f768f0487bcf8d33827ce2c8dcecb645c1..4b9fcbec101a6ff8ec68e0f95131ccda4861407f 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.5.0 +0.5.1 diff --git a/authorization.go b/authorization.go index c12ac90118d3710e0ebe569cb120531b566c4d1b..4f58a6d270150649666fdf0effd8aa930cb3515d 100644 --- a/authorization.go +++ b/authorization.go @@ -9,7 +9,7 @@ import ( ) func (u *upstream) newUpstreamRequest(r *http.Request, body io.Reader, suffix string) (*http.Request, error) { - url := u.authBackend + r.URL.RequestURI() + suffix + url := u.authBackend + "/" + strings.TrimPrefix(r.URL.RequestURI(), u.relativeURLRoot) + suffix authReq, err := http.NewRequest(r.Method, url, body) if err != nil { return nil, err diff --git a/main.go b/main.go index e58feb96593ad7cb94534b0885fc091f657a31de..360793268b28b2b9d57dddbbd9229aee83a99e71 100644 --- a/main.go +++ b/main.go @@ -36,7 +36,6 @@ var listenUmask = flag.Int("listenUmask", 022, "Umask for Unix socket, default: var authBackend = flag.String("authBackend", "http://localhost:8080", "Authentication/authorization backend") var authSocket = flag.String("authSocket", "", "Optional: Unix domain socket to dial authBackend at") var pprofListenAddr = flag.String("pprofListenAddr", "", "pprof listening address, e.g. 'localhost:6060'") -var relativeURLRoot = flag.String("relativeURLRoot", "/", "GitLab relative URL root") var documentRoot = flag.String("documentRoot", "public", "Path to static files content") var responseHeadersTimeout = flag.Duration("proxyHeadersTimeout", time.Minute, "How long to wait for response headers when proxying the request") var developmentMode = flag.Bool("developmentMode", false, "Allow to serve assets from Rails app") @@ -180,6 +179,5 @@ func main() { } upstream := newUpstream(*authBackend, proxyTransport) - upstream.SetRelativeURLRoot(*relativeURLRoot) log.Fatal(http.Serve(listener, upstream)) } diff --git a/main_test.go b/main_test.go index 67ef3a5e0bdcf5bd8d947f5506b16c8f08a772c7..9caa11859d5797e2428086a15424f82c8a9ff85c 100644 --- a/main_test.go +++ b/main_test.go @@ -13,6 +13,7 @@ import ( "os/exec" "path" "regexp" + "strings" "testing" "time" ) @@ -238,6 +239,29 @@ func TestAllowedApiDownloadZip(t *testing.T) { runOrFail(t, extractCmd) } +func TestAllowedApiDownloadZipWithSlash(t *testing.T) { + prepareDownloadDir(t) + + // Prepare test server and backend + archiveName := "foobar.zip" + ts := testAuthServer(nil, 200, archiveOkBody(t, archiveName)) + defer ts.Close() + ws := startWorkhorseServer(ts.URL) + defer ws.Close() + + // Use foo%2Fbar instead of a numeric ID + downloadCmd := exec.Command("curl", "-J", "-O", fmt.Sprintf("%s/api/v3/projects/foo%%2Fbar/repository/archive.zip", ws.URL)) + if !strings.Contains(downloadCmd.Args[3], `projects/foo%2Fbar/repository`) { + t.Fatalf("Cannot find percent-2F: %v", downloadCmd.Args) + } + downloadCmd.Dir = scratchDir + runOrFail(t, downloadCmd) + + extractCmd := exec.Command("unzip", archiveName) + extractCmd.Dir = scratchDir + runOrFail(t, extractCmd) +} + func TestDownloadCacheHit(t *testing.T) { prepareDownloadDir(t) diff --git a/upstream.go b/upstream.go index b6ac589714f675b5057cfc62830f94445c7b337e..4f100165c4ce5bc642012d1c8cfa5e5ea02e66ae 100644 --- a/upstream.go +++ b/upstream.go @@ -64,29 +64,30 @@ type gitRequest struct { } func newUpstream(authBackend string, authTransport http.RoundTripper) *upstream { - u, err := url.Parse(authBackend) + gitlabURL, err := url.Parse(authBackend) if err != nil { log.Fatalln(err) } + relativeURLRoot := gitlabURL.Path + if !strings.HasSuffix(relativeURLRoot, "/") { + relativeURLRoot += "/" + } + + // If the relative URL is '/foobar' and we tell httputil.ReverseProxy to proxy + // to 'http://example.com/foobar' then we get a redirect loop, so we clear the + // Path field here. + gitlabURL.Path = "" up := &upstream{ authBackend: authBackend, httpClient: &http.Client{Transport: authTransport}, - httpProxy: httputil.NewSingleHostReverseProxy(u), - relativeURLRoot: "/", + httpProxy: httputil.NewSingleHostReverseProxy(gitlabURL), + relativeURLRoot: relativeURLRoot, } up.httpProxy.Transport = authTransport return up } -func (u *upstream) SetRelativeURLRoot(relativeURLRoot string) { - u.relativeURLRoot = relativeURLRoot - - if !strings.HasSuffix(u.relativeURLRoot, "/") { - u.relativeURLRoot += "/" - } -} - func (u *upstream) ServeHTTP(ow http.ResponseWriter, r *http.Request) { var g httpRoute @@ -106,8 +107,8 @@ func (u *upstream) ServeHTTP(ow http.ResponseWriter, r *http.Request) { } // Check URL Root - URIPath := cleanURIPath(r.URL.Path) - if !strings.HasPrefix(URIPath, u.relativeURLRoot) { + URIPath := cleanURIPath(r.URL.EscapedPath()) + if !strings.HasPrefix(URIPath, u.relativeURLRoot) && URIPath+"/" != u.relativeURLRoot { httpError(&w, r, fmt.Sprintf("Not found %q", URIPath), http.StatusNotFound) return }