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:
2025-07-26 21:58:57 +02:00
parent a6f24e9581
commit 75c3d22b62
9 changed files with 558 additions and 90 deletions

View File

@@ -39,10 +39,12 @@ func newAddCmd() *cobra.Command {
}
func newGetCmd() *cobra.Command {
cli := NewCLIInstance()
cmd := &cobra.Command{
Use: "get <secret-name>",
Short: "Retrieve a secret from the vault",
Args: cobra.ExactArgs(1),
Use: "get <secret-name>",
Short: "Retrieve a secret from the vault",
Args: cobra.ExactArgs(1),
ValidArgsFunction: getSecretNamesCompletionFunc(cli.fs, cli.stateDir),
RunE: func(cmd *cobra.Command, args []string) error {
version, _ := cmd.Flags().GetString("version")
cli := NewCLIInstance()
@@ -108,13 +110,15 @@ func newImportCmd() *cobra.Command {
}
func newRemoveCmd() *cobra.Command {
cli := NewCLIInstance()
cmd := &cobra.Command{
Use: "remove <secret-name>",
Aliases: []string{"rm"},
Short: "Remove a secret from the vault",
Long: `Remove a secret and all its versions from the current vault. This action is permanent and ` +
`cannot be undone.`,
Args: cobra.ExactArgs(1),
Args: cobra.ExactArgs(1),
ValidArgsFunction: getSecretNamesCompletionFunc(cli.fs, cli.stateDir),
RunE: func(cmd *cobra.Command, args []string) error {
cli := NewCLIInstance()
@@ -126,6 +130,7 @@ func newRemoveCmd() *cobra.Command {
}
func newMoveCmd() *cobra.Command {
cli := NewCLIInstance()
cmd := &cobra.Command{
Use: "move <source> <destination>",
Aliases: []string{"mv", "rename"},
@@ -133,6 +138,14 @@ func newMoveCmd() *cobra.Command {
Long: `Move or rename a secret within the current vault. ` +
`If the destination already exists, the operation will fail.`,
Args: cobra.ExactArgs(2), //nolint:mnd // Command requires exactly 2 arguments: source and destination
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
// Only complete the first argument (source)
if len(args) == 0 {
return getSecretNamesCompletionFunc(cli.fs, cli.stateDir)(cmd, args, toComplete)
}
return nil, cobra.ShellCompDirectiveNoFileComp
},
RunE: func(cmd *cobra.Command, args []string) error {
cli := NewCLIInstance()
@@ -360,20 +373,21 @@ func (cli *Instance) ListSecrets(cmd *cobra.Command, jsonOutput bool, quietOutpu
return fmt.Errorf("failed to marshal JSON: %w", err)
}
cmd.Println(string(jsonBytes))
_, _ = fmt.Fprintln(cmd.OutOrStdout(), string(jsonBytes))
} else if quietOutput {
// Quiet output - just secret names
for _, secretName := range filteredSecrets {
cmd.Println(secretName)
_, _ = fmt.Fprintln(cmd.OutOrStdout(), secretName)
}
} else {
// Pretty table output
out := cmd.OutOrStdout()
if len(filteredSecrets) == 0 {
if filter != "" {
cmd.Printf("No secrets found in vault '%s' matching filter '%s'.\n", vlt.GetName(), filter)
_, _ = fmt.Fprintf(out, "No secrets found in vault '%s' matching filter '%s'.\n", vlt.GetName(), filter)
} else {
cmd.Println("No secrets found in current vault.")
cmd.Println("Run 'secret add <name>' to create one.")
_, _ = fmt.Fprintln(out, "No secrets found in current vault.")
_, _ = fmt.Fprintln(out, "Run 'secret add <name>' to create one.")
}
return nil
@@ -381,12 +395,25 @@ func (cli *Instance) ListSecrets(cmd *cobra.Command, jsonOutput bool, quietOutpu
// Get current vault name for display
if filter != "" {
cmd.Printf("Secrets in vault '%s' matching '%s':\n\n", vlt.GetName(), filter)
_, _ = fmt.Fprintf(out, "Secrets in vault '%s' matching '%s':\n\n", vlt.GetName(), filter)
} else {
cmd.Printf("Secrets in vault '%s':\n\n", vlt.GetName())
_, _ = fmt.Fprintf(out, "Secrets in vault '%s':\n\n", vlt.GetName())
}
cmd.Printf("%-40s %-20s\n", "NAME", "LAST UPDATED")
cmd.Printf("%-40s %-20s\n", "----", "------------")
// Calculate the maximum name length for proper column alignment
maxNameLen := len("NAME") // Start with header length
for _, secretName := range filteredSecrets {
if len(secretName) > maxNameLen {
maxNameLen = len(secretName)
}
}
// Add some padding
maxNameLen += 2
// Print headers with dynamic width
nameFormat := fmt.Sprintf("%%-%ds", maxNameLen)
_, _ = fmt.Fprintf(out, nameFormat+" %-20s\n", "NAME", "LAST UPDATED")
_, _ = fmt.Fprintf(out, nameFormat+" %-20s\n", strings.Repeat("-", len("NAME")), "------------")
for _, secretName := range filteredSecrets {
lastUpdated := "unknown"
@@ -394,14 +421,14 @@ func (cli *Instance) ListSecrets(cmd *cobra.Command, jsonOutput bool, quietOutpu
metadata := secretObj.GetMetadata()
lastUpdated = metadata.UpdatedAt.Format("2006-01-02 15:04")
}
cmd.Printf("%-40s %-20s\n", secretName, lastUpdated)
_, _ = fmt.Fprintf(out, nameFormat+" %-20s\n", secretName, lastUpdated)
}
cmd.Printf("\nTotal: %d secret(s)", len(filteredSecrets))
_, _ = fmt.Fprintf(out, "\nTotal: %d secret(s)", len(filteredSecrets))
if filter != "" {
cmd.Printf(" (filtered from %d)", len(secrets))
_, _ = fmt.Fprintf(out, " (filtered from %d)", len(secrets))
}
cmd.Println()
_, _ = fmt.Fprintln(out)
}
return nil