aboutsummaryrefslogtreecommitdiffstats
path: root/cmd
diff options
context:
space:
mode:
authorConcurrentCrab <102517200+ConcurrentCrab@users.noreply.github.com>2024-09-27 19:57:37 +0530
committerGitHub <noreply@github.com>2024-09-27 10:27:37 -0400
commit8a9fd7f771f4f694594a0652e95ddda2b7479b3e (patch)
treeeaad5bf33ca2a745645fc2368b4b01d598e35486 /cmd
parentfdb1df9eca2f7adabf08883042325af26c3fd509 (diff)
downloadgitea-8a9fd7f771f4f694594a0652e95ddda2b7479b3e.tar.gz
gitea-8a9fd7f771f4f694594a0652e95ddda2b7479b3e.zip
Add pure SSH LFS support (#31516)
Fixes #17554 /claim #17554 Docs PR https://gitea.com/gitea/docs/pulls/49 To test, run pushes like: `GIT_TRACE=1` git push. The trace output should mention "pure SSH connection".
Diffstat (limited to 'cmd')
-rw-r--r--cmd/serv.go129
1 files changed, 85 insertions, 44 deletions
diff --git a/cmd/serv.go b/cmd/serv.go
index f74a8fd3d0..2d2df8aa23 100644
--- a/cmd/serv.go
+++ b/cmd/serv.go
@@ -20,8 +20,10 @@ import (
asymkey_model "code.gitea.io/gitea/models/asymkey"
git_model "code.gitea.io/gitea/models/git"
"code.gitea.io/gitea/models/perm"
+ "code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/json"
+ "code.gitea.io/gitea/modules/lfstransfer"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/pprof"
"code.gitea.io/gitea/modules/private"
@@ -36,7 +38,11 @@ import (
)
const (
- lfsAuthenticateVerb = "git-lfs-authenticate"
+ verbUploadPack = "git-upload-pack"
+ verbUploadArchive = "git-upload-archive"
+ verbReceivePack = "git-receive-pack"
+ verbLfsAuthenticate = "git-lfs-authenticate"
+ verbLfsTransfer = "git-lfs-transfer"
)
// CmdServ represents the available serv sub-command.
@@ -73,12 +79,18 @@ func setup(ctx context.Context, debug bool) {
}
var (
- allowedCommands = map[string]perm.AccessMode{
- "git-upload-pack": perm.AccessModeRead,
- "git-upload-archive": perm.AccessModeRead,
- "git-receive-pack": perm.AccessModeWrite,
- lfsAuthenticateVerb: perm.AccessModeNone,
- }
+ // keep getAccessMode() in sync
+ allowedCommands = container.SetOf(
+ verbUploadPack,
+ verbUploadArchive,
+ verbReceivePack,
+ verbLfsAuthenticate,
+ verbLfsTransfer,
+ )
+ allowedCommandsLfs = container.SetOf(
+ verbLfsAuthenticate,
+ verbLfsTransfer,
+ )
alphaDashDotPattern = regexp.MustCompile(`[^\w-\.]`)
)
@@ -124,6 +136,45 @@ func handleCliResponseExtra(extra private.ResponseExtra) error {
return nil
}
+func getAccessMode(verb, lfsVerb string) perm.AccessMode {
+ switch verb {
+ case verbUploadPack, verbUploadArchive:
+ return perm.AccessModeRead
+ case verbReceivePack:
+ return perm.AccessModeWrite
+ case verbLfsAuthenticate, verbLfsTransfer:
+ switch lfsVerb {
+ case "upload":
+ return perm.AccessModeWrite
+ case "download":
+ return perm.AccessModeRead
+ }
+ }
+ // should be unreachable
+ return perm.AccessModeNone
+}
+
+func getLFSAuthToken(ctx context.Context, lfsVerb string, results *private.ServCommandResults) (string, error) {
+ now := time.Now()
+ claims := lfs.Claims{
+ RegisteredClaims: jwt.RegisteredClaims{
+ ExpiresAt: jwt.NewNumericDate(now.Add(setting.LFS.HTTPAuthExpiry)),
+ NotBefore: jwt.NewNumericDate(now),
+ },
+ RepoID: results.RepoID,
+ Op: lfsVerb,
+ UserID: results.UserID,
+ }
+ token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
+
+ // Sign and get the complete encoded token as a string using the secret
+ tokenString, err := token.SignedString(setting.LFS.JWTSecretBytes)
+ if err != nil {
+ return "", fail(ctx, "Failed to sign JWT Token", "Failed to sign JWT token: %v", err)
+ }
+ return fmt.Sprintf("Bearer %s", tokenString), nil
+}
+
func runServ(c *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()
@@ -198,15 +249,6 @@ func runServ(c *cli.Context) error {
repoPath := strings.TrimPrefix(words[1], "/")
var lfsVerb string
- if verb == lfsAuthenticateVerb {
- if !setting.LFS.StartServer {
- return fail(ctx, "Unknown git command", "LFS authentication request over SSH denied, LFS support is disabled")
- }
-
- if len(words) > 2 {
- lfsVerb = words[2]
- }
- }
rr := strings.SplitN(repoPath, "/", 2)
if len(rr) != 2 {
@@ -243,53 +285,52 @@ func runServ(c *cli.Context) error {
}()
}
- requestedMode, has := allowedCommands[verb]
- if !has {
+ if allowedCommands.Contains(verb) {
+ if allowedCommandsLfs.Contains(verb) {
+ if !setting.LFS.StartServer {
+ return fail(ctx, "Unknown git command", "LFS authentication request over SSH denied, LFS support is disabled")
+ }
+ if verb == verbLfsTransfer && !setting.LFS.AllowPureSSH {
+ return fail(ctx, "Unknown git command", "LFS SSH transfer connection denied, pure SSH protocol is disabled")
+ }
+ if len(words) > 2 {
+ lfsVerb = words[2]
+ }
+ }
+ } else {
return fail(ctx, "Unknown git command", "Unknown git command %s", verb)
}
- if verb == lfsAuthenticateVerb {
- if lfsVerb == "upload" {
- requestedMode = perm.AccessModeWrite
- } else if lfsVerb == "download" {
- requestedMode = perm.AccessModeRead
- } else {
- return fail(ctx, "Unknown LFS verb", "Unknown lfs verb %s", lfsVerb)
- }
- }
+ requestedMode := getAccessMode(verb, lfsVerb)
results, extra := private.ServCommand(ctx, keyID, username, reponame, requestedMode, verb, lfsVerb)
if extra.HasError() {
return fail(ctx, extra.UserMsg, "ServCommand failed: %s", extra.Error)
}
+ // LFS SSH protocol
+ if verb == verbLfsTransfer {
+ token, err := getLFSAuthToken(ctx, lfsVerb, results)
+ if err != nil {
+ return err
+ }
+ return lfstransfer.Main(ctx, repoPath, lfsVerb, token)
+ }
+
// LFS token authentication
- if verb == lfsAuthenticateVerb {
+ if verb == verbLfsAuthenticate {
url := fmt.Sprintf("%s%s/%s.git/info/lfs", setting.AppURL, url.PathEscape(results.OwnerName), url.PathEscape(results.RepoName))
- now := time.Now()
- claims := lfs.Claims{
- RegisteredClaims: jwt.RegisteredClaims{
- ExpiresAt: jwt.NewNumericDate(now.Add(setting.LFS.HTTPAuthExpiry)),
- NotBefore: jwt.NewNumericDate(now),
- },
- RepoID: results.RepoID,
- Op: lfsVerb,
- UserID: results.UserID,
- }
- token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
-
- // Sign and get the complete encoded token as a string using the secret
- tokenString, err := token.SignedString(setting.LFS.JWTSecretBytes)
+ token, err := getLFSAuthToken(ctx, lfsVerb, results)
if err != nil {
- return fail(ctx, "Failed to sign JWT Token", "Failed to sign JWT token: %v", err)
+ return err
}
tokenAuthentication := &git_model.LFSTokenResponse{
Header: make(map[string]string),
Href: url,
}
- tokenAuthentication.Header["Authorization"] = fmt.Sprintf("Bearer %s", tokenString)
+ tokenAuthentication.Header["Authorization"] = token
enc := json.NewEncoder(os.Stdout)
err = enc.Encode(tokenAuthentication)