Fix vault creation to require mnemonic and set up initial unlocker
- Vault creation now prompts for mnemonic if not in environment - Automatically creates passphrase unlocker during vault creation - Prevents 'missing public key' error when adding secrets to new vaults - Updates tests to reflect new vault creation flow
This commit is contained in:
@@ -66,10 +66,13 @@ func newVaultCreateCmd() *cobra.Command {
|
||||
}
|
||||
|
||||
func newVaultSelectCmd() *cobra.Command {
|
||||
cli := NewCLIInstance()
|
||||
|
||||
return &cobra.Command{
|
||||
Use: "select <name>",
|
||||
Short: "Select a vault as current",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Use: "select <name>",
|
||||
Short: "Select a vault as current",
|
||||
Args: cobra.ExactArgs(1),
|
||||
ValidArgsFunction: getVaultNamesCompletionFunc(cli.fs, cli.stateDir),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
cli := NewCLIInstance()
|
||||
|
||||
@@ -79,11 +82,14 @@ func newVaultSelectCmd() *cobra.Command {
|
||||
}
|
||||
|
||||
func newVaultImportCmd() *cobra.Command {
|
||||
cli := NewCLIInstance()
|
||||
|
||||
return &cobra.Command{
|
||||
Use: "import <vault-name>",
|
||||
Short: "Import a mnemonic into a vault",
|
||||
Long: `Import a BIP39 mnemonic phrase into the specified vault (default if not specified).`,
|
||||
Args: cobra.MaximumNArgs(1),
|
||||
Use: "import <vault-name>",
|
||||
Short: "Import a mnemonic into a vault",
|
||||
Long: `Import a BIP39 mnemonic phrase into the specified vault (default if not specified).`,
|
||||
Args: cobra.MaximumNArgs(1),
|
||||
ValidArgsFunction: getVaultNamesCompletionFunc(cli.fs, cli.stateDir),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
vaultName := "default"
|
||||
if len(args) > 0 {
|
||||
@@ -98,13 +104,15 @@ func newVaultImportCmd() *cobra.Command {
|
||||
}
|
||||
|
||||
func newVaultRemoveCmd() *cobra.Command {
|
||||
cli := NewCLIInstance()
|
||||
cmd := &cobra.Command{
|
||||
Use: "remove <name>",
|
||||
Aliases: []string{"rm"},
|
||||
Short: "Remove a vault",
|
||||
Long: `Remove a vault. Requires --force if the vault contains secrets. Will automatically ` +
|
||||
`switch to another vault if removing the currently selected one.`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
Args: cobra.ExactArgs(1),
|
||||
ValidArgsFunction: getVaultNamesCompletionFunc(cli.fs, cli.stateDir),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
force, _ := cmd.Flags().GetBool("force")
|
||||
cli := NewCLIInstance()
|
||||
@@ -171,12 +179,95 @@ func (cli *Instance) ListVaults(cmd *cobra.Command, jsonOutput bool) error {
|
||||
func (cli *Instance) CreateVault(cmd *cobra.Command, name string) error {
|
||||
secret.Debug("Creating new vault", "name", name, "state_dir", cli.stateDir)
|
||||
|
||||
// Get or prompt for mnemonic
|
||||
var mnemonicStr string
|
||||
if envMnemonic := os.Getenv(secret.EnvMnemonic); envMnemonic != "" {
|
||||
secret.Debug("Using mnemonic from environment variable")
|
||||
mnemonicStr = envMnemonic
|
||||
} else {
|
||||
secret.Debug("Prompting user for mnemonic phrase")
|
||||
// Read mnemonic securely without echo
|
||||
mnemonicBuffer, err := secret.ReadPassphrase("Enter your BIP39 mnemonic phrase: ")
|
||||
if err != nil {
|
||||
secret.Debug("Failed to read mnemonic from stdin", "error", err)
|
||||
|
||||
return fmt.Errorf("failed to read mnemonic: %w", err)
|
||||
}
|
||||
defer mnemonicBuffer.Destroy()
|
||||
|
||||
mnemonicStr = mnemonicBuffer.String()
|
||||
fmt.Fprintln(os.Stderr) // Add newline after hidden input
|
||||
}
|
||||
|
||||
if mnemonicStr == "" {
|
||||
return fmt.Errorf("mnemonic cannot be empty")
|
||||
}
|
||||
|
||||
// Validate the mnemonic
|
||||
mnemonicWords := strings.Fields(mnemonicStr)
|
||||
secret.Debug("Validating BIP39 mnemonic", "word_count", len(mnemonicWords))
|
||||
if !bip39.IsMnemonicValid(mnemonicStr) {
|
||||
return fmt.Errorf("invalid BIP39 mnemonic phrase")
|
||||
}
|
||||
|
||||
// Set mnemonic in environment for CreateVault to use
|
||||
originalMnemonic := os.Getenv(secret.EnvMnemonic)
|
||||
_ = os.Setenv(secret.EnvMnemonic, mnemonicStr)
|
||||
defer func() {
|
||||
if originalMnemonic != "" {
|
||||
_ = os.Setenv(secret.EnvMnemonic, originalMnemonic)
|
||||
} else {
|
||||
_ = os.Unsetenv(secret.EnvMnemonic)
|
||||
}
|
||||
}()
|
||||
|
||||
// Create the vault - it will handle key derivation internally
|
||||
vlt, err := vault.CreateVault(cli.fs, cli.stateDir, name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get the vault metadata to retrieve the derivation index
|
||||
vaultDir := filepath.Join(cli.stateDir, "vaults.d", name)
|
||||
metadata, err := vault.LoadVaultMetadata(cli.fs, vaultDir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load vault metadata: %w", err)
|
||||
}
|
||||
|
||||
// Derive the long-term key using the same index that CreateVault used
|
||||
ltIdentity, err := agehd.DeriveIdentity(mnemonicStr, metadata.DerivationIndex)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to derive long-term key from mnemonic: %w", err)
|
||||
}
|
||||
|
||||
// Unlock the vault with the derived long-term key
|
||||
vlt.Unlock(ltIdentity)
|
||||
|
||||
// Get or prompt for passphrase
|
||||
var passphraseBuffer *memguard.LockedBuffer
|
||||
if envPassphrase := os.Getenv(secret.EnvUnlockPassphrase); envPassphrase != "" {
|
||||
secret.Debug("Using unlock passphrase from environment variable")
|
||||
passphraseBuffer = memguard.NewBufferFromBytes([]byte(envPassphrase))
|
||||
} else {
|
||||
secret.Debug("Prompting user for unlock passphrase")
|
||||
// Use secure passphrase input with confirmation
|
||||
passphraseBuffer, err = readSecurePassphrase("Enter passphrase for unlocker: ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read passphrase: %w", err)
|
||||
}
|
||||
}
|
||||
defer passphraseBuffer.Destroy()
|
||||
|
||||
// Create passphrase-protected unlocker
|
||||
secret.Debug("Creating passphrase-protected unlocker")
|
||||
passphraseUnlocker, err := vlt.CreatePassphraseUnlocker(passphraseBuffer)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create unlocker: %w", err)
|
||||
}
|
||||
|
||||
cmd.Printf("Created vault '%s'\n", vlt.GetName())
|
||||
cmd.Printf("Long-term public key: %s\n", ltIdentity.Recipient().String())
|
||||
cmd.Printf("Unlocker ID: %s\n", passphraseUnlocker.GetID())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user