90 lines
2 KiB
Go
90 lines
2 KiB
Go
package secretstore
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"runtime"
|
|
"strings"
|
|
|
|
"github.com/99designs/keyring"
|
|
)
|
|
|
|
var ErrNotFound = errors.New("secret not found")
|
|
|
|
type Options struct {
|
|
ServiceName string
|
|
}
|
|
|
|
type Store interface {
|
|
SetSecret(name, label, secret string) error
|
|
GetSecret(name string) (string, error)
|
|
DeleteSecret(name string) error
|
|
}
|
|
|
|
type keyringStore struct {
|
|
ring keyring.Keyring
|
|
serviceName string
|
|
}
|
|
|
|
func Open(options Options) (Store, error) {
|
|
serviceName := strings.TrimSpace(options.ServiceName)
|
|
if serviceName == "" {
|
|
return nil, errors.New("service name must not be empty")
|
|
}
|
|
|
|
ring, err := keyring.Open(keyring.Config{
|
|
ServiceName: serviceName,
|
|
})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("open OS wallet backend %q for service %q: %w", BackendName(), serviceName, err)
|
|
}
|
|
|
|
return &keyringStore{
|
|
ring: ring,
|
|
serviceName: serviceName,
|
|
}, nil
|
|
}
|
|
|
|
func BackendName() string {
|
|
switch runtime.GOOS {
|
|
case "darwin":
|
|
return "macOS Keychain"
|
|
case "windows":
|
|
return "Windows Credential Manager"
|
|
case "linux":
|
|
return "Linux Secret Service or KWallet"
|
|
default:
|
|
return "system wallet"
|
|
}
|
|
}
|
|
|
|
func (s *keyringStore) SetSecret(name, label, secret string) error {
|
|
if err := s.ring.Set(keyring.Item{
|
|
Key: name,
|
|
Label: label,
|
|
Data: []byte(secret),
|
|
}); err != nil {
|
|
return fmt.Errorf("save secret %q in OS wallet for service %q: %w", name, s.serviceName, err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *keyringStore) GetSecret(name string) (string, error) {
|
|
item, err := s.ring.Get(name)
|
|
if err != nil {
|
|
if errors.Is(err, keyring.ErrKeyNotFound) {
|
|
return "", ErrNotFound
|
|
}
|
|
return "", fmt.Errorf("read secret %q from OS wallet for service %q: %w", name, s.serviceName, err)
|
|
}
|
|
|
|
return string(item.Data), nil
|
|
}
|
|
|
|
func (s *keyringStore) DeleteSecret(name string) error {
|
|
if err := s.ring.Remove(name); err != nil && !errors.Is(err, keyring.ErrKeyNotFound) {
|
|
return fmt.Errorf("delete secret %q from OS wallet for service %q: %w", name, s.serviceName, err)
|
|
}
|
|
return nil
|
|
}
|