1
Fork 0

Merge pull request 'Allow pushmirror to use publickey authentication' (#4819) from ironmagma/forgejo:publickey-auth-push-mirror into forgejo

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/4819
Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org>
This commit is contained in:
Gusted 2024-08-24 16:53:56 +00:00
commit 5dbacb70f4
24 changed files with 648 additions and 66 deletions

View file

@ -22,5 +22,6 @@ func ToPushMirror(ctx context.Context, pm *repo_model.PushMirror) (*api.PushMirr
LastError: pm.LastError,
Interval: pm.Interval.String(),
SyncOnCommit: pm.SyncOnCommit,
PublicKey: pm.GetPublicKey(),
}, nil
}

View file

@ -6,8 +6,10 @@
package forms
import (
"fmt"
"net/http"
"net/url"
"regexp"
"strings"
"code.gitea.io/gitea/models"
@ -88,6 +90,9 @@ func (f *MigrateRepoForm) Validate(req *http.Request, errs binding.Errors) bindi
return middleware.Validate(errs, ctx.Data, f, ctx.Locale)
}
// scpRegex matches the SCP-like addresses used by Git to access repositories over SSH.
var scpRegex = regexp.MustCompile(`^([a-zA-Z0-9_]+)@([a-zA-Z0-9._-]+):(.*)$`)
// ParseRemoteAddr checks if given remote address is valid,
// and returns composed URL with needed username and password.
func ParseRemoteAddr(remoteAddr, authUsername, authPassword string) (string, error) {
@ -103,7 +108,15 @@ func ParseRemoteAddr(remoteAddr, authUsername, authPassword string) (string, err
if len(authUsername)+len(authPassword) > 0 {
u.User = url.UserPassword(authUsername, authPassword)
}
remoteAddr = u.String()
return u.String(), nil
}
// Detect SCP-like remote addresses and return host.
if m := scpRegex.FindStringSubmatch(remoteAddr); m != nil {
// Match SCP-like syntax and convert it to a URL.
// Eg, "git@forgejo.org:user/repo" becomes
// "ssh://git@forgejo.org/user/repo".
return fmt.Sprintf("ssh://%s@%s/%s", url.User(m[1]), m[2], m[3]), nil
}
return remoteAddr, nil
@ -127,6 +140,7 @@ type RepoSettingForm struct {
PushMirrorPassword string
PushMirrorSyncOnCommit bool
PushMirrorInterval string
PushMirrorUseSSH bool
Private bool
Template bool
EnablePrune bool

View file

@ -71,7 +71,7 @@ func IsMigrateURLAllowed(remoteURL string, doer *user_model.User) error {
return &models.ErrInvalidCloneAddr{Host: u.Host, IsURLError: true}
}
if u.Opaque != "" || u.Scheme != "" && u.Scheme != "http" && u.Scheme != "https" && u.Scheme != "git" {
if u.Opaque != "" || u.Scheme != "" && u.Scheme != "http" && u.Scheme != "https" && u.Scheme != "git" && u.Scheme != "ssh" {
return &models.ErrInvalidCloneAddr{Host: u.Host, IsProtocolInvalid: true, IsPermissionDenied: true, IsURLError: true}
}

View file

@ -8,6 +8,7 @@ import (
"errors"
"fmt"
"io"
"os"
"regexp"
"strings"
"time"
@ -169,11 +170,43 @@ func runPushSync(ctx context.Context, m *repo_model.PushMirror) error {
log.Trace("Pushing %s mirror[%d] remote %s", path, m.ID, m.RemoteName)
// OpenSSH isn't very intuitive when you want to specify a specific keypair.
// Therefore, we need to create a temporary file that stores the private key, so that OpenSSH can use it.
// We delete the the temporary file afterwards.
privateKeyPath := ""
if m.PublicKey != "" {
f, err := os.CreateTemp(os.TempDir(), m.RemoteName)
if err != nil {
log.Error("os.CreateTemp: %v", err)
return errors.New("unexpected error")
}
defer func() {
f.Close()
if err := os.Remove(f.Name()); err != nil {
log.Error("os.Remove: %v", err)
}
}()
privateKey, err := m.Privatekey()
if err != nil {
log.Error("Privatekey: %v", err)
return errors.New("unexpected error")
}
if _, err := f.Write(privateKey); err != nil {
log.Error("f.Write: %v", err)
return errors.New("unexpected error")
}
privateKeyPath = f.Name()
}
if err := git.Push(ctx, path, git.PushOptions{
Remote: m.RemoteName,
Force: true,
Mirror: true,
Timeout: timeout,
Remote: m.RemoteName,
Force: true,
Mirror: true,
Timeout: timeout,
PrivateKeyPath: privateKeyPath,
}); err != nil {
log.Error("Error pushing %s mirror[%d] remote %s: %v", path, m.ID, m.RemoteName, err)