refactor: remove confusing dual ID method pattern from Unlocker interface - Removed redundant ID() method from Unlocker interface - Removed ID field from UnlockerMetadata struct - Modified GetID() to generate IDs dynamically based on unlocker type and data - Updated vault package to create unlocker instances when searching by ID - Fixed all tests and CLI code to remove ID field references - IDs are now consistently generated from unlocker data, preventing redundancy

This commit is contained in:
Jeffrey Paul 2025-06-11 15:21:20 -07:00
parent 9adf0c0803
commit 03e0ee2f95
9 changed files with 68 additions and 70 deletions

View File

@ -149,12 +149,12 @@ func (cli *CLIInstance) UnlockersList(jsonOutput bool) error {
// Check if this is the right unlocker by comparing metadata // Check if this is the right unlocker by comparing metadata
metadataBytes, err := afero.ReadFile(cli.fs, metadataPath) metadataBytes, err := afero.ReadFile(cli.fs, metadataPath)
if err != nil { if err != nil {
continue continue //FIXME this error needs to be handled
} }
var diskMetadata secret.UnlockerMetadata var diskMetadata secret.UnlockerMetadata
if err := json.Unmarshal(metadataBytes, &diskMetadata); err != nil { if err := json.Unmarshal(metadataBytes, &diskMetadata); err != nil {
continue continue //FIXME this error needs to be handled
} }
// Match by type and creation time // Match by type and creation time
@ -177,7 +177,8 @@ func (cli *CLIInstance) UnlockersList(jsonOutput bool) error {
if unlocker != nil { if unlocker != nil {
properID = unlocker.GetID() properID = unlocker.GetID()
} else { } else {
properID = metadata.ID // fallback to metadata ID // Generate ID as fallback
properID = fmt.Sprintf("%s-%s", metadata.CreatedAt.Format("2006-01-02.15.04"), metadata.Type)
} }
unlockerInfo := UnlockerInfo{ unlockerInfo := UnlockerInfo{

View File

@ -131,18 +131,14 @@ func (k *KeychainUnlocker) GetDirectory() string {
return k.Directory return k.Directory
} }
// GetID implements Unlocker interface // GetID implements Unlocker interface - generates ID from keychain item name
func (k *KeychainUnlocker) GetID() string { func (k *KeychainUnlocker) GetID() string {
return k.Metadata.ID
}
// ID implements Unlocker interface - generates ID from keychain item name
func (k *KeychainUnlocker) ID() string {
// Generate ID using keychain item name // Generate ID using keychain item name
keychainItemName, err := k.GetKeychainItemName() keychainItemName, err := k.GetKeychainItemName()
if err != nil { if err != nil {
// Fallback to metadata ID if we can't read the keychain item name // Fallback to creation time-based ID if we can't read the keychain item name
return k.Metadata.ID createdAt := k.Metadata.CreatedAt
return fmt.Sprintf("%s-keychain", createdAt.Format("2006-01-02.15.04"))
} }
return fmt.Sprintf("%s-keychain", keychainItemName) return fmt.Sprintf("%s-keychain", keychainItemName)
} }
@ -362,12 +358,8 @@ func CreateKeychainUnlocker(fs afero.Fs, stateDir string) (*KeychainUnlocker, er
} }
// Step 9: Create and write enhanced metadata // Step 9: Create and write enhanced metadata
// Generate the key ID directly using the keychain item name
keyID := fmt.Sprintf("%s-keychain", keychainItemName)
keychainMetadata := KeychainUnlockerMetadata{ keychainMetadata := KeychainUnlockerMetadata{
UnlockerMetadata: UnlockerMetadata{ UnlockerMetadata: UnlockerMetadata{
ID: keyID,
Type: "keychain", Type: "keychain",
CreatedAt: time.Now(), CreatedAt: time.Now(),
Flags: []string{"keychain", "macos"}, Flags: []string{"keychain", "macos"},

View File

@ -14,7 +14,6 @@ type VaultMetadata struct {
// UnlockerMetadata contains information about an unlocker // UnlockerMetadata contains information about an unlocker
type UnlockerMetadata struct { type UnlockerMetadata struct {
ID string `json:"id"`
Type string `json:"type"` // passphrase, pgp, keychain Type string `json:"type"` // passphrase, pgp, keychain
CreatedAt time.Time `json:"createdAt"` CreatedAt time.Time `json:"createdAt"`
Flags []string `json:"flags,omitempty"` Flags []string `json:"flags,omitempty"`

View File

@ -40,7 +40,6 @@ func TestPassphraseUnlockerWithRealFS(t *testing.T) {
// Set up test metadata // Set up test metadata
metadata := secret.UnlockerMetadata{ metadata := secret.UnlockerMetadata{
ID: "test-passphrase",
Type: "passphrase", Type: "passphrase",
CreatedAt: time.Now(), CreatedAt: time.Now(),
Flags: []string{}, Flags: []string{},

View File

@ -107,13 +107,8 @@ func (p *PassphraseUnlocker) GetDirectory() string {
return p.Directory return p.Directory
} }
// GetID implements Unlocker interface // GetID implements Unlocker interface - generates ID from creation timestamp
func (p *PassphraseUnlocker) GetID() string { func (p *PassphraseUnlocker) GetID() string {
return p.Metadata.ID
}
// ID implements Unlocker interface - generates ID from creation timestamp
func (p *PassphraseUnlocker) ID() string {
// Generate ID using creation timestamp: YYYY-MM-DD.HH.mm-passphrase // Generate ID using creation timestamp: YYYY-MM-DD.HH.mm-passphrase
createdAt := p.Metadata.CreatedAt createdAt := p.Metadata.CreatedAt
return fmt.Sprintf("%s-passphrase", createdAt.Format("2006-01-02.15.04")) return fmt.Sprintf("%s-passphrase", createdAt.Format("2006-01-02.15.04"))

View File

@ -413,7 +413,6 @@ Passphrase: ` + testPassphrase + `
// Set up test metadata // Set up test metadata
metadata := secret.UnlockerMetadata{ metadata := secret.UnlockerMetadata{
ID: fmt.Sprintf("%s-pgp", keyID),
Type: "pgp", Type: "pgp",
CreatedAt: time.Now(), CreatedAt: time.Now(),
Flags: []string{"gpg", "encrypted"}, Flags: []string{"gpg", "encrypted"},

View File

@ -106,18 +106,14 @@ func (p *PGPUnlocker) GetDirectory() string {
return p.Directory return p.Directory
} }
// GetID implements Unlocker interface // GetID implements Unlocker interface - generates ID from GPG key ID
func (p *PGPUnlocker) GetID() string { func (p *PGPUnlocker) GetID() string {
return p.Metadata.ID
}
// ID implements Unlocker interface - generates ID from GPG key ID
func (p *PGPUnlocker) ID() string {
// Generate ID using GPG key ID: <keyid>-pgp // Generate ID using GPG key ID: <keyid>-pgp
gpgKeyID, err := p.GetGPGKeyID() gpgKeyID, err := p.GetGPGKeyID()
if err != nil { if err != nil {
// Fallback to metadata ID if we can't read the GPG key ID // Fallback to creation time-based ID if we can't read the GPG key ID
return p.Metadata.ID createdAt := p.Metadata.CreatedAt
return fmt.Sprintf("%s-pgp", createdAt.Format("2006-01-02.15.04"))
} }
return fmt.Sprintf("%s-pgp", gpgKeyID) return fmt.Sprintf("%s-pgp", gpgKeyID)
} }
@ -290,12 +286,8 @@ func CreatePGPUnlocker(fs afero.Fs, stateDir string, gpgKeyID string) (*PGPUnloc
} }
// Step 9: Create and write enhanced metadata // Step 9: Create and write enhanced metadata
// Generate the key ID directly using the GPG key ID
keyID := fmt.Sprintf("%s-pgp", gpgKeyID)
pgpMetadata := PGPUnlockerMetadata{ pgpMetadata := PGPUnlockerMetadata{
UnlockerMetadata: UnlockerMetadata{ UnlockerMetadata: UnlockerMetadata{
ID: keyID,
Type: "pgp", Type: "pgp",
CreatedAt: time.Now(), CreatedAt: time.Now(),
Flags: []string{"gpg", "encrypted"}, Flags: []string{"gpg", "encrypted"},

View File

@ -10,7 +10,6 @@ type Unlocker interface {
GetType() string GetType() string
GetMetadata() UnlockerMetadata GetMetadata() UnlockerMetadata
GetDirectory() string GetDirectory() string
GetID() string GetID() string // Generate ID based on unlocker type and data
ID() string // Generate ID from the unlocker's public key
Remove() error // Remove the unlocker and any associated resources Remove() error // Remove the unlocker and any associated resources
} }

View File

@ -75,7 +75,6 @@ func (v *Vault) GetCurrentUnlocker() (secret.Unlocker, error) {
} }
secret.DebugWith("Parsed unlocker metadata", secret.DebugWith("Parsed unlocker metadata",
slog.String("unlocker_id", metadata.ID),
slog.String("unlocker_type", metadata.Type), slog.String("unlocker_type", metadata.Type),
slog.Time("created_at", metadata.CreatedAt), slog.Time("created_at", metadata.CreatedAt),
slog.Any("flags", metadata.Flags), slog.Any("flags", metadata.Flags),
@ -87,16 +86,16 @@ func (v *Vault) GetCurrentUnlocker() (secret.Unlocker, error) {
secretMetadata := secret.UnlockerMetadata(metadata) secretMetadata := secret.UnlockerMetadata(metadata)
switch metadata.Type { switch metadata.Type {
case "passphrase": case "passphrase":
secret.Debug("Creating passphrase unlocker instance", "unlocker_id", metadata.ID) secret.Debug("Creating passphrase unlocker instance", "unlocker_type", metadata.Type)
unlocker = secret.NewPassphraseUnlocker(v.fs, unlockerDir, secretMetadata) unlocker = secret.NewPassphraseUnlocker(v.fs, unlockerDir, secretMetadata)
case "pgp": case "pgp":
secret.Debug("Creating PGP unlocker instance", "unlocker_id", metadata.ID) secret.Debug("Creating PGP unlocker instance", "unlocker_type", metadata.Type)
unlocker = secret.NewPGPUnlocker(v.fs, unlockerDir, secretMetadata) unlocker = secret.NewPGPUnlocker(v.fs, unlockerDir, secretMetadata)
case "keychain": case "keychain":
secret.Debug("Creating keychain unlocker instance", "unlocker_id", metadata.ID) secret.Debug("Creating keychain unlocker instance", "unlocker_type", metadata.Type)
unlocker = secret.NewKeychainUnlocker(v.fs, unlockerDir, secretMetadata) unlocker = secret.NewKeychainUnlocker(v.fs, unlockerDir, secretMetadata)
default: default:
secret.Debug("Unsupported unlocker type", "type", metadata.Type, "unlocker_id", metadata.ID) secret.Debug("Unsupported unlocker type", "type", metadata.Type)
return nil, fmt.Errorf("unsupported unlocker type: %s", metadata.Type) return nil, fmt.Errorf("unsupported unlocker type: %s", metadata.Type)
} }
@ -200,23 +199,27 @@ func (v *Vault) RemoveUnlocker(unlockerID string) error {
continue continue
} }
if metadata.ID == unlockerID { unlockerDirPath = filepath.Join(unlockersDir, file.Name())
unlockerDirPath = filepath.Join(unlockersDir, file.Name())
// Convert our metadata to secret.UnlockerMetadata // Convert our metadata to secret.UnlockerMetadata
secretMetadata := secret.UnlockerMetadata(metadata) secretMetadata := secret.UnlockerMetadata(metadata)
// Create the appropriate unlocker instance // Create the appropriate unlocker instance
switch metadata.Type { var tempUnlocker secret.Unlocker
case "passphrase": switch metadata.Type {
unlocker = secret.NewPassphraseUnlocker(v.fs, unlockerDirPath, secretMetadata) case "passphrase":
case "pgp": tempUnlocker = secret.NewPassphraseUnlocker(v.fs, unlockerDirPath, secretMetadata)
unlocker = secret.NewPGPUnlocker(v.fs, unlockerDirPath, secretMetadata) case "pgp":
case "keychain": tempUnlocker = secret.NewPGPUnlocker(v.fs, unlockerDirPath, secretMetadata)
unlocker = secret.NewKeychainUnlocker(v.fs, unlockerDirPath, secretMetadata) case "keychain":
default: tempUnlocker = secret.NewKeychainUnlocker(v.fs, unlockerDirPath, secretMetadata)
return fmt.Errorf("unsupported unlocker type: %s", metadata.Type) default:
} continue
}
// Check if this unlocker's ID matches
if tempUnlocker.GetID() == unlockerID {
unlocker = tempUnlocker
break break
} }
} }
@ -266,8 +269,27 @@ func (v *Vault) SelectUnlocker(unlockerID string) error {
continue continue
} }
if metadata.ID == unlockerID { unlockerDirPath := filepath.Join(unlockersDir, file.Name())
targetUnlockerDir = filepath.Join(unlockersDir, file.Name())
// Convert our metadata to secret.UnlockerMetadata
secretMetadata := secret.UnlockerMetadata(metadata)
// Create the appropriate unlocker instance
var tempUnlocker secret.Unlocker
switch metadata.Type {
case "passphrase":
tempUnlocker = secret.NewPassphraseUnlocker(v.fs, unlockerDirPath, secretMetadata)
case "pgp":
tempUnlocker = secret.NewPGPUnlocker(v.fs, unlockerDirPath, secretMetadata)
case "keychain":
tempUnlocker = secret.NewKeychainUnlocker(v.fs, unlockerDirPath, secretMetadata)
default:
continue
}
// Check if this unlocker's ID matches
if tempUnlocker.GetID() == unlockerID {
targetUnlockerDir = unlockerDirPath
break break
} }
} }
@ -298,8 +320,7 @@ func (v *Vault) CreatePassphraseUnlocker(passphrase string) (*secret.PassphraseU
return nil, fmt.Errorf("failed to get vault directory: %w", err) return nil, fmt.Errorf("failed to get vault directory: %w", err)
} }
// Create unlocker directory with timestamp // Create unlocker directory
timestamp := time.Now().Format("2006-01-02.15.04")
unlockerDir := filepath.Join(vaultDir, "unlockers.d", "passphrase") unlockerDir := filepath.Join(vaultDir, "unlockers.d", "passphrase")
if err := v.fs.MkdirAll(unlockerDir, secret.DirPerms); err != nil { if err := v.fs.MkdirAll(unlockerDir, secret.DirPerms); err != nil {
return nil, fmt.Errorf("failed to create unlocker directory: %w", err) return nil, fmt.Errorf("failed to create unlocker directory: %w", err)
@ -331,9 +352,7 @@ func (v *Vault) CreatePassphraseUnlocker(passphrase string) (*secret.PassphraseU
} }
// Create metadata // Create metadata
unlockerID := fmt.Sprintf("%s-passphrase", timestamp)
metadata := UnlockerMetadata{ metadata := UnlockerMetadata{
ID: unlockerID,
Type: "passphrase", Type: "passphrase",
CreatedAt: time.Now(), CreatedAt: time.Now(),
Flags: []string{}, Flags: []string{},
@ -368,13 +387,16 @@ func (v *Vault) CreatePassphraseUnlocker(passphrase string) (*secret.PassphraseU
return nil, fmt.Errorf("failed to write encrypted long-term private key: %w", err) return nil, fmt.Errorf("failed to write encrypted long-term private key: %w", err)
} }
// Select this unlocker as current
if err := v.SelectUnlocker(unlockerID); err != nil {
return nil, fmt.Errorf("failed to select new unlocker: %w", err)
}
// Convert our metadata to secret.UnlockerMetadata for the constructor // Convert our metadata to secret.UnlockerMetadata for the constructor
secretMetadata := secret.UnlockerMetadata(metadata) secretMetadata := secret.UnlockerMetadata(metadata)
return secret.NewPassphraseUnlocker(v.fs, unlockerDir, secretMetadata), nil // Create the unlocker instance
unlocker := secret.NewPassphraseUnlocker(v.fs, unlockerDir, secretMetadata)
// Select this unlocker as current
if err := v.SelectUnlocker(unlocker.GetID()); err != nil {
return nil, fmt.Errorf("failed to select new unlocker: %w", err)
}
return unlocker, nil
} }