From d5ab18eba7c53a4fc5d7cb2bb9feb3d8c241c188 Mon Sep 17 00:00:00 2001 From: Norwin Roosen Date: Mon, 7 Dec 2020 15:40:25 +0100 Subject: [PATCH 1/8] checkout: use configured protocol for PR checkout instead of defaulting to ssh if that is enabled this might fix #262 --- modules/task/pull_checkout.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/modules/task/pull_checkout.go b/modules/task/pull_checkout.go index eba917b..b4d0864 100644 --- a/modules/task/pull_checkout.go +++ b/modules/task/pull_checkout.go @@ -54,9 +54,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 +63,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 +72,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 -- 2.40.1 From 8b2b5c991bd92ebc422de8177392ebcd64644cf1 Mon Sep 17 00:00:00 2001 From: Norwin Roosen Date: Mon, 7 Dec 2020 18:45:06 +0100 Subject: [PATCH 2/8] login add: try to find a matching ssh key & store it in config possibly expensive operation should be done once --- modules/config/login.go | 74 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/modules/config/login.go b/modules/config/login.go index e4488a9..27bb655 100644 --- a/modules/config/login.go +++ b/modules/config/login.go @@ -6,17 +6,21 @@ package config import ( "crypto/tls" + "encoding/base64" "errors" "fmt" + "io/ioutil" "log" "net/http" "net/http/cookiejar" "net/url" "os" + "path/filepath" "strings" "time" "code.gitea.io/tea/modules/utils" + "golang.org/x/crypto/ssh" "code.gitea.io/sdk/gitea" ) @@ -97,6 +101,68 @@ func (l *Login) GenerateToken(user, pass string) (string, error) { return t.Token, err } +// 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 +} + // GetDefaultLogin return the default login func GetDefaultLogin() (*Login, error) { if len(Config.Logins) == 0 { @@ -194,6 +260,14 @@ 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() + fmt.Println(login.SSHKey) + 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) -- 2.40.1 From bd964cc08b6d9b586c73be5f9577fc0d379caf6f Mon Sep 17 00:00:00 2001 From: Norwin Roosen Date: Mon, 7 Dec 2020 18:46:23 +0100 Subject: [PATCH 3/8] 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. --- modules/task/pull_checkout.go | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/modules/task/pull_checkout.go b/modules/task/pull_checkout.go index b4d0864..888c35d 100644 --- a/modules/task/pull_checkout.go +++ b/modules/task/pull_checkout.go @@ -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 } -- 2.40.1 From 455269c1427b2136639dda125c9e8bcd11f2f08f Mon Sep 17 00:00:00 2001 From: Norwin Roosen Date: Mon, 7 Dec 2020 18:56:04 +0100 Subject: [PATCH 4/8] fix import order --- modules/config/login.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/config/login.go b/modules/config/login.go index 27bb655..756984d 100644 --- a/modules/config/login.go +++ b/modules/config/login.go @@ -20,9 +20,9 @@ import ( "time" "code.gitea.io/tea/modules/utils" - "golang.org/x/crypto/ssh" "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 -- 2.40.1 From 7c55588ea6410ecccebf8d8d4fab1037a4dde921 Mon Sep 17 00:00:00 2001 From: Norwin Roosen Date: Tue, 8 Dec 2020 01:17:39 +0100 Subject: [PATCH 5/8] remove debug print statement --- modules/config/login.go | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/config/login.go b/modules/config/login.go index 756984d..7922c37 100644 --- a/modules/config/login.go +++ b/modules/config/login.go @@ -262,7 +262,6 @@ func AddLogin(name, token, user, passwd, sshKey, giteaURL string, insecure bool) if len(sshKey) == 0 { login.SSHKey, err = login.FindSSHKey() - fmt.Println(login.SSHKey) if err != nil { fmt.Printf("Warning: problem while finding a SSH key: %s\n", err) } -- 2.40.1 From 7d94a9545c122be628b31e77d27dca7924379c18 Mon Sep 17 00:00:00 2001 From: Norwin Roosen Date: Wed, 9 Dec 2020 15:27:28 +0100 Subject: [PATCH 6/8] improve ssh-key value docs --- cmd/login/add.go | 2 +- modules/interact/login.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/login/add.go b/cmd/login/add.go index ca53c9c..9beed16 100644 --- a/cmd/login/add.go +++ b/cmd/login/add.go @@ -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", diff --git a/modules/interact/login.go b/modules/interact/login.go index 27d6b26..2ada533 100644 --- a/modules/interact/login.go +++ b/modules/interact/login.go @@ -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 } -- 2.40.1 From a5d289ac27e414ea7f8f59bdf282c6e6205487cd Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Fri, 11 Dec 2020 11:39:23 +0100 Subject: [PATCH 7/8] fix merge-conflict relicts --- modules/config/login.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/modules/config/login.go b/modules/config/login.go index 5df2654..4ed47bc 100644 --- a/modules/config/login.go +++ b/modules/config/login.go @@ -14,10 +14,8 @@ import ( "net/http" "net/http/cookiejar" "net/url" - "os" "path/filepath" "strings" - "time" "code.gitea.io/tea/modules/utils" -- 2.40.1 From 8029fbe8dd17c52a4fdb08032dd451d70f62bbdf Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Fri, 11 Dec 2020 14:35:25 +0100 Subject: [PATCH 8/8] rm named return & fix pwCallback nil check --- modules/git/auth.go | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/modules/git/auth.go b/modules/git/auth.go index 7b78bad..2334ab7 100644 --- a/modules/git/auth.go +++ b/modules/git/auth.go @@ -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 -- 2.40.1