Skip to content

Commit d5a890c

Browse files
authored
Merge pull request #1898 from felixfontein/age-errors
Collect age identity loading errors and only report if decryption failed
2 parents c1dadc6 + c122e0c commit d5a890c

File tree

2 files changed

+75
-52
lines changed

2 files changed

+75
-52
lines changed

age/keysource.go

Lines changed: 49 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,18 @@ func MasterKeysFromRecipients(commaSeparatedRecipients string) ([]*MasterKey, er
9494
return keys, nil
9595
}
9696

97+
// errSet is a collection of captured errors.
98+
type errSet []error
99+
100+
// Error joins the errors into a "; " separated string.
101+
func (e errSet) Error() string {
102+
str := make([]string, len(e))
103+
for i, err := range e {
104+
str[i] = err.Error()
105+
}
106+
return strings.Join(str, "; ")
107+
}
108+
97109
// MasterKeyFromRecipient takes a Bech32-encoded age public key, parses it, and
98110
// returns a new MasterKey.
99111
func MasterKeyFromRecipient(recipient string) (*MasterKey, error) {
@@ -197,11 +209,13 @@ func (key *MasterKey) SetEncryptedDataKey(enc []byte) {
197209
// Decrypt decrypts the EncryptedKey with the parsed or loaded identities, and
198210
// returns the result.
199211
func (key *MasterKey) Decrypt() ([]byte, error) {
212+
var errs errSet
200213
if len(key.parsedIdentities) == 0 {
201-
ids, err := key.loadIdentities()
202-
if err != nil {
214+
var ids ParsedIdentities
215+
ids, errs = key.loadIdentities()
216+
if len(ids) == 0 {
203217
log.Info("Decryption failed")
204-
return nil, fmt.Errorf("failed to load age identities: %w", err)
218+
return nil, fmt.Errorf("failed to load age identities: %w", errs)
205219
}
206220
ids.ApplyToMasterKey(key)
207221
}
@@ -211,7 +225,11 @@ func (key *MasterKey) Decrypt() ([]byte, error) {
211225
r, err := age.Decrypt(ar, key.parsedIdentities...)
212226
if err != nil {
213227
log.Info("Decryption failed")
214-
return nil, fmt.Errorf("failed to create reader for decrypting sops data key with age: %w", err)
228+
var loadErrors string
229+
if len(errs) > 0 {
230+
loadErrors = fmt.Sprintf(". Errors while loading age identities: %s", errs.Error())
231+
}
232+
return nil, fmt.Errorf("failed to create reader for decrypting sops data key with age: %w%s", err, loadErrors)
215233
}
216234

217235
var b bytes.Buffer
@@ -289,14 +307,15 @@ func getUserConfigDir() (string, error) {
289307
// environment configurations (e.g. SopsAgeKeyEnv, SopsAgeKeyFileEnv,
290308
// SopsAgeSshPrivateKeyFileEnv, SopsAgeKeyUserConfigPath). It will load all
291309
// found references, and expects at least one configuration to be present.
292-
func (key *MasterKey) loadIdentities() (ParsedIdentities, error) {
310+
func (key *MasterKey) loadIdentities() (ParsedIdentities, errSet) {
293311
var identities ParsedIdentities
294312

313+
var errs errSet
314+
295315
sshIdentity, err := loadAgeSSHIdentity()
296316
if err != nil {
297-
return nil, fmt.Errorf("failed to get SSH identity: %w", err)
298-
}
299-
if sshIdentity != nil {
317+
errs = append(errs, fmt.Errorf("failed to get SSH identity: %w", err))
318+
} else if sshIdentity != nil {
300319
identities = append(identities, sshIdentity)
301320
}
302321

@@ -309,39 +328,39 @@ func (key *MasterKey) loadIdentities() (ParsedIdentities, error) {
309328
if ageKeyFile, ok := os.LookupEnv(SopsAgeKeyFileEnv); ok {
310329
f, err := os.Open(ageKeyFile)
311330
if err != nil {
312-
return nil, fmt.Errorf("failed to open %s file: %w", SopsAgeKeyFileEnv, err)
331+
errs = append(errs, fmt.Errorf("failed to open %s file: %w", SopsAgeKeyFileEnv, err))
332+
} else {
333+
defer f.Close()
334+
readers[SopsAgeKeyFileEnv] = f
313335
}
314-
defer f.Close()
315-
readers[SopsAgeKeyFileEnv] = f
316336
}
317337

318338
if ageKeyCmd, ok := os.LookupEnv(SopsAgeKeyCmdEnv); ok {
319339
args, err := shlex.Split(ageKeyCmd)
320340
if err != nil {
321-
return nil, fmt.Errorf("failed to parse command %s from %s: %w", ageKeyCmd, SopsAgeKeyCmdEnv, err)
341+
errs = append(errs, fmt.Errorf("failed to parse command %s from %s: %w", ageKeyCmd, SopsAgeKeyCmdEnv, err))
342+
} else {
343+
out, err := exec.Command(args[0], args[1:]...).Output()
344+
if err != nil {
345+
errs = append(errs, fmt.Errorf("failed to execute command %s from %s: %w", ageKeyCmd, SopsAgeKeyCmdEnv, err))
346+
} else {
347+
readers[SopsAgeKeyCmdEnv] = bytes.NewReader(out)
348+
}
322349
}
323-
out, err := exec.Command(args[0], args[1:]...).Output()
324-
if err != nil {
325-
return nil, fmt.Errorf("failed to execute command %s from %s: %w", ageKeyCmd, SopsAgeKeyCmdEnv, err)
326-
}
327-
readers[SopsAgeKeyCmdEnv] = bytes.NewReader(out)
328350
}
329351

330352
userConfigDir, err := getUserConfigDir()
331353
if err != nil && len(readers) == 0 && len(identities) == 0 {
332-
return nil, fmt.Errorf("user config directory could not be determined: %w", err)
333-
}
334-
if userConfigDir != "" {
354+
errs = append(errs, fmt.Errorf("user config directory could not be determined: %w", err))
355+
} else if userConfigDir != "" {
335356
ageKeyFilePath := filepath.Join(userConfigDir, filepath.FromSlash(SopsAgeKeyUserConfigPath))
336357
f, err := os.Open(ageKeyFilePath)
337358
if err != nil && !errors.Is(err, os.ErrNotExist) {
338-
return nil, fmt.Errorf("failed to open file: %w", err)
339-
}
340-
if errors.Is(err, os.ErrNotExist) && len(readers) == 0 && len(identities) == 0 {
359+
errs = append(errs, fmt.Errorf("failed to open file: %w", err))
360+
} else if errors.Is(err, os.ErrNotExist) && len(readers) == 0 && len(identities) == 0 {
341361
// If we have no other readers, presence of the file is required.
342-
return nil, fmt.Errorf("failed to open file: %w", err)
343-
}
344-
if err == nil {
362+
errs = append(errs, fmt.Errorf("failed to open file: %w", err))
363+
} else if err == nil {
345364
defer f.Close()
346365
readers[ageKeyFilePath] = f
347366
}
@@ -350,11 +369,12 @@ func (key *MasterKey) loadIdentities() (ParsedIdentities, error) {
350369
for n, r := range readers {
351370
ids, err := unwrapIdentities(n, r)
352371
if err != nil {
353-
return nil, err
372+
errs = append(errs, err)
373+
} else {
374+
identities = append(identities, ids...)
354375
}
355-
identities = append(identities, ids...)
356376
}
357-
return identities, nil
377+
return identities, errs
358378
}
359379

360380
// parseRecipient attempts to parse a string containing an encoded age public

age/keysource_test.go

Lines changed: 26 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -369,8 +369,8 @@ func TestMasterKey_loadIdentities(t *testing.T) {
369369
t.Setenv(SopsAgeKeyEnv, mockIdentity)
370370

371371
key := &MasterKey{}
372-
got, err := key.loadIdentities()
373-
assert.NoError(t, err)
372+
got, errs := key.loadIdentities()
373+
assert.Len(t, errs, 0)
374374
assert.Len(t, got, 1)
375375
})
376376

@@ -382,8 +382,8 @@ func TestMasterKey_loadIdentities(t *testing.T) {
382382
t.Setenv(SopsAgeKeyEnv, mockIdentity+"\n"+mockOtherIdentity)
383383

384384
key := &MasterKey{}
385-
got, err := key.loadIdentities()
386-
assert.NoError(t, err)
385+
got, errs := key.loadIdentities()
386+
assert.Len(t, errs, 0)
387387
assert.Len(t, got, 2)
388388
})
389389

@@ -398,8 +398,8 @@ func TestMasterKey_loadIdentities(t *testing.T) {
398398
t.Setenv(SopsAgeKeyFileEnv, keyPath)
399399

400400
key := &MasterKey{}
401-
got, err := key.loadIdentities()
402-
assert.NoError(t, err)
401+
got, errs := key.loadIdentities()
402+
assert.Len(t, errs, 0)
403403
assert.Len(t, got, 1)
404404
})
405405

@@ -416,8 +416,8 @@ func TestMasterKey_loadIdentities(t *testing.T) {
416416
assert.NoError(t, os.MkdirAll(filepath.Dir(keyPath), 0o700))
417417
assert.NoError(t, os.WriteFile(keyPath, []byte(mockIdentity), 0o644))
418418

419-
got, err := (&MasterKey{}).loadIdentities()
420-
assert.NoError(t, err)
419+
got, errs := (&MasterKey{}).loadIdentities()
420+
assert.Len(t, errs, 0)
421421
assert.Len(t, got, 1)
422422
})
423423

@@ -435,18 +435,19 @@ func TestMasterKey_loadIdentities(t *testing.T) {
435435
t.Setenv(SopsAgeSshPrivateKeyFileEnv, keyPath)
436436

437437
key := &MasterKey{}
438-
got, err := key.loadIdentities()
439-
assert.NoError(t, err)
438+
got, errs := key.loadIdentities()
439+
assert.Len(t, errs, 0)
440440
assert.Len(t, got, 1)
441441
})
442442

443443
t.Run("no identity", func(t *testing.T) {
444444
tmpDir := t.TempDir()
445445
overwriteUserConfigDir(t, tmpDir)
446446

447-
got, err := (&MasterKey{}).loadIdentities()
448-
assert.Error(t, err)
449-
assert.ErrorContains(t, err, "failed to open file")
447+
got, errs := (&MasterKey{}).loadIdentities()
448+
assert.Len(t, errs, 1)
449+
assert.Error(t, errs[0])
450+
assert.ErrorContains(t, errs[0], "failed to open file")
450451
assert.Nil(t, got)
451452
})
452453

@@ -467,8 +468,8 @@ func TestMasterKey_loadIdentities(t *testing.T) {
467468
assert.NoError(t, os.WriteFile(keyPath2, []byte(mockOtherIdentity), 0o644))
468469
t.Setenv(SopsAgeKeyFileEnv, keyPath2)
469470

470-
got, err := (&MasterKey{}).loadIdentities()
471-
assert.NoError(t, err)
471+
got, errs := (&MasterKey{}).loadIdentities()
472+
assert.Len(t, errs, 0)
472473
assert.Len(t, got, 2)
473474
})
474475

@@ -480,9 +481,10 @@ func TestMasterKey_loadIdentities(t *testing.T) {
480481
t.Setenv(SopsAgeKeyEnv, "invalid")
481482

482483
key := &MasterKey{}
483-
got, err := key.loadIdentities()
484-
assert.Error(t, err)
485-
assert.ErrorContains(t, err, fmt.Sprintf("failed to parse '%s' age identities", SopsAgeKeyEnv))
484+
got, errs := key.loadIdentities()
485+
assert.Len(t, errs, 1)
486+
assert.Error(t, errs[0])
487+
assert.ErrorContains(t, errs[0], fmt.Sprintf("failed to parse '%s' age identities", SopsAgeKeyEnv))
486488
assert.Nil(t, got)
487489
})
488490

@@ -494,8 +496,8 @@ func TestMasterKey_loadIdentities(t *testing.T) {
494496
t.Setenv(SopsAgeKeyCmdEnv, "echo '"+mockIdentity+"'")
495497

496498
key := &MasterKey{}
497-
got, err := key.loadIdentities()
498-
assert.NoError(t, err)
499+
got, errs := key.loadIdentities()
500+
assert.Len(t, errs, 0)
499501
assert.Len(t, got, 1)
500502
})
501503

@@ -507,9 +509,10 @@ func TestMasterKey_loadIdentities(t *testing.T) {
507509
t.Setenv(SopsAgeKeyCmdEnv, "meow")
508510

509511
key := &MasterKey{}
510-
got, err := key.loadIdentities()
511-
assert.Error(t, err)
512-
assert.ErrorContains(t, err, "failed to execute command meow")
512+
got, errs := key.loadIdentities()
513+
assert.Len(t, errs, 2)
514+
assert.Error(t, errs[0])
515+
assert.ErrorContains(t, errs[0], "failed to execute command meow")
513516
assert.Nil(t, got)
514517
})
515518
}

0 commit comments

Comments
 (0)