@@ -8,13 +8,14 @@ import (
8
8
"errors"
9
9
"fmt"
10
10
"os"
11
+ "sync"
11
12
12
13
"github.com/adrg/xdg"
13
- "github.com/zalando/go-keyring"
14
14
"golang.org/x/term"
15
15
16
16
"github.com/stacklok/toolhive/pkg/logger"
17
17
"github.com/stacklok/toolhive/pkg/process"
18
+ "github.com/stacklok/toolhive/pkg/secrets/keyring"
18
19
)
19
20
20
21
const (
@@ -27,6 +28,18 @@ const (
27
28
keyringService = "toolhive"
28
29
)
29
30
31
+ var (
32
+ keyringProvider keyring.Provider
33
+ keyringOnce sync.Once
34
+ )
35
+
36
+ func getKeyringProvider () keyring.Provider {
37
+ keyringOnce .Do (func () {
38
+ keyringProvider = keyring .NewCompositeProvider ()
39
+ })
40
+ return keyringProvider
41
+ }
42
+
30
43
// ProviderType represents an enum of the types of available secrets providers.
31
44
type ProviderType string
32
45
@@ -157,20 +170,10 @@ var ErrKeyringNotAvailable = errors.New("OS keyring is not available. " +
157
170
"Please use a different secrets provider (e.g., 1password) " +
158
171
"or ensure your system has a keyring service available" )
159
172
160
- // IsKeyringAvailable tests if the OS keyring is available by attempting to set and delete a test value.
173
+ // IsKeyringAvailable tests if any keyring backend is available
161
174
func IsKeyringAvailable () bool {
162
- testKey := "toolhive-keyring-test"
163
- testValue := "test"
164
-
165
- // Try to set a test value
166
- if err := keyring .Set (keyringService , testKey , testValue ); err != nil {
167
- return false
168
- }
169
-
170
- // Clean up the test value
171
- _ = keyring .Delete (keyringService , testKey )
172
-
173
- return true
175
+ provider := getKeyringProvider ()
176
+ return provider .IsAvailable ()
174
177
}
175
178
176
179
// CreateSecretProvider creates the specified type of secrets provider.
@@ -214,14 +217,15 @@ func CreateSecretProviderWithPassword(managerType ProviderType, password string)
214
217
// If optionalPassword is provided and keyring is not yet setup, it uses that password and stores it.
215
218
// Otherwise, it uses the current functionality (read from keyring or stdin).
216
219
func GetSecretsPassword (optionalPassword string ) ([]byte , error ) {
217
- // Attempt to load the password from the OS keyring.
218
- keyringSecret , err := keyring .Get (keyringService , keyringService )
220
+ provider := getKeyringProvider ()
221
+
222
+ // Attempt to load the password from the keyring
223
+ keyringSecret , err := provider .Get (keyringService , keyringService )
219
224
if err == nil {
220
225
return []byte (keyringSecret ), nil
221
226
}
222
227
223
- // We need to determine if the error is due to a lack of keyring on the
224
- // system or if the keyring is available but nothing was stored.
228
+ // Handle key not found
225
229
if errors .Is (err , keyring .ErrNotFound ) {
226
230
var password []byte
227
231
@@ -233,9 +237,6 @@ func GetSecretsPassword(optionalPassword string) ([]byte, error) {
233
237
password = []byte (optionalPassword )
234
238
} else {
235
239
// Keyring is available but no password stored - this should only happen during setup
236
- // We cannot ask for a password in a detached process.
237
- // We should never trigger this, but this ensures that if there's a bug
238
- // then it's easier to find.
239
240
if process .IsDetached () {
240
241
return nil , fmt .Errorf ("detached process detected, cannot ask for password" )
241
242
}
@@ -248,10 +249,9 @@ func GetSecretsPassword(optionalPassword string) ([]byte, error) {
248
249
}
249
250
}
250
251
251
- // TODO GET function should not be saving anything into keyring
252
252
// Store the password in the keyring for future use
253
- logger .Info ("writing password to os keyring" )
254
- err = keyring .Set (keyringService , keyringService , string (password ))
253
+ logger .Info (fmt . Sprintf ( "writing password to %s" , provider . Name ()) )
254
+ err = provider .Set (keyringService , keyringService , string (password ))
255
255
if err != nil {
256
256
return nil , fmt .Errorf ("failed to store password in keyring: %w" , err )
257
257
}
@@ -260,7 +260,7 @@ func GetSecretsPassword(optionalPassword string) ([]byte, error) {
260
260
}
261
261
262
262
// Assume any other keyring error means keyring is not available
263
- return nil , fmt .Errorf ("OS keyring is not available: %w" , err )
263
+ return nil , fmt .Errorf ("keyring is not available: %w" , err )
264
264
}
265
265
266
266
func readPasswordStdin () ([]byte , error ) {
@@ -281,7 +281,8 @@ func readPasswordStdin() ([]byte, error) {
281
281
282
282
// ResetKeyringSecret clears out the secret from the keystore (if present).
283
283
func ResetKeyringSecret () error {
284
- return keyring .DeleteAll (keyringService )
284
+ provider := getKeyringProvider ()
285
+ return provider .DeleteAll (keyringService )
285
286
}
286
287
287
288
// GenerateSecurePassword generates a cryptographically secure random password
0 commit comments