passes tests now!

This commit is contained in:
2025-06-20 07:24:48 -07:00
parent 0b31fba663
commit 004dce5472
19 changed files with 165 additions and 756 deletions

View File

@@ -9,6 +9,7 @@ import (
"git.eeqj.de/sneak/secret/internal/secret"
"github.com/spf13/afero"
"github.com/spf13/cobra"
"golang.org/x/term"
)
@@ -19,6 +20,7 @@ var stdinScanner *bufio.Scanner
type CLIInstance struct {
fs afero.Fs
stateDir string
cmd *cobra.Command
}
// NewCLIInstance creates a new CLI instance with the real filesystem

View File

@@ -22,6 +22,7 @@ func newEncryptCmd() *cobra.Command {
outputFile, _ := cmd.Flags().GetString("output")
cli := NewCLIInstance()
cli.cmd = cmd
return cli.Encrypt(args[0], inputFile, outputFile)
},
}
@@ -42,6 +43,7 @@ func newDecryptCmd() *cobra.Command {
outputFile, _ := cmd.Flags().GetString("output")
cli := NewCLIInstance()
cli.cmd = cmd
return cli.Decrypt(args[0], inputFile, outputFile)
},
}
@@ -127,7 +129,7 @@ func (cli *CLIInstance) Encrypt(secretName, inputFile, outputFile string) error
}
// Set up output writer
var output io.Writer = os.Stdout
var output io.Writer = cli.cmd.OutOrStdout()
if outputFile != "" {
file, err := cli.fs.Create(outputFile)
if err != nil {
@@ -213,7 +215,7 @@ func (cli *CLIInstance) Decrypt(secretName, inputFile, outputFile string) error
}
// Set up output writer
var output io.Writer = os.Stdout
var output io.Writer = cli.cmd.OutOrStdout()
if outputFile != "" {
file, err := cli.fs.Create(outputFile)
if err != nil {

View File

@@ -11,6 +11,7 @@ import (
"time"
"git.eeqj.de/sneak/secret/internal/cli"
"git.eeqj.de/sneak/secret/internal/secret"
"git.eeqj.de/sneak/secret/pkg/agehd"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -50,10 +51,13 @@ func TestMain(m *testing.M) {
// all functionality of the secret manager using a real filesystem in a temporary directory.
// This test serves as both validation and documentation of the program's behavior.
func TestSecretManagerIntegration(t *testing.T) {
// Enable debug logging to diagnose test failures
// Enable debug logging to diagnose issues
os.Setenv("GODEBUG", "berlin.sneak.pkg.secret")
defer os.Unsetenv("GODEBUG")
// Reinitialize debug logging to pick up the environment variable change
secret.InitDebugLogging()
// Test configuration
testMnemonic := "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
testPassphrase := "test-passphrase-123"
@@ -349,10 +353,18 @@ func test01Initialize(t *testing.T, tempDir, testMnemonic, testPassphrase string
currentUnlockerFile := filepath.Join(defaultVaultDir, "current-unlocker")
verifyFileExists(t, currentUnlockerFile)
// Read the current-unlocker file to see what it contains
currentUnlockerContent := readFile(t, currentUnlockerFile)
// The file likely contains the unlocker ID
assert.Contains(t, string(currentUnlockerContent), "passphrase", "current unlocker should be passphrase type")
// Read the current-unlocker symlink to see what it points to
symlinkTarget, err := os.Readlink(currentUnlockerFile)
if err != nil {
t.Logf("DEBUG: failed to read symlink %s: %v", currentUnlockerFile, err)
// Fallback to reading as file if it's not a symlink
currentUnlockerContent := readFile(t, currentUnlockerFile)
t.Logf("DEBUG: current-unlocker file content: %q", string(currentUnlockerContent))
assert.Contains(t, string(currentUnlockerContent), "passphrase", "current unlocker should be passphrase type")
} else {
t.Logf("DEBUG: current-unlocker symlink points to: %q", symlinkTarget)
assert.Contains(t, symlinkTarget, "passphrase", "current unlocker should be passphrase type")
}
// Verify vault-metadata.json in vault
vaultMetadata := filepath.Join(defaultVaultDir, "vault-metadata.json")
@@ -1006,6 +1018,7 @@ func test13UnlockerManagement(t *testing.T, tempDir, testMnemonic string, runSec
// List unlockers
output, err := runSecret("unlockers", "list")
require.NoError(t, err, "unlockers list should succeed")
t.Logf("DEBUG: unlockers list output: %q", output)
// Should have the passphrase unlocker created during init
assert.Contains(t, output, "passphrase", "should have passphrase unlocker")
@@ -1034,6 +1047,7 @@ func test13UnlockerManagement(t *testing.T, tempDir, testMnemonic string, runSec
}
// Note: This might still show 1 if the implementation doesn't support multiple passphrase unlockers
// Just verify we have at least 1
t.Logf("DEBUG: passphrase count: %d, output lines: %v", passphraseCount, lines)
assert.GreaterOrEqual(t, passphraseCount, 1, "should have at least 1 passphrase unlocker")
// Test JSON output
@@ -1309,6 +1323,7 @@ func test18AgeKeyOperations(t *testing.T, tempDir, secretPath, testMnemonic stri
"SB_SECRET_MNEMONIC": testMnemonic,
}, "encrypt", "encryption/key", "--input", testFile)
require.NoError(t, err, "encrypt to stdout should succeed")
t.Logf("DEBUG: encrypt output: %q", output)
assert.Contains(t, output, "age-encryption.org", "should output age format")
// Test that the age key was stored as a secret
@@ -1804,10 +1819,10 @@ func test28VaultMetadata(t *testing.T, tempDir string) {
require.NoError(t, err, "default vault metadata should be valid JSON")
// Verify required fields
assert.Equal(t, "default", defaultMetadata["name"])
assert.Equal(t, float64(0), defaultMetadata["derivation_index"])
assert.Contains(t, defaultMetadata, "createdAt")
assert.Contains(t, defaultMetadata, "public_key_hash")
assert.Contains(t, defaultMetadata, "mnemonic_family_hash")
// Check work vault metadata
workMetadataPath := filepath.Join(tempDir, "vaults.d", "work", "vault-metadata.json")
@@ -1819,13 +1834,12 @@ func test28VaultMetadata(t *testing.T, tempDir string) {
require.NoError(t, err, "work vault metadata should be valid JSON")
// Work vault should have different derivation index
assert.Equal(t, "work", workMetadata["name"])
workIndex := workMetadata["derivation_index"].(float64)
assert.NotEqual(t, float64(0), workIndex, "work vault should have non-zero derivation index")
// Both vaults created with same mnemonic should have same public_key_hash
assert.Equal(t, defaultMetadata["public_key_hash"], workMetadata["public_key_hash"],
"vaults from same mnemonic should have same public_key_hash")
// Both vaults created with same mnemonic should have same mnemonic_family_hash
assert.Equal(t, defaultMetadata["mnemonic_family_hash"], workMetadata["mnemonic_family_hash"],
"vaults from same mnemonic should have same mnemonic_family_hash")
}
func test29SymlinkHandling(t *testing.T, tempDir, secretPath, testMnemonic string) {
@@ -2025,7 +2039,7 @@ func test31EnvMnemonicUsesVaultDerivationIndex(t *testing.T, tempDir, secretPath
// This is the expected behavior with the current bug
assert.Error(t, err, "get should fail due to wrong derivation index")
assert.Contains(t, getOutput, "failed to decrypt", "should indicate decryption failure")
assert.Contains(t, getOutput, "derived public key does not match vault", "should indicate key derivation failure")
// Document what should happen when the bug is fixed
t.Log("When the bug is fixed, GetValue should read vault metadata and use derivation index 1")

View File

@@ -4,7 +4,6 @@ import (
"encoding/json"
"fmt"
"io"
"os"
"strings"
"git.eeqj.de/sneak/secret/internal/secret"
@@ -25,6 +24,7 @@ func newAddCmd() *cobra.Command {
secret.Debug("Got force flag", "force", force)
cli := NewCLIInstance()
cli.cmd = cmd // Set the command for stdin access
secret.Debug("Created CLI instance, calling AddSecret")
return cli.AddSecret(args[0], force)
},
@@ -110,7 +110,7 @@ func (cli *CLIInstance) AddSecret(secretName string, force bool) error {
// Read secret value from stdin
secret.Debug("Reading secret value from stdin")
value, err := io.ReadAll(os.Stdin)
value, err := io.ReadAll(cli.cmd.InOrStdin())
if err != nil {
return fmt.Errorf("failed to read secret value: %w", err)
}
@@ -167,6 +167,15 @@ func (cli *CLIInstance) GetSecretWithVersion(cmd *cobra.Command, secretName stri
// Print the secret value to stdout
cmd.Print(string(value))
secret.Debug("Printed value to cmd")
// Debug: Log what we're actually printing
secret.Debug("Secret retrieval debug info",
"secretName", secretName,
"version", version,
"valueLength", len(value),
"valueAsString", string(value),
"isEmpty", len(value) == 0)
return nil
}

View File

@@ -45,6 +45,11 @@ func ExecuteCommandInProcess(args []string, stdin string, env map[string]string)
output := buf.String()
secret.Debug("Command execution completed", "error", err, "outputLength", len(output), "output", output)
// Add debug info for troubleshooting
if len(output) == 0 && err == nil {
secret.Debug("Warning: Command executed successfully but produced no output", "args", args)
}
// Restore environment
for k, v := range savedEnv {
if v == "" {

View File

@@ -40,6 +40,7 @@ func newUnlockersListCmd() *cobra.Command {
jsonOutput, _ := cmd.Flags().GetBool("json")
cli := NewCLIInstance()
cli.cmd = cmd
return cli.UnlockersList(jsonOutput)
},
}
@@ -201,31 +202,31 @@ func (cli *CLIInstance) UnlockersList(jsonOutput bool) error {
return fmt.Errorf("failed to marshal JSON: %w", err)
}
fmt.Println(string(jsonBytes))
cli.cmd.Println(string(jsonBytes))
} else {
// Pretty table output
if len(unlockers) == 0 {
fmt.Println("No unlockers found in current vault.")
fmt.Println("Run 'secret unlockers add passphrase' to create one.")
cli.cmd.Println("No unlockers found in current vault.")
cli.cmd.Println("Run 'secret unlockers add passphrase' to create one.")
return nil
}
fmt.Printf("%-18s %-12s %-20s %s\n", "UNLOCKER ID", "TYPE", "CREATED", "FLAGS")
fmt.Printf("%-18s %-12s %-20s %s\n", "-----------", "----", "-------", "-----")
cli.cmd.Printf("%-18s %-12s %-20s %s\n", "UNLOCKER ID", "TYPE", "CREATED", "FLAGS")
cli.cmd.Printf("%-18s %-12s %-20s %s\n", "-----------", "----", "-------", "-----")
for _, unlocker := range unlockers {
flags := ""
if len(unlocker.Flags) > 0 {
flags = strings.Join(unlocker.Flags, ",")
}
fmt.Printf("%-18s %-12s %-20s %s\n",
cli.cmd.Printf("%-18s %-12s %-20s %s\n",
unlocker.ID,
unlocker.Type,
unlocker.CreatedAt.Format("2006-01-02 15:04:05"),
flags)
}
fmt.Printf("\nTotal: %d unlocker(s)\n", len(unlockers))
cli.cmd.Printf("\nTotal: %d unlocker(s)\n", len(unlockers))
}
return nil

View File

@@ -223,13 +223,17 @@ func (cli *CLIInstance) VaultImport(cmd *cobra.Command, vaultName string) error
return fmt.Errorf("failed to store long-term public key: %w", err)
}
// Calculate public key hash from index 0 (same for all vaults with this mnemonic)
// Calculate public key hash from the actual derivation index being used
// This is used to verify that the derived key matches what was stored
publicKeyHash := vault.ComputeDoubleSHA256([]byte(ltIdentity.Recipient().String()))
// Calculate family hash from index 0 (same for all vaults with this mnemonic)
// This is used to identify which vaults belong to the same mnemonic family
identity0, err := agehd.DeriveIdentity(mnemonic, 0)
if err != nil {
return fmt.Errorf("failed to derive identity for index 0: %w", err)
}
publicKeyHash := vault.ComputeDoubleSHA256([]byte(identity0.Recipient().String()))
familyHash := vault.ComputeDoubleSHA256([]byte(identity0.Recipient().String()))
// Load existing metadata
existingMetadata, err := vault.LoadVaultMetadata(cli.fs, vaultDir)
@@ -243,6 +247,7 @@ func (cli *CLIInstance) VaultImport(cmd *cobra.Command, vaultName string) error
// Update metadata with new derivation info
existingMetadata.DerivationIndex = derivationIndex
existingMetadata.PublicKeyHash = publicKeyHash
existingMetadata.MnemonicFamilyHash = familyHash
if err := vault.SaveVaultMetadata(cli.fs, vaultDir, existingMetadata); err != nil {
secret.Debug("Failed to save vault metadata", "error", err)

View File

@@ -2,7 +2,6 @@ package cli
import (
"fmt"
"os"
"path/filepath"
"strings"
"text/tabwriter"
@@ -110,7 +109,7 @@ func (cli *CLIInstance) ListVersions(cmd *cobra.Command, secretName string) erro
}
// Create table writer
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
w := tabwriter.NewWriter(cmd.OutOrStdout(), 0, 0, 2, ' ', 0)
fmt.Fprintln(w, "VERSION\tCREATED\tSTATUS\tNOT_BEFORE\tNOT_AFTER")
// Load and display each version's metadata
@@ -185,15 +184,8 @@ func (cli *CLIInstance) PromoteVersion(cmd *cobra.Command, secretName string, ve
return fmt.Errorf("version '%s' not found for secret '%s'", version, secretName)
}
// Update the current symlink
currentLink := filepath.Join(secretDir, "current")
// Remove existing symlink
_ = cli.fs.Remove(currentLink)
// Create new symlink to the selected version
relativePath := filepath.Join("versions", version)
if err := afero.WriteFile(cli.fs, currentLink, []byte(relativePath), 0644); err != nil {
// Update the current symlink using the proper function
if err := secret.SetCurrentVersion(cli.fs, secretDir, version); err != nil {
return fmt.Errorf("failed to update current version: %w", err)
}