blob: 6f0ec55c1e8fe5911c556de39fc2a6a0d9802fbd [file] [log] [blame]
Earl Lee2e463fb2025-04-17 11:22:22 -07001package dockerimg
2
3import (
Philip Zeyliger5e227dd2025-04-21 15:55:29 -07004 "crypto/subtle"
Earl Lee2e463fb2025-04-17 11:22:22 -07005 "fmt"
6 "log/slog"
7 "net/http"
8 "net/http/cgi"
9 "os/exec"
Philip Zeyliger5e227dd2025-04-21 15:55:29 -070010 "runtime"
Earl Lee2e463fb2025-04-17 11:22:22 -070011 "strings"
12)
13
14type gitHTTP struct {
15 gitRepoRoot string
Philip Zeyliger5e227dd2025-04-21 15:55:29 -070016 pass string
Earl Lee2e463fb2025-04-17 11:22:22 -070017}
18
19func (g *gitHTTP) ServeHTTP(w http.ResponseWriter, r *http.Request) {
20 defer func() {
21 if err := recover(); err != nil {
22 slog.ErrorContext(r.Context(), "gitHTTP.ServeHTTP panic", slog.Any("recovered_err", err))
23
24 // Return an error response to the client
25 http.Error(w, fmt.Sprintf("panic: %v\n", err), http.StatusInternalServerError)
26 }
27 }()
Philip Zeyliger5e227dd2025-04-21 15:55:29 -070028
29 // Get the Authorization header
30 username, password, ok := r.BasicAuth()
31
32 // Check if credentials were provided
33 if !ok {
34 // No credentials provided, return 401 Unauthorized
35 w.Header().Set("WWW-Authenticate", `Basic realm="Sketch Git Repository"`)
36 http.Error(w, "Unauthorized", http.StatusUnauthorized)
37 slog.InfoContext(r.Context(), "githttp: denied (basic auth)", "remote addr", r.RemoteAddr)
Earl Lee2e463fb2025-04-17 11:22:22 -070038 return
39 }
Philip Zeyliger5e227dd2025-04-21 15:55:29 -070040
41 // Perform constant-time comparison to prevent timing attacks
42 usernameMatch := subtle.ConstantTimeCompare([]byte(username), []byte("sketch")) == 1
43 passwordMatch := subtle.ConstantTimeCompare([]byte(password), []byte(g.pass)) == 1
44
45 // Check if credentials are valid
46 if !usernameMatch || !passwordMatch {
47 w.Header().Set("WWW-Authenticate", `Basic realm="Git Repository"`)
48 http.Error(w, "Unauthorized", http.StatusUnauthorized)
49 slog.InfoContext(r.Context(), "githttp: denied (basic auth)", "remote addr", r.RemoteAddr)
50 return
51 }
52
53 if runtime.GOOS == "darwin" {
54 // On the Mac, Docker connections show up from localhost. On Linux, the docker
55 // network is more arbitrary, so we don't do this additional check there.
56 if !strings.HasPrefix(r.RemoteAddr, "127.0.0.1:") {
57 slog.InfoContext(r.Context(), "githttp: denied", "remote addr", r.RemoteAddr)
58 http.Error(w, "no", http.StatusUnauthorized)
59 return
60 }
61 }
Earl Lee2e463fb2025-04-17 11:22:22 -070062 gitBin, err := exec.LookPath("git")
63 if err != nil {
64 http.Error(w, "no git: "+err.Error(), http.StatusInternalServerError)
65 return
66 }
67
68 w.Header().Set("Cache-Control", "no-cache")
69 h := &cgi.Handler{
70 Path: gitBin,
71 Args: []string{"http-backend"},
72 Dir: g.gitRepoRoot,
73 Env: []string{
74 "GIT_PROJECT_ROOT=" + g.gitRepoRoot,
75 "PATH_INFO=" + r.URL.Path,
76 "QUERY_STRING=" + r.URL.RawQuery,
77 "REQUEST_METHOD=" + r.Method,
78 "GIT_HTTP_EXPORT_ALL=true",
79 "GIT_HTTP_ALLOW_REPACK=true",
80 "GIT_HTTP_ALLOW_PUSH=true",
81 "GIT_HTTP_VERBOSE=1",
82 },
83 }
84 h.ServeHTTP(w, r)
85}