Refactor vault functionality to dedicated package, fix import cycles with interface pattern, fix tests
This commit is contained in:
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
"filippo.io/age"
|
||||
"git.eeqj.de/sneak/secret/internal/secret"
|
||||
"git.eeqj.de/sneak/secret/internal/vault"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -53,7 +54,7 @@ func newDecryptCmd() *cobra.Command {
|
||||
// Encrypt encrypts data using an age secret key stored in a secret
|
||||
func (cli *CLIInstance) Encrypt(secretName, inputFile, outputFile string) error {
|
||||
// Get current vault
|
||||
vault, err := secret.GetCurrentVault(cli.fs, cli.stateDir)
|
||||
vlt, err := vault.GetCurrentVault(cli.fs, cli.stateDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -61,7 +62,7 @@ func (cli *CLIInstance) Encrypt(secretName, inputFile, outputFile string) error
|
||||
var ageSecretKey string
|
||||
|
||||
// Check if secret exists
|
||||
secretObj := secret.NewSecret(vault, secretName)
|
||||
secretObj := secret.NewSecret(vlt, secretName)
|
||||
exists, err := secretObj.Exists()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to check if secret exists: %w", err)
|
||||
@@ -73,7 +74,7 @@ func (cli *CLIInstance) Encrypt(secretName, inputFile, outputFile string) error
|
||||
if os.Getenv(secret.EnvMnemonic) != "" {
|
||||
secretValue, err = secretObj.GetValue(nil)
|
||||
} else {
|
||||
unlockKey, unlockErr := vault.GetCurrentUnlockKey()
|
||||
unlockKey, unlockErr := vlt.GetCurrentUnlockKey()
|
||||
if unlockErr != nil {
|
||||
return fmt.Errorf("failed to get current unlock key: %w", unlockErr)
|
||||
}
|
||||
@@ -90,29 +91,28 @@ func (cli *CLIInstance) Encrypt(secretName, inputFile, outputFile string) error
|
||||
return fmt.Errorf("secret '%s' does not contain a valid age secret key", secretName)
|
||||
}
|
||||
} else {
|
||||
// Secret doesn't exist, generate a new age secret key
|
||||
// Secret doesn't exist, generate new age key and store it
|
||||
identity, err := age.GenerateX25519Identity()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate age secret key: %w", err)
|
||||
return fmt.Errorf("failed to generate age key: %w", err)
|
||||
}
|
||||
|
||||
ageSecretKey = identity.String()
|
||||
|
||||
// Store the new secret
|
||||
if err := vault.AddSecret(secretName, []byte(ageSecretKey), false); err != nil {
|
||||
return fmt.Errorf("failed to store age secret key: %w", err)
|
||||
// Store the generated key as a secret
|
||||
err = vlt.AddSecret(secretName, []byte(ageSecretKey), false)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to store age key: %w", err)
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stderr, "Generated new age secret key and stored in secret '%s'\n", secretName)
|
||||
}
|
||||
|
||||
// Parse the age secret key to get the identity
|
||||
// Parse the secret key
|
||||
identity, err := age.ParseX25519Identity(ageSecretKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse age secret key: %w", err)
|
||||
}
|
||||
|
||||
// Get the recipient (public key) for encryption
|
||||
// Get recipient from identity
|
||||
recipient := identity.Recipient()
|
||||
|
||||
// Set up input reader
|
||||
@@ -157,13 +157,13 @@ func (cli *CLIInstance) Encrypt(secretName, inputFile, outputFile string) error
|
||||
// Decrypt decrypts data using an age secret key stored in a secret
|
||||
func (cli *CLIInstance) Decrypt(secretName, inputFile, outputFile string) error {
|
||||
// Get current vault
|
||||
vault, err := secret.GetCurrentVault(cli.fs, cli.stateDir)
|
||||
vlt, err := vault.GetCurrentVault(cli.fs, cli.stateDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Check if secret exists
|
||||
secretObj := secret.NewSecret(vault, secretName)
|
||||
secretObj := secret.NewSecret(vlt, secretName)
|
||||
exists, err := secretObj.Exists()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to check if secret exists: %w", err)
|
||||
@@ -178,7 +178,7 @@ func (cli *CLIInstance) Decrypt(secretName, inputFile, outputFile string) error
|
||||
if os.Getenv(secret.EnvMnemonic) != "" {
|
||||
secretValue, err = secretObj.GetValue(nil)
|
||||
} else {
|
||||
unlockKey, unlockErr := vault.GetCurrentUnlockKey()
|
||||
unlockKey, unlockErr := vlt.GetCurrentUnlockKey()
|
||||
if unlockErr != nil {
|
||||
return fmt.Errorf("failed to get current unlock key: %w", unlockErr)
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
|
||||
"filippo.io/age"
|
||||
"git.eeqj.de/sneak/secret/internal/secret"
|
||||
"git.eeqj.de/sneak/secret/internal/vault"
|
||||
"git.eeqj.de/sneak/secret/pkg/agehd"
|
||||
"github.com/spf13/afero"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -83,17 +84,17 @@ func (cli *CLIInstance) Init(cmd *cobra.Command) error {
|
||||
return fmt.Errorf("failed to derive long-term key from mnemonic: %w", err)
|
||||
}
|
||||
|
||||
// Create default vault
|
||||
// Create the default vault
|
||||
secret.Debug("Creating default vault")
|
||||
vault, err := secret.CreateVault(cli.fs, cli.stateDir, "default")
|
||||
vlt, err := vault.CreateVault(cli.fs, cli.stateDir, "default")
|
||||
if err != nil {
|
||||
secret.Debug("Failed to create default vault", "error", err)
|
||||
return fmt.Errorf("failed to create default vault: %w", err)
|
||||
}
|
||||
|
||||
// Set default vault as current
|
||||
// Set as current vault
|
||||
secret.Debug("Setting default vault as current")
|
||||
if err := secret.SelectVault(cli.fs, cli.stateDir, "default"); err != nil {
|
||||
if err := vault.SelectVault(cli.fs, cli.stateDir, "default"); err != nil {
|
||||
secret.Debug("Failed to select default vault", "error", err)
|
||||
return fmt.Errorf("failed to select default vault: %w", err)
|
||||
}
|
||||
@@ -108,7 +109,7 @@ func (cli *CLIInstance) Init(cmd *cobra.Command) error {
|
||||
}
|
||||
|
||||
// Unlock the vault with the derived long-term key
|
||||
vault.Unlock(ltIdentity)
|
||||
vlt.Unlock(ltIdentity)
|
||||
|
||||
// Prompt for passphrase for unlock key
|
||||
var passphraseStr string
|
||||
@@ -127,7 +128,7 @@ func (cli *CLIInstance) Init(cmd *cobra.Command) error {
|
||||
|
||||
// Create passphrase-protected unlock key
|
||||
secret.Debug("Creating passphrase-protected unlock key")
|
||||
passphraseKey, err := vault.CreatePassphraseKey(passphraseStr)
|
||||
passphraseKey, err := vlt.CreatePassphraseKey(passphraseStr)
|
||||
if err != nil {
|
||||
secret.Debug("Failed to create unlock key", "error", err)
|
||||
return fmt.Errorf("failed to create unlock key: %w", err)
|
||||
@@ -162,7 +163,7 @@ func (cli *CLIInstance) Init(cmd *cobra.Command) error {
|
||||
if cmd != nil {
|
||||
cmd.Printf("\nDefault vault created and configured\n")
|
||||
cmd.Printf("Long-term public key: %s\n", ltPubKey)
|
||||
cmd.Printf("Unlock key ID: %s\n", passphraseKey.GetMetadata().ID)
|
||||
cmd.Printf("Unlock key ID: %s\n", passphraseKey.GetID())
|
||||
cmd.Println("\nYour secret manager is ready to use!")
|
||||
cmd.Println("Note: When using SB_SECRET_MNEMONIC environment variable,")
|
||||
cmd.Println("unlock keys are not required for secret operations.")
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"time"
|
||||
|
||||
"git.eeqj.de/sneak/secret/internal/secret"
|
||||
"git.eeqj.de/sneak/secret/internal/vault"
|
||||
"github.com/spf13/afero"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@@ -98,13 +99,13 @@ func newKeySelectSubCmd() *cobra.Command {
|
||||
// KeysList lists unlock keys in the current vault
|
||||
func (cli *CLIInstance) KeysList(jsonOutput bool) error {
|
||||
// Get current vault
|
||||
vault, err := secret.GetCurrentVault(cli.fs, cli.stateDir)
|
||||
vlt, err := vault.GetCurrentVault(cli.fs, cli.stateDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get the metadata first
|
||||
keyMetadataList, err := vault.ListUnlockKeys()
|
||||
keyMetadataList, err := vlt.ListUnlockKeys()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -120,7 +121,7 @@ func (cli *CLIInstance) KeysList(jsonOutput bool) error {
|
||||
var keys []KeyInfo
|
||||
for _, metadata := range keyMetadataList {
|
||||
// Create unlock key instance to get the proper ID
|
||||
vaultDir, err := vault.GetDirectory()
|
||||
vaultDir, err := vlt.GetDirectory()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
@@ -157,11 +158,11 @@ func (cli *CLIInstance) KeysList(jsonOutput bool) error {
|
||||
// Create the appropriate unlock key instance
|
||||
switch metadata.Type {
|
||||
case "passphrase":
|
||||
unlockKey = secret.NewPassphraseUnlockKey(cli.fs, keyDir, metadata)
|
||||
unlockKey = secret.NewPassphraseUnlockKey(cli.fs, keyDir, diskMetadata)
|
||||
case "keychain":
|
||||
unlockKey = secret.NewKeychainUnlockKey(cli.fs, keyDir, metadata)
|
||||
unlockKey = secret.NewKeychainUnlockKey(cli.fs, keyDir, diskMetadata)
|
||||
case "pgp":
|
||||
unlockKey = secret.NewPGPUnlockKey(cli.fs, keyDir, metadata)
|
||||
unlockKey = secret.NewPGPUnlockKey(cli.fs, keyDir, diskMetadata)
|
||||
}
|
||||
break
|
||||
}
|
||||
@@ -170,7 +171,7 @@ func (cli *CLIInstance) KeysList(jsonOutput bool) error {
|
||||
// Get the proper ID using the unlock key's ID() method
|
||||
var properID string
|
||||
if unlockKey != nil {
|
||||
properID = unlockKey.ID()
|
||||
properID = unlockKey.GetID()
|
||||
} else {
|
||||
properID = metadata.ID // fallback to metadata ID
|
||||
}
|
||||
@@ -230,14 +231,14 @@ func (cli *CLIInstance) KeysAdd(keyType string, cmd *cobra.Command) error {
|
||||
switch keyType {
|
||||
case "passphrase":
|
||||
// Get current vault
|
||||
vault, err := secret.GetCurrentVault(cli.fs, cli.stateDir)
|
||||
vlt, err := vault.GetCurrentVault(cli.fs, cli.stateDir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get current vault: %w", err)
|
||||
}
|
||||
|
||||
// Try to unlock the vault if not already unlocked
|
||||
if vault.Locked() {
|
||||
_, err := vault.UnlockVault()
|
||||
if vlt.Locked() {
|
||||
_, err := vlt.UnlockVault()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to unlock vault: %w", err)
|
||||
}
|
||||
@@ -255,12 +256,12 @@ func (cli *CLIInstance) KeysAdd(keyType string, cmd *cobra.Command) error {
|
||||
}
|
||||
}
|
||||
|
||||
passphraseKey, err := vault.CreatePassphraseKey(passphraseStr)
|
||||
passphraseKey, err := vlt.CreatePassphraseKey(passphraseStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.Printf("Created passphrase unlock key: %s\n", passphraseKey.GetMetadata().ID)
|
||||
cmd.Printf("Created passphrase unlock key: %s\n", passphraseKey.GetID())
|
||||
return nil
|
||||
|
||||
case "keychain":
|
||||
@@ -269,7 +270,7 @@ func (cli *CLIInstance) KeysAdd(keyType string, cmd *cobra.Command) error {
|
||||
return fmt.Errorf("failed to create macOS Keychain unlock key: %w", err)
|
||||
}
|
||||
|
||||
cmd.Printf("Created macOS Keychain unlock key: %s\n", keychainKey.GetMetadata().ID)
|
||||
cmd.Printf("Created macOS Keychain unlock key: %s\n", keychainKey.GetID())
|
||||
if keyName, err := keychainKey.GetKeychainItemName(); err == nil {
|
||||
cmd.Printf("Keychain Item Name: %s\n", keyName)
|
||||
}
|
||||
@@ -291,7 +292,7 @@ func (cli *CLIInstance) KeysAdd(keyType string, cmd *cobra.Command) error {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.Printf("Created PGP unlock key: %s\n", pgpKey.GetMetadata().ID)
|
||||
cmd.Printf("Created PGP unlock key: %s\n", pgpKey.GetID())
|
||||
cmd.Printf("GPG Key ID: %s\n", gpgKeyID)
|
||||
return nil
|
||||
|
||||
@@ -303,21 +304,21 @@ func (cli *CLIInstance) KeysAdd(keyType string, cmd *cobra.Command) error {
|
||||
// KeysRemove removes an unlock key
|
||||
func (cli *CLIInstance) KeysRemove(keyID string) error {
|
||||
// Get current vault
|
||||
vault, err := secret.GetCurrentVault(cli.fs, cli.stateDir)
|
||||
vlt, err := vault.GetCurrentVault(cli.fs, cli.stateDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return vault.RemoveUnlockKey(keyID)
|
||||
return vlt.RemoveUnlockKey(keyID)
|
||||
}
|
||||
|
||||
// KeySelect selects an unlock key as current
|
||||
func (cli *CLIInstance) KeySelect(keyID string) error {
|
||||
// Get current vault
|
||||
vault, err := secret.GetCurrentVault(cli.fs, cli.stateDir)
|
||||
vlt, err := vault.GetCurrentVault(cli.fs, cli.stateDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return vault.SelectUnlockKey(keyID)
|
||||
return vlt.SelectUnlockKey(keyID)
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"git.eeqj.de/sneak/secret/internal/secret"
|
||||
"git.eeqj.de/sneak/secret/internal/vault"
|
||||
"github.com/spf13/afero"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@@ -90,26 +91,26 @@ func newImportCmd() *cobra.Command {
|
||||
return cmd
|
||||
}
|
||||
|
||||
// AddSecret adds a secret to the vault
|
||||
// AddSecret adds a secret to the current vault
|
||||
func (cli *CLIInstance) AddSecret(secretName string, force bool) error {
|
||||
secret.Debug("CLI AddSecret starting", "secret_name", secretName, "force", force)
|
||||
|
||||
// Get current vault
|
||||
secret.Debug("Getting current vault")
|
||||
vault, err := secret.GetCurrentVault(cli.fs, cli.stateDir)
|
||||
vlt, err := vault.GetCurrentVault(cli.fs, cli.stateDir)
|
||||
if err != nil {
|
||||
secret.Debug("Failed to get current vault", "error", err)
|
||||
return err
|
||||
}
|
||||
secret.Debug("Got current vault", "vault_name", vault.Name)
|
||||
|
||||
secret.Debug("Got current vault", "vault_name", vlt.GetName())
|
||||
|
||||
// Read secret value from stdin
|
||||
secret.Debug("Reading secret value from stdin")
|
||||
value, err := io.ReadAll(os.Stdin)
|
||||
if err != nil {
|
||||
secret.Debug("Failed to read secret from stdin", "error", err)
|
||||
return fmt.Errorf("failed to read secret from stdin: %w", err)
|
||||
return fmt.Errorf("failed to read secret value: %w", err)
|
||||
}
|
||||
|
||||
secret.Debug("Read secret value from stdin", "value_length", len(value))
|
||||
|
||||
// Remove trailing newline if present
|
||||
@@ -118,32 +119,32 @@ func (cli *CLIInstance) AddSecret(secretName string, force bool) error {
|
||||
secret.Debug("Removed trailing newline", "new_length", len(value))
|
||||
}
|
||||
|
||||
// Add the secret to the vault
|
||||
secret.Debug("Calling vault.AddSecret", "secret_name", secretName, "value_length", len(value), "force", force)
|
||||
err = vault.AddSecret(secretName, value, force)
|
||||
if err != nil {
|
||||
if err := vlt.AddSecret(secretName, value, force); err != nil {
|
||||
secret.Debug("vault.AddSecret failed", "error", err)
|
||||
return err
|
||||
}
|
||||
secret.Debug("vault.AddSecret completed successfully")
|
||||
|
||||
secret.Debug("vault.AddSecret completed successfully")
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSecret retrieves a secret from the vault
|
||||
// GetSecret retrieves and prints a secret from the current vault
|
||||
func (cli *CLIInstance) GetSecret(secretName string) error {
|
||||
// Get current vault
|
||||
vault, err := secret.GetCurrentVault(cli.fs, cli.stateDir)
|
||||
vlt, err := vault.GetCurrentVault(cli.fs, cli.stateDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get the secret value using the vault's GetSecret method
|
||||
// This handles the per-secret key architecture internally
|
||||
value, err := vault.GetSecret(secretName)
|
||||
// Get the secret value
|
||||
value, err := vlt.GetSecret(secretName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Print the secret value to stdout
|
||||
fmt.Print(string(value))
|
||||
return nil
|
||||
}
|
||||
@@ -151,14 +152,15 @@ func (cli *CLIInstance) GetSecret(secretName string) error {
|
||||
// ListSecrets lists all secrets in the current vault
|
||||
func (cli *CLIInstance) ListSecrets(jsonOutput bool, filter string) error {
|
||||
// Get current vault
|
||||
vault, err := secret.GetCurrentVault(cli.fs, cli.stateDir)
|
||||
vlt, err := vault.GetCurrentVault(cli.fs, cli.stateDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
secrets, err := vault.ListSecrets()
|
||||
// Get list of secrets
|
||||
secrets, err := vlt.ListSecrets()
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("failed to list secrets: %w", err)
|
||||
}
|
||||
|
||||
// Filter secrets if filter is provided
|
||||
@@ -183,7 +185,7 @@ func (cli *CLIInstance) ListSecrets(jsonOutput bool, filter string) error {
|
||||
}
|
||||
|
||||
// Try to get metadata using GetSecretObject
|
||||
if secretObj, err := vault.GetSecretObject(secretName); err == nil {
|
||||
if secretObj, err := vlt.GetSecretObject(secretName); err == nil {
|
||||
metadata := secretObj.GetMetadata()
|
||||
secretInfo["created_at"] = metadata.CreatedAt
|
||||
secretInfo["updated_at"] = metadata.UpdatedAt
|
||||
@@ -209,7 +211,7 @@ func (cli *CLIInstance) ListSecrets(jsonOutput bool, filter string) error {
|
||||
// Pretty table output
|
||||
if len(filteredSecrets) == 0 {
|
||||
if filter != "" {
|
||||
fmt.Printf("No secrets found in vault '%s' matching filter '%s'.\n", vault.Name, filter)
|
||||
fmt.Printf("No secrets found in vault '%s' matching filter '%s'.\n", vlt.GetName(), filter)
|
||||
} else {
|
||||
fmt.Println("No secrets found in current vault.")
|
||||
fmt.Println("Run 'secret add <name>' to create one.")
|
||||
@@ -219,16 +221,16 @@ func (cli *CLIInstance) ListSecrets(jsonOutput bool, filter string) error {
|
||||
|
||||
// Get current vault name for display
|
||||
if filter != "" {
|
||||
fmt.Printf("Secrets in vault '%s' matching '%s':\n\n", vault.Name, filter)
|
||||
fmt.Printf("Secrets in vault '%s' matching '%s':\n\n", vlt.GetName(), filter)
|
||||
} else {
|
||||
fmt.Printf("Secrets in vault '%s':\n\n", vault.Name)
|
||||
fmt.Printf("Secrets in vault '%s':\n\n", vlt.GetName())
|
||||
}
|
||||
fmt.Printf("%-40s %-20s\n", "NAME", "LAST UPDATED")
|
||||
fmt.Printf("%-40s %-20s\n", "----", "------------")
|
||||
|
||||
for _, secretName := range filteredSecrets {
|
||||
lastUpdated := "unknown"
|
||||
if secretObj, err := vault.GetSecretObject(secretName); err == nil {
|
||||
if secretObj, err := vlt.GetSecretObject(secretName); err == nil {
|
||||
metadata := secretObj.GetMetadata()
|
||||
lastUpdated = metadata.UpdatedAt.Format("2006-01-02 15:04")
|
||||
}
|
||||
@@ -248,7 +250,7 @@ func (cli *CLIInstance) ListSecrets(jsonOutput bool, filter string) error {
|
||||
// ImportSecret imports a secret from a file
|
||||
func (cli *CLIInstance) ImportSecret(secretName, sourceFile string, force bool) error {
|
||||
// Get current vault
|
||||
vault, err := secret.GetCurrentVault(cli.fs, cli.stateDir)
|
||||
vlt, err := vault.GetCurrentVault(cli.fs, cli.stateDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -260,7 +262,7 @@ func (cli *CLIInstance) ImportSecret(secretName, sourceFile string, force bool)
|
||||
}
|
||||
|
||||
// Store the secret in the vault
|
||||
if err := vault.AddSecret(secretName, value, force); err != nil {
|
||||
if err := vlt.AddSecret(secretName, value, force); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -4,15 +4,14 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
"strings"
|
||||
|
||||
"git.eeqj.de/sneak/secret/internal/secret"
|
||||
"git.eeqj.de/sneak/secret/internal/vault"
|
||||
"git.eeqj.de/sneak/secret/pkg/agehd"
|
||||
"github.com/spf13/afero"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/tyler-smith/go-bip39"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
func newVaultCmd() *cobra.Command {
|
||||
@@ -38,7 +37,7 @@ func newVaultListCmd() *cobra.Command {
|
||||
jsonOutput, _ := cmd.Flags().GetBool("json")
|
||||
|
||||
cli := NewCLIInstance()
|
||||
return cli.VaultList(jsonOutput)
|
||||
return cli.ListVaults(jsonOutput)
|
||||
},
|
||||
}
|
||||
|
||||
@@ -53,7 +52,7 @@ func newVaultCreateCmd() *cobra.Command {
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
cli := NewCLIInstance()
|
||||
return cli.VaultCreate(args[0])
|
||||
return cli.CreateVault(args[0])
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -65,7 +64,7 @@ func newVaultSelectCmd() *cobra.Command {
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
cli := NewCLIInstance()
|
||||
return cli.VaultSelect(args[0])
|
||||
return cli.SelectVault(args[0])
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -83,166 +82,155 @@ func newVaultImportCmd() *cobra.Command {
|
||||
}
|
||||
|
||||
cli := NewCLIInstance()
|
||||
return cli.Import(vaultName)
|
||||
return cli.VaultImport(vaultName)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// VaultList lists available vaults
|
||||
func (cli *CLIInstance) VaultList(jsonOutput bool) error {
|
||||
vaults, err := secret.ListVaults(cli.fs, cli.stateDir)
|
||||
// ListVaults lists all available vaults
|
||||
func (cli *CLIInstance) ListVaults(jsonOutput bool) error {
|
||||
vaults, err := vault.ListVaults(cli.fs, cli.stateDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if jsonOutput {
|
||||
// JSON output
|
||||
output := map[string]interface{}{
|
||||
"vaults": vaults,
|
||||
// Get current vault name for context
|
||||
currentVault := ""
|
||||
if currentVlt, err := vault.GetCurrentVault(cli.fs, cli.stateDir); err == nil {
|
||||
currentVault = currentVlt.GetName()
|
||||
}
|
||||
|
||||
jsonBytes, err := json.MarshalIndent(output, "", " ")
|
||||
result := map[string]interface{}{
|
||||
"vaults": vaults,
|
||||
"current_vault": currentVault,
|
||||
}
|
||||
|
||||
jsonBytes, err := json.MarshalIndent(result, "", " ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal JSON: %w", err)
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println(string(jsonBytes))
|
||||
} else {
|
||||
// Pretty table output
|
||||
// Text output
|
||||
fmt.Println("Available vaults:")
|
||||
if len(vaults) == 0 {
|
||||
fmt.Println("No vaults found.")
|
||||
fmt.Println("Run 'secret init' to create the default vault.")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get current vault for highlighting
|
||||
currentVault, err := secret.GetCurrentVault(cli.fs, cli.stateDir)
|
||||
if err != nil {
|
||||
fmt.Printf("%-20s %s\n", "VAULT", "STATUS")
|
||||
fmt.Printf("%-20s %s\n", "-----", "------")
|
||||
|
||||
for _, vault := range vaults {
|
||||
fmt.Printf("%-20s %s\n", vault, "")
|
||||
}
|
||||
fmt.Println(" (none)")
|
||||
} else {
|
||||
fmt.Printf("%-20s %s\n", "VAULT", "STATUS")
|
||||
fmt.Printf("%-20s %s\n", "-----", "------")
|
||||
// Try to get current vault for marking
|
||||
currentVault := ""
|
||||
if currentVlt, err := vault.GetCurrentVault(cli.fs, cli.stateDir); err == nil {
|
||||
currentVault = currentVlt.GetName()
|
||||
}
|
||||
|
||||
for _, vault := range vaults {
|
||||
status := ""
|
||||
if vault == currentVault.Name {
|
||||
status = "(current)"
|
||||
for _, vaultName := range vaults {
|
||||
if vaultName == currentVault {
|
||||
fmt.Printf(" %s (current)\n", vaultName)
|
||||
} else {
|
||||
fmt.Printf(" %s\n", vaultName)
|
||||
}
|
||||
fmt.Printf("%-20s %s\n", vault, status)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("\nTotal: %d vault(s)\n", len(vaults))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// VaultCreate creates a new vault
|
||||
func (cli *CLIInstance) VaultCreate(name string) error {
|
||||
_, err := secret.CreateVault(cli.fs, cli.stateDir, name)
|
||||
return err
|
||||
}
|
||||
// CreateVault creates a new vault
|
||||
func (cli *CLIInstance) CreateVault(name string) error {
|
||||
secret.Debug("Creating new vault", "name", name, "state_dir", cli.stateDir)
|
||||
|
||||
// VaultSelect selects a vault as current
|
||||
func (cli *CLIInstance) VaultSelect(name string) error {
|
||||
return secret.SelectVault(cli.fs, cli.stateDir, name)
|
||||
}
|
||||
|
||||
// Import imports a mnemonic into a vault
|
||||
func (cli *CLIInstance) Import(vaultName string) error {
|
||||
var mnemonicStr string
|
||||
|
||||
// Check if mnemonic is set in environment variable
|
||||
if envMnemonic := os.Getenv(secret.EnvMnemonic); envMnemonic != "" {
|
||||
mnemonicStr = envMnemonic
|
||||
} else {
|
||||
// Read mnemonic from stdin using shared line reader
|
||||
var err error
|
||||
mnemonicStr, err = readLineFromStdin("Enter your BIP39 mnemonic phrase: ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read mnemonic: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if mnemonicStr == "" {
|
||||
return fmt.Errorf("mnemonic cannot be empty")
|
||||
}
|
||||
|
||||
// Validate the mnemonic using BIP39
|
||||
if !bip39.IsMnemonicValid(mnemonicStr) {
|
||||
return fmt.Errorf("invalid BIP39 mnemonic phrase\nRun 'secret generate mnemonic' to create a valid mnemonic")
|
||||
}
|
||||
|
||||
return cli.importMnemonic(vaultName, mnemonicStr)
|
||||
}
|
||||
|
||||
// importMnemonic imports a BIP39 mnemonic into the specified vault
|
||||
func (cli *CLIInstance) importMnemonic(vaultName, mnemonic string) error {
|
||||
// Derive long-term keypair from mnemonic
|
||||
ltIdentity, err := agehd.DeriveIdentity(mnemonic, 0)
|
||||
vlt, err := vault.CreateVault(cli.fs, cli.stateDir, name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to derive long-term key from mnemonic: %w", err)
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("Created vault '%s'\n", vlt.GetName())
|
||||
return nil
|
||||
}
|
||||
|
||||
// SelectVault selects a vault as the current one
|
||||
func (cli *CLIInstance) SelectVault(name string) error {
|
||||
if err := vault.SelectVault(cli.fs, cli.stateDir, name); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("Selected vault '%s' as current\n", name)
|
||||
return nil
|
||||
}
|
||||
|
||||
// VaultImport imports a mnemonic into a specific vault
|
||||
func (cli *CLIInstance) VaultImport(vaultName string) error {
|
||||
secret.Debug("Importing mnemonic into vault", "vault_name", vaultName, "state_dir", cli.stateDir)
|
||||
|
||||
// Get the specific vault by name
|
||||
vlt := vault.NewVault(cli.fs, vaultName, cli.stateDir)
|
||||
|
||||
// Check if vault exists
|
||||
stateDir := cli.GetStateDir()
|
||||
vaultDir := filepath.Join(stateDir, "vaults.d", vaultName)
|
||||
vaultDir, err := vlt.GetDirectory()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
exists, err := afero.DirExists(cli.fs, vaultDir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to check if vault exists: %w", err)
|
||||
}
|
||||
if !exists {
|
||||
return fmt.Errorf("vault %s does not exist", vaultName)
|
||||
return fmt.Errorf("vault '%s' does not exist", vaultName)
|
||||
}
|
||||
|
||||
// Get mnemonic from environment
|
||||
mnemonic := os.Getenv(secret.EnvMnemonic)
|
||||
if mnemonic == "" {
|
||||
return fmt.Errorf("SB_SECRET_MNEMONIC environment variable not set")
|
||||
}
|
||||
|
||||
// Validate the mnemonic
|
||||
mnemonicWords := strings.Fields(mnemonic)
|
||||
secret.Debug("Validating BIP39 mnemonic", "word_count", len(mnemonicWords))
|
||||
if !bip39.IsMnemonicValid(mnemonic) {
|
||||
return fmt.Errorf("invalid BIP39 mnemonic")
|
||||
}
|
||||
|
||||
// Derive long-term key from mnemonic
|
||||
secret.Debug("Deriving long-term key from mnemonic", "index", 0)
|
||||
ltIdentity, err := agehd.DeriveIdentity(mnemonic, 0)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to derive long-term key: %w", err)
|
||||
}
|
||||
|
||||
// Store long-term public key in vault
|
||||
ltPubKey := ltIdentity.Recipient().String()
|
||||
if err := afero.WriteFile(cli.fs, filepath.Join(vaultDir, "pub.age"), []byte(ltPubKey), 0600); err != nil {
|
||||
return fmt.Errorf("failed to write long-term public key: %w", err)
|
||||
ltPublicKey := ltIdentity.Recipient().String()
|
||||
secret.Debug("Storing long-term public key", "pubkey", ltPublicKey, "vault_dir", vaultDir)
|
||||
|
||||
pubKeyPath := fmt.Sprintf("%s/pub.age", vaultDir)
|
||||
if err := afero.WriteFile(cli.fs, pubKeyPath, []byte(ltPublicKey), 0600); err != nil {
|
||||
return fmt.Errorf("failed to store long-term public key: %w", err)
|
||||
}
|
||||
|
||||
// Get the vault instance and unlock it
|
||||
vault := secret.NewVault(cli.fs, vaultName, cli.stateDir)
|
||||
vault.Unlock(ltIdentity)
|
||||
// Get passphrase from environment variable
|
||||
passphraseStr := os.Getenv(secret.EnvUnlockPassphrase)
|
||||
if passphraseStr == "" {
|
||||
return fmt.Errorf("SB_UNLOCK_PASSPHRASE environment variable not set")
|
||||
}
|
||||
|
||||
secret.Debug("Using unlock passphrase from environment variable")
|
||||
|
||||
// Unlock the vault with the derived long-term key
|
||||
vlt.Unlock(ltIdentity)
|
||||
|
||||
// Create passphrase-protected unlock key
|
||||
secret.Debug("Creating passphrase-protected unlock key")
|
||||
passphraseKey, err := vlt.CreatePassphraseKey(passphraseStr)
|
||||
if err != nil {
|
||||
secret.Debug("Failed to create unlock key", "error", err)
|
||||
return fmt.Errorf("failed to create unlock key: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Successfully imported mnemonic into vault '%s'\n", vaultName)
|
||||
fmt.Printf("Long-term public key: %s\n", ltPubKey)
|
||||
|
||||
// Try to create unlock key only if running interactively
|
||||
if term.IsTerminal(int(syscall.Stderr)) {
|
||||
// Get or create passphrase for unlock key
|
||||
var passphraseStr string
|
||||
if envPassphrase := os.Getenv(secret.EnvUnlockPassphrase); envPassphrase != "" {
|
||||
passphraseStr = envPassphrase
|
||||
} else {
|
||||
// Use secure passphrase input with confirmation
|
||||
passphraseStr, err = readSecurePassphrase("Enter passphrase for unlock key: ")
|
||||
if err != nil {
|
||||
fmt.Printf("Warning: Failed to create unlock key: %v\n", err)
|
||||
fmt.Printf("You can create unlock keys later with 'secret keys add passphrase'\n")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Create passphrase-protected unlock key (vault is now unlocked)
|
||||
passphraseKey, err := vault.CreatePassphraseKey(passphraseStr)
|
||||
if err != nil {
|
||||
fmt.Printf("Warning: Failed to create unlock key: %v\n", err)
|
||||
fmt.Printf("You can create unlock keys later with 'secret keys add passphrase'\n")
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Printf("Unlock key ID: %s\n", passphraseKey.GetMetadata().ID)
|
||||
} else {
|
||||
fmt.Printf("Running in non-interactive mode - unlock key not created\n")
|
||||
fmt.Printf("You can create unlock keys later with 'secret keys add passphrase'\n")
|
||||
}
|
||||
fmt.Printf("Long-term public key: %s\n", ltPublicKey)
|
||||
fmt.Printf("Unlock key ID: %s\n", passphraseKey.GetID())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user