Improve ssh handling (#277)
checkout: use configured protocol for PR checkout instead of defaulting to ssh if that is enabled this might fix #262 login add: try to find a matching ssh key & store it in config possibly expensive operation should be done once pr checkout: don't fetch ssh keys As a result, we don't try to pull via ssh, if no privkey was configured. This increases chances of a using ssh only on a working ssh setup. fix import order remove debug print statement improve ssh-key value docs rm named return & fix pwCallback nil check Co-authored-by: Norwin Roosen <git@nroo.de> Co-authored-by: 6543 <6543@obermui.de> Reviewed-on: https://gitea.com/gitea/tea/pulls/277 Reviewed-by: khmarbaise <khmarbaise@noreply.gitea.io> Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com> Reviewed-by: 6543 <6543@obermui.de> Co-Authored-By: Norwin <noerw@noreply.gitea.io> Co-Committed-By: Norwin <noerw@noreply.gitea.io>
This commit is contained in:
parent
7e191eb18b
commit
0f38da068c
|
@ -52,7 +52,7 @@ var CmdLoginAdd = cli.Command{
|
|||
&cli.StringFlag{
|
||||
Name: "ssh-key",
|
||||
Aliases: []string{"s"},
|
||||
Usage: "Path to a SSH key to use for pull/push operations",
|
||||
Usage: "Path to a SSH key to use, overrides auto-discovery",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "insecure",
|
||||
|
|
|
@ -6,16 +6,21 @@ package config
|
|||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/cookiejar"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/tea/modules/utils"
|
||||
|
||||
"code.gitea.io/sdk/gitea"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
// Login represents a login to a gitea server, you even could add multiple logins for one gitea server
|
||||
|
@ -133,3 +138,65 @@ func (l *Login) GetSSHHost() string {
|
|||
|
||||
return u.Hostname()
|
||||
}
|
||||
|
||||
// FindSSHKey retrieves the ssh keys registered in gitea, and tries to find
|
||||
// a matching private key in ~/.ssh/. If no match is found, path is empty.
|
||||
func (l *Login) FindSSHKey() (string, error) {
|
||||
// get keys registered on gitea instance
|
||||
keys, _, err := l.Client().ListMyPublicKeys(gitea.ListPublicKeysOptions{})
|
||||
if err != nil || len(keys) == 0 {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// enumerate ~/.ssh/*.pub files
|
||||
glob, err := utils.AbsPathWithExpansion("~/.ssh/*.pub")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
localPubkeyPaths, err := filepath.Glob(glob)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// parse each local key with present privkey & compare fingerprints to online keys
|
||||
for _, pubkeyPath := range localPubkeyPaths {
|
||||
var pubkeyFile []byte
|
||||
pubkeyFile, err = ioutil.ReadFile(pubkeyPath)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
fields := strings.Split(string(pubkeyFile), " ")
|
||||
if len(fields) < 2 { // first word is key type, second word is key material
|
||||
continue
|
||||
}
|
||||
|
||||
var keymaterial []byte
|
||||
keymaterial, err = base64.StdEncoding.DecodeString(fields[1])
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
var pubkey ssh.PublicKey
|
||||
pubkey, err = ssh.ParsePublicKey(keymaterial)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
privkeyPath := strings.TrimSuffix(pubkeyPath, ".pub")
|
||||
var exists bool
|
||||
exists, err = utils.FileExist(privkeyPath)
|
||||
if err != nil || !exists {
|
||||
continue
|
||||
}
|
||||
|
||||
// if pubkey fingerprints match, return path to corresponding privkey.
|
||||
fingerprint := ssh.FingerprintSHA256(pubkey)
|
||||
for _, key := range keys {
|
||||
if fingerprint == key.Fingerprint {
|
||||
return privkeyPath, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "", err
|
||||
}
|
||||
|
|
|
@ -89,6 +89,13 @@ func AddLogin(name, token, user, passwd, sshKey, giteaURL string, insecure bool)
|
|||
// so we just use the hostname
|
||||
login.SSHHost = serverURL.Hostname()
|
||||
|
||||
if len(sshKey) == 0 {
|
||||
login.SSHKey, err = login.FindSSHKey()
|
||||
if err != nil {
|
||||
fmt.Printf("Warning: problem while finding a SSH key: %s\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
// save login to global var
|
||||
Config.Logins = append(Config.Logins, login)
|
||||
|
||||
|
|
|
@ -22,29 +22,26 @@ type pwCallback = func(string) (string, error)
|
|||
// GetAuthForURL returns the appropriate AuthMethod to be used in Push() / Pull()
|
||||
// operations depending on the protocol, and prompts the user for credentials if
|
||||
// necessary.
|
||||
func GetAuthForURL(remoteURL *url.URL, authToken, keyFile string, passwordCallback pwCallback) (auth git_transport.AuthMethod, err error) {
|
||||
func GetAuthForURL(remoteURL *url.URL, authToken, keyFile string, passwordCallback pwCallback) (git_transport.AuthMethod, error) {
|
||||
switch remoteURL.Scheme {
|
||||
case "http", "https":
|
||||
// gitea supports push/pull via app token as username.
|
||||
auth = &gogit_http.BasicAuth{Password: "", Username: authToken}
|
||||
return &gogit_http.BasicAuth{Password: "", Username: authToken}, nil
|
||||
|
||||
case "ssh":
|
||||
// try to select right key via ssh-agent. if it fails, try to read a key manually
|
||||
user := remoteURL.User.Username()
|
||||
auth, err = gogit_ssh.DefaultAuthBuilder(user)
|
||||
if err != nil && passwordCallback != nil {
|
||||
auth, err := gogit_ssh.DefaultAuthBuilder(user)
|
||||
if err != nil {
|
||||
signer, err2 := readSSHPrivKey(keyFile, passwordCallback)
|
||||
if err2 != nil {
|
||||
return nil, err2
|
||||
}
|
||||
auth = &gogit_ssh.PublicKeys{User: user, Signer: signer}
|
||||
}
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("don't know how to handle url scheme %v", remoteURL.Scheme)
|
||||
return auth, nil
|
||||
}
|
||||
|
||||
return
|
||||
return nil, fmt.Errorf("don't know how to handle url scheme %v", remoteURL.Scheme)
|
||||
}
|
||||
|
||||
func readSSHPrivKey(keyFile string, passwordCallback pwCallback) (sig ssh.Signer, err error) {
|
||||
|
@ -61,7 +58,7 @@ func readSSHPrivKey(keyFile string, passwordCallback pwCallback) (sig ssh.Signer
|
|||
return nil, err
|
||||
}
|
||||
sig, err = ssh.ParsePrivateKey(sshKey)
|
||||
if _, ok := err.(*ssh.PassphraseMissingError); ok {
|
||||
if _, ok := err.(*ssh.PassphraseMissingError); ok && passwordCallback != nil {
|
||||
// allow for up to 3 password attempts
|
||||
for i := 0; i < 3; i++ {
|
||||
var pass string
|
||||
|
|
|
@ -73,7 +73,7 @@ func CreateLogin() error {
|
|||
return err
|
||||
}
|
||||
if optSettings {
|
||||
promptI = &survey.Input{Message: "SSH Key Path: "}
|
||||
promptI = &survey.Input{Message: "SSH Key Path (leave empty for auto-discovery):"}
|
||||
if err := survey.AskOne(promptI, &sshKey); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ package task
|
|||
import (
|
||||
"fmt"
|
||||
|
||||
"code.gitea.io/sdk/gitea"
|
||||
"code.gitea.io/tea/modules/config"
|
||||
local_git "code.gitea.io/tea/modules/git"
|
||||
|
||||
|
@ -29,13 +28,11 @@ func PullCheckout(login *config.Login, repoOwner, repoName string, index int64,
|
|||
return err
|
||||
}
|
||||
|
||||
// test if we can pull via SSH, and configure git remote accordingly
|
||||
remoteURL := pr.Head.Repository.CloneURL
|
||||
keys, _, err := client.ListMyPublicKeys(gitea.ListPublicKeysOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(keys) != 0 {
|
||||
if len(login.SSHKey) != 0 {
|
||||
// login.SSHKey is nonempty, if user specified a key manually or we automatically
|
||||
// found a matching private key on this machine during login creation.
|
||||
// this means, we are very likely to have a working ssh setup.
|
||||
remoteURL = pr.Head.Repository.SSHURL
|
||||
}
|
||||
|
||||
|
@ -54,9 +51,8 @@ func PullCheckout(login *config.Login, repoOwner, repoName string, index int64,
|
|||
}
|
||||
localRemoteName := localRemote.Config().Name
|
||||
|
||||
// get auth & fetch remote
|
||||
fmt.Printf("Fetching PR %v (head %s:%s) from remote '%s'\n", index, remoteURL, pr.Head.Ref, localRemoteName)
|
||||
url, err := local_git.ParseURL(remoteURL)
|
||||
// get auth & fetch remote via its configured protocol
|
||||
url, err := localRepo.TeaRemoteURL(localRemoteName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -64,6 +60,7 @@ func PullCheckout(login *config.Login, repoOwner, repoName string, index int64,
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("Fetching PR %v (head %s:%s) from remote '%s'\n", index, url, pr.Head.Ref, localRemoteName)
|
||||
err = localRemote.Fetch(&git.FetchOptions{Auth: auth})
|
||||
if err == git.NoErrAlreadyUpToDate {
|
||||
fmt.Println(err)
|
||||
|
@ -72,9 +69,10 @@ func PullCheckout(login *config.Login, repoOwner, repoName string, index int64,
|
|||
}
|
||||
|
||||
// checkout local branch
|
||||
fmt.Printf("Creating branch '%s'\n", localBranchName)
|
||||
err = localRepo.TeaCreateBranch(localBranchName, pr.Head.Ref, localRemoteName)
|
||||
if err == git.ErrBranchExists {
|
||||
if err == nil {
|
||||
fmt.Printf("Created branch '%s'\n", localBranchName)
|
||||
} else if err == git.ErrBranchExists {
|
||||
fmt.Println("There may be changes since you last checked out, run `git pull` to get them.")
|
||||
} else if err != nil {
|
||||
return err
|
||||
|
|
Loading…
Reference in New Issue