Monorepo for Tangled tangled.org

knotserver/git: reject requests to unknown repos #1169

merged opened by boltless.me targeting master from sl/lvnwqspuwzom
Labels

None yet.

assignee

None yet.

Participants 2
AT URI
at://did:plc:xasnlahkri4ewmbuzly2rlc5/sh.tangled.repo.pull/3mh5rm2se5o22
+64 -34
Diff #2
+24 -34
knotserver/git.go
··· 5 5 "fmt" 6 6 "io" 7 7 "net/http" 8 - "path/filepath" 8 + "os" 9 9 "strings" 10 10 11 - securejoin "github.com/cyphar/filepath-securejoin" 12 11 "github.com/go-chi/chi/v5" 13 12 "tangled.org/core/knotserver/git/service" 14 13 ) 15 14 16 15 func (h *Knot) InfoRefs(w http.ResponseWriter, r *http.Request) { 17 - did := chi.URLParam(r, "did") 18 16 name := chi.URLParam(r, "name") 19 - repoName, err := securejoin.SecureJoin(did, name) 20 - if err != nil { 21 - gitError(w, "repository not found", http.StatusNotFound) 22 - h.l.Error("git: failed to secure join repo path", "handler", "InfoRefs", "error", err) 23 - return 24 - } 25 - 26 - repoPath, err := securejoin.SecureJoin(h.c.Repo.ScanPath, repoName) 27 - if err != nil { 28 - gitError(w, "repository not found", http.StatusNotFound) 29 - h.l.Error("git: failed to secure join repo path", "handler", "InfoRefs", "error", err) 17 + repoPath, ok := repoPathFromcontext(r.Context()) 18 + if !ok { 19 + w.WriteHeader(http.StatusInternalServerError) 20 + w.Write([]byte("Failed to find repository path")) 30 21 return 31 22 } 32 23 ··· 57 48 } 58 49 59 50 func (h *Knot) UploadArchive(w http.ResponseWriter, r *http.Request) { 60 - did := chi.URLParam(r, "did") 61 - name := chi.URLParam(r, "name") 62 - repo, err := securejoin.SecureJoin(h.c.Repo.ScanPath, filepath.Join(did, name)) 63 - if err != nil { 64 - gitError(w, err.Error(), http.StatusInternalServerError) 65 - h.l.Error("git: failed to secure join repo path", "handler", "UploadPack", "error", err) 51 + repo, ok := repoPathFromcontext(r.Context()) 52 + if !ok { 53 + w.WriteHeader(http.StatusInternalServerError) 54 + w.Write([]byte("Failed to find repository path")) 66 55 return 67 56 } 68 57 ··· 104 93 } 105 94 106 95 func (h *Knot) UploadPack(w http.ResponseWriter, r *http.Request) { 107 - did := chi.URLParam(r, "did") 108 - name := chi.URLParam(r, "name") 109 - repo, err := securejoin.SecureJoin(h.c.Repo.ScanPath, filepath.Join(did, name)) 110 - if err != nil { 111 - gitError(w, err.Error(), http.StatusInternalServerError) 112 - h.l.Error("git: failed to secure join repo path", "handler", "UploadPack", "error", err) 96 + repo, ok := repoPathFromcontext(r.Context()) 97 + if !ok { 98 + w.WriteHeader(http.StatusInternalServerError) 99 + w.Write([]byte("Failed to find repository path")) 113 100 return 114 101 } 115 102 ··· 153 140 } 154 141 155 142 func (h *Knot) ReceivePack(w http.ResponseWriter, r *http.Request) { 156 - did := chi.URLParam(r, "did") 157 143 name := chi.URLParam(r, "name") 158 - _, err := securejoin.SecureJoin(h.c.Repo.ScanPath, filepath.Join(did, name)) 159 - if err != nil { 160 - gitError(w, err.Error(), http.StatusForbidden) 161 - h.l.Error("git: failed to secure join repo path", "handler", "ReceivePack", "error", err) 162 - return 163 - } 164 - 165 144 h.RejectPush(w, r, name) 166 145 } 167 146 ··· 192 171 fmt.Fprintf(w, "\n\n") 193 172 } 194 173 174 + func isDir(path string) (bool, error) { 175 + info, err := os.Stat(path) 176 + if err == nil && info.IsDir() { 177 + return true, nil 178 + } 179 + if os.IsNotExist(err) { 180 + return false, nil 181 + } 182 + return false, err 183 + } 184 + 195 185 func gitError(w http.ResponseWriter, msg string, status int) { 196 186 w.Header().Set("content-type", "text/plain; charset=UTF-8") 197 187 w.WriteHeader(status)
+40
knotserver/router.go
··· 5 5 "fmt" 6 6 "log/slog" 7 7 "net/http" 8 + "path/filepath" 8 9 "strings" 9 10 11 + securejoin "github.com/cyphar/filepath-securejoin" 10 12 "github.com/go-chi/chi/v5" 11 13 "tangled.org/core/idresolver" 12 14 "tangled.org/core/jetstream" ··· 81 83 82 84 r.Route("/{did}", func(r chi.Router) { 83 85 r.Use(h.resolveDidRedirect) 86 + r.Use(h.resolveRepo) 84 87 r.Route("/{name}", func(r chi.Router) { 85 88 // routes for git operations 86 89 r.Get("/info/refs", h.InfoRefs) ··· 141 144 }) 142 145 } 143 146 147 + type ctxRepoPathKey struct{} 148 + 149 + func repoPathFromcontext(ctx context.Context) (string, bool) { 150 + v, ok := ctx.Value(ctxRepoPathKey{}).(string) 151 + return v, ok 152 + } 153 + 154 + // resolveRepo is a http middleware that constructs git repo path from given did & name pair. 155 + // It will reject the requests to unknown repos (when dir doesn't exist) 156 + func (h *Knot) resolveRepo(next http.Handler) http.Handler { 157 + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 158 + did := chi.URLParam(r, "did") 159 + name := chi.URLParam(r, "name") 160 + repoPath, err := securejoin.SecureJoin(h.c.Repo.ScanPath, filepath.Join(did, name)) 161 + if err != nil { 162 + w.WriteHeader(http.StatusNotFound) 163 + w.Write([]byte("Repository not found")) 164 + return 165 + } 166 + 167 + exist, err := isDir(repoPath) 168 + if err != nil { 169 + w.WriteHeader(http.StatusInternalServerError) 170 + w.Write([]byte("Failed to check repository path")) 171 + return 172 + } 173 + if !exist { 174 + w.WriteHeader(http.StatusNotFound) 175 + w.Write([]byte("Repository not found")) 176 + return 177 + } 178 + 179 + ctx := context.WithValue(r.Context(), "repoPath", repoPath) 180 + next.ServeHTTP(w, r.WithContext(ctx)) 181 + }) 182 + } 183 + 144 184 func (h *Knot) configureOwner() error { 145 185 cfgOwner := h.c.Server.Owner 146 186

History

3 rounds 5 comments
sign up or login to add to the discussion
1 commit
expand
knotserver/git: reject requests to unknown repos
3/3 success
expand
expand 2 comments

tested and now it works as expected

before#

% git clone https://knot.tngl.boltless.dev/did:plc:cqojjfqu74dcdde3ql3imdjf/whatever
Cloning into 'whatever'...
fatal: protocol error: bad line length character: fail

after#

% git clone https://knot.tngl.boltless.dev/did:plc:cqojjfqu74dcdde3ql3imdjf/whatever
Cloning into 'whatever'...
remote: Repository not found
fatal: repository 'https://knot.tngl.boltless.dev/did:plc:cqojjfqu74dcdde3ql3imdjf/whatever/' not found
pull request successfully merged
1 commit
expand
knotserver/git: reject requests to unknown repos
3/3 success
expand
expand 0 comments
1 commit
expand
knotserver/git: reject requests to unknown repos
expand 3 comments

This solves the fatal: protocol error: bad line length character: fail bug while git clone over http.

Most cases, that error happens when target repository is not found. So I added a middleware resolveRepo to check the repository existence first and resolve its absolute path at one place.

knotserver/git.go:54: the text here can be "Failed to find repository path"

knotserver/git.go:99: likewise here

knotserver/router.go:168: why not handle th error as 500 here?

knotserver/router.go:166: do we know for sure that the repo path will include .git? none of the paths on my knot include this.

Ah right. I forgot to update after minimum PoC. Sorry for the incomplete PR.

I resubmitted. Unfortunately couldn't test it right now, but it should work fine.