diff options
author | Michael Muré <batolettre@gmail.com> | 2020-07-27 00:14:01 +0200 |
---|---|---|
committer | Michael Muré <batolettre@gmail.com> | 2020-09-29 20:42:21 +0200 |
commit | b127481364176ac7ecb56c1604e1460251574859 (patch) | |
tree | 389e4f4596183c009ed53a078ad93bc72d6c4564 /repository | |
parent | d171e11028f5993137a5f83beb7fe002bed866f5 (diff) | |
download | git-bug-b127481364176ac7ecb56c1604e1460251574859.tar.gz |
repository: add access to the system keyring, with fallback on a file
Diffstat (limited to 'repository')
-rw-r--r-- | repository/config.go | 6 | ||||
-rw-r--r-- | repository/git.go | 16 | ||||
-rw-r--r-- | repository/gogit.go | 18 | ||||
-rw-r--r-- | repository/keyring.go | 73 | ||||
-rw-r--r-- | repository/mock_repo.go | 9 | ||||
-rw-r--r-- | repository/repo.go | 11 |
6 files changed, 126 insertions, 7 deletions
diff --git a/repository/config.go b/repository/config.go index 4fa5c69b..2133b169 100644 --- a/repository/config.go +++ b/repository/config.go @@ -1,10 +1,16 @@ package repository import ( + "errors" "strconv" "time" ) +var ( + ErrNoConfigEntry = errors.New("no config entry for the given key") + ErrMultipleConfigEntry = errors.New("multiple config entry for the given key") +) + // Config represent the common function interacting with the repository config storage type Config interface { // Store writes a single key/value pair in the config diff --git a/repository/git.go b/repository/git.go index 3d756324..85107ba5 100644 --- a/repository/git.go +++ b/repository/git.go @@ -26,6 +26,8 @@ type GitRepo struct { clocksMutex sync.Mutex clocks map[string]lamport.Clock + + keyring Keyring } // LocalConfig give access to the repository scoped configuration @@ -38,6 +40,10 @@ func (repo *GitRepo) GlobalConfig() Config { return newGitConfig(repo, true) } +func (repo *GitRepo) Keyring() Keyring { + return repo.keyring +} + // Run the given git command with the given I/O reader/writers, returning an error if it fails. func (repo *GitRepo) runGitCommandWithIO(stdin io.Reader, stdout, stderr io.Writer, args ...string) error { // make sure that the working directory for the command @@ -83,9 +89,15 @@ func (repo *GitRepo) runGitCommand(args ...string) (string, error) { // NewGitRepo determines if the given working directory is inside of a git repository, // and returns the corresponding GitRepo instance if it is. func NewGitRepo(path string, clockLoaders []ClockLoader) (*GitRepo, error) { + k, err := defaultKeyring() + if err != nil { + return nil, err + } + repo := &GitRepo{ - path: path, - clocks: make(map[string]lamport.Clock), + path: path, + clocks: make(map[string]lamport.Clock), + keyring: k, } // Check the repo and retrieve the root path diff --git a/repository/gogit.go b/repository/gogit.go index 71a7e6d0..f115fab5 100644 --- a/repository/gogit.go +++ b/repository/gogit.go @@ -24,6 +24,8 @@ type GoGitRepo struct { clocksMutex sync.Mutex clocks map[string]lamport.Clock + + keyring Keyring } func NewGoGitRepo(path string, clockLoaders []ClockLoader) (*GoGitRepo, error) { @@ -37,10 +39,16 @@ func NewGoGitRepo(path string, clockLoaders []ClockLoader) (*GoGitRepo, error) { return nil, err } + k, err := defaultKeyring() + if err != nil { + return nil, err + } + repo := &GoGitRepo{ - r: r, - path: path, - clocks: make(map[string]lamport.Clock), + r: r, + path: path, + clocks: make(map[string]lamport.Clock), + keyring: k, } for _, loader := range clockLoaders { @@ -154,6 +162,10 @@ func (repo *GoGitRepo) GlobalConfig() Config { panic("go-git doesn't support writing global config") } +func (repo *GoGitRepo) Keyring() Keyring { + return repo.keyring +} + // GetPath returns the path to the repo. func (repo *GoGitRepo) GetPath() string { return repo.path diff --git a/repository/keyring.go b/repository/keyring.go new file mode 100644 index 00000000..9f8171db --- /dev/null +++ b/repository/keyring.go @@ -0,0 +1,73 @@ +package repository + +import ( + "os" + "path" + + "github.com/99designs/keyring" +) + +type Item = keyring.Item + +var ErrKeyringKeyNotFound = keyring.ErrKeyNotFound + +// Keyring provides the uniform interface over the underlying backends +type Keyring interface { + // Returns an Item matching the key or ErrKeyringKeyNotFound + Get(key string) (Item, error) + // Stores an Item on the keyring + Set(item Item) error + // Removes the item with matching key + Remove(key string) error + // Provides a slice of all keys stored on the keyring + Keys() ([]string, error) +} + +func defaultKeyring() (Keyring, error) { + ucd, err := os.UserConfigDir() + if err != nil { + return nil, err + } + + backends := []keyring.BackendType{ + keyring.WinCredBackend, + keyring.KeychainBackend, + keyring.PassBackend, + keyring.FileBackend, + } + + return keyring.Open(keyring.Config{ + // TODO: ideally this would not be there, it disable the freedesktop backend on linux + // due to https://github.com/99designs/keyring/issues/44 + AllowedBackends: backends, + + ServiceName: "git-bug", + + // MacOS keychain + KeychainName: "git-bug", + KeychainTrustApplication: true, + + // KDE Wallet + KWalletAppID: "git-bug", + KWalletFolder: "git-bug", + + // Windows + WinCredPrefix: "git-bug", + + // freedesktop.org's Secret Service + LibSecretCollectionName: "git-bug", + + // Pass (https://www.passwordstore.org/) + PassPrefix: "git-bug", + + // Fallback encrypted file + FileDir: path.Join(ucd, "git-bug", "keyring"), + // As we write the file in the user's config directory, this file should already be protected by the OS against + // other user's access. We actually don't terribly need to protect it further and a password prompt across all + // UI's would be a pain. Therefore we use here a constant password so the file will be unreadable by generic file + // scanners if the user's machine get compromised. + FilePasswordFunc: func(string) (string, error) { + return "git-bug", nil + }, + }) +} diff --git a/repository/mock_repo.go b/repository/mock_repo.go index 576e984e..b3b4cb41 100644 --- a/repository/mock_repo.go +++ b/repository/mock_repo.go @@ -5,6 +5,8 @@ import ( "fmt" "strings" + "github.com/99designs/keyring" + "github.com/MichaelMure/git-bug/util/lamport" ) @@ -15,6 +17,7 @@ var _ TestedRepo = &mockRepoForTest{} type mockRepoForTest struct { config *MemConfig globalConfig *MemConfig + keyring *keyring.ArrayKeyring blobs map[Hash][]byte trees map[Hash]string commits map[Hash]commit @@ -31,6 +34,7 @@ func NewMockRepoForTest() *mockRepoForTest { return &mockRepoForTest{ config: NewMemConfig(), globalConfig: NewMemConfig(), + keyring: keyring.NewArrayKeyring(nil), blobs: make(map[Hash][]byte), trees: make(map[Hash]string), commits: make(map[Hash]commit), @@ -49,6 +53,11 @@ func (r *mockRepoForTest) GlobalConfig() Config { return r.globalConfig } +// Keyring give access to a user-wide storage for secrets +func (r *mockRepoForTest) Keyring() Keyring { + return r.keyring +} + // GetPath returns the path to the repo. func (r *mockRepoForTest) GetPath() string { return "~/mockRepo/" diff --git a/repository/repo.go b/repository/repo.go index 30d95806..696b032e 100644 --- a/repository/repo.go +++ b/repository/repo.go @@ -10,8 +10,6 @@ import ( ) var ( - ErrNoConfigEntry = errors.New("no config entry for the given key") - ErrMultipleConfigEntry = errors.New("multiple config entry for the given key") // ErrNotARepo is the error returned when the git repo root wan't be found ErrNotARepo = errors.New("not a git repository") // ErrClockNotExist is the error returned when a clock can't be found @@ -24,9 +22,17 @@ type RepoConfig interface { LocalConfig() Config // GlobalConfig give access to the git global configuration + // Deprecated: to remove in favor of Keyring() + // TODO: remove GlobalConfig() Config } +// RepoKeyring give access to a user-wide storage for secrets +type RepoKeyring interface { + // Keyring give access to a user-wide storage for secrets + Keyring() Keyring +} + // RepoCommon represent the common function the we want all the repo to implement type RepoCommon interface { // GetPath returns the path to the repo. @@ -48,6 +54,7 @@ type RepoCommon interface { // Repo represents a source code repository. type Repo interface { RepoConfig + RepoKeyring RepoCommon // FetchRefs fetch git refs from a remote |