-
Notifications
You must be signed in to change notification settings - Fork 8
fix(secrets): persist concrete backend ownership #1225
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
2463d81
2f8e955
1121724
7624df7
ceb75ad
e667a61
a451635
74f629f
0c56299
c53b7d6
5b3e9dc
62c6fdf
0420323
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,124 @@ | ||
| package secrets | ||
|
|
||
| import ( | ||
| "fmt" | ||
| "os" | ||
| "path/filepath" | ||
| "strings" | ||
|
|
||
| "filippo.io/age" | ||
| ) | ||
|
|
||
| type backendRegistry interface { | ||
| Open(kind Backend, idx *index, create bool) (backend, error) | ||
| ResolveForNewSecret(preference Backend, idx *index) (Backend, error) | ||
| } | ||
|
|
||
| type fixedBackendRegistry struct{ b backend } | ||
|
|
||
| func (r fixedBackendRegistry) Open(_ Backend, _ *index, _ bool) (backend, error) { return r.b, nil } | ||
| func (r fixedBackendRegistry) ResolveForNewSecret(_ Backend, _ *index) (Backend, error) { | ||
| return BackendKeyring, nil | ||
| } | ||
|
|
||
| type systemBackendRegistry struct{ dir string } | ||
|
|
||
| func newSystemBackendRegistry( | ||
| dir string, | ||
| ) backendRegistry { | ||
| return &systemBackendRegistry{dir: dir} | ||
| } | ||
|
|
||
| func (r *systemBackendRegistry) Open(kind Backend, idx *index, create bool) (backend, error) { | ||
| switch kind { | ||
| case BackendKeyring: | ||
| return keyringBackend{}, nil | ||
| case BackendFile: | ||
| var key *fileKey | ||
| var err error | ||
| if create { | ||
| key, err = resolveFileKey(r.dir, os.Getenv(EnvPassphrase)) | ||
| } else { | ||
| key, err = openExistingFileKey(r.dir, idx) | ||
|
Comment on lines
+38
to
+42
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When an existing file-owned secret is updated, |
||
| } | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| if create && idx != nil { | ||
| idx.data.KeySource = string(key.source) | ||
| } | ||
| return newFileBackend(filepath.Join(r.dir, EncryptedFileName), key), nil | ||
| default: | ||
| return nil, fmt.Errorf("invalid secrets backend %q", kind) | ||
| } | ||
| } | ||
|
|
||
| func (r *systemBackendRegistry) ResolveForNewSecret( | ||
| preference Backend, | ||
| idx *index, | ||
| ) (Backend, error) { | ||
| switch preference { | ||
| case BackendKeyring: | ||
| return BackendKeyring, nil | ||
| case BackendFile: | ||
| return BackendFile, nil | ||
| case BackendAuto: | ||
| if keyringAvailable() { | ||
| return BackendKeyring, nil | ||
| } | ||
| return BackendFile, nil | ||
| default: | ||
| return "", fmt.Errorf("invalid secrets backend %q", preference) | ||
| } | ||
| } | ||
|
|
||
| func openExistingFileKey(dir string, idx *index) (*fileKey, error) { | ||
| source := keySource(idx.data.KeySource) | ||
| if source == "" { | ||
| return nil, fmt.Errorf("encrypted secrets are missing their key source metadata") | ||
| } | ||
| if source == keySourcePassphrase { | ||
| return openPassphraseFileKey() | ||
| } | ||
| var store keyStore | ||
| switch source { | ||
| case keySourceKeyring: | ||
| store = keyringKeyStore{} | ||
| case keySourceAutoFile: | ||
| store = fileKeyStore{path: filepath.Join(dir, KeyFileName)} | ||
| default: | ||
| return nil, fmt.Errorf("invalid secrets key source %q", source) | ||
| } | ||
| return keyFromStore(store, source) | ||
| } | ||
|
|
||
| func openPassphraseFileKey() (*fileKey, error) { | ||
| passphrase := os.Getenv(EnvPassphrase) | ||
| if passphrase == "" { | ||
| return nil, fmt.Errorf("encrypted secrets require %s", EnvPassphrase) | ||
| } | ||
| recipient, err := age.NewScryptRecipient(passphrase) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| identity, err := age.NewScryptIdentity(passphrase) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| return &fileKey{recipient: recipient, identity: identity, source: keySourcePassphrase}, nil | ||
| } | ||
|
|
||
| func keyFromStore(store keyStore, source keySource) (*fileKey, error) { | ||
| encoded, err := store.load() | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| if strings.TrimSpace(encoded) == "" { | ||
| return nil, ErrSecretNotFound | ||
| } | ||
| identity, err := age.ParseX25519Identity(strings.TrimSpace(encoded)) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("parse stored secrets key: %w", err) | ||
| } | ||
| return &fileKey{recipient: identity.Recipient(), identity: identity, source: source}, nil | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Cover
secretsErrortransitions in both store paths.SecretsPage.sveltedisplays the error banner from$secretsError, butsecrets.test.tsnever imports or assertssecretsError. Add rejection-then-success cases for bothinitSecrets()andrefreshSecrets()to assert that the error message is recorded and then cleared.🤖 Prompt for AI Agents