diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ef8afaf969cb21e03928696e7de2ffe9fea2877b..c3a2fb80e26302cec10826c1bde0cb48d43eb2e1 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -12,14 +12,22 @@ test:1.5: image: golang:1.5 script: - make verify-lite + - make acceptance test:1.6: image: golang:1.6 script: - make verify + - make acceptance test:1.7: image: golang:1.7 script: - make verify - make acceptance + +test:1.8: + image: golang:1.8 + script: + - make verify + - make acceptance diff --git a/Makefile b/Makefile index 2c1b8e1a89690d915928ada480775a79ac8bc2d6..1bee4e6a6537fb603877bb006ddbec8a5e16b0c8 100644 --- a/Makefile +++ b/Makefile @@ -40,10 +40,11 @@ complexity: test: go get golang.org/x/tools/cmd/cover - go test ./... -cover + go test ./... -short -cover -v -timeout 1m acceptance: gitlab-pages - go test ./... -run-acceptance-tests + go get golang.org/x/tools/cmd/cover + go test ./... -cover -v -timeout 1m docker: docker run --rm -it -v ${PWD}:/go/src/pages -w /go/src/pages golang:1.5 /bin/bash diff --git a/README.md b/README.md index 295ca7a78ac1c3bce7c6fd72d10aeffeea1b96a2..7ff395cc2055b811390cd9b3aac4b484bbbd7acb 100644 --- a/README.md +++ b/README.md @@ -39,15 +39,18 @@ If load balancer is run in SSL-offloading mode the custom TLS certificate will n Example: ``` -go build +CGO_ENABLED=0 GO15VENDOREXPERIMENT=1 go build ./gitlab-pages -listen-https "" -listen-http ":8090" -pages-root path/to/gitlab/shared/pages -pages-domain example.com ``` ### Run daemon **in secure mode** -The daemon can be run in chroot with dropped privileges. +When compiled with `CGO_ENABLED=0` (which is the default), `gitlab-pages` is a +static binary and so can be run in chroot with dropped privileges. -Run daemon as root user and pass the `-daemon-uid` and `-daemon-gid`. +To enter this mode, run `gitlab-pages` as the root user and pass it the +`-daemon-uid` and `-daemon-gid` arguments to specify the user you want it to run +as. The daemon start listening on ports as root, reads certificates as root and re-executes itself as specified user. When re-executing it copies it's own binary to `pages-root` and changes root to that directory. @@ -74,8 +77,8 @@ This is most useful in dual-stack environments (IPv4+IPv6) where both Gitlab Pag ### Enable Prometheus Metrics -For monitoring purposes, one could pass the `-metrics-address` flag when -starting. This will expose general metrics about the Go runtime and pages +For monitoring purposes, one could pass the `-metrics-address` flag when +starting. This will expose general metrics about the Go runtime and pages application for [Prometheus](https://prometheus.io/) to scrape. Example: diff --git a/acceptance_test.go b/acceptance_test.go index 1cfd133a6cc3e464546c9978ffe84322c6db6081..e1a7167ac7ed9519286201f41bdd9cb40450cbb3 100644 --- a/acceptance_test.go +++ b/acceptance_test.go @@ -4,12 +4,12 @@ import ( "flag" "io/ioutil" "net/http" + "os" "testing" "github.com/stretchr/testify/assert" ) -var shouldRun = flag.Bool("run-acceptance-tests", false, "Run the acceptance tests?") var pagesBinary = flag.String("gitlab-pages-binary", "./gitlab-pages", "Path to the gitlab-pages binary") // TODO: Use TCP port 0 everywhere to avoid conflicts. The binary could output @@ -25,12 +25,15 @@ var listeners = []ListenSpec{ } func skipUnlessEnabled(t *testing.T) { - if *shouldRun { - return + if testing.Short() { + t.Log("Acceptance tests disabled") + t.SkipNow() } - t.Log("Acceptance tests disabled") - t.SkipNow() + if _, err := os.Stat(*pagesBinary); os.IsNotExist(err) { + t.Errorf("Couldn't find gitlab-pages binary at %s", *pagesBinary) + t.FailNow() + } } func TestUnknownHostReturnsNotFound(t *testing.T) { diff --git a/daemon.go b/daemon.go index 8d68bb446efe1806c6b42eef6c64af4266251ebd..bbe69118ffdda145ae5510838809a9590d2836fb 100644 --- a/daemon.go +++ b/daemon.go @@ -188,7 +188,7 @@ func daemonize(config appConfig, uid, gid uint) { // Run daemon in chroot environment temporaryExecutable, err := daemonChroot(cmd) if err != nil { - println("Chroot failed", err) + log.Println("Chroot failed", err) return } defer os.Remove(temporaryExecutable) @@ -211,7 +211,7 @@ func daemonize(config appConfig, uid, gid uint) { // Start the process if err = cmd.Start(); err != nil { - println("Start failed", err) + log.Println("Start failed", err) return } diff --git a/domain.go b/domain.go index 46f57ed028d6795228ffeb558c3e5c74c710a58e..19e099ac16aef9f2cd7575198f6f7ddf640a285e 100644 --- a/domain.go +++ b/domain.go @@ -34,7 +34,7 @@ func (d *domain) serveFile(w http.ResponseWriter, r *http.Request, fullPath stri return err } - println("Serving", fullPath, "for", r.URL.Path) + fmt.Println("Serving", fullPath, "for", r.URL.Path) http.ServeContent(w, r, filepath.Base(file.Name()), fi.ModTime(), file) return nil } @@ -52,7 +52,7 @@ func (d *domain) serveCustomFile(w http.ResponseWriter, r *http.Request, code in return err } - println("Serving", fullPath, "for", r.URL.Path, "with", code) + fmt.Println("Serving", fullPath, "for", r.URL.Path, "with", code) // Serve the file _, haveType := w.Header()["Content-Type"] diff --git a/helpers_test.go b/helpers_test.go index e7fd744e6d7986a84966c81452aeaeca5a3dc488..99a44ea17cb32d06182e914a202a1bd5d04e94e0 100644 --- a/helpers_test.go +++ b/helpers_test.go @@ -1,6 +1,7 @@ package main import ( + "bytes" "crypto/tls" "fmt" "io/ioutil" @@ -15,6 +16,16 @@ import ( "github.com/stretchr/testify/assert" ) +type tWriter struct { + t *testing.T +} + +func (t *tWriter) Write(b []byte) (int, error) { + t.t.Log(string(bytes.TrimRight(b, "\r\n"))) + + return len(b), nil +} + var chdirSet = false func setUpTests() { @@ -131,6 +142,8 @@ func RunPagesProcess(t *testing.T, pagesPath string, listeners []ListenSpec, pro args, tempfiles := getPagesArgs(t, listeners, promPort) cmd := exec.Command(pagesPath, args...) + cmd.Stdout = &tWriter{t} + cmd.Stderr = &tWriter{t} cmd.Start() t.Logf("Running %s %v", pagesPath, args) @@ -144,10 +157,10 @@ func RunPagesProcess(t *testing.T, pagesPath string, listeners []ListenSpec, pro for _, spec := range listeners { spec.WaitUntilListening() } - time.Sleep(50 * time.Millisecond) + time.Sleep(500 * time.Millisecond) return func() { - cmd.Process.Kill() + cmd.Process.Signal(os.Interrupt) cmd.Process.Wait() for _, tempfile := range tempfiles { os.Remove(tempfile) @@ -176,9 +189,11 @@ func getPagesArgs(t *testing.T, listeners []ListenSpec, promPort string) (args, args = append(args, "-metrics-address", promPort) } - if os.Geteuid() == 0 && os.Getenv("SUDO_UID") != "" && os.Getenv("SUDO_GID") != "" { - t.Log("Pages process will drop privileges") - args = append(args, "-daemon-uid", os.Getenv("SUDO_UID"), "-daemon-gid", os.Getenv("SUDO_GID")) + // At least one of `-daemon-uid` and `-daemon-gid` must be non-zero + if os.Geteuid() == 0 { + t.Log("Running pages as a daemon") + args = append(args, "-daemon-uid", "0") + args = append(args, "-daemon-gid", "65534") // Root user can switch to "nobody" } return