@@ -20,8 +20,10 @@ import (
2020 asymkey_model "code.gitea.io/gitea/models/asymkey"
2121 git_model "code.gitea.io/gitea/models/git"
2222 "code.gitea.io/gitea/models/perm"
23+ "code.gitea.io/gitea/modules/container"
2324 "code.gitea.io/gitea/modules/git"
2425 "code.gitea.io/gitea/modules/json"
26+ "code.gitea.io/gitea/modules/lfstransfer"
2527 "code.gitea.io/gitea/modules/log"
2628 "code.gitea.io/gitea/modules/pprof"
2729 "code.gitea.io/gitea/modules/private"
@@ -36,7 +38,11 @@ import (
3638)
3739
3840const (
39- lfsAuthenticateVerb = "git-lfs-authenticate"
41+ verbUploadPack = "git-upload-pack"
42+ verbUploadArchive = "git-upload-archive"
43+ verbReceivePack = "git-receive-pack"
44+ verbLfsAuthenticate = "git-lfs-authenticate"
45+ verbLfsTransfer = "git-lfs-transfer"
4046)
4147
4248// CmdServ represents the available serv sub-command.
@@ -73,12 +79,18 @@ func setup(ctx context.Context, debug bool) {
7379}
7480
7581var (
76- allowedCommands = map [string ]perm.AccessMode {
77- "git-upload-pack" : perm .AccessModeRead ,
78- "git-upload-archive" : perm .AccessModeRead ,
79- "git-receive-pack" : perm .AccessModeWrite ,
80- lfsAuthenticateVerb : perm .AccessModeNone ,
81- }
82+ // keep getAccessMode() in sync
83+ allowedCommands = container .SetOf (
84+ verbUploadPack ,
85+ verbUploadArchive ,
86+ verbReceivePack ,
87+ verbLfsAuthenticate ,
88+ verbLfsTransfer ,
89+ )
90+ allowedCommandsLfs = container .SetOf (
91+ verbLfsAuthenticate ,
92+ verbLfsTransfer ,
93+ )
8294 alphaDashDotPattern = regexp .MustCompile (`[^\w-\.]` )
8395)
8496
@@ -124,6 +136,45 @@ func handleCliResponseExtra(extra private.ResponseExtra) error {
124136 return nil
125137}
126138
139+ func getAccessMode (verb , lfsVerb string ) perm.AccessMode {
140+ switch verb {
141+ case verbUploadPack , verbUploadArchive :
142+ return perm .AccessModeRead
143+ case verbReceivePack :
144+ return perm .AccessModeWrite
145+ case verbLfsAuthenticate , verbLfsTransfer :
146+ switch lfsVerb {
147+ case "upload" :
148+ return perm .AccessModeWrite
149+ case "download" :
150+ return perm .AccessModeRead
151+ }
152+ }
153+ // should be unreachable
154+ return perm .AccessModeNone
155+ }
156+
157+ func getLFSAuthToken (ctx context.Context , lfsVerb string , results * private.ServCommandResults ) (string , error ) {
158+ now := time .Now ()
159+ claims := lfs.Claims {
160+ RegisteredClaims : jwt.RegisteredClaims {
161+ ExpiresAt : jwt .NewNumericDate (now .Add (setting .LFS .HTTPAuthExpiry )),
162+ NotBefore : jwt .NewNumericDate (now ),
163+ },
164+ RepoID : results .RepoID ,
165+ Op : lfsVerb ,
166+ UserID : results .UserID ,
167+ }
168+ token := jwt .NewWithClaims (jwt .SigningMethodHS256 , claims )
169+
170+ // Sign and get the complete encoded token as a string using the secret
171+ tokenString , err := token .SignedString (setting .LFS .JWTSecretBytes )
172+ if err != nil {
173+ return "" , fail (ctx , "Failed to sign JWT Token" , "Failed to sign JWT token: %v" , err )
174+ }
175+ return fmt .Sprintf ("Bearer %s" , tokenString ), nil
176+ }
177+
127178func runServ (c * cli.Context ) error {
128179 ctx , cancel := installSignals ()
129180 defer cancel ()
@@ -198,15 +249,6 @@ func runServ(c *cli.Context) error {
198249 repoPath := strings .TrimPrefix (words [1 ], "/" )
199250
200251 var lfsVerb string
201- if verb == lfsAuthenticateVerb {
202- if ! setting .LFS .StartServer {
203- return fail (ctx , "Unknown git command" , "LFS authentication request over SSH denied, LFS support is disabled" )
204- }
205-
206- if len (words ) > 2 {
207- lfsVerb = words [2 ]
208- }
209- }
210252
211253 rr := strings .SplitN (repoPath , "/" , 2 )
212254 if len (rr ) != 2 {
@@ -243,53 +285,52 @@ func runServ(c *cli.Context) error {
243285 }()
244286 }
245287
246- requestedMode , has := allowedCommands [verb ]
247- if ! has {
288+ if allowedCommands .Contains (verb ) {
289+ if allowedCommandsLfs .Contains (verb ) {
290+ if ! setting .LFS .StartServer {
291+ return fail (ctx , "Unknown git command" , "LFS authentication request over SSH denied, LFS support is disabled" )
292+ }
293+ if verb == verbLfsTransfer && ! setting .LFS .AllowPureSSH {
294+ return fail (ctx , "Unknown git command" , "LFS SSH transfer connection denied, pure SSH protocol is disabled" )
295+ }
296+ if len (words ) > 2 {
297+ lfsVerb = words [2 ]
298+ }
299+ }
300+ } else {
248301 return fail (ctx , "Unknown git command" , "Unknown git command %s" , verb )
249302 }
250303
251- if verb == lfsAuthenticateVerb {
252- if lfsVerb == "upload" {
253- requestedMode = perm .AccessModeWrite
254- } else if lfsVerb == "download" {
255- requestedMode = perm .AccessModeRead
256- } else {
257- return fail (ctx , "Unknown LFS verb" , "Unknown lfs verb %s" , lfsVerb )
258- }
259- }
304+ requestedMode := getAccessMode (verb , lfsVerb )
260305
261306 results , extra := private .ServCommand (ctx , keyID , username , reponame , requestedMode , verb , lfsVerb )
262307 if extra .HasError () {
263308 return fail (ctx , extra .UserMsg , "ServCommand failed: %s" , extra .Error )
264309 }
265310
311+ // LFS SSH protocol
312+ if verb == verbLfsTransfer {
313+ token , err := getLFSAuthToken (ctx , lfsVerb , results )
314+ if err != nil {
315+ return err
316+ }
317+ return lfstransfer .Main (ctx , repoPath , lfsVerb , token )
318+ }
319+
266320 // LFS token authentication
267- if verb == lfsAuthenticateVerb {
321+ if verb == verbLfsAuthenticate {
268322 url := fmt .Sprintf ("%s%s/%s.git/info/lfs" , setting .AppURL , url .PathEscape (results .OwnerName ), url .PathEscape (results .RepoName ))
269323
270- now := time .Now ()
271- claims := lfs.Claims {
272- RegisteredClaims : jwt.RegisteredClaims {
273- ExpiresAt : jwt .NewNumericDate (now .Add (setting .LFS .HTTPAuthExpiry )),
274- NotBefore : jwt .NewNumericDate (now ),
275- },
276- RepoID : results .RepoID ,
277- Op : lfsVerb ,
278- UserID : results .UserID ,
279- }
280- token := jwt .NewWithClaims (jwt .SigningMethodHS256 , claims )
281-
282- // Sign and get the complete encoded token as a string using the secret
283- tokenString , err := token .SignedString (setting .LFS .JWTSecretBytes )
324+ token , err := getLFSAuthToken (ctx , lfsVerb , results )
284325 if err != nil {
285- return fail ( ctx , "Failed to sign JWT Token" , "Failed to sign JWT token: %v" , err )
326+ return err
286327 }
287328
288329 tokenAuthentication := & git_model.LFSTokenResponse {
289330 Header : make (map [string ]string ),
290331 Href : url ,
291332 }
292- tokenAuthentication .Header ["Authorization" ] = fmt . Sprintf ( "Bearer %s" , tokenString )
333+ tokenAuthentication .Header ["Authorization" ] = token
293334
294335 enc := json .NewEncoder (os .Stdout )
295336 err = enc .Encode (tokenAuthentication )
0 commit comments