package vault import ( "fmt" "os" "path/filepath" "git.eeqj.de/sneak/secret/internal/secret" "github.com/spf13/afero" ) // Register the GetCurrentVault function with the secret package func init() { secret.RegisterGetCurrentVaultFunc(func(fs afero.Fs, stateDir string) (secret.VaultInterface, error) { return GetCurrentVault(fs, stateDir) }) } // resolveVaultSymlink resolves the currentvault symlink by changing into it and getting the absolute path func resolveVaultSymlink(fs afero.Fs, symlinkPath string) (string, error) { secret.Debug("resolveVaultSymlink starting", "symlink_path", symlinkPath) // For real filesystems, we can use os.Chdir and os.Getwd if _, ok := fs.(*afero.OsFs); ok { secret.Debug("Using real filesystem symlink resolution") // Check what the symlink points to first secret.Debug("Checking symlink target", "symlink_path", symlinkPath) linkTarget, err := os.Readlink(symlinkPath) if err != nil { secret.Debug("Failed to read symlink target", "error", err, "symlink_path", symlinkPath) // Maybe it's not a symlink, try reading as file secret.Debug("Trying to read as file instead of symlink") targetBytes, err := os.ReadFile(symlinkPath) if err != nil { secret.Debug("Failed to read as file", "error", err) return "", fmt.Errorf("failed to read vault symlink: %w", err) } targetPath := string(targetBytes) secret.Debug("Read target path from file", "target_path", targetPath) return targetPath, nil } secret.Debug("Symlink points to", "target", linkTarget) // Save current directory so we can restore it secret.Debug("Getting current directory") originalDir, err := os.Getwd() if err != nil { secret.Debug("Failed to get current directory", "error", err) return "", fmt.Errorf("failed to get current directory: %w", err) } secret.Debug("Got current directory", "original_dir", originalDir) // Change to the symlink directory secret.Debug("Changing to symlink directory", "symlink_path", symlinkPath) secret.Debug("About to call os.Chdir - this might hang if symlink is broken") if err := os.Chdir(symlinkPath); err != nil { secret.Debug("Failed to change to symlink directory", "error", err) return "", fmt.Errorf("failed to change to symlink directory: %w", err) } secret.Debug("Changed to symlink directory successfully - os.Chdir completed") // Get absolute path of current directory (which is the resolved symlink) secret.Debug("Getting absolute path of current directory") absolutePath, err := os.Getwd() if err != nil { secret.Debug("Failed to get absolute path", "error", err) // Try to restore original directory before returning error if restoreErr := os.Chdir(originalDir); restoreErr != nil { secret.Debug("Failed to restore original directory after error", "error", restoreErr) } return "", fmt.Errorf("failed to get absolute path: %w", err) } secret.Debug("Got absolute path", "absolute_path", absolutePath) // Restore original directory secret.Debug("Restoring original directory", "original_dir", originalDir) if err := os.Chdir(originalDir); err != nil { secret.Debug("Failed to restore original directory", "error", err) // Don't return error here since we got what we needed } else { secret.Debug("Restored original directory successfully") } secret.Debug("resolveVaultSymlink completed successfully", "result", absolutePath) return absolutePath, nil } // For in-memory filesystems, read the symlink content directly secret.Debug("Using in-memory filesystem symlink resolution") content, err := afero.ReadFile(fs, symlinkPath) if err != nil { secret.Debug("Failed to read symlink content", "error", err) return "", fmt.Errorf("failed to read vault symlink: %w", err) } targetPath := string(content) secret.Debug("Read symlink target from in-memory filesystem", "target_path", targetPath) return targetPath, nil } // GetCurrentVault gets the currently selected vault func GetCurrentVault(fs afero.Fs, stateDir string) (*Vault, error) { secret.Debug("Getting current vault", "state_dir", stateDir) // Check if current vault symlink exists currentVaultPath := filepath.Join(stateDir, "currentvault") secret.Debug("Checking current vault symlink", "path", currentVaultPath) _, err := fs.Stat(currentVaultPath) if err != nil { secret.Debug("Failed to stat current vault symlink", "error", err, "path", currentVaultPath) return nil, fmt.Errorf("failed to read current vault symlink: %w", err) } secret.Debug("Current vault symlink exists") // Resolve symlink to get target path secret.Debug("Resolving vault symlink") targetPath, err := resolveVaultSymlink(fs, currentVaultPath) if err != nil { secret.Debug("Failed to resolve vault symlink", "error", err) return nil, fmt.Errorf("failed to resolve vault symlink: %w", err) } secret.Debug("Resolved vault symlink", "target_path", targetPath) // Extract vault name from target path vaultName := filepath.Base(targetPath) secret.Debug("Extracted vault name", "vault_name", vaultName) secret.Debug("Current vault resolved", "vault_name", vaultName, "target_path", targetPath) // Create and return Vault instance secret.Debug("Creating NewVault instance") vault := NewVault(fs, vaultName, stateDir) secret.Debug("Created NewVault instance successfully") return vault, nil } // ListVaults returns a list of available vault names func ListVaults(fs afero.Fs, stateDir string) ([]string, error) { vaultsDir := filepath.Join(stateDir, "vaults.d") // Check if vaults directory exists exists, err := afero.DirExists(fs, vaultsDir) if err != nil { return nil, fmt.Errorf("failed to check vaults directory: %w", err) } if !exists { return []string{}, nil } // Read directory contents files, err := afero.ReadDir(fs, vaultsDir) if err != nil { return nil, fmt.Errorf("failed to read vaults directory: %w", err) } var vaults []string for _, file := range files { if file.IsDir() { vaults = append(vaults, file.Name()) } } return vaults, nil } // CreateVault creates a new vault with the given name func CreateVault(fs afero.Fs, stateDir string, name string) (*Vault, error) { secret.Debug("Creating new vault", "name", name, "state_dir", stateDir) // Create vault directory structure vaultDir := filepath.Join(stateDir, "vaults.d", name) secret.Debug("Creating vault directory structure", "vault_dir", vaultDir) // Check if vault already exists exists, err := afero.DirExists(fs, vaultDir) if err != nil { return nil, fmt.Errorf("failed to check if vault exists: %w", err) } if exists { return nil, fmt.Errorf("vault %s already exists", name) } // Create vault directory if err := fs.MkdirAll(vaultDir, 0700); err != nil { return nil, fmt.Errorf("failed to create vault directory: %w", err) } // Create subdirectories secretsDir := filepath.Join(vaultDir, "secrets.d") if err := fs.MkdirAll(secretsDir, 0700); err != nil { return nil, fmt.Errorf("failed to create secrets directory: %w", err) } unlockKeysDir := filepath.Join(vaultDir, "unlock.d") if err := fs.MkdirAll(unlockKeysDir, 0700); err != nil { return nil, fmt.Errorf("failed to create unlock keys directory: %w", err) } // Select the new vault as current secret.Debug("Selecting newly created vault as current", "name", name) if err := SelectVault(fs, stateDir, name); err != nil { return nil, fmt.Errorf("failed to select new vault: %w", err) } secret.Debug("Successfully created vault", "name", name) return NewVault(fs, name, stateDir), nil } // SelectVault selects the given vault as the current vault func SelectVault(fs afero.Fs, stateDir string, name string) error { secret.Debug("Selecting vault", "vault_name", name, "state_dir", stateDir) // Check if vault exists vaultDir := filepath.Join(stateDir, "vaults.d", name) exists, err := afero.DirExists(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", name) } // Create/update current vault symlink currentVaultPath := filepath.Join(stateDir, "currentvault") // Remove existing symlink if it exists if exists, _ := afero.Exists(fs, currentVaultPath); exists { secret.Debug("Removing existing current vault symlink", "path", currentVaultPath) if err := fs.Remove(currentVaultPath); err != nil { secret.Debug("Failed to remove existing symlink", "error", err, "path", currentVaultPath) } } // Create new symlink pointing to the vault targetPath := vaultDir secret.Debug("Creating vault symlink", "target", targetPath, "link", currentVaultPath) // For real filesystems, try to create a real symlink first if _, ok := fs.(*afero.OsFs); ok { if err := os.Symlink(targetPath, currentVaultPath); err != nil { // If symlink creation fails, fall back to writing target path to file secret.Debug("Failed to create real symlink, falling back to file", "error", err) if err := afero.WriteFile(fs, currentVaultPath, []byte(targetPath), 0600); err != nil { return fmt.Errorf("failed to create vault symlink: %w", err) } } } else { // For in-memory filesystems, write target path to file if err := afero.WriteFile(fs, currentVaultPath, []byte(targetPath), 0600); err != nil { return fmt.Errorf("failed to create vault symlink: %w", err) } } secret.Debug("Successfully selected vault", "vault_name", name) return nil }