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:
parent
e9d03987f9
commit
9adf0c0803
@ -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)
|
||||||
|
@ -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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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"`
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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, "", " ")
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user