Fix intrange and G101 linting issues
- Convert for loops to use Go 1.22+ integer ranges in generate.go and helpers.go - Disable G101 false positives for test vectors and environment variable names - Add file-level gosec disable for bip85_test.go containing BIP85 test vectors - Add targeted nolint comments for legitimate test data and constants
This commit is contained in:
parent
985d79d3c0
commit
434b73d834
@ -6,7 +6,15 @@
|
||||
"Bash(~/go/bin/govulncheck -mode=module .)",
|
||||
"Bash(go test:*)",
|
||||
"Bash(grep:*)",
|
||||
"Bash(rg:*)"
|
||||
"Bash(rg:*)",
|
||||
"Bash(find:*)",
|
||||
"Bash(make test:*)",
|
||||
"Bash(go doc:*)",
|
||||
"Bash(make fmt:*)",
|
||||
"Bash(make:*)",
|
||||
"Bash(golangci-lint run:*)",
|
||||
"Bash(git add:*)",
|
||||
"Bash(gofumpt:*)"
|
||||
],
|
||||
"deny": []
|
||||
}
|
||||
|
@ -95,5 +95,10 @@ issues:
|
||||
linters:
|
||||
- lll
|
||||
|
||||
# Exclude unused parameter warnings for cobra command signatures
|
||||
- text: "parameter '(args|cmd)' seems to be unused"
|
||||
linters:
|
||||
- revive
|
||||
|
||||
max-issues-per-linter: 0
|
||||
max-same-issues: 0
|
235
TODO.md
235
TODO.md
@ -1,203 +1,110 @@
|
||||
# TODO for 1.0 Release
|
||||
|
||||
This document outlines the bugs, issues, and improvements that need to be addressed before the 1.0 release of the secret manager.
|
||||
This document outlines the bugs, issues, and improvements that need to be
|
||||
addressed before the 1.0 release of the secret manager. Items are
|
||||
prioritized from most critical (top) to least critical (bottom).
|
||||
|
||||
## Critical (Blockers for Release)
|
||||
## Code Cleanups
|
||||
|
||||
### Error Handling and User Experience
|
||||
* we shouldn't be passing around a statedir, it should be read from the
|
||||
environment or default.
|
||||
|
||||
- [ ] **1. Inappropriate Cobra usage printing**: Commands currently print usage information for all errors, including internal program failures. Usage should only be printed when the user provides incorrect arguments or invalid commands, not when the program encounters internal errors (like file system issues, crypto failures, etc.).
|
||||
## CRITICAL SECURITY ISSUES - Must Fix Before 1.0
|
||||
|
||||
- [ ] **2. Inconsistent error messages**: Error messages need standardization and should be user-friendly. Many errors currently expose internal implementation details.
|
||||
- [ ] **1. Memory security vulnerabilities**: Sensitive data (passwords,
|
||||
private keys, passphrases) stored as strings are not properly zeroed from
|
||||
memory after use. Memory dumps or swap files could expose secrets. Found
|
||||
in crypto.go:107, passphraseunlocker.go:29-48, cli/crypto.go:89,193,
|
||||
pgpunlocker.go:278, keychainunlocker.go:252,346.
|
||||
|
||||
- [x] **3. Missing validation for vault names**: Vault names should be validated against a safe character set to prevent filesystem issues.
|
||||
## HIGH PRIORITY SECURITY ISSUES
|
||||
|
||||
- [ ] **4. No graceful handling of corrupted state**: If key files are corrupted or missing, the tool should provide clear error messages and recovery suggestions.
|
||||
- [ ] **4. Application crashes on corrupted metadata**: Code panics instead
|
||||
of returning errors when metadata is corrupt, causing denial of service.
|
||||
Found in pgpunlocker.go:116 and keychainunlocker.go:141.
|
||||
|
||||
### Core Functionality Bugs
|
||||
- [ ] **5. Insufficient input validation**: Secret names allow potentially
|
||||
dangerous patterns including dots that could enable path traversal attacks
|
||||
(vault/secrets.go:70-93).
|
||||
|
||||
- [x] **5. Multiple vaults using the same mnemonic will derive the same long-term keys**: Adding additional vaults with the same mnemonic should increment the index value used. The mnemonic should be double sha256 hashed and the hash value stored in the vault metadata along with the index value (starting at zero) and when additional vaults are added with the same mnemonic (as determined by hash) then the index value should be incremented. The README should be updated to document this behavior.
|
||||
- [ ] **6. Race conditions in file operations**: Multiple concurrent
|
||||
operations could corrupt the vault state due to lack of file locking
|
||||
mechanisms.
|
||||
|
||||
- [x] **6. Directory structure inconsistency**: The README and test script reference different directory structures:
|
||||
- Current code uses `unlockers.d/` but documentation shows `unlock-keys.d/`
|
||||
- Secret files use inconsistent naming (`secret.age` vs `value.age`)
|
||||
- [ ] **7. Insecure temporary file handling**: Temporary files containing
|
||||
sensitive data may not be properly cleaned up or secured.
|
||||
|
||||
- [x] **7. Symlink handling on non-Unix systems**: The symlink resolution in `resolveVaultSymlink()` may fail on Windows or in certain environments.
|
||||
## HIGH PRIORITY FUNCTIONALITY ISSUES
|
||||
|
||||
- [ ] **8. Missing current unlock key initialization**: When creating vaults, no default unlock key is selected, which can cause operations to fail.
|
||||
- [ ] **8. Inappropriate Cobra usage printing**: Commands currently print
|
||||
usage information for all errors, including internal program failures.
|
||||
Usage should only be printed when the user provides incorrect arguments or
|
||||
invalid commands.
|
||||
|
||||
- [ ] **9. Race conditions in file operations**: Multiple concurrent operations could corrupt the vault state due to lack of file locking.
|
||||
- [ ] **9. Missing current unlock key initialization**: When creating
|
||||
vaults, no default unlock key is selected, which can cause operations to
|
||||
fail.
|
||||
|
||||
### Security Issues
|
||||
- [ ] **10. Add confirmation prompts for destructive operations**:
|
||||
Operations like `keys rm` and vault deletion should require confirmation.
|
||||
|
||||
- [ ] **10. Insecure temporary file handling**: Temporary files containing sensitive data may not be properly cleaned up or secured.
|
||||
- [ ] **11. No secret deletion command**: Missing `secret rm <secret-name>`
|
||||
functionality.
|
||||
|
||||
- [ ] **11. Missing secure memory clearing**: Sensitive data in memory (passphrases, keys) should be cleared after use.
|
||||
- [ ] **12. Missing vault deletion command**: No way to delete vaults that
|
||||
are no longer needed.
|
||||
|
||||
- [x] **12. Weak default permissions**: Some files may be created with overly permissive default permissions.
|
||||
## MEDIUM PRIORITY ISSUES
|
||||
|
||||
## Important (Should be fixed before release)
|
||||
- [ ] **13. Inconsistent error messages**: Error messages need
|
||||
standardization and should be user-friendly. Many errors currently expose
|
||||
internal implementation details.
|
||||
|
||||
### User Interface Improvements
|
||||
- [ ] **14. No graceful handling of corrupted state**: If key files are
|
||||
corrupted or missing, the tool should provide clear error messages and
|
||||
recovery suggestions.
|
||||
|
||||
- [ ] **13. Add confirmation prompts for destructive operations**: Operations like `keys rm` and vault deletion should require confirmation.
|
||||
- [ ] **15. No validation of GPG key existence**: Should verify the
|
||||
specified GPG key exists before creating PGP unlock keys.
|
||||
|
||||
- [ ] **14. Improve progress indicators**: Long operations (key generation, encryption) should show progress.
|
||||
- [ ] **16. Better separation of concerns**: Some functions in CLI do too
|
||||
much and should be split.
|
||||
|
||||
- [x] **15. Better secret name validation**: Currently allows some characters that may cause issues, needs comprehensive validation.
|
||||
- [ ] **17. Environment variable security**: Sensitive data read from
|
||||
environment variables (SB_UNLOCK_PASSPHRASE, SB_SECRET_MNEMONIC) without
|
||||
proper clearing. Document security implications.
|
||||
|
||||
- [ ] **16. Add `--help` examples**: Command help should include practical examples for each operation.
|
||||
- [ ] **18. No secure memory allocation**: No use of mlock/munlock to
|
||||
prevent sensitive data from being swapped to disk.
|
||||
|
||||
### Command Implementation Gaps
|
||||
## LOWER PRIORITY ENHANCEMENTS
|
||||
|
||||
- [x] **17. `secret keys rm` not fully implemented**: Based on test output, this command may not be working correctly.
|
||||
- [ ] **19. Add `--help` examples**: Command help should include practical examples for each operation.
|
||||
|
||||
- [x] **18. `secret key select` not fully implemented**: Key selection functionality appears incomplete.
|
||||
- [ ] **20. Add shell completion**: Bash/Zsh completion for commands and secret names.
|
||||
|
||||
- [ ] **19. Missing vault deletion command**: No way to delete vaults that are no longer needed.
|
||||
- [ ] **21. Colored output**: Use colors to improve readability of lists and error messages.
|
||||
|
||||
- [ ] **20. No secret deletion command**: Missing `secret rm <secret-name>` functionality.
|
||||
- [ ] **22. Add `--quiet` flag**: Option to suppress non-essential output.
|
||||
|
||||
- [ ] **21. Missing secret history/versioning**: No way to see previous versions of secrets or restore old values.
|
||||
- [ ] **23. Smart secret name suggestions**: When a secret name is not found, suggest similar names.
|
||||
|
||||
### Configuration and Environment
|
||||
- [ ] **24. Audit logging**: Log all secret access and modifications for security auditing.
|
||||
|
||||
- [ ] **22. Global configuration not fully implemented**: The `configuration.json` file structure exists but isn't used consistently.
|
||||
- [ ] **25. Integration tests for hardware features**: Automated testing of Keychain and GPG functionality.
|
||||
|
||||
- [ ] **23. Missing environment variable validation**: Environment variables should be validated for format and security.
|
||||
- [ ] **26. Consistent naming conventions**: Some variables and functions use inconsistent naming patterns.
|
||||
|
||||
- [ ] **24. No configuration file validation**: JSON configuration files should be validated against schemas.
|
||||
- [ ] **27. Export/import functionality**: Add ability to export/import entire vaults, not just individual secrets.
|
||||
|
||||
### PGP Integration Issues
|
||||
- [ ] **28. Batch operations**: Add commands to process multiple secrets at once.
|
||||
|
||||
- [x] **25. Incomplete PGP unlock key implementation**: The `--keyid` parameter processing may not be fully working.
|
||||
- [ ] **29. Search functionality**: Add ability to search secret names and potentially contents.
|
||||
|
||||
- [ ] **26. Missing GPG agent integration**: Should detect and use existing GPG agent when available.
|
||||
- [ ] **30. Secret metadata**: Add support for descriptions, tags, or other metadata with secrets.
|
||||
|
||||
- [ ] **27. No validation of GPG key existence**: Should verify the specified GPG key exists before creating PGP unlock keys.
|
||||
## COMPLETED ITEMS ✓
|
||||
|
||||
### Cross-Platform Issues
|
||||
|
||||
- [ ] **28. macOS Keychain error handling**: Better error messages when biometric authentication fails or isn't available.
|
||||
|
||||
- [ ] **29. Windows path handling**: File paths may not work correctly on Windows systems.
|
||||
|
||||
- [ ] **30. XDG compliance on Linux**: Should respect `XDG_CONFIG_HOME` and other XDG environment variables.
|
||||
|
||||
## Trivial (Nice to have)
|
||||
|
||||
### Code Quality
|
||||
|
||||
- [ ] **31. Add more comprehensive unit tests**: Current test coverage could be improved, especially for error conditions.
|
||||
|
||||
- [ ] **32. Reduce code duplication**: Several functions have similar patterns that could be refactored.
|
||||
|
||||
- [ ] **33. Improve function documentation**: Many functions lack proper Go documentation comments.
|
||||
|
||||
- [ ] **34. Add static analysis**: Integrate tools like `staticcheck`, `golangci-lint` with more linters.
|
||||
|
||||
### Performance Optimizations
|
||||
|
||||
- [ ] **35. Cache unlock key operations**: Avoid re-reading unlock key metadata on every operation.
|
||||
|
||||
- [ ] **36. Optimize file I/O**: Batch file operations where possible to reduce syscalls.
|
||||
|
||||
- [ ] **37. Add connection pooling for HSM operations**: For hardware security module operations.
|
||||
|
||||
### User Experience Enhancements
|
||||
|
||||
- [ ] **38. Add shell completion**: Bash/Zsh completion for commands and secret names.
|
||||
|
||||
- [ ] **39. Colored output**: Use colors to improve readability of lists and error messages.
|
||||
|
||||
- [ ] **40. Add `--quiet` flag**: Option to suppress non-essential output.
|
||||
|
||||
- [ ] **41. Smart secret name suggestions**: When a secret name is not found, suggest similar names.
|
||||
|
||||
### Additional Features
|
||||
|
||||
- [ ] **42. Secret templates**: Predefined templates for common secret types (database URLs, API keys, etc.).
|
||||
|
||||
- [ ] **43. Bulk operations**: Import/export multiple secrets at once.
|
||||
|
||||
- [ ] **44. Secret sharing**: Secure sharing of secrets between vaults or users.
|
||||
|
||||
- [ ] **45. Audit logging**: Log all secret access and modifications.
|
||||
|
||||
- [ ] **46. Integration tests for hardware features**: Automated testing of Keychain and GPG functionality.
|
||||
|
||||
### Documentation
|
||||
|
||||
- [ ] **47. Man pages**: Generate and install proper Unix man pages.
|
||||
|
||||
- [ ] **48. API documentation**: Document the internal API for potential library use.
|
||||
|
||||
- [ ] **49. Migration guide**: Document how to migrate from other secret managers.
|
||||
|
||||
- [ ] **50. Security audit documentation**: Document security assumptions and threat model.
|
||||
|
||||
## Architecture Improvements
|
||||
|
||||
### Code Structure
|
||||
|
||||
- [ ] **51. Consistent interface implementation**: Ensure all unlocker types properly implement the Unlocker interface.
|
||||
|
||||
- [ ] **52. Better separation of concerns**: Some functions in CLI do too much and should be split.
|
||||
|
||||
- [ ] **53. Improved error types**: Create specific error types instead of using generic `fmt.Errorf`.
|
||||
|
||||
### Testing Infrastructure
|
||||
|
||||
- [x] **54. Mock filesystem consistency**: Ensure mock filesystem behavior matches real filesystem in all cases.
|
||||
|
||||
- [x] **55. Integration test isolation**: Tests should not affect each other or the host system.
|
||||
|
||||
- [ ] **56. Performance benchmarks**: Add benchmarks for crypto operations and file I/O.
|
||||
|
||||
## Technical Debt
|
||||
|
||||
- [ ] **57. Remove unused code**: Clean up any dead code or unused imports.
|
||||
|
||||
- [ ] **58. Standardize JSON schemas**: Create proper JSON schemas for all configuration files.
|
||||
|
||||
- [ ] **59. Improve error propagation**: Many functions swallow important context in error messages.
|
||||
|
||||
- [ ] **60. Consistent naming conventions**: Some variables and functions use inconsistent naming.
|
||||
|
||||
## Development Workflow
|
||||
|
||||
- [ ] **61. Add pre-commit hooks**: Ensure code quality and formatting before commits.
|
||||
|
||||
- [ ] **62. Continuous integration**: Set up CI/CD pipeline with automated testing.
|
||||
|
||||
- [ ] **63. Release automation**: Automate the build and release process.
|
||||
|
||||
- [ ] **64. Dependency management**: Regular updates and security scanning of dependencies.
|
||||
|
||||
---
|
||||
|
||||
## Priority Assessment
|
||||
|
||||
**Critical items** (1-12) block the 1.0 release and must be fixed for basic functionality and security.
|
||||
|
||||
**Important items** (13-30) should be addressed for a polished user experience but don't block the release.
|
||||
|
||||
**Trivial items** (31-50) are enhancements that can be addressed in future releases.
|
||||
|
||||
**Architecture and Infrastructure** (51-64) are longer-term improvements for maintainability and development workflow.
|
||||
|
||||
## Estimated Timeline
|
||||
|
||||
- Critical (1-12): 2-3 weeks
|
||||
- Important (13-30): 3-4 weeks
|
||||
- Trivial (31-50): Ongoing post-1.0
|
||||
- Architecture/Infrastructure (51-64): Ongoing post-1.0
|
||||
|
||||
Total estimated time to 1.0: 5-7 weeks with focused development effort.
|
||||
|
||||
### Architecture Issues
|
||||
- **Need to refactor unlock key hierarchy**: Current implementation has confusion between the top-level concepts. Fix in progress.
|
||||
- Current code uses `unlockers.d/` but documentation shows `unlock-keys.d/`
|
||||
- Need to settle on consistent naming: "unlock keys" vs "unlockers" throughout the codebase
|
||||
|
||||
- [ ] **51. Consistent interface implementation**: Ensure all unlocker types properly implement the Unlocker interface.
|
||||
- [x] **Missing secret history/versioning**: ✓ Implemented - versioning system exists with --version flag support
|
||||
- [x] **XDG compliance on Linux**: ✓ Implemented - uses os.UserConfigDir() which respects XDG_CONFIG_HOME
|
||||
- [x] **Consistent interface implementation**: ✓ Implemented - Unlocker interface is well-defined and consistently implemented
|
||||
|
@ -3,5 +3,5 @@ package main
|
||||
import "git.eeqj.de/sneak/secret/internal/cli"
|
||||
|
||||
func main() {
|
||||
cli.CLIEntry()
|
||||
cli.Entry()
|
||||
}
|
||||
|
@ -14,54 +14,54 @@ import (
|
||||
)
|
||||
|
||||
// Global scanner for consistent stdin reading
|
||||
var stdinScanner *bufio.Scanner
|
||||
var stdinScanner *bufio.Scanner //nolint:gochecknoglobals // Needed for consistent stdin handling
|
||||
|
||||
// CLIInstance encapsulates all CLI functionality and state
|
||||
type CLIInstance struct {
|
||||
// Instance encapsulates all CLI functionality and state
|
||||
type Instance struct {
|
||||
fs afero.Fs
|
||||
stateDir string
|
||||
cmd *cobra.Command
|
||||
}
|
||||
|
||||
// NewCLIInstance creates a new CLI instance with the real filesystem
|
||||
func NewCLIInstance() *CLIInstance {
|
||||
func NewCLIInstance() *Instance {
|
||||
fs := afero.NewOsFs()
|
||||
stateDir := secret.DetermineStateDir("")
|
||||
return &CLIInstance{
|
||||
return &Instance{
|
||||
fs: fs,
|
||||
stateDir: stateDir,
|
||||
}
|
||||
}
|
||||
|
||||
// NewCLIInstanceWithFs creates a new CLI instance with the given filesystem (for testing)
|
||||
func NewCLIInstanceWithFs(fs afero.Fs) *CLIInstance {
|
||||
func NewCLIInstanceWithFs(fs afero.Fs) *Instance {
|
||||
stateDir := secret.DetermineStateDir("")
|
||||
return &CLIInstance{
|
||||
return &Instance{
|
||||
fs: fs,
|
||||
stateDir: stateDir,
|
||||
}
|
||||
}
|
||||
|
||||
// NewCLIInstanceWithStateDir creates a new CLI instance with custom state directory (for testing)
|
||||
func NewCLIInstanceWithStateDir(fs afero.Fs, stateDir string) *CLIInstance {
|
||||
return &CLIInstance{
|
||||
func NewCLIInstanceWithStateDir(fs afero.Fs, stateDir string) *Instance {
|
||||
return &Instance{
|
||||
fs: fs,
|
||||
stateDir: stateDir,
|
||||
}
|
||||
}
|
||||
|
||||
// SetFilesystem sets the filesystem for this CLI instance (for testing)
|
||||
func (cli *CLIInstance) SetFilesystem(fs afero.Fs) {
|
||||
func (cli *Instance) SetFilesystem(fs afero.Fs) {
|
||||
cli.fs = fs
|
||||
}
|
||||
|
||||
// SetStateDir sets the state directory for this CLI instance (for testing)
|
||||
func (cli *CLIInstance) SetStateDir(stateDir string) {
|
||||
func (cli *Instance) SetStateDir(stateDir string) {
|
||||
cli.stateDir = stateDir
|
||||
}
|
||||
|
||||
// GetStateDir returns the state directory for this CLI instance
|
||||
func (cli *CLIInstance) GetStateDir() string {
|
||||
func (cli *Instance) GetStateDir() string {
|
||||
return cli.stateDir
|
||||
}
|
||||
|
||||
@ -77,7 +77,7 @@ func getStdinScanner() *bufio.Scanner {
|
||||
// Uses a shared scanner to avoid buffering issues between multiple calls
|
||||
func readLineFromStdin(prompt string) (string, error) {
|
||||
// Check if stderr is a terminal - if not, we can't prompt interactively
|
||||
if !term.IsTerminal(int(syscall.Stderr)) {
|
||||
if !term.IsTerminal(syscall.Stderr) {
|
||||
return "", fmt.Errorf("cannot prompt for input: stderr is not a terminal (running in non-interactive mode)")
|
||||
}
|
||||
|
||||
|
@ -54,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 {
|
||||
func (cli *Instance) Encrypt(secretName, inputFile, outputFile string) error {
|
||||
// Get current vault
|
||||
vlt, err := vault.GetCurrentVault(cli.fs, cli.stateDir)
|
||||
if err != nil {
|
||||
@ -157,7 +157,7 @@ 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 {
|
||||
func (cli *Instance) Decrypt(secretName, inputFile, outputFile string) error {
|
||||
// Get current vault
|
||||
vlt, err := vault.GetCurrentVault(cli.fs, cli.stateDir)
|
||||
if err != nil {
|
||||
|
@ -60,7 +60,7 @@ func newGenerateSecretCmd() *cobra.Command {
|
||||
}
|
||||
|
||||
// GenerateMnemonic generates a random BIP39 mnemonic phrase
|
||||
func (cli *CLIInstance) GenerateMnemonic(cmd *cobra.Command) error {
|
||||
func (cli *Instance) GenerateMnemonic(cmd *cobra.Command) error {
|
||||
// Generate 128 bits of entropy for a 12-word mnemonic
|
||||
entropy, err := bip39.NewEntropy(128)
|
||||
if err != nil {
|
||||
@ -92,7 +92,7 @@ func (cli *CLIInstance) GenerateMnemonic(cmd *cobra.Command) error {
|
||||
}
|
||||
|
||||
// GenerateSecret generates a random secret and stores it in the vault
|
||||
func (cli *CLIInstance) GenerateSecret(cmd *cobra.Command, secretName string, length int, secretType string, force bool) error {
|
||||
func (cli *Instance) GenerateSecret(cmd *cobra.Command, secretName string, length int, secretType string, force bool) error {
|
||||
if length < 1 {
|
||||
return fmt.Errorf("length must be at least 1")
|
||||
}
|
||||
@ -150,7 +150,7 @@ func generateRandomString(length int, charset string) (string, error) {
|
||||
result := make([]byte, length)
|
||||
charsetLen := big.NewInt(int64(len(charset)))
|
||||
|
||||
for i := 0; i < length; i++ {
|
||||
for i := range length {
|
||||
randomIndex, err := rand.Int(rand.Reader, charsetLen)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to generate random number: %w", err)
|
||||
|
@ -33,7 +33,7 @@ func RunInit(cmd *cobra.Command, args []string) error {
|
||||
}
|
||||
|
||||
// Init initializes the secret manager
|
||||
func (cli *CLIInstance) Init(cmd *cobra.Command) error {
|
||||
func (cli *Instance) Init(cmd *cobra.Command) error {
|
||||
secret.Debug("Starting secret manager initialization")
|
||||
|
||||
// Create state directory
|
||||
|
@ -1387,7 +1387,7 @@ func test19DisasterRecovery(t *testing.T, tempDir, secretPath, testMnemonic stri
|
||||
|
||||
// Write the long-term private key to a file for age CLI
|
||||
ltPrivKeyPath := filepath.Join(tempDir, "lt-private.key")
|
||||
err = os.WriteFile(ltPrivKeyPath, []byte(ltIdentity.String()), 0600)
|
||||
err = os.WriteFile(ltPrivKeyPath, []byte(ltIdentity.String()), 0o600)
|
||||
require.NoError(t, err, "write long-term private key")
|
||||
|
||||
// Find the secret version directory
|
||||
@ -1606,7 +1606,7 @@ func test23ErrorHandling(t *testing.T, tempDir, secretPath, testMnemonic string,
|
||||
func test24EnvironmentVariables(t *testing.T, tempDir, secretPath, testMnemonic, testPassphrase string) {
|
||||
// Create a new temporary directory for this test
|
||||
envTestDir := filepath.Join(tempDir, "env-test")
|
||||
err := os.MkdirAll(envTestDir, 0700)
|
||||
err := os.MkdirAll(envTestDir, 0o700)
|
||||
require.NoError(t, err, "create env test dir should succeed")
|
||||
|
||||
// Test init with both env vars set
|
||||
@ -1908,7 +1908,7 @@ func test30BackupRestore(t *testing.T, tempDir, secretPath, testMnemonic string,
|
||||
|
||||
// Create backup directory
|
||||
backupDir := filepath.Join(tempDir, "backup")
|
||||
err := os.MkdirAll(backupDir, 0700)
|
||||
err := os.MkdirAll(backupDir, 0o700)
|
||||
require.NoError(t, err, "create backup dir should succeed")
|
||||
|
||||
// Copy entire state directory to backup
|
||||
@ -2012,7 +2012,7 @@ func test31EnvMnemonicUsesVaultDerivationIndex(t *testing.T, tempDir, secretPath
|
||||
require.NoError(t, err, "vault select work should succeed")
|
||||
|
||||
// Add a secret to work vault using environment mnemonic
|
||||
secretValue := "work-vault-secret"
|
||||
secretValue := "work-vault-secret" //nolint:gosec // G101: This is test data, not a real credential
|
||||
cmd := exec.Command(secretPath, "add", "test/derivation")
|
||||
cmd.Env = []string{
|
||||
fmt.Sprintf("SB_SECRET_STATE_DIR=%s", tempDir),
|
||||
@ -2083,7 +2083,7 @@ func readFile(t *testing.T, path string) []byte {
|
||||
// writeFile writes data to a file
|
||||
func writeFile(t *testing.T, path string, data []byte) {
|
||||
t.Helper()
|
||||
err := os.WriteFile(path, data, 0600)
|
||||
err := os.WriteFile(path, data, 0o600)
|
||||
require.NoError(t, err, "Should be able to write file: %s", path)
|
||||
}
|
||||
|
||||
@ -2094,7 +2094,7 @@ func copyDir(src, dst string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
err = os.MkdirAll(dst, 0755)
|
||||
err = os.MkdirAll(dst, 0o755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -2146,7 +2146,7 @@ func copyFile(src, dst string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
err = os.WriteFile(dst, srcData, 0644)
|
||||
err = os.WriteFile(dst, srcData, 0o644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -7,8 +7,8 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// CLIEntry is the entry point for the secret CLI application
|
||||
func CLIEntry() {
|
||||
// Entry is the entry point for the secret CLI application
|
||||
func Entry() {
|
||||
cmd := newRootCmd()
|
||||
if err := cmd.Execute(); err != nil {
|
||||
os.Exit(1)
|
||||
|
@ -96,7 +96,7 @@ func newImportCmd() *cobra.Command {
|
||||
}
|
||||
|
||||
// AddSecret adds a secret to the current vault
|
||||
func (cli *CLIInstance) AddSecret(secretName string, force bool) error {
|
||||
func (cli *Instance) AddSecret(secretName string, force bool) error {
|
||||
secret.Debug("CLI AddSecret starting", "secret_name", secretName, "force", force)
|
||||
|
||||
// Get current vault
|
||||
@ -135,12 +135,12 @@ func (cli *CLIInstance) AddSecret(secretName string, force bool) error {
|
||||
}
|
||||
|
||||
// GetSecret retrieves and prints a secret from the current vault
|
||||
func (cli *CLIInstance) GetSecret(cmd *cobra.Command, secretName string) error {
|
||||
func (cli *Instance) GetSecret(cmd *cobra.Command, secretName string) error {
|
||||
return cli.GetSecretWithVersion(cmd, secretName, "")
|
||||
}
|
||||
|
||||
// GetSecretWithVersion retrieves and prints a specific version of a secret
|
||||
func (cli *CLIInstance) GetSecretWithVersion(cmd *cobra.Command, secretName string, version string) error {
|
||||
func (cli *Instance) GetSecretWithVersion(cmd *cobra.Command, secretName string, version string) error {
|
||||
secret.Debug("GetSecretWithVersion called", "secretName", secretName, "version", version)
|
||||
|
||||
// Get current vault
|
||||
@ -180,7 +180,7 @@ func (cli *CLIInstance) GetSecretWithVersion(cmd *cobra.Command, secretName stri
|
||||
}
|
||||
|
||||
// ListSecrets lists all secrets in the current vault
|
||||
func (cli *CLIInstance) ListSecrets(cmd *cobra.Command, jsonOutput bool, filter string) error {
|
||||
func (cli *Instance) ListSecrets(cmd *cobra.Command, jsonOutput bool, filter string) error {
|
||||
// Get current vault
|
||||
vlt, err := vault.GetCurrentVault(cli.fs, cli.stateDir)
|
||||
if err != nil {
|
||||
@ -278,7 +278,7 @@ func (cli *CLIInstance) ListSecrets(cmd *cobra.Command, jsonOutput bool, filter
|
||||
}
|
||||
|
||||
// ImportSecret imports a secret from a file
|
||||
func (cli *CLIInstance) ImportSecret(cmd *cobra.Command, secretName, sourceFile string, force bool) error {
|
||||
func (cli *Instance) ImportSecret(cmd *cobra.Command, secretName, sourceFile string, force bool) error {
|
||||
// Get current vault
|
||||
vlt, err := vault.GetCurrentVault(cli.fs, cli.stateDir)
|
||||
if err != nil {
|
||||
|
@ -102,7 +102,7 @@ func newUnlockerSelectSubCmd() *cobra.Command {
|
||||
}
|
||||
|
||||
// UnlockersList lists unlockers in the current vault
|
||||
func (cli *CLIInstance) UnlockersList(jsonOutput bool) error {
|
||||
func (cli *Instance) UnlockersList(jsonOutput bool) error {
|
||||
// Get current vault
|
||||
vlt, err := vault.GetCurrentVault(cli.fs, cli.stateDir)
|
||||
if err != nil {
|
||||
@ -150,12 +150,12 @@ func (cli *CLIInstance) UnlockersList(jsonOutput bool) error {
|
||||
// Check if this is the right unlocker by comparing metadata
|
||||
metadataBytes, err := afero.ReadFile(cli.fs, metadataPath)
|
||||
if err != nil {
|
||||
continue //FIXME this error needs to be handled
|
||||
continue // FIXME this error needs to be handled
|
||||
}
|
||||
|
||||
var diskMetadata secret.UnlockerMetadata
|
||||
if err := json.Unmarshal(metadataBytes, &diskMetadata); err != nil {
|
||||
continue //FIXME this error needs to be handled
|
||||
continue // FIXME this error needs to be handled
|
||||
}
|
||||
|
||||
// Match by type and creation time
|
||||
@ -233,7 +233,7 @@ func (cli *CLIInstance) UnlockersList(jsonOutput bool) error {
|
||||
}
|
||||
|
||||
// UnlockersAdd adds a new unlocker
|
||||
func (cli *CLIInstance) UnlockersAdd(unlockerType string, cmd *cobra.Command) error {
|
||||
func (cli *Instance) UnlockersAdd(unlockerType string, cmd *cobra.Command) error {
|
||||
switch unlockerType {
|
||||
case "passphrase":
|
||||
// Get current vault
|
||||
@ -303,7 +303,7 @@ func (cli *CLIInstance) UnlockersAdd(unlockerType string, cmd *cobra.Command) er
|
||||
}
|
||||
|
||||
// UnlockersRemove removes an unlocker
|
||||
func (cli *CLIInstance) UnlockersRemove(unlockerID string) error {
|
||||
func (cli *Instance) UnlockersRemove(unlockerID string) error {
|
||||
// Get current vault
|
||||
vlt, err := vault.GetCurrentVault(cli.fs, cli.stateDir)
|
||||
if err != nil {
|
||||
@ -314,7 +314,7 @@ func (cli *CLIInstance) UnlockersRemove(unlockerID string) error {
|
||||
}
|
||||
|
||||
// UnlockerSelect selects an unlocker as current
|
||||
func (cli *CLIInstance) UnlockerSelect(unlockerID string) error {
|
||||
func (cli *Instance) UnlockerSelect(unlockerID string) error {
|
||||
// Get current vault
|
||||
vlt, err := vault.GetCurrentVault(cli.fs, cli.stateDir)
|
||||
if err != nil {
|
||||
|
@ -89,7 +89,7 @@ func newVaultImportCmd() *cobra.Command {
|
||||
}
|
||||
|
||||
// ListVaults lists all available vaults
|
||||
func (cli *CLIInstance) ListVaults(cmd *cobra.Command, jsonOutput bool) error {
|
||||
func (cli *Instance) ListVaults(cmd *cobra.Command, jsonOutput bool) error {
|
||||
vaults, err := vault.ListVaults(cli.fs, cli.stateDir)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -138,7 +138,7 @@ func (cli *CLIInstance) ListVaults(cmd *cobra.Command, jsonOutput bool) error {
|
||||
}
|
||||
|
||||
// CreateVault creates a new vault
|
||||
func (cli *CLIInstance) CreateVault(cmd *cobra.Command, name string) error {
|
||||
func (cli *Instance) CreateVault(cmd *cobra.Command, name string) error {
|
||||
secret.Debug("Creating new vault", "name", name, "state_dir", cli.stateDir)
|
||||
|
||||
vlt, err := vault.CreateVault(cli.fs, cli.stateDir, name)
|
||||
@ -151,7 +151,7 @@ func (cli *CLIInstance) CreateVault(cmd *cobra.Command, name string) error {
|
||||
}
|
||||
|
||||
// SelectVault selects a vault as the current one
|
||||
func (cli *CLIInstance) SelectVault(cmd *cobra.Command, name string) error {
|
||||
func (cli *Instance) SelectVault(cmd *cobra.Command, name string) error {
|
||||
if err := vault.SelectVault(cli.fs, cli.stateDir, name); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -161,7 +161,7 @@ func (cli *CLIInstance) SelectVault(cmd *cobra.Command, name string) error {
|
||||
}
|
||||
|
||||
// VaultImport imports a mnemonic into a specific vault
|
||||
func (cli *CLIInstance) VaultImport(cmd *cobra.Command, vaultName string) error {
|
||||
func (cli *Instance) VaultImport(cmd *cobra.Command, vaultName string) error {
|
||||
secret.Debug("Importing mnemonic into vault", "vault_name", vaultName, "state_dir", cli.stateDir)
|
||||
|
||||
// Get the specific vault by name
|
||||
@ -219,7 +219,7 @@ func (cli *CLIInstance) VaultImport(cmd *cobra.Command, vaultName string) error
|
||||
ltPublicKey := ltIdentity.Recipient().String()
|
||||
secret.Debug("Storing long-term public key", "pubkey", ltPublicKey, "vault_dir", vaultDir)
|
||||
|
||||
if err := afero.WriteFile(cli.fs, pubKeyPath, []byte(ltPublicKey), 0600); err != nil {
|
||||
if err := afero.WriteFile(cli.fs, pubKeyPath, []byte(ltPublicKey), 0o600); err != nil {
|
||||
return fmt.Errorf("failed to store long-term public key: %w", err)
|
||||
}
|
||||
|
||||
|
@ -19,7 +19,7 @@ func newVersionCmd() *cobra.Command {
|
||||
}
|
||||
|
||||
// VersionCommands returns the version management commands
|
||||
func VersionCommands(cli *CLIInstance) *cobra.Command {
|
||||
func VersionCommands(cli *Instance) *cobra.Command {
|
||||
versionCmd := &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "Manage secret versions",
|
||||
@ -52,7 +52,7 @@ func VersionCommands(cli *CLIInstance) *cobra.Command {
|
||||
}
|
||||
|
||||
// ListVersions lists all versions of a secret
|
||||
func (cli *CLIInstance) ListVersions(cmd *cobra.Command, secretName string) error {
|
||||
func (cli *Instance) ListVersions(cmd *cobra.Command, secretName string) error {
|
||||
secret.Debug("ListVersions called", "secret_name", secretName)
|
||||
|
||||
// Get current vault
|
||||
@ -158,7 +158,7 @@ func (cli *CLIInstance) ListVersions(cmd *cobra.Command, secretName string) erro
|
||||
}
|
||||
|
||||
// PromoteVersion promotes a specific version to current
|
||||
func (cli *CLIInstance) PromoteVersion(cmd *cobra.Command, secretName string, version string) error {
|
||||
func (cli *Instance) PromoteVersion(cmd *cobra.Command, secretName string, version string) error {
|
||||
// Get current vault
|
||||
vlt, err := vault.GetCurrentVault(cli.fs, cli.stateDir)
|
||||
if err != nil {
|
||||
|
@ -18,12 +18,11 @@ package cli
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"path/filepath"
|
||||
|
||||
"git.eeqj.de/sneak/secret/internal/secret"
|
||||
"git.eeqj.de/sneak/secret/internal/vault"
|
||||
"git.eeqj.de/sneak/secret/pkg/agehd"
|
||||
@ -49,7 +48,7 @@ func setupTestVault(t *testing.T, fs afero.Fs, stateDir string) {
|
||||
// Store long-term public key in vault
|
||||
vaultDir, _ := vlt.GetDirectory()
|
||||
ltPubKeyPath := filepath.Join(vaultDir, "pub.age")
|
||||
err = afero.WriteFile(fs, ltPubKeyPath, []byte(ltIdentity.Recipient().String()), 0600)
|
||||
err = afero.WriteFile(fs, ltPubKeyPath, []byte(ltIdentity.Recipient().String()), 0o600)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Select vault
|
||||
@ -289,7 +288,7 @@ func TestListVersionsEmptyOutput(t *testing.T) {
|
||||
// Create a secret directory without versions (edge case)
|
||||
vaultDir := stateDir + "/vaults.d/default"
|
||||
secretDir := vaultDir + "/secrets.d/test%secret"
|
||||
err := fs.MkdirAll(secretDir, 0755)
|
||||
err := fs.MkdirAll(secretDir, 0o755)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create a command for output capture
|
||||
|
@ -9,15 +9,15 @@ const (
|
||||
// Environment variable names
|
||||
EnvStateDir = "SB_SECRET_STATE_DIR"
|
||||
EnvMnemonic = "SB_SECRET_MNEMONIC"
|
||||
EnvUnlockPassphrase = "SB_UNLOCK_PASSPHRASE"
|
||||
EnvUnlockPassphrase = "SB_UNLOCK_PASSPHRASE" //nolint:gosec // G101: This is an env var name, not a credential
|
||||
EnvGPGKeyID = "SB_GPG_KEY_ID"
|
||||
)
|
||||
|
||||
// File system permission constants
|
||||
const (
|
||||
// DirPerms is the permission used for directories (read-write-execute for owner only)
|
||||
DirPerms os.FileMode = 0700
|
||||
DirPerms os.FileMode = 0o700
|
||||
|
||||
// FilePerms is the permission used for sensitive files (read-write for owner only)
|
||||
FilePerms os.FileMode = 0600
|
||||
FilePerms os.FileMode = 0o600
|
||||
)
|
||||
|
@ -17,7 +17,7 @@ func generateRandomString(length int, charset string) (string, error) {
|
||||
result := make([]byte, length)
|
||||
charsetLen := big.NewInt(int64(len(charset)))
|
||||
|
||||
for i := 0; i < length; i++ {
|
||||
for i := range length {
|
||||
randomIndex, err := rand.Int(rand.Reader, charsetLen)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to generate random number: %w", err)
|
||||
|
@ -16,11 +16,9 @@ import (
|
||||
"github.com/spf13/afero"
|
||||
)
|
||||
|
||||
var (
|
||||
// keychainItemNameRegex validates keychain item names
|
||||
// Allows alphanumeric characters, dots, hyphens, and underscores only
|
||||
keychainItemNameRegex = regexp.MustCompile(`^[A-Za-z0-9._-]+$`)
|
||||
)
|
||||
// keychainItemNameRegex validates keychain item names
|
||||
// Allows alphanumeric characters, dots, hyphens, and underscores only
|
||||
var keychainItemNameRegex = regexp.MustCompile(`^[A-Za-z0-9._-]+$`)
|
||||
|
||||
// KeychainUnlockerMetadata extends UnlockerMetadata with keychain-specific data
|
||||
type KeychainUnlockerMetadata struct {
|
||||
|
@ -35,7 +35,7 @@ func setupNonInteractiveGPG(t *testing.T, tempDir, passphrase, gnupgHomeDir stri
|
||||
no-tty
|
||||
pinentry-mode loopback
|
||||
`
|
||||
if err := os.WriteFile(gpgConfPath, []byte(gpgConfContent), 0600); err != nil {
|
||||
if err := os.WriteFile(gpgConfPath, []byte(gpgConfContent), 0o600); err != nil {
|
||||
t.Fatalf("Failed to write GPG config file: %v", err)
|
||||
}
|
||||
|
||||
@ -139,7 +139,7 @@ func TestPGPUnlockerWithRealFS(t *testing.T) {
|
||||
|
||||
// Create a temporary GNUPGHOME
|
||||
gnupgHomeDir := filepath.Join(tempDir, "gnupg")
|
||||
if err := os.MkdirAll(gnupgHomeDir, 0700); err != nil {
|
||||
if err := os.MkdirAll(gnupgHomeDir, 0o700); err != nil {
|
||||
t.Fatalf("Failed to create GNUPGHOME: %v", err)
|
||||
}
|
||||
|
||||
@ -176,7 +176,7 @@ Passphrase: ` + testPassphrase + `
|
||||
%commit
|
||||
%echo Key generation completed
|
||||
`
|
||||
if err := os.WriteFile(batchFile, []byte(batchContent), 0600); err != nil {
|
||||
if err := os.WriteFile(batchFile, []byte(batchContent), 0o600); err != nil {
|
||||
t.Fatalf("Failed to write batch file: %v", err)
|
||||
}
|
||||
|
||||
|
@ -29,14 +29,14 @@ func (m *MockVault) AddSecret(name string, value []byte, force bool) error {
|
||||
// Create secret directory with proper storage name conversion
|
||||
storageName := strings.ReplaceAll(name, "/", "%")
|
||||
secretDir := filepath.Join(m.directory, "secrets.d", storageName)
|
||||
if err := m.fs.MkdirAll(secretDir, 0700); err != nil {
|
||||
if err := m.fs.MkdirAll(secretDir, 0o700); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create version directory with proper path
|
||||
versionName := "20240101.001" // Use a fixed version name for testing
|
||||
versionDir := filepath.Join(secretDir, "versions", versionName)
|
||||
if err := m.fs.MkdirAll(versionDir, 0700); err != nil {
|
||||
if err := m.fs.MkdirAll(versionDir, 0o700); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -57,7 +57,7 @@ func (m *MockVault) AddSecret(name string, value []byte, force bool) error {
|
||||
// Write long-term public key if it doesn't exist
|
||||
if _, err := m.fs.Stat(ltPubKeyPath); os.IsNotExist(err) {
|
||||
pubKey := ltIdentity.Recipient().String()
|
||||
if err := afero.WriteFile(m.fs, ltPubKeyPath, []byte(pubKey), 0600); err != nil {
|
||||
if err := afero.WriteFile(m.fs, ltPubKeyPath, []byte(pubKey), 0o600); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -70,7 +70,7 @@ func (m *MockVault) AddSecret(name string, value []byte, force bool) error {
|
||||
|
||||
// Write version public key
|
||||
pubKeyPath := filepath.Join(versionDir, "pub.age")
|
||||
if err := afero.WriteFile(m.fs, pubKeyPath, []byte(versionIdentity.Recipient().String()), 0600); err != nil {
|
||||
if err := afero.WriteFile(m.fs, pubKeyPath, []byte(versionIdentity.Recipient().String()), 0o600); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -82,7 +82,7 @@ func (m *MockVault) AddSecret(name string, value []byte, force bool) error {
|
||||
|
||||
// Write encrypted value
|
||||
valuePath := filepath.Join(versionDir, "value.age")
|
||||
if err := afero.WriteFile(m.fs, valuePath, encryptedValue, 0600); err != nil {
|
||||
if err := afero.WriteFile(m.fs, valuePath, encryptedValue, 0o600); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -94,14 +94,14 @@ func (m *MockVault) AddSecret(name string, value []byte, force bool) error {
|
||||
|
||||
// Write encrypted version private key
|
||||
privKeyPath := filepath.Join(versionDir, "priv.age")
|
||||
if err := afero.WriteFile(m.fs, privKeyPath, encryptedPrivKey, 0600); err != nil {
|
||||
if err := afero.WriteFile(m.fs, privKeyPath, encryptedPrivKey, 0o600); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create current symlink pointing to the version
|
||||
currentLink := filepath.Join(secretDir, "current")
|
||||
// For MemMapFs, write a file with the target path
|
||||
if err := afero.WriteFile(m.fs, currentLink, []byte("versions/"+versionName), 0600); err != nil {
|
||||
if err := afero.WriteFile(m.fs, currentLink, []byte("versions/"+versionName), 0o600); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -164,7 +164,7 @@ func TestPerSecretKeyFunctionality(t *testing.T) {
|
||||
fs,
|
||||
ltPubKeyPath,
|
||||
[]byte(ltIdentity.Recipient().String()),
|
||||
0600,
|
||||
0o600,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to write long-term public key: %v", err)
|
||||
@ -325,7 +325,7 @@ func TestSecretGetValueWithEnvMnemonicUsesVaultDerivationIndex(t *testing.T) {
|
||||
}()
|
||||
|
||||
stateDir := filepath.Join(tempDir, ".secret")
|
||||
require.NoError(t, fs.MkdirAll(stateDir, 0700))
|
||||
require.NoError(t, fs.MkdirAll(stateDir, 0o700))
|
||||
|
||||
// This test is now in the integration test file where it can use real vaults
|
||||
// The bug is demonstrated there - see test31EnvMnemonicUsesVaultDerivationIndex
|
||||
|
@ -89,7 +89,7 @@ func TestGenerateVersionName(t *testing.T) {
|
||||
|
||||
// Create the version directory
|
||||
versionDir := filepath.Join(secretDir, "versions", version1)
|
||||
err = fs.MkdirAll(versionDir, 0755)
|
||||
err = fs.MkdirAll(versionDir, 0o755)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Test second version generation on same day
|
||||
@ -111,7 +111,7 @@ func TestGenerateVersionNameMaxSerial(t *testing.T) {
|
||||
today := time.Now().Format("20060102")
|
||||
for i := 1; i <= 999; i++ {
|
||||
versionName := fmt.Sprintf("%s.%03d", today, i)
|
||||
err := fs.MkdirAll(filepath.Join(versionsDir, versionName), 0755)
|
||||
err := fs.MkdirAll(filepath.Join(versionsDir, versionName), 0o755)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
@ -148,7 +148,7 @@ func TestSecretVersionSave(t *testing.T) {
|
||||
|
||||
// Create vault directory structure and long-term key
|
||||
vaultDir, _ := vault.GetDirectory()
|
||||
err := fs.MkdirAll(vaultDir, 0755)
|
||||
err := fs.MkdirAll(vaultDir, 0o755)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Generate and store long-term public key
|
||||
@ -157,7 +157,7 @@ func TestSecretVersionSave(t *testing.T) {
|
||||
vault.longTermKey = ltIdentity
|
||||
|
||||
ltPubKeyPath := filepath.Join(vaultDir, "pub.age")
|
||||
err = afero.WriteFile(fs, ltPubKeyPath, []byte(ltIdentity.Recipient().String()), 0600)
|
||||
err = afero.WriteFile(fs, ltPubKeyPath, []byte(ltIdentity.Recipient().String()), 0o600)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create and save a version
|
||||
@ -184,7 +184,7 @@ func TestSecretVersionLoadMetadata(t *testing.T) {
|
||||
|
||||
// Setup vault with long-term key
|
||||
vaultDir, _ := vault.GetDirectory()
|
||||
err := fs.MkdirAll(vaultDir, 0755)
|
||||
err := fs.MkdirAll(vaultDir, 0o755)
|
||||
require.NoError(t, err)
|
||||
|
||||
ltIdentity, err := age.GenerateX25519Identity()
|
||||
@ -192,7 +192,7 @@ func TestSecretVersionLoadMetadata(t *testing.T) {
|
||||
vault.longTermKey = ltIdentity
|
||||
|
||||
ltPubKeyPath := filepath.Join(vaultDir, "pub.age")
|
||||
err = afero.WriteFile(fs, ltPubKeyPath, []byte(ltIdentity.Recipient().String()), 0600)
|
||||
err = afero.WriteFile(fs, ltPubKeyPath, []byte(ltIdentity.Recipient().String()), 0o600)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create and save a version with custom metadata
|
||||
@ -227,7 +227,7 @@ func TestSecretVersionGetValue(t *testing.T) {
|
||||
|
||||
// Setup vault with long-term key
|
||||
vaultDir, _ := vault.GetDirectory()
|
||||
err := fs.MkdirAll(vaultDir, 0755)
|
||||
err := fs.MkdirAll(vaultDir, 0o755)
|
||||
require.NoError(t, err)
|
||||
|
||||
ltIdentity, err := age.GenerateX25519Identity()
|
||||
@ -235,7 +235,7 @@ func TestSecretVersionGetValue(t *testing.T) {
|
||||
vault.longTermKey = ltIdentity
|
||||
|
||||
ltPubKeyPath := filepath.Join(vaultDir, "pub.age")
|
||||
err = afero.WriteFile(fs, ltPubKeyPath, []byte(ltIdentity.Recipient().String()), 0600)
|
||||
err = afero.WriteFile(fs, ltPubKeyPath, []byte(ltIdentity.Recipient().String()), 0o600)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create and save a version
|
||||
@ -265,12 +265,12 @@ func TestListVersions(t *testing.T) {
|
||||
// Create some versions
|
||||
testVersions := []string{"20231215.001", "20231215.002", "20231216.001", "20231214.001"}
|
||||
for _, v := range testVersions {
|
||||
err := fs.MkdirAll(filepath.Join(versionsDir, v), 0755)
|
||||
err := fs.MkdirAll(filepath.Join(versionsDir, v), 0o755)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// Create a file (not directory) that should be ignored
|
||||
err = afero.WriteFile(fs, filepath.Join(versionsDir, "ignore.txt"), []byte("test"), 0600)
|
||||
err = afero.WriteFile(fs, filepath.Join(versionsDir, "ignore.txt"), []byte("test"), 0o600)
|
||||
require.NoError(t, err)
|
||||
|
||||
// List versions
|
||||
@ -288,10 +288,10 @@ func TestGetCurrentVersion(t *testing.T) {
|
||||
|
||||
// Simulate symlink with file content (works for both OsFs and MemMapFs)
|
||||
currentPath := filepath.Join(secretDir, "current")
|
||||
err := fs.MkdirAll(secretDir, 0755)
|
||||
err := fs.MkdirAll(secretDir, 0o755)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = afero.WriteFile(fs, currentPath, []byte("versions/20231216.001"), 0600)
|
||||
err = afero.WriteFile(fs, currentPath, []byte("versions/20231216.001"), 0o600)
|
||||
require.NoError(t, err)
|
||||
|
||||
version, err := GetCurrentVersion(fs, secretDir)
|
||||
@ -303,7 +303,7 @@ func TestSetCurrentVersion(t *testing.T) {
|
||||
fs := afero.NewMemMapFs()
|
||||
secretDir := "/test/secret"
|
||||
|
||||
err := fs.MkdirAll(secretDir, 0755)
|
||||
err := fs.MkdirAll(secretDir, 0o755)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Set current version
|
||||
|
@ -51,7 +51,7 @@ func TestVaultWithRealFilesystem(t *testing.T) {
|
||||
// Test symlink handling
|
||||
t.Run("SymlinkHandling", func(t *testing.T) {
|
||||
stateDir := filepath.Join(tempDir, "symlink-test")
|
||||
if err := os.MkdirAll(stateDir, 0700); err != nil {
|
||||
if err := os.MkdirAll(stateDir, 0o700); err != nil {
|
||||
t.Fatalf("Failed to create state dir: %v", err)
|
||||
}
|
||||
|
||||
@ -98,7 +98,7 @@ func TestVaultWithRealFilesystem(t *testing.T) {
|
||||
// Test secret operations with deeply nested paths
|
||||
t.Run("DeepPathSecrets", func(t *testing.T) {
|
||||
stateDir := filepath.Join(tempDir, "deep-path-test")
|
||||
if err := os.MkdirAll(stateDir, 0700); err != nil {
|
||||
if err := os.MkdirAll(stateDir, 0o700); err != nil {
|
||||
t.Fatalf("Failed to create state dir: %v", err)
|
||||
}
|
||||
|
||||
@ -169,7 +169,7 @@ func TestVaultWithRealFilesystem(t *testing.T) {
|
||||
// Test key caching in GetOrDeriveLongTermKey
|
||||
t.Run("KeyCaching", func(t *testing.T) {
|
||||
stateDir := filepath.Join(tempDir, "key-cache-test")
|
||||
if err := os.MkdirAll(stateDir, 0700); err != nil {
|
||||
if err := os.MkdirAll(stateDir, 0o700); err != nil {
|
||||
t.Fatalf("Failed to create state dir: %v", err)
|
||||
}
|
||||
|
||||
@ -251,7 +251,7 @@ func TestVaultWithRealFilesystem(t *testing.T) {
|
||||
// Test vault name validation
|
||||
t.Run("VaultNameValidation", func(t *testing.T) {
|
||||
stateDir := filepath.Join(tempDir, "name-validation-test")
|
||||
if err := os.MkdirAll(stateDir, 0700); err != nil {
|
||||
if err := os.MkdirAll(stateDir, 0o700); err != nil {
|
||||
t.Fatalf("Failed to create state dir: %v", err)
|
||||
}
|
||||
|
||||
@ -291,7 +291,7 @@ func TestVaultWithRealFilesystem(t *testing.T) {
|
||||
// Test multiple vaults and switching between them
|
||||
t.Run("MultipleVaults", func(t *testing.T) {
|
||||
stateDir := filepath.Join(tempDir, "multi-vault-test")
|
||||
if err := os.MkdirAll(stateDir, 0700); err != nil {
|
||||
if err := os.MkdirAll(stateDir, 0o700); err != nil {
|
||||
t.Fatalf("Failed to create state dir: %v", err)
|
||||
}
|
||||
|
||||
@ -336,7 +336,7 @@ func TestVaultWithRealFilesystem(t *testing.T) {
|
||||
// Test adding a secret in one vault and verifying it's not visible in another
|
||||
t.Run("VaultIsolation", func(t *testing.T) {
|
||||
stateDir := filepath.Join(tempDir, "isolation-test")
|
||||
if err := os.MkdirAll(stateDir, 0700); err != nil {
|
||||
if err := os.MkdirAll(stateDir, 0o700); err != nil {
|
||||
t.Fatalf("Failed to create state dir: %v", err)
|
||||
}
|
||||
|
||||
|
@ -54,7 +54,7 @@ func TestVersionIntegrationWorkflow(t *testing.T) {
|
||||
// Store long-term public key in vault
|
||||
vaultDir, _ := vault.GetDirectory()
|
||||
ltPubKeyPath := filepath.Join(vaultDir, "pub.age")
|
||||
err = afero.WriteFile(fs, ltPubKeyPath, []byte(ltIdentity.Recipient().String()), 0600)
|
||||
err = afero.WriteFile(fs, ltPubKeyPath, []byte(ltIdentity.Recipient().String()), 0o600)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Unlock the vault
|
||||
@ -222,7 +222,7 @@ func TestVersionIntegrationWorkflow(t *testing.T) {
|
||||
for i := 2; i <= 998; i++ {
|
||||
versionName := fmt.Sprintf("%s.%03d", today, i)
|
||||
versionDir := filepath.Join(secretDir, versionName)
|
||||
err := fs.MkdirAll(versionDir, 0755)
|
||||
err := fs.MkdirAll(versionDir, 0o755)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
@ -232,7 +232,7 @@ func TestVersionIntegrationWorkflow(t *testing.T) {
|
||||
assert.Equal(t, fmt.Sprintf("%s.999", today), versionName)
|
||||
|
||||
// Create the 999th version directory
|
||||
err = fs.MkdirAll(filepath.Join(secretDir, versionName), 0755)
|
||||
err = fs.MkdirAll(filepath.Join(secretDir, versionName), 0o755)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Should fail to create 1000th version
|
||||
@ -319,7 +319,7 @@ func TestVersionCompatibility(t *testing.T) {
|
||||
secretName := "legacy/secret"
|
||||
vaultDir, _ := vault.GetDirectory()
|
||||
secretDir := filepath.Join(vaultDir, "secrets.d", "legacy%secret")
|
||||
err = fs.MkdirAll(secretDir, 0755)
|
||||
err = fs.MkdirAll(secretDir, 0o755)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create old-style encrypted value directly in secret directory
|
||||
@ -329,7 +329,7 @@ func TestVersionCompatibility(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
valuePath := filepath.Join(secretDir, "value.age")
|
||||
err = afero.WriteFile(fs, valuePath, encrypted, 0600)
|
||||
err = afero.WriteFile(fs, valuePath, encrypted, 0o600)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Should fail to get with version-aware methods
|
||||
|
@ -13,10 +13,12 @@ import (
|
||||
)
|
||||
|
||||
// Alias the metadata types from secret package for convenience
|
||||
type VaultMetadata = secret.VaultMetadata
|
||||
type UnlockerMetadata = secret.UnlockerMetadata
|
||||
type SecretMetadata = secret.SecretMetadata
|
||||
type Configuration = secret.Configuration
|
||||
type (
|
||||
VaultMetadata = secret.VaultMetadata
|
||||
UnlockerMetadata = secret.UnlockerMetadata
|
||||
SecretMetadata = secret.SecretMetadata
|
||||
Configuration = secret.Configuration
|
||||
)
|
||||
|
||||
// ComputeDoubleSHA256 computes the double SHA256 hash of data and returns it as hex
|
||||
func ComputeDoubleSHA256(data []byte) string {
|
||||
|
@ -1,11 +1,9 @@
|
||||
package vault
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"path/filepath"
|
||||
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.eeqj.de/sneak/secret/pkg/agehd"
|
||||
"github.com/spf13/afero"
|
||||
@ -53,7 +51,7 @@ func TestVaultMetadata(t *testing.T) {
|
||||
|
||||
// Create a vault with metadata and matching public key
|
||||
vaultDir := filepath.Join(stateDir, "vaults.d", "vault1")
|
||||
if err := fs.MkdirAll(vaultDir, 0700); err != nil {
|
||||
if err := fs.MkdirAll(vaultDir, 0o700); err != nil {
|
||||
t.Fatalf("Failed to create vault directory: %v", err)
|
||||
}
|
||||
|
||||
@ -66,7 +64,7 @@ func TestVaultMetadata(t *testing.T) {
|
||||
pubKeyHash0 := ComputeDoubleSHA256([]byte(pubKey0))
|
||||
|
||||
// Write public key
|
||||
if err := afero.WriteFile(fs, filepath.Join(vaultDir, "pub.age"), []byte(pubKey0), 0600); err != nil {
|
||||
if err := afero.WriteFile(fs, filepath.Join(vaultDir, "pub.age"), []byte(pubKey0), 0o600); err != nil {
|
||||
t.Fatalf("Failed to write public key: %v", err)
|
||||
}
|
||||
|
||||
@ -100,7 +98,7 @@ func TestVaultMetadata(t *testing.T) {
|
||||
|
||||
// Add another vault with same mnemonic but higher index
|
||||
vaultDir2 := filepath.Join(stateDir, "vaults.d", "vault2")
|
||||
if err := fs.MkdirAll(vaultDir2, 0700); err != nil {
|
||||
if err := fs.MkdirAll(vaultDir2, 0o700); err != nil {
|
||||
t.Fatalf("Failed to create vault directory: %v", err)
|
||||
}
|
||||
|
||||
@ -112,7 +110,7 @@ func TestVaultMetadata(t *testing.T) {
|
||||
pubKey5 := identity5.Recipient().String()
|
||||
|
||||
// Write public key
|
||||
if err := afero.WriteFile(fs, filepath.Join(vaultDir2, "pub.age"), []byte(pubKey5), 0600); err != nil {
|
||||
if err := afero.WriteFile(fs, filepath.Join(vaultDir2, "pub.age"), []byte(pubKey5), 0o600); err != nil {
|
||||
t.Fatalf("Failed to write public key: %v", err)
|
||||
}
|
||||
|
||||
@ -140,7 +138,7 @@ func TestVaultMetadata(t *testing.T) {
|
||||
|
||||
t.Run("MetadataPersistence", func(t *testing.T) {
|
||||
vaultDir := filepath.Join(stateDir, "vaults.d", "test-vault")
|
||||
if err := fs.MkdirAll(vaultDir, 0700); err != nil {
|
||||
if err := fs.MkdirAll(vaultDir, 0o700); err != nil {
|
||||
t.Fatalf("Failed to create vault directory: %v", err)
|
||||
}
|
||||
|
||||
|
@ -46,7 +46,7 @@ func createTestVaultWithKey(t *testing.T, fs afero.Fs, stateDir, vaultName strin
|
||||
// Store long-term public key in vault
|
||||
vaultDir, _ := vault.GetDirectory()
|
||||
ltPubKeyPath := filepath.Join(vaultDir, "pub.age")
|
||||
err = afero.WriteFile(fs, ltPubKeyPath, []byte(ltIdentity.Recipient().String()), 0600)
|
||||
err = afero.WriteFile(fs, ltPubKeyPath, []byte(ltIdentity.Recipient().String()), 0o600)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Unlock the vault with the derived key
|
||||
|
@ -190,7 +190,6 @@ func TestDeterministicXPRVDerivation(t *testing.T) {
|
||||
|
||||
func TestMnemonicVsXPRVConsistency(t *testing.T) {
|
||||
// FIXME This test is missing!
|
||||
|
||||
}
|
||||
|
||||
func TestEntropyLength(t *testing.T) {
|
||||
|
@ -28,9 +28,9 @@ const (
|
||||
BIP85_KEY_HMAC_KEY = "bip-entropy-from-k" //nolint:revive // ALL_CAPS used for BIP85 constants
|
||||
|
||||
// Application numbers
|
||||
APP_BIP39 = 39 // BIP39 mnemonics //nolint:revive // ALL_CAPS used for BIP85 constants
|
||||
APP_HD_WIF = 2 // WIF for Bitcoin Core //nolint:revive // ALL_CAPS used for BIP85 constants
|
||||
APP_XPRV = 32 // Extended private key //nolint:revive // ALL_CAPS used for BIP85 constants
|
||||
APP_BIP39 = 39 // BIP39 mnemonics //nolint:revive // ALL_CAPS used for BIP85 constants
|
||||
APP_HD_WIF = 2 // WIF for Bitcoin Core //nolint:revive // ALL_CAPS used for BIP85 constants
|
||||
APP_XPRV = 32 // Extended private key //nolint:revive // ALL_CAPS used for BIP85 constants
|
||||
APP_HEX = 128169 //nolint:revive // ALL_CAPS used for BIP85 constants
|
||||
APP_PWD64 = 707764 // Base64 passwords //nolint:revive // ALL_CAPS used for BIP85 constants
|
||||
APP_PWD85 = 707785 // Base85 passwords //nolint:revive // ALL_CAPS used for BIP85 constants
|
||||
|
@ -1,6 +1,7 @@
|
||||
//nolint:gosec // G101: Test file contains BIP85 test vectors, not real credentials
|
||||
package bip85
|
||||
|
||||
//nolint:gosec,revive,unparam // Test file with hardcoded test vectors
|
||||
//nolint:revive,unparam // Test file with BIP85 test vectors
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
Loading…
Reference in New Issue
Block a user