Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ssh authenticate #1146

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
82 changes: 78 additions & 4 deletions repository/gogit.go
Expand Up @@ -9,6 +9,7 @@ import (
"io/ioutil"
"os"
"path/filepath"
"regexp"
"sort"
"strings"
"sync"
Expand All @@ -21,6 +22,7 @@ import (
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/filemode"
"github.com/go-git/go-git/v5/plumbing/object"
"github.com/go-git/go-git/v5/plumbing/transport/ssh"
"golang.org/x/sync/errgroup"
"golang.org/x/sys/execabs"

Expand Down Expand Up @@ -383,14 +385,31 @@ func (repo *GoGitRepo) FetchRefs(remote string, prefixes ...string) (string, err

buf := bytes.NewBuffer(nil)

err := repo.r.Fetch(&gogit.FetchOptions{
fetchOptions := &gogit.FetchOptions{
RemoteName: remote,
RefSpecs: refSpecs,
Progress: buf,
})
}

publicKeys, err := repo.SSHAuth(remote)
if err != nil {
return "", err
}

err = repo.r.Fetch(fetchOptions)
if err == gogit.NoErrAlreadyUpToDate {
return "already up-to-date", nil
}
// ssh-agent is required if ssh or scp-like url is configured in repository config
// we can not fetch if the ssh-agent has invalid keys or ssh-agent is not running
// retry to fetch again if we can retreive public keys from the users home directory
if err != nil && publicKeys != nil {
fetchOptions.Auth = publicKeys
err = repo.r.Fetch(fetchOptions)
if err == gogit.NoErrAlreadyUpToDate {
return "already up-to-date", nil
}
}
if err != nil {
return "", err
}
Expand Down Expand Up @@ -436,21 +455,76 @@ func (repo *GoGitRepo) PushRefs(remote string, prefixes ...string) (string, erro

buf := bytes.NewBuffer(nil)

err = remo.Push(&gogit.PushOptions{
pushOptions := &gogit.PushOptions{
RemoteName: remote,
RefSpecs: refSpecs,
Progress: buf,
})
}

publicKeys, err := repo.SSHAuth(remote)
if err != nil {
return "", err
}

err = repo.r.Push(pushOptions)
if err == gogit.NoErrAlreadyUpToDate {
return "already up-to-date", nil
}
// ssh-agent is required if ssh or scp-like url is configured in repository config
// we can not push if the ssh-agent has invalid keys or ssh-agent is not running
// retry to push again if we can retreive public keys from the users home directory
if err != nil && publicKeys != nil {
pushOptions.Auth = publicKeys
err = repo.r.Push(pushOptions)
if err == gogit.NoErrAlreadyUpToDate {
return "already up-to-date", nil
}
}
if err != nil {
return "", err
}

return buf.String(), nil
}

// SSHAuth will attempt to read public keys for SSH auth
// if the repository remote contains a ssh or scp-like url
func (repo *GoGitRepo) SSHAuth(remote string) (*ssh.PublicKeys, error) {
// get the repository config
config, err := repo.r.Config()
if err != nil {
return nil, err
}

// check if the repository config has at least one remote url
remotes, found := config.Remotes[remote]
if !found || len(remotes.URLs) < 1 {
return nil, fmt.Errorf("remote %s url not found in repository config", remote)
}

schemeRegexp := regexp.MustCompile(`^[^:]+://`)
scpLikeRegexp := regexp.MustCompile(`^(?:(?P<user>[^@]+)@)?(?P<host>)`)

// try to load public keys from the users home directory
// if the repository remote contains a ssh or scp-like url
if strings.HasPrefix(remotes.URLs[0], "ssh://") || (scpLikeRegexp.MatchString(remotes.URLs[0]) && !schemeRegexp.MatchString(remotes.URLs[0])) {
home, err := os.UserHomeDir()
if err != nil {
return nil, err
}

// try to find and load valid public keys from the users home directory
attemptKeys := []string{"id_rsa", "id_ecdsa", "id_ecdsa_sk", "id_ed25519", "id_ed25519_sk", "id_xmss", "id_dsa"}
for _, key := range attemptKeys {
authMethod, err := ssh.NewPublicKeysFromFile("git", filepath.Join(home, ".ssh", key), "")
if err == nil {
return authMethod, nil
}
}
}
return nil, nil
}

// StoreData will store arbitrary data and return the corresponding hash
func (repo *GoGitRepo) StoreData(data []byte) (Hash, error) {
obj := repo.r.Storer.NewEncodedObject()
Expand Down
46 changes: 46 additions & 0 deletions repository/gogit_test.go
Expand Up @@ -2,6 +2,7 @@ package repository

import (
"fmt"
"log"
"os"
"path"
"path/filepath"
Expand Down Expand Up @@ -96,3 +97,48 @@ func TestGoGit_DetectsSubmodules(t *testing.T) {
assert.Empty(t, err)
assert.Equal(t, expected, result)
}

func TestGoGitRepoSSH(t *testing.T) {
repo := CreateGoGitTestRepo(t, false)

err := repo.AddRemote("ssh", "ssh://git@github.com:MichaelMure/git-bug.git")
if err != nil {
log.Fatal(err)
}
keys, err := repo.SSHAuth("ssh")
require.NotNil(t, keys)
require.Empty(t, err)

err = repo.AddRemote("http", "http://github.com/MichaelMure/git-bug.git")
if err != nil {
log.Fatal(err)
}
keys, err = repo.SSHAuth("http")
require.Nil(t, keys)
require.Empty(t, err)

err = repo.AddRemote("https", "https://github.com/MichaelMure/git-bug.git")
if err != nil {
log.Fatal(err)
}
keys, err = repo.SSHAuth("https")
require.Nil(t, keys)
require.Empty(t, err)

err = repo.AddRemote("git", "git://github.com/MichaelMure/git-bug.git")
if err != nil {
log.Fatal(err)
}
keys, err = repo.SSHAuth("git")
require.Nil(t, keys)
require.Empty(t, err)

err = repo.AddRemote("scp-like", "git@github.com:MichaelMure/git-bug.git")
if err != nil {
log.Fatal(err)
}
keys, err = repo.SSHAuth("scp-like")
require.NotNil(t, keys)
require.Empty(t, err)

}
5 changes: 5 additions & 0 deletions repository/mock_repo.go
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/99designs/keyring"
"github.com/ProtonMail/go-crypto/openpgp"
"github.com/go-git/go-billy/v5/memfs"
"github.com/go-git/go-git/v5/plumbing/transport/ssh"

"github.com/MichaelMure/git-bug/util/lamport"
)
Expand Down Expand Up @@ -252,6 +253,10 @@ func (r *mockRepoData) PushRefs(remote string, prefixes ...string) (string, erro
panic("implement me")
}

func (r *mockRepoData) SSHAuth(remote string) (*ssh.PublicKeys, error) {
panic("implement me")
}

func (r *mockRepoData) StoreData(data []byte) (Hash, error) {
rawHash := sha1.Sum(data)
hash := Hash(fmt.Sprintf("%x", rawHash))
Expand Down
4 changes: 4 additions & 0 deletions repository/repo.go
Expand Up @@ -7,6 +7,7 @@ import (

"github.com/ProtonMail/go-crypto/openpgp"
"github.com/go-git/go-billy/v5"
"github.com/go-git/go-git/v5/plumbing/transport/ssh"

"github.com/MichaelMure/git-bug/util/lamport"
)
Expand Down Expand Up @@ -142,6 +143,9 @@ type RepoData interface {
// the remote state.
PushRefs(remote string, prefixes ...string) (string, error)

// SSHAuth will attempt to read public keys for SSH auth
SSHAuth(remote string) (*ssh.PublicKeys, error)

// StoreData will store arbitrary data and return the corresponding hash
StoreData(data []byte) (Hash, error)

Expand Down