Commit 956288f7 authored by Jacob Vosmaer's avatar Jacob Vosmaer

Merge branch 'master' of https://gitlab.com/gitlab-org/gitlab-workhorse into serve-uploads

parents 86971fa6 c2697091
PREFIX=/usr/local
VERSION=$(shell git describe)-$(shell date -u +%Y%m%d.%H%M%S)
gitlab-workhorse: main.go githandler.go archive.go git-http.go helpers.go upload.go
gitlab-workhorse: main.go upstream.go archive.go git-http.go helpers.go upload.go
go build -ldflags "-X main.Version ${VERSION}" -o gitlab-workhorse
install: gitlab-workhorse
......
......@@ -9,7 +9,7 @@ backend (for authentication and authorization) and local disk access
to Git repositories managed by GitLab. In GitLab, this role was previously
performed by gitlab-grack.
In this file we start the web server and hand off to the gitHandler type.
In this file we start the web server and hand off to the upstream type.
*/
package main
......@@ -92,6 +92,6 @@ func main() {
// Because net/http/pprof installs itself in the DefaultServeMux
// we create a fresh one for the Git server.
serveMux := http.NewServeMux()
serveMux.Handle("/", newGitHandler(*authBackend, authTransport))
serveMux.Handle("/", newUpstream(*authBackend, authTransport))
log.Fatal(http.Serve(listener, serveMux))
}
......@@ -269,7 +269,7 @@ func testAuthServer(code int, body string) *httptest.Server {
}
func startServerOrFail(t *testing.T, ts *httptest.Server) *exec.Cmd {
cmd := exec.Command("go", "run", "main.go", "githandler.go", "archive.go", "git-http.go", "helpers.go", fmt.Sprintf("-authBackend=%s", ts.URL), fmt.Sprintf("-listenAddr=%s", servAddr))
cmd := exec.Command("go", "run", "main.go", "upstream.go", "archive.go", "git-http.go", "helpers.go", fmt.Sprintf("-authBackend=%s", ts.URL), fmt.Sprintf("-listenAddr=%s", servAddr))
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
......
/*
The gitHandler type implements http.Handler.
The upstream type implements http.Handler.
In this file we handle request routing and interaction with the authBackend.
*/
......@@ -11,20 +11,23 @@ import (
"io"
"log"
"net/http"
"os"
"path"
"regexp"
"strings"
)
type gitHandler struct {
type upstream struct {
httpClient *http.Client
authBackend string
}
type gitService struct {
method string
regex *regexp.Regexp
handleFunc func(w http.ResponseWriter, r *gitRequest, rpc string)
rpc string
method string
regex *regexp.Regexp
middlewareFunc func(u *upstream, w http.ResponseWriter, r *http.Request, handleFunc func(w http.ResponseWriter, r *gitRequest, rpc string), rpc string)
handleFunc func(w http.ResponseWriter, r *gitRequest, rpc string)
rpc string
}
// A gitReqest is an *http.Request decorated with attributes returned by the
......@@ -46,30 +49,25 @@ type gitRequest struct {
// CommitId is used do prevent race conditions between the 'time of check'
// in the GitLab Rails app and the 'time of use' in gitlab-workhorse.
CommitId string
// ContentPath is a file on disk we can serve to the client
ContentPath string
// ContentDisposition is used to set the Content-Disposition header
ContentDisposition string
}
// Routing table
var gitServices = [...]gitService{
gitService{"GET", regexp.MustCompile(`/info/refs\z`), handleGetInfoRefs, ""},
gitService{"POST", regexp.MustCompile(`/git-upload-pack\z`), handlePostRPC, "git-upload-pack"},
gitService{"POST", regexp.MustCompile(`/git-receive-pack\z`), handlePostRPC, "git-receive-pack"},
gitService{"GET", regexp.MustCompile(`/repository/archive\z`), handleGetArchive, "tar.gz"},
gitService{"GET", regexp.MustCompile(`/repository/archive.zip\z`), handleGetArchive, "zip"},
gitService{"GET", regexp.MustCompile(`/repository/archive.tar\z`), handleGetArchive, "tar"},
gitService{"GET", regexp.MustCompile(`/repository/archive.tar.gz\z`), handleGetArchive, "tar.gz"},
gitService{"GET", regexp.MustCompile(`/repository/archive.tar.bz2\z`), handleGetArchive, "tar.bz2"},
gitService{"GET", regexp.MustCompile(`/uploads/`), handleGetUpload, ""},
gitService{"GET", regexp.MustCompile(`/info/refs\z`), repoPreAuth, handleGetInfoRefs, ""},
gitService{"POST", regexp.MustCompile(`/git-upload-pack\z`), repoPreAuth, handlePostRPC, "git-upload-pack"},
gitService{"POST", regexp.MustCompile(`/git-receive-pack\z`), repoPreAuth, handlePostRPC, "git-receive-pack"},
gitService{"GET", regexp.MustCompile(`/repository/archive\z`), repoPreAuth, handleGetArchive, "tar.gz"},
gitService{"GET", regexp.MustCompile(`/repository/archive.zip\z`), repoPreAuth, handleGetArchive, "zip"},
gitService{"GET", regexp.MustCompile(`/repository/archive.tar\z`), repoPreAuth, handleGetArchive, "tar"},
gitService{"GET", regexp.MustCompile(`/repository/archive.tar.gz\z`), repoPreAuth, handleGetArchive, "tar.gz"},
gitService{"GET", regexp.MustCompile(`/repository/archive.tar.bz2\z`), repoPreAuth, handleGetArchive, "tar.bz2"},
}
func newGitHandler(authBackend string, authTransport http.RoundTripper) *gitHandler {
return &gitHandler{&http.Client{Transport: authTransport}, authBackend}
func newUpstream(authBackend string, authTransport http.RoundTripper) *upstream {
return &upstream{&http.Client{Transport: authTransport}, authBackend}
}
func (h *gitHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
func (u *upstream) ServeHTTP(w http.ResponseWriter, r *http.Request) {
var g gitService
log.Printf("%s %q", r.Method, r.URL)
......@@ -89,16 +87,38 @@ func (h *gitHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return
}
// Ask the auth backend if the request is allowed, and what the
// user ID (GL_ID) is.
authResponse, err := h.doAuthRequest(r)
g.middlewareFunc(u, w, r, g.handleFunc, g.rpc)
}
func repoPreAuth(u *upstream, w http.ResponseWriter, r *http.Request, handleFunc func(w http.ResponseWriter, r *gitRequest, rpc string), rpc string) {
url := u.authBackend + r.URL.RequestURI()
authReq, err := http.NewRequest(r.Method, url, nil)
if err != nil {
fail500(w, "doAuthRequest", err)
return
}
// Forward all headers from our client to the auth backend. This includes
// HTTP Basic authentication credentials (the 'Authorization' header).
for k, v := range r.Header {
authReq.Header[k] = v
}
// Also forward the Host header, which is excluded from the Header map by the http libary.
// This allows the Host header received by the backend to be consistent with other
// requests not going through gitlab-workhorse.
authReq.Host = r.Host
// Set a custom header for the request. This can be used in some
// configurations (Passenger) to solve auth request routing problems.
authReq.Header.Set("GitLab-Git-HTTP-Server", Version)
authResponse, err := u.httpClient.Do(authReq)
if err != nil {
fail500(w, "doAuthRequest", err)
return
}
defer authResponse.Body.Close()
if authResponse.StatusCode != 200 || authResponse.Header.Get("GitLab-Workhorse") == "reject" {
if authResponse.StatusCode != 200 {
// The Git request is not allowed by the backend. Maybe the
// client needs to send HTTP Basic credentials. Forward the
// response from the auth backend to our client. This includes
......@@ -137,26 +157,20 @@ func (h *gitHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
}
g.handleFunc(w, gitReq, g.rpc)
if !looksLikeRepo(gitReq.RepoPath) {
http.Error(w, "Not Found", 404)
return
}
handleFunc(w, gitReq, rpc)
}
func (h *gitHandler) doAuthRequest(r *http.Request) (result *http.Response, err error) {
url := h.authBackend + r.URL.RequestURI()
authReq, err := http.NewRequest(r.Method, url, nil)
if err != nil {
return nil, err
func looksLikeRepo(p string) bool {
// If /path/to/foo.git/objects exists then let's assume it is a valid Git
// repository.
if _, err := os.Stat(path.Join(p, "objects")); err != nil {
log.Print(err)
return false
}
// Forward all headers from our client to the auth backend. This includes
// HTTP Basic authentication credentials (the 'Authorization' header).
for k, v := range r.Header {
authReq.Header[k] = v
}
// Also forward the Host header, which is excluded from the Header map by the http libary.
// This allows the Host header received by the backend to be consistent with other
// requests not going through gitlab-workhorse.
authReq.Host = r.Host
// Set a custom header for the request. This can be used in some
// configurations (Passenger) to solve auth request routing problems.
authReq.Header.Set("GitLab-Git-HTTP-Server", Version)
return h.httpClient.Do(authReq)
return true
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment