mirror of
https://github.com/go-gitea/gitea.git
synced 2026-09-20 23:09:16 +02:00
SSH Push/Pull Mirroring & Migrations
This commit is contained in:
+67
-1
@@ -88,11 +88,66 @@ func IsRemoteNotExistError(err error) bool {
|
||||
return strings.HasPrefix(err.Error(), prefix1) || strings.HasPrefix(err.Error(), prefix2)
|
||||
}
|
||||
|
||||
// normalizeSSHURL converts SSH-SCP format URLs to standard ssh:// format for security
|
||||
func normalizeSSHURL(remoteAddr string) (string, error) {
|
||||
if strings.Contains(remoteAddr, "://") {
|
||||
return remoteAddr, fmt.Errorf("remoteAddr has a scheme")
|
||||
}
|
||||
if strings.Contains(remoteAddr, "\\") {
|
||||
return remoteAddr, fmt.Errorf("remoteAddr has Windows path slashes")
|
||||
}
|
||||
if strings.Contains(remoteAddr, ":/") {
|
||||
return remoteAddr, fmt.Errorf("remoteAddr could be Windows drive with forward slash")
|
||||
}
|
||||
if remoteAddr != "" && (remoteAddr[0] == '/' || remoteAddr[0] == '\\') {
|
||||
return remoteAddr, fmt.Errorf("remoteAddr is a local file path")
|
||||
}
|
||||
|
||||
// Parse SSH-SCP format: [user@]host:path
|
||||
colonIndex := strings.Index(remoteAddr, ":")
|
||||
if colonIndex == -1 {
|
||||
return remoteAddr, fmt.Errorf("remoteAddr has no colon")
|
||||
}
|
||||
|
||||
if colonIndex == 1 && len(remoteAddr) > 2 {
|
||||
return remoteAddr, fmt.Errorf("remoteAddr could be Windows drive letter check (C:, D:, etc.)")
|
||||
}
|
||||
|
||||
hostPart := remoteAddr[:colonIndex]
|
||||
pathPart := remoteAddr[colonIndex+1:]
|
||||
|
||||
if hostPart == "" || pathPart == "" {
|
||||
return remoteAddr, fmt.Errorf("remoteAddr has empty host or path")
|
||||
}
|
||||
|
||||
var user, host string
|
||||
if atIndex := strings.LastIndex(hostPart, "@"); atIndex != -1 {
|
||||
user = hostPart[:atIndex+1] // Include the @
|
||||
host = hostPart[atIndex+1:]
|
||||
} else {
|
||||
user = "git@"
|
||||
host = hostPart
|
||||
}
|
||||
|
||||
if host == "" {
|
||||
return remoteAddr, fmt.Errorf("Must have SSH host")
|
||||
}
|
||||
|
||||
return fmt.Sprintf("ssh://%s%s/%s", user, host, pathPart), nil
|
||||
}
|
||||
|
||||
// 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) {
|
||||
remoteAddr = strings.TrimSpace(remoteAddr)
|
||||
// Remote address can be HTTP/HTTPS/Git URL or local path.
|
||||
|
||||
// First, try to normalize SSH-SCP format URLs to ssh:// format for security
|
||||
normalizedAddr, err := normalizeSSHURL(remoteAddr)
|
||||
if err == nil {
|
||||
remoteAddr = normalizedAddr
|
||||
}
|
||||
|
||||
// Remote address can be HTTP/HTTPS/Git URL or SSH URL or local path.
|
||||
if strings.HasPrefix(remoteAddr, "http://") ||
|
||||
strings.HasPrefix(remoteAddr, "https://") ||
|
||||
strings.HasPrefix(remoteAddr, "git://") {
|
||||
@@ -104,6 +159,17 @@ func ParseRemoteAddr(remoteAddr, authUsername, authPassword string) (string, err
|
||||
u.User = url.UserPassword(authUsername, authPassword)
|
||||
}
|
||||
remoteAddr = u.String()
|
||||
} else if strings.HasPrefix(remoteAddr, "ssh://") {
|
||||
// Handle ssh:// URLs (including normalized ones)
|
||||
u, err := url.Parse(remoteAddr)
|
||||
if err != nil {
|
||||
return "", &ErrInvalidCloneAddr{IsURLError: true, Host: remoteAddr}
|
||||
}
|
||||
if len(authUsername)+len(authPassword) > 0 {
|
||||
// SSH URLs don't support username/password auth, only key-based auth
|
||||
return "", &ErrInvalidCloneAddr{IsURLError: true, Host: remoteAddr}
|
||||
}
|
||||
remoteAddr = u.String()
|
||||
}
|
||||
|
||||
return remoteAddr, nil
|
||||
|
||||
Reference in New Issue
Block a user