@@ -20,8 +20,10 @@ import (
20
20
asymkey_model "code.gitea.io/gitea/models/asymkey"
21
21
git_model "code.gitea.io/gitea/models/git"
22
22
"code.gitea.io/gitea/models/perm"
23
+ "code.gitea.io/gitea/modules/container"
23
24
"code.gitea.io/gitea/modules/git"
24
25
"code.gitea.io/gitea/modules/json"
26
+ "code.gitea.io/gitea/modules/lfstransfer"
25
27
"code.gitea.io/gitea/modules/log"
26
28
"code.gitea.io/gitea/modules/pprof"
27
29
"code.gitea.io/gitea/modules/private"
@@ -36,7 +38,11 @@ import (
36
38
)
37
39
38
40
const (
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"
40
46
)
41
47
42
48
// CmdServ represents the available serv sub-command.
@@ -73,12 +79,18 @@ func setup(ctx context.Context, debug bool) {
73
79
}
74
80
75
81
var (
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
+ )
82
94
alphaDashDotPattern = regexp .MustCompile (`[^\w-\.]` )
83
95
)
84
96
@@ -124,6 +136,45 @@ func handleCliResponseExtra(extra private.ResponseExtra) error {
124
136
return nil
125
137
}
126
138
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
+
127
178
func runServ (c * cli.Context ) error {
128
179
ctx , cancel := installSignals ()
129
180
defer cancel ()
@@ -198,15 +249,6 @@ func runServ(c *cli.Context) error {
198
249
repoPath := strings .TrimPrefix (words [1 ], "/" )
199
250
200
251
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
- }
210
252
211
253
rr := strings .SplitN (repoPath , "/" , 2 )
212
254
if len (rr ) != 2 {
@@ -243,53 +285,52 @@ func runServ(c *cli.Context) error {
243
285
}()
244
286
}
245
287
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 {
248
301
return fail (ctx , "Unknown git command" , "Unknown git command %s" , verb )
249
302
}
250
303
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 )
260
305
261
306
results , extra := private .ServCommand (ctx , keyID , username , reponame , requestedMode , verb , lfsVerb )
262
307
if extra .HasError () {
263
308
return fail (ctx , extra .UserMsg , "ServCommand failed: %s" , extra .Error )
264
309
}
265
310
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
+
266
320
// LFS token authentication
267
- if verb == lfsAuthenticateVerb {
321
+ if verb == verbLfsAuthenticate {
268
322
url := fmt .Sprintf ("%s%s/%s.git/info/lfs" , setting .AppURL , url .PathEscape (results .OwnerName ), url .PathEscape (results .RepoName ))
269
323
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 )
284
325
if err != nil {
285
- return fail ( ctx , "Failed to sign JWT Token" , "Failed to sign JWT token: %v" , err )
326
+ return err
286
327
}
287
328
288
329
tokenAuthentication := & git_model.LFSTokenResponse {
289
330
Header : make (map [string ]string ),
290
331
Href : url ,
291
332
}
292
- tokenAuthentication .Header ["Authorization" ] = fmt . Sprintf ( "Bearer %s" , tokenString )
333
+ tokenAuthentication .Header ["Authorization" ] = token
293
334
294
335
enc := json .NewEncoder (os .Stdout )
295
336
err = enc .Encode (tokenAuthentication )
0 commit comments