refactor: fix redundant metadata fields across the codebase - Removed VaultMetadata.Name (redundant with directory structure) - Removed SecretMetadata.Name (redundant with Secret.Name field) - Removed AgePublicKey and AgeRecipient from PGPUnlockerMetadata - Removed AgePublicKey from KeychainUnlockerMetadata - Changed PGP and Keychain unlockers to store recipient in pub.txt instead of pub.age - Fixed all tests to reflect these changes - Follows DRY principle and prevents data inconsistency

This commit is contained in:
Jeffrey Paul 2025-06-09 17:44:10 -07:00
parent e9d03987f9
commit 9adf0c0803
9 changed files with 21 additions and 42 deletions

View File

@ -9,7 +9,6 @@ import (
// CLIEntry is the entry point for the secret CLI application // CLIEntry is the entry point for the secret CLI application
func CLIEntry() { func CLIEntry() {
secret.Debug("CLIEntry starting - debug output is working")
cmd := newRootCmd() cmd := newRootCmd()
if err := cmd.Execute(); err != nil { if err := cmd.Execute(); err != nil {
os.Exit(1) os.Exit(1)

View File

@ -236,7 +236,6 @@ func (cli *CLIInstance) VaultImport(vaultName string) error {
if err != nil { if err != nil {
// If metadata doesn't exist, create new // If metadata doesn't exist, create new
existingMetadata = &vault.VaultMetadata{ existingMetadata = &vault.VaultMetadata{
Name: vaultName,
CreatedAt: time.Now(), CreatedAt: time.Now(),
} }
} }

View File

@ -18,8 +18,6 @@ import (
// KeychainUnlockerMetadata extends UnlockerMetadata with keychain-specific data // KeychainUnlockerMetadata extends UnlockerMetadata with keychain-specific data
type KeychainUnlockerMetadata struct { type KeychainUnlockerMetadata struct {
UnlockerMetadata UnlockerMetadata
// Age keypair information
AgePublicKey string `json:"age_public_key"`
// Keychain item name // Keychain item name
KeychainItemName string `json:"keychain_item_name"` KeychainItemName string `json:"keychain_item_name"`
} }
@ -256,11 +254,11 @@ func CreateKeychainUnlocker(fs afero.Fs, stateDir string) (*KeychainUnlocker, er
return nil, fmt.Errorf("failed to generate age private key passphrase: %w", err) return nil, fmt.Errorf("failed to generate age private key passphrase: %w", err)
} }
// Step 3: Store age public key as plaintext // Step 3: Store age recipient as plaintext
agePublicKeyString := ageIdentity.Recipient().String() ageRecipient := ageIdentity.Recipient().String()
agePubKeyPath := filepath.Join(unlockerDir, "pub.age") recipientPath := filepath.Join(unlockerDir, "pub.txt")
if err := afero.WriteFile(fs, agePubKeyPath, []byte(agePublicKeyString), FilePerms); err != nil { if err := afero.WriteFile(fs, recipientPath, []byte(ageRecipient), FilePerms); err != nil {
return nil, fmt.Errorf("failed to write age public key: %w", err) return nil, fmt.Errorf("failed to write age recipient: %w", err)
} }
// Step 4: Encrypt age private key with the generated passphrase and store on disk // Step 4: Encrypt age private key with the generated passphrase and store on disk
@ -348,7 +346,7 @@ func CreateKeychainUnlocker(fs afero.Fs, stateDir string) (*KeychainUnlocker, er
// Step 7: Prepare keychain data // Step 7: Prepare keychain data
keychainData := KeychainData{ keychainData := KeychainData{
AgePublicKey: agePublicKeyString, AgePublicKey: ageRecipient,
AgePrivKeyPassphrase: agePrivKeyPassphrase, AgePrivKeyPassphrase: agePrivKeyPassphrase,
EncryptedLongtermKey: hex.EncodeToString(encryptedLtPrivKeyToAge), EncryptedLongtermKey: hex.EncodeToString(encryptedLtPrivKeyToAge),
} }
@ -374,7 +372,6 @@ func CreateKeychainUnlocker(fs afero.Fs, stateDir string) (*KeychainUnlocker, er
CreatedAt: time.Now(), CreatedAt: time.Now(),
Flags: []string{"keychain", "macos"}, Flags: []string{"keychain", "macos"},
}, },
AgePublicKey: agePublicKeyString,
KeychainItemName: keychainItemName, KeychainItemName: keychainItemName,
} }

View File

@ -6,7 +6,6 @@ import (
// VaultMetadata contains information about a vault // VaultMetadata contains information about a vault
type VaultMetadata struct { type VaultMetadata struct {
Name string `json:"name"`
CreatedAt time.Time `json:"createdAt"` CreatedAt time.Time `json:"createdAt"`
Description string `json:"description,omitempty"` Description string `json:"description,omitempty"`
DerivationIndex uint32 `json:"derivation_index"` DerivationIndex uint32 `json:"derivation_index"`
@ -23,7 +22,6 @@ type UnlockerMetadata struct {
// SecretMetadata contains information about a secret // SecretMetadata contains information about a secret
type SecretMetadata struct { type SecretMetadata struct {
Name string `json:"name"`
CreatedAt time.Time `json:"createdAt"` CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"` UpdatedAt time.Time `json:"updatedAt"`
} }

View File

@ -342,13 +342,13 @@ Passphrase: ` + testPassphrase + `
} }
// Check if required files exist // Check if required files exist
pubKeyPath := filepath.Join(unlockerDir, "pub.age") recipientPath := filepath.Join(unlockerDir, "pub.txt")
pubKeyExists, err := afero.Exists(fs, pubKeyPath) recipientExists, err := afero.Exists(fs, recipientPath)
if err != nil { if err != nil {
t.Fatalf("Failed to check if public key file exists: %v", err) t.Fatalf("Failed to check if recipient file exists: %v", err)
} }
if !pubKeyExists { if !recipientExists {
t.Errorf("PGP unlock key public key file does not exist: %s", pubKeyPath) t.Errorf("PGP unlock key recipient file does not exist: %s", recipientPath)
} }
privKeyPath := filepath.Join(unlockerDir, "priv.age.gpg") privKeyPath := filepath.Join(unlockerDir, "priv.age.gpg")
@ -465,10 +465,10 @@ Passphrase: ` + testPassphrase + `
t.Fatalf("Failed to generate age identity: %v", err) t.Fatalf("Failed to generate age identity: %v", err)
} }
// Write the public key // Write the recipient
pubKeyPath := filepath.Join(unlockerDir, "pub.age") recipientPath := filepath.Join(unlockerDir, "pub.txt")
if err := afero.WriteFile(fs, pubKeyPath, []byte(ageIdentity.Recipient().String()), secret.FilePerms); err != nil { if err := afero.WriteFile(fs, recipientPath, []byte(ageIdentity.Recipient().String()), secret.FilePerms); err != nil {
t.Fatalf("Failed to write public key: %v", err) t.Fatalf("Failed to write recipient: %v", err)
} }
// GPG encrypt the private key using our custom encrypt function // GPG encrypt the private key using our custom encrypt function

View File

@ -31,9 +31,6 @@ type PGPUnlockerMetadata struct {
UnlockerMetadata UnlockerMetadata
// GPG key ID used for encryption // GPG key ID used for encryption
GPGKeyID string `json:"gpg_key_id"` GPGKeyID string `json:"gpg_key_id"`
// Age keypair information
AgePublicKey string `json:"age_public_key"`
AgeRecipient string `json:"age_recipient"`
} }
// PGPUnlocker represents a PGP-protected unlocker // PGPUnlocker represents a PGP-protected unlocker
@ -209,11 +206,11 @@ func CreatePGPUnlocker(fs afero.Fs, stateDir string, gpgKeyID string) (*PGPUnloc
return nil, fmt.Errorf("failed to generate age keypair: %w", err) return nil, fmt.Errorf("failed to generate age keypair: %w", err)
} }
// Step 2: Store age public key as plaintext // Step 2: Store age recipient as plaintext
agePublicKeyString := ageIdentity.Recipient().String() ageRecipient := ageIdentity.Recipient().String()
agePubKeyPath := filepath.Join(unlockerDir, "pub.age") recipientPath := filepath.Join(unlockerDir, "pub.txt")
if err := afero.WriteFile(fs, agePubKeyPath, []byte(agePublicKeyString), FilePerms); err != nil { if err := afero.WriteFile(fs, recipientPath, []byte(ageRecipient), FilePerms); err != nil {
return nil, fmt.Errorf("failed to write age public key: %w", err) return nil, fmt.Errorf("failed to write age recipient: %w", err)
} }
// Step 3: Get or derive the long-term private key // Step 3: Get or derive the long-term private key
@ -303,9 +300,7 @@ func CreatePGPUnlocker(fs afero.Fs, stateDir string, gpgKeyID string) (*PGPUnloc
CreatedAt: time.Now(), CreatedAt: time.Now(),
Flags: []string{"gpg", "encrypted"}, Flags: []string{"gpg", "encrypted"},
}, },
GPGKeyID: gpgKeyID, GPGKeyID: gpgKeyID,
AgePublicKey: agePublicKeyString,
AgeRecipient: ageIdentity.Recipient().String(),
} }
metadataBytes, err := json.MarshalIndent(pgpMetadata, "", " ") metadataBytes, err := json.MarshalIndent(pgpMetadata, "", " ")

View File

@ -55,7 +55,6 @@ func NewSecret(vault VaultInterface, name string) *Secret {
Directory: secretDir, Directory: secretDir,
vault: vault, vault: vault,
Metadata: SecretMetadata{ Metadata: SecretMetadata{
Name: name,
CreatedAt: time.Now(), CreatedAt: time.Now(),
UpdatedAt: time.Now(), UpdatedAt: time.Now(),
}, },
@ -218,7 +217,6 @@ func (s *Secret) LoadMetadata() error {
// For backward compatibility, we'll populate with basic info // For backward compatibility, we'll populate with basic info
now := time.Now() now := time.Now()
s.Metadata = SecretMetadata{ s.Metadata = SecretMetadata{
Name: s.Name,
CreatedAt: now, CreatedAt: now,
UpdatedAt: now, UpdatedAt: now,
} }

View File

@ -247,7 +247,6 @@ func CreateVault(fs afero.Fs, stateDir string, name string) (*Vault, error) {
// Save vault metadata // Save vault metadata
metadata := &VaultMetadata{ metadata := &VaultMetadata{
Name: name,
CreatedAt: time.Now(), CreatedAt: time.Now(),
DerivationIndex: derivationIndex, DerivationIndex: derivationIndex,
PublicKeyHash: publicKeyHash, PublicKeyHash: publicKeyHash,

View File

@ -71,7 +71,6 @@ func TestVaultMetadata(t *testing.T) {
} }
metadata1 := &VaultMetadata{ metadata1 := &VaultMetadata{
Name: "vault1",
DerivationIndex: 0, DerivationIndex: 0,
PublicKeyHash: pubKeyHash0, PublicKeyHash: pubKeyHash0,
} }
@ -117,7 +116,6 @@ func TestVaultMetadata(t *testing.T) {
} }
metadata2 := &VaultMetadata{ metadata2 := &VaultMetadata{
Name: "vault2",
DerivationIndex: 5, DerivationIndex: 5,
PublicKeyHash: pubKeyHash0, // Same hash since it's from the same mnemonic PublicKeyHash: pubKeyHash0, // Same hash since it's from the same mnemonic
} }
@ -143,7 +141,6 @@ func TestVaultMetadata(t *testing.T) {
// Create and save metadata // Create and save metadata
metadata := &VaultMetadata{ metadata := &VaultMetadata{
Name: "test-vault",
DerivationIndex: 3, DerivationIndex: 3,
PublicKeyHash: "test-public-key-hash", PublicKeyHash: "test-public-key-hash",
} }
@ -158,9 +155,6 @@ func TestVaultMetadata(t *testing.T) {
t.Fatalf("Failed to load metadata: %v", err) t.Fatalf("Failed to load metadata: %v", err)
} }
if loaded.Name != metadata.Name {
t.Errorf("Name mismatch: expected %s, got %s", metadata.Name, loaded.Name)
}
if loaded.DerivationIndex != metadata.DerivationIndex { if loaded.DerivationIndex != metadata.DerivationIndex {
t.Errorf("DerivationIndex mismatch: expected %d, got %d", metadata.DerivationIndex, loaded.DerivationIndex) t.Errorf("DerivationIndex mismatch: expected %d, got %d", metadata.DerivationIndex, loaded.DerivationIndex)
} }