cloudflare-native port of the tangled knot server
0

Configure Feed

Select the types of activity you want to include in your feed.

knotserver: bind git push service auth lxm

Use an explicit service-auth lexicon method for Git receive-pack discovery and POST routes instead of deriving lxm from Git smart-HTTP path segments such as info/refs or git-receive-pack. This lets real HTTPS pushes present a PDS-issued service auth token scoped to sh.tangled.git.receivePack.

Patch category: upstreamable-core

+70 -5
+1 -1
knotserver/git.go
··· 131 131 h.RejectPush(w, r, chi.URLParam(r, "name")) 132 132 return 133 133 } 134 - h.ServiceAuth.VerifyServiceAuth(http.HandlerFunc(h.receivePackInfoRefs)).ServeHTTP(w, r) 134 + h.ServiceAuth.VerifyServiceAuthFor(http.HandlerFunc(h.receivePackInfoRefs), syntax.NSID(gitReceivePackLexiconMethod)).ServeHTTP(w, r) 135 135 return 136 136 } 137 137
+3 -1
knotserver/router.go
··· 42 42 maintainer repoMaintainer 43 43 } 44 44 45 + const gitReceivePackLexiconMethod = "sh.tangled.git.receivePack" 46 + 45 47 func Setup(ctx context.Context, c *config.Config, db *db.DB, e *rbac.Enforcer, jc *jetstream.JetstreamClient, n *notifier.Notifier, resolver *idresolver.Resolver, sb sandbox.Backend) (http.Handler, error) { 46 48 h := Knot{ 47 49 c: c, ··· 166 168 next.ServeHTTP(w, r) 167 169 return 168 170 } 169 - h.ServiceAuth.VerifyServiceAuth(next).ServeHTTP(w, r) 171 + h.ServiceAuth.VerifyServiceAuthFor(next, syntax.NSID(gitReceivePackLexiconMethod)).ServeHTTP(w, r) 170 172 }) 171 173 } 172 174
+13 -3
xrpc/serviceauth/service_auth.go
··· 39 39 40 40 func (sa *ServiceAuth) VerifyServiceAuth(next http.Handler) http.Handler { 41 41 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 42 - token := r.Header.Get("Authorization") 43 - token = strings.TrimPrefix(token, "Bearer ") 44 - 45 42 lxm, err := syntax.ParseNSID(path.Base(r.URL.Path)) 46 43 if err != nil { 47 44 sa.logger.Error("could not derive lexicon method from request path", "path", r.URL.Path, "err", err) 48 45 writeError(w, xrpcerr.AuthError(err), http.StatusForbidden) 49 46 return 50 47 } 48 + 49 + sa.verifyServiceAuthFor(next, lxm).ServeHTTP(w, r) 50 + }) 51 + } 52 + 53 + func (sa *ServiceAuth) VerifyServiceAuthFor(next http.Handler, lxm syntax.NSID) http.Handler { 54 + return sa.verifyServiceAuthFor(next, lxm) 55 + } 56 + 57 + func (sa *ServiceAuth) verifyServiceAuthFor(next http.Handler, lxm syntax.NSID) http.Handler { 58 + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 59 + token := r.Header.Get("Authorization") 60 + token = strings.TrimPrefix(token, "Bearer ") 51 61 52 62 s := auth.ServiceAuthValidator{ 53 63 Audience: sa.audienceDid,
+53
xrpc/serviceauth/service_auth_test.go
··· 112 112 t.Fatalf("status = %d, want 403 for an unverifiable token", rec.Code) 113 113 } 114 114 } 115 + 116 + func TestVerifyServiceAuthForUsesExplicitLexiconMethod(t *testing.T) { 117 + issuer := syntax.DID("did:example:pusher") 118 + audience := "did:web:knot.example" 119 + lxm := syntax.NSID("sh.tangled.git.receivePack") 120 + 121 + priv, err := atcrypto.GeneratePrivateKeyP256() 122 + if err != nil { 123 + t.Fatalf("GeneratePrivateKeyP256: %v", err) 124 + } 125 + pub, err := priv.PublicKey() 126 + if err != nil { 127 + t.Fatalf("PublicKey: %v", err) 128 + } 129 + 130 + dir := identity.NewMockDirectory() 131 + dir.Insert(identity.Identity{ 132 + DID: issuer, 133 + Keys: map[string]identity.VerificationMethod{ 134 + "atproto": { 135 + Type: "Multikey", 136 + PublicKeyMultibase: pub.Multibase(), 137 + }, 138 + }, 139 + }) 140 + 141 + token, err := auth.SignServiceAuth(issuer, audience, time.Minute, &lxm, priv) 142 + if err != nil { 143 + t.Fatalf("SignServiceAuth: %v", err) 144 + } 145 + 146 + sa := NewServiceAuth(slog.New(slog.NewTextHandler(io.Discard, nil)), dir, audience) 147 + handler := sa.VerifyServiceAuthFor(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 148 + got, ok := r.Context().Value(ActorDid).(syntax.DID) 149 + if !ok { 150 + t.Fatalf("ActorDid missing from context") 151 + } 152 + if got != issuer { 153 + t.Fatalf("ActorDid = %s, want %s", got, issuer) 154 + } 155 + w.WriteHeader(http.StatusNoContent) 156 + }), lxm) 157 + 158 + req := httptest.NewRequest(http.MethodGet, "/did:example:owner/repo/info/refs?service=git-receive-pack", nil) 159 + req.Header.Set("Authorization", "Bearer "+token) 160 + rec := httptest.NewRecorder() 161 + 162 + handler.ServeHTTP(rec, req) 163 + 164 + if rec.Code != http.StatusNoContent { 165 + t.Fatalf("status = %d, want %d; body: %q", rec.Code, http.StatusNoContent, rec.Body.String()) 166 + } 167 + }