Skip to content

Commit 11772b0

Browse files
lujunsandmjb
andauthored
Force OS keyring for encrypted provider (#741)
Enforces the use of OS keyring for encrypted provider (#741) Signed-off-by: lujunsan <[email protected]> Co-authored-by: Don Browne <[email protected]>
1 parent fcf408c commit 11772b0

File tree

1 file changed

+50
-25
lines changed

1 file changed

+50
-25
lines changed

pkg/secrets/factory.go

Lines changed: 50 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -143,10 +143,37 @@ func validateNoneProvider(result *SetupResult) *SetupResult {
143143
return result
144144
}
145145

146+
// ErrKeyringNotAvailable is returned when the OS keyring is not available for the encrypted provider.
147+
var ErrKeyringNotAvailable = errors.New("OS keyring is not available. " +
148+
"The encrypted provider requires an OS keyring to securely store passwords. " +
149+
"Please use a different secrets provider (e.g., 'none' for API/server environments) " +
150+
"or ensure your system has a keyring service available")
151+
152+
// IsKeyringAvailable tests if the OS keyring is available by attempting to set and delete a test value.
153+
func IsKeyringAvailable() bool {
154+
testKey := "toolhive-keyring-test"
155+
testValue := "test"
156+
157+
// Try to set a test value
158+
if err := keyring.Set(keyringService, testKey, testValue); err != nil {
159+
return false
160+
}
161+
162+
// Clean up the test value
163+
_ = keyring.Delete(keyringService, testKey)
164+
165+
return true
166+
}
167+
146168
// CreateSecretProvider creates the specified type of secrets provider.
147169
func CreateSecretProvider(managerType ProviderType) (Provider, error) {
148170
switch managerType {
149171
case EncryptedType:
172+
// Enforce keyring availability for encrypted provider
173+
if !IsKeyringAvailable() {
174+
return nil, ErrKeyringNotAvailable
175+
}
176+
150177
password, err := GetSecretsPassword()
151178
if err != nil {
152179
return nil, fmt.Errorf("failed to get secrets password: %w", err)
@@ -168,47 +195,45 @@ func CreateSecretProvider(managerType ProviderType) (Provider, error) {
168195
}
169196

170197
// GetSecretsPassword returns the password to use for encrypting and decrypting secrets.
171-
// It will attempt to retrieve it from the environment variable TOOLHIVE_SECRETS_PASSWORD.
172-
// If the environment variable is not set, it will prompt the user to enter a password.
198+
// It will attempt to retrieve from the OS keyring.
199+
// If not available, it will fail with an error.
173200
func GetSecretsPassword() ([]byte, error) {
174-
// First, attempt to load the password from the environment variable.
175-
password := []byte(os.Getenv(PasswordEnvVar))
176-
if len(password) > 0 {
177-
return password, nil
178-
}
179-
180-
// If not present, attempt to load the password from the OS keyring.
201+
// Attempt to load the password from the OS keyring.
181202
keyringSecret, err := keyring.Get(keyringService, keyringService)
182203
if err == nil {
183204
return []byte(keyringSecret), nil
184205
}
185206

186207
// We need to determine if the error is due to a lack of keyring on the
187208
// system or if the keyring is available but nothing was stored.
188-
keyringAvailable := errors.Is(err, keyring.ErrNotFound)
189-
190-
// We cannot ask for a password in a detached process.
191-
// We should never trigger this, but this ensures that if there's a bug
192-
// then it's easier to find.
193-
if process.IsDetached() {
194-
return nil, fmt.Errorf("detached process detected, cannot ask for password")
195-
}
209+
if errors.Is(err, keyring.ErrNotFound) {
210+
211+
// Keyring is available but no password stored - this should only happen during setup
212+
// We cannot ask for a password in a detached process.
213+
// We should never trigger this, but this ensures that if there's a bug
214+
// then it's easier to find.
215+
if process.IsDetached() {
216+
return nil, fmt.Errorf("detached process detected, cannot ask for password")
217+
}
196218

197-
// If the keyring is not available, prompt the user for a password.
198-
password, err = readPasswordStdin()
199-
if err != nil {
200-
return nil, fmt.Errorf("failed to read password: %w", err)
201-
}
219+
// Prompt for password during setup
220+
password, err := readPasswordStdin()
221+
if err != nil {
222+
return nil, fmt.Errorf("failed to read password: %w", err)
223+
}
202224

203-
// If the keyring is available, we can store the password for future use.
204-
if keyringAvailable {
225+
// Store the password in the keyring for future use
205226
logger.Info("writing password to os keyring")
206227
err = keyring.Set(keyringService, keyringService, string(password))
207228
if err != nil {
208229
return nil, fmt.Errorf("failed to store password in keyring: %w", err)
209230
}
231+
232+
return password, nil
210233
}
211-
return password, nil
234+
235+
// Assume any other keyring error means keyring is not available
236+
return nil, fmt.Errorf("OS keyring is not available: %w", err)
212237
}
213238

214239
func readPasswordStdin() ([]byte, error) {

0 commit comments

Comments
 (0)