Skip to content

Commit a59d4e7

Browse files
committed
add secrets package to manage secrets
- Add secret store that supports different backends - We use a registry map for a few secrets and the registry gets persisted as one secret to the keyring. We don't waant to create a keyring secret for every different secret - Store is opened once to load the registry.
1 parent 5c8a6d6 commit a59d4e7

File tree

2 files changed

+117
-14
lines changed

2 files changed

+117
-14
lines changed
Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
// Package keyring provides secure credential storage using the system keychain.
2-
package keyring
1+
package secrets
32

43
import (
54
"github.com/99designs/keyring"
@@ -8,13 +7,13 @@ import (
87

98
const serviceName = "sourcegraph-cli"
109

11-
// Store provides secure credential storage operations.
12-
type Store struct {
10+
// keyringStore provides secure credential storage operations.
11+
type keyringStore struct {
1312
ring keyring.Keyring
1413
}
1514

16-
// Open opens the system keyring for the Sourcegraph CLI.
17-
func Open() (*Store, error) {
15+
// open opens the system keyring for the Sourcegraph CLI.
16+
func openKeyring() (*keyringStore, error) {
1817
ring, err := keyring.Open(keyring.Config{
1918
ServiceName: serviceName,
2019
KeychainName: "login", // This is the default name for the keychain where MacOS puts all login passwords
@@ -23,12 +22,12 @@ func Open() (*Store, error) {
2322
if err != nil {
2423
return nil, errors.Wrap(err, "opening keyring")
2524
}
26-
return &Store{ring: ring}, nil
25+
return &keyringStore{ring: ring}, nil
2726
}
2827

2928
// Set stores a key-value pair in the keyring.
30-
func (s *Store) Set(key string, data []byte) error {
31-
err := s.ring.Set(keyring.Item{
29+
func (k *keyringStore) Put(key string, data []byte) error {
30+
err := k.ring.Set(keyring.Item{
3231
Key: key,
3332
Data: data,
3433
Label: key,
@@ -41,20 +40,20 @@ func (s *Store) Set(key string, data []byte) error {
4140

4241
// Get retrieves a value by key from the keyring.
4342
// Returns nil, nil if the key is not found.
44-
func (s *Store) Get(key string) ([]byte, error) {
45-
item, err := s.ring.Get(key)
43+
func (k *keyringStore) Get(key string) ([]byte, error) {
44+
item, err := k.ring.Get(key)
4645
if err != nil {
4746
if err == keyring.ErrKeyNotFound {
48-
return nil, nil
47+
return nil, ErrSecretNotFound
4948
}
5049
return nil, errors.Wrap(err, "getting item from keyring")
5150
}
5251
return item.Data, nil
5352
}
5453

5554
// Delete removes a key from the keyring.
56-
func (s *Store) Delete(key string) error {
57-
err := s.ring.Remove(key)
55+
func (k *keyringStore) Delete(key string) error {
56+
err := k.ring.Remove(key)
5857
if err != nil && err != keyring.ErrKeyNotFound {
5958
return errors.Wrap(err, "removing item from keyring")
6059
}

internal/secrets/store.go

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
package secrets
2+
3+
import (
4+
"encoding/json"
5+
"sync"
6+
7+
"github.com/sourcegraph/sourcegraph/lib/errors"
8+
)
9+
10+
const keyRegistry = "secret-registry"
11+
12+
var ErrSecretNotFound = errors.New("secret not found")
13+
14+
var openOnce = sync.OnceValues(Open)
15+
16+
type SecretStorage interface {
17+
Get(key string) ([]byte, error)
18+
Put(key string, data []byte) error
19+
Delete(key string) error
20+
}
21+
22+
type store struct {
23+
backend SecretStorage
24+
registry map[string][]byte
25+
26+
mu sync.Mutex
27+
}
28+
29+
func Store() (SecretStorage, error) {
30+
return openOnce()
31+
}
32+
33+
func Open() (SecretStorage, error) {
34+
keyring, err := openKeyring()
35+
if err != nil {
36+
return nil, err
37+
}
38+
39+
registry, err := getRegistry(keyring)
40+
if err != nil {
41+
return nil, err
42+
}
43+
s := &store{
44+
backend: keyring,
45+
registry: registry,
46+
}
47+
48+
return s, nil
49+
}
50+
51+
func getRegistry(s SecretStorage) (map[string][]byte, error) {
52+
data, err := s.Get(keyRegistry)
53+
if err != nil {
54+
return nil, errors.Wrap(err, "failed to load registry from backing store")
55+
}
56+
57+
var registry map[string][]byte
58+
if err := json.Unmarshal(data, &registry); err != nil {
59+
return nil, errors.Wrap(err, "failed to decode registry from backing store")
60+
}
61+
62+
return registry, nil
63+
}
64+
65+
func saveRegistry(s SecretStorage, registry map[string][]byte) error {
66+
data, err := json.Marshal(&registry)
67+
if err != nil {
68+
return errors.Wrap(err, "registry encoding failure")
69+
}
70+
71+
if err = s.Put(keyRegistry, data); err != nil {
72+
return errors.Wrap(err, "failed to persist registry to backing store")
73+
}
74+
75+
return nil
76+
}
77+
78+
func (s *store) Get(key string) ([]byte, error) {
79+
s.mu.Lock()
80+
defer s.mu.Unlock()
81+
v, ok := s.registry[key]
82+
if !ok {
83+
return nil, ErrSecretNotFound
84+
}
85+
86+
return v, nil
87+
}
88+
89+
func (s *store) Put(key string, data []byte) error {
90+
s.mu.Lock()
91+
defer s.mu.Unlock()
92+
93+
s.registry[key] = data
94+
95+
return saveRegistry(s.backend, s.registry)
96+
}
97+
98+
func (s *store) Delete(key string) error {
99+
s.mu.Lock()
100+
defer s.mu.Unlock()
101+
102+
delete(s.registry, key)
103+
return saveRegistry(s.backend, s.registry)
104+
}

0 commit comments

Comments
 (0)