aboutsummaryrefslogtreecommitdiffstats
path: root/repository
diff options
context:
space:
mode:
authorMichael Muré <batolettre@gmail.com>2020-07-27 00:14:01 +0200
committerMichael Muré <batolettre@gmail.com>2020-09-29 20:42:21 +0200
commitb127481364176ac7ecb56c1604e1460251574859 (patch)
tree389e4f4596183c009ed53a078ad93bc72d6c4564 /repository
parentd171e11028f5993137a5f83beb7fe002bed866f5 (diff)
downloadgit-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.go6
-rw-r--r--repository/git.go16
-rw-r--r--repository/gogit.go18
-rw-r--r--repository/keyring.go73
-rw-r--r--repository/mock_repo.go9
-rw-r--r--repository/repo.go11
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