forked from sneak/secret
Compare commits
10 Commits
fix/issue-
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 6ff00c696a | |||
| c6551e4901 | |||
| b06d7fa3f4 | |||
| 16d5b237d2 | |||
| 660de5716a | |||
| 51fb2805fd | |||
| 6ffb24b544 | |||
|
|
4419ef7730 | ||
|
|
991b1a5a0b | ||
|
|
341428d9ca |
@ -7,12 +7,10 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"filippo.io/age"
|
|
||||||
"git.eeqj.de/sneak/secret/internal/secret"
|
"git.eeqj.de/sneak/secret/internal/secret"
|
||||||
"git.eeqj.de/sneak/secret/internal/vault"
|
"git.eeqj.de/sneak/secret/internal/vault"
|
||||||
"git.eeqj.de/sneak/secret/pkg/agehd"
|
"git.eeqj.de/sneak/secret/pkg/agehd"
|
||||||
"github.com/awnumar/memguard"
|
"github.com/awnumar/memguard"
|
||||||
"github.com/spf13/afero"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/tyler-smith/go-bip39"
|
"github.com/tyler-smith/go-bip39"
|
||||||
)
|
)
|
||||||
@ -154,35 +152,8 @@ func (cli *Instance) Init(cmd *cobra.Command) error {
|
|||||||
return fmt.Errorf("failed to create unlocker: %w", err)
|
return fmt.Errorf("failed to create unlocker: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encrypt long-term private key to the unlocker
|
// Note: CreatePassphraseUnlocker already encrypts and writes the long-term
|
||||||
unlockerDir := passphraseUnlocker.GetDirectory()
|
// private key to longterm.age, so no need to do it again here.
|
||||||
|
|
||||||
// Read unlocker public key
|
|
||||||
unlockerPubKeyData, err := afero.ReadFile(cli.fs, filepath.Join(unlockerDir, "pub.age"))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to read unlocker public key: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
unlockerRecipient, err := age.ParseX25519Recipient(string(unlockerPubKeyData))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to parse unlocker public key: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encrypt long-term private key to unlocker
|
|
||||||
// Use memguard to protect the private key in memory
|
|
||||||
ltPrivKeyBuffer := memguard.NewBufferFromBytes([]byte(ltIdentity.String()))
|
|
||||||
defer ltPrivKeyBuffer.Destroy()
|
|
||||||
|
|
||||||
encryptedLtPrivKey, err := secret.EncryptToRecipient(ltPrivKeyBuffer, unlockerRecipient)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to encrypt long-term private key: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write encrypted long-term private key
|
|
||||||
ltPrivKeyPath := filepath.Join(unlockerDir, "longterm.age")
|
|
||||||
if err := afero.WriteFile(cli.fs, ltPrivKeyPath, encryptedLtPrivKey, secret.FilePerms); err != nil {
|
|
||||||
return fmt.Errorf("failed to write encrypted long-term private key: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if cmd != nil {
|
if cmd != nil {
|
||||||
cmd.Printf("\nDefault vault created and configured\n")
|
cmd.Printf("\nDefault vault created and configured\n")
|
||||||
|
|||||||
@ -4,6 +4,8 @@
|
|||||||
package secret
|
package secret
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"filippo.io/age"
|
"filippo.io/age"
|
||||||
"github.com/awnumar/memguard"
|
"github.com/awnumar/memguard"
|
||||||
"github.com/spf13/afero"
|
"github.com/spf13/afero"
|
||||||
@ -22,52 +24,59 @@ type KeychainUnlocker struct {
|
|||||||
fs afero.Fs
|
fs afero.Fs
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetIdentity panics on non-Darwin platforms
|
var errKeychainNotSupported = fmt.Errorf("keychain unlockers are only supported on macOS")
|
||||||
|
|
||||||
|
// GetIdentity returns an error on non-Darwin platforms
|
||||||
func (k *KeychainUnlocker) GetIdentity() (*age.X25519Identity, error) {
|
func (k *KeychainUnlocker) GetIdentity() (*age.X25519Identity, error) {
|
||||||
panic("keychain unlockers are only supported on macOS")
|
return nil, errKeychainNotSupported
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetType panics on non-Darwin platforms
|
// GetType returns the unlocker type
|
||||||
func (k *KeychainUnlocker) GetType() string {
|
func (k *KeychainUnlocker) GetType() string {
|
||||||
panic("keychain unlockers are only supported on macOS")
|
return "keychain"
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetMetadata panics on non-Darwin platforms
|
// GetMetadata returns the unlocker metadata
|
||||||
func (k *KeychainUnlocker) GetMetadata() UnlockerMetadata {
|
func (k *KeychainUnlocker) GetMetadata() UnlockerMetadata {
|
||||||
panic("keychain unlockers are only supported on macOS")
|
return k.Metadata
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDirectory panics on non-Darwin platforms
|
// GetDirectory returns the unlocker directory
|
||||||
func (k *KeychainUnlocker) GetDirectory() string {
|
func (k *KeychainUnlocker) GetDirectory() string {
|
||||||
panic("keychain unlockers are only supported on macOS")
|
return k.Directory
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetID returns the unlocker ID
|
// GetID returns the unlocker ID
|
||||||
func (k *KeychainUnlocker) GetID() string {
|
func (k *KeychainUnlocker) GetID() string {
|
||||||
panic("keychain unlockers are only supported on macOS")
|
return fmt.Sprintf("%s-keychain", k.Metadata.CreatedAt.Format("2006-01-02.15.04"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetKeychainItemName panics on non-Darwin platforms
|
// GetKeychainItemName returns an error on non-Darwin platforms
|
||||||
func (k *KeychainUnlocker) GetKeychainItemName() (string, error) {
|
func (k *KeychainUnlocker) GetKeychainItemName() (string, error) {
|
||||||
panic("keychain unlockers are only supported on macOS")
|
return "", errKeychainNotSupported
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove panics on non-Darwin platforms
|
// Remove returns an error on non-Darwin platforms
|
||||||
func (k *KeychainUnlocker) Remove() error {
|
func (k *KeychainUnlocker) Remove() error {
|
||||||
panic("keychain unlockers are only supported on macOS")
|
return errKeychainNotSupported
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewKeychainUnlocker panics on non-Darwin platforms
|
// NewKeychainUnlocker creates a stub KeychainUnlocker on non-Darwin platforms.
|
||||||
|
// The returned instance's methods that require macOS functionality will return errors.
|
||||||
func NewKeychainUnlocker(fs afero.Fs, directory string, metadata UnlockerMetadata) *KeychainUnlocker {
|
func NewKeychainUnlocker(fs afero.Fs, directory string, metadata UnlockerMetadata) *KeychainUnlocker {
|
||||||
panic("keychain unlockers are only supported on macOS")
|
return &KeychainUnlocker{
|
||||||
|
Directory: directory,
|
||||||
|
Metadata: metadata,
|
||||||
|
fs: fs,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateKeychainUnlocker panics on non-Darwin platforms
|
// CreateKeychainUnlocker returns an error on non-Darwin platforms
|
||||||
func CreateKeychainUnlocker(fs afero.Fs, stateDir string) (*KeychainUnlocker, error) {
|
func CreateKeychainUnlocker(_ afero.Fs, _ string) (*KeychainUnlocker, error) {
|
||||||
panic("keychain unlockers are only supported on macOS")
|
return nil, errKeychainNotSupported
|
||||||
}
|
}
|
||||||
|
|
||||||
// getLongTermPrivateKey panics on non-Darwin platforms
|
// getLongTermPrivateKey returns an error on non-Darwin platforms
|
||||||
func getLongTermPrivateKey(fs afero.Fs, vault VaultInterface) (*memguard.LockedBuffer, error) {
|
func getLongTermPrivateKey(_ afero.Fs, _ VaultInterface) (*memguard.LockedBuffer, error) {
|
||||||
panic("keychain unlockers are only supported on macOS")
|
return nil, errKeychainNotSupported
|
||||||
}
|
}
|
||||||
|
|||||||
@ -227,27 +227,23 @@ func (v *Vault) NumSecrets() (int, error) {
|
|||||||
return 0, fmt.Errorf("failed to read secrets directory: %w", err)
|
return 0, fmt.Errorf("failed to read secrets directory: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Count only directories that contain at least one version file
|
// Count only directories that have a "current" version pointer file
|
||||||
count := 0
|
count := 0
|
||||||
for _, entry := range entries {
|
for _, entry := range entries {
|
||||||
if !entry.IsDir() {
|
if !entry.IsDir() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if this secret directory contains any version files
|
// A valid secret has a "current" file pointing to the active version
|
||||||
secretDir := filepath.Join(secretsDir, entry.Name())
|
secretDir := filepath.Join(secretsDir, entry.Name())
|
||||||
versionFiles, err := afero.ReadDir(v.fs, secretDir)
|
currentFile := filepath.Join(secretDir, "current")
|
||||||
|
exists, err := afero.Exists(v.fs, currentFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue // Skip directories we can't read
|
continue // Skip directories we can't read
|
||||||
}
|
}
|
||||||
|
|
||||||
// Look for at least one version file (excluding "current" symlink)
|
if exists {
|
||||||
for _, vFile := range versionFiles {
|
|
||||||
if !vFile.IsDir() && vFile.Name() != "current" {
|
|
||||||
count++
|
count++
|
||||||
|
|
||||||
break // Found at least one version, count this secret
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -162,6 +162,24 @@ func TestVaultOperations(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Test NumSecrets
|
||||||
|
t.Run("NumSecrets", func(t *testing.T) {
|
||||||
|
vlt, err := GetCurrentVault(fs, stateDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to get current vault: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
numSecrets, err := vlt.NumSecrets()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to count secrets: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We added one secret in SecretOperations
|
||||||
|
if numSecrets != 1 {
|
||||||
|
t.Errorf("Expected 1 secret, got %d", numSecrets)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// Test unlocker operations
|
// Test unlocker operations
|
||||||
t.Run("UnlockerOperations", func(t *testing.T) {
|
t.Run("UnlockerOperations", func(t *testing.T) {
|
||||||
vlt, err := GetCurrentVault(fs, stateDir)
|
vlt, err := GetCurrentVault(fs, stateDir)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user