sketch: Linux support
Together with 8a89e1cbd3e7c9bbe1c561e88141527a0c894e06, this
should resolve https://github.com/boldsoftware/sketch/issues/3,
in supporting Linux.
To test on a Mac, run "limactl shell default go run ./cmd/sketch -verbose", assuming
you have a lima default VM with sketch's dependencies already prepped.
The issues we encountered were:
1. Explicit checks for "is Colima installed", "is docker installed",
etc. Easy enough to fix these.
2. When go's toolchain builds, it puts the output in different places
depending whether it's cross-compiled or not. Fine, we adapt.
Note that os.Rename() doesn't always work, and we fall back to
copy/delete. Mode has to be set appropriately to, to make the
/bin/sketch executable executable.
Re-building is kind of silly: we could use /proc/self/exe, for
example, or cross-mount the binary into the container, or any
number of other things. That said, here we keep the paths the same.
3. Docker networking setup for the git server. The host git repo
is served with git's cgi-bin wrapper. host.docker.internal
seems to work different in colima vs Ubuntu's docker.io, so
an extra command line flag to docker was added. Then,
it turns out that our git server checks that the remote is
127.0.0.1, which seems to be how Colima does its networking,
but isn't the case for Docker in general. To handle this, we
use HTTP Basic Auth with a random password. (Git also has
an "http.extraHeader" configuration, but basic auth seems just
as good and more obvious.)
Incidentally, I'm also adding the git remote as a git remote
named "sketch-host" to make it easier to work with in the terminal,
if necessary.
diff --git a/dockerimg/githttp.go b/dockerimg/githttp.go
index d618ab6..6f0ec55 100644
--- a/dockerimg/githttp.go
+++ b/dockerimg/githttp.go
@@ -1,16 +1,19 @@
package dockerimg
import (
+ "crypto/subtle"
"fmt"
"log/slog"
"net/http"
"net/http/cgi"
"os/exec"
+ "runtime"
"strings"
)
type gitHTTP struct {
gitRepoRoot string
+ pass string
}
func (g *gitHTTP) ServeHTTP(w http.ResponseWriter, r *http.Request) {
@@ -22,11 +25,40 @@
http.Error(w, fmt.Sprintf("panic: %v\n", err), http.StatusInternalServerError)
}
}()
- if !strings.HasPrefix(r.RemoteAddr, "127.0.0.1:") {
- slog.InfoContext(r.Context(), "githttp: denied", "remote addr", r.RemoteAddr)
- http.Error(w, "no", http.StatusUnauthorized)
+
+ // Get the Authorization header
+ username, password, ok := r.BasicAuth()
+
+ // Check if credentials were provided
+ if !ok {
+ // No credentials provided, return 401 Unauthorized
+ w.Header().Set("WWW-Authenticate", `Basic realm="Sketch Git Repository"`)
+ http.Error(w, "Unauthorized", http.StatusUnauthorized)
+ slog.InfoContext(r.Context(), "githttp: denied (basic auth)", "remote addr", r.RemoteAddr)
return
}
+
+ // Perform constant-time comparison to prevent timing attacks
+ usernameMatch := subtle.ConstantTimeCompare([]byte(username), []byte("sketch")) == 1
+ passwordMatch := subtle.ConstantTimeCompare([]byte(password), []byte(g.pass)) == 1
+
+ // Check if credentials are valid
+ if !usernameMatch || !passwordMatch {
+ w.Header().Set("WWW-Authenticate", `Basic realm="Git Repository"`)
+ http.Error(w, "Unauthorized", http.StatusUnauthorized)
+ slog.InfoContext(r.Context(), "githttp: denied (basic auth)", "remote addr", r.RemoteAddr)
+ return
+ }
+
+ if runtime.GOOS == "darwin" {
+ // On the Mac, Docker connections show up from localhost. On Linux, the docker
+ // network is more arbitrary, so we don't do this additional check there.
+ if !strings.HasPrefix(r.RemoteAddr, "127.0.0.1:") {
+ slog.InfoContext(r.Context(), "githttp: denied", "remote addr", r.RemoteAddr)
+ http.Error(w, "no", http.StatusUnauthorized)
+ return
+ }
+ }
gitBin, err := exec.LookPath("git")
if err != nil {
http.Error(w, "no git: "+err.Error(), http.StatusInternalServerError)