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
func CLIEntry() {
secret.Debug("CLIEntry starting - debug output is working")
cmd := newRootCmd()
if err := cmd.Execute(); err != nil {
os.Exit(1)

View File

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

View File

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

View File

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

View File

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

View File

@ -31,9 +31,6 @@ type PGPUnlockerMetadata struct {
UnlockerMetadata
// GPG key ID used for encryption
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
@ -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)
}
// Step 2: Store age public key as plaintext
agePublicKeyString := ageIdentity.Recipient().String()
agePubKeyPath := filepath.Join(unlockerDir, "pub.age")
if err := afero.WriteFile(fs, agePubKeyPath, []byte(agePublicKeyString), FilePerms); err != nil {
return nil, fmt.Errorf("failed to write age public key: %w", err)
// Step 2: Store age recipient as plaintext
ageRecipient := ageIdentity.Recipient().String()
recipientPath := filepath.Join(unlockerDir, "pub.txt")
if err := afero.WriteFile(fs, recipientPath, []byte(ageRecipient), FilePerms); err != nil {
return nil, fmt.Errorf("failed to write age recipient: %w", err)
}
// Step 3: Get or derive the long-term private key
@ -304,8 +301,6 @@ func CreatePGPUnlocker(fs afero.Fs, stateDir string, gpgKeyID string) (*PGPUnloc
Flags: []string{"gpg", "encrypted"},
},
GPGKeyID: gpgKeyID,
AgePublicKey: agePublicKeyString,
AgeRecipient: ageIdentity.Recipient().String(),
}
metadataBytes, err := json.MarshalIndent(pgpMetadata, "", " ")

View File

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

View File

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

View File

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