From 2573a3dbfe95387d83cebb0a2f8286d10de89994 Mon Sep 17 00:00:00 2001 From: Jonathan Apodaca Date: Thu, 15 May 2025 22:42:53 -0600 Subject: [PATCH] clean up code some --- cmd/envvault/main.go | 623 +++++++++++++++++++++---------------------- 1 file changed, 301 insertions(+), 322 deletions(-) diff --git a/cmd/envvault/main.go b/cmd/envvault/main.go index 3e5f9ae..eefcaeb 100644 --- a/cmd/envvault/main.go +++ b/cmd/envvault/main.go @@ -46,78 +46,6 @@ func init() { localDbFilePath = ".envvault.json.enc" } -type EnvStore struct { - Vars map[string]string `json:"vars"` - Tags map[string][]string `json:"tags,omitempty"` -} - -// GetTags returns all tags for a given variable name -func (s *EnvStore) GetTags(varName string) []string { - if s.Tags == nil { - return []string{} - } - return s.Tags[varName] -} - -// SetTags sets the tags for a given variable name -func (s *EnvStore) SetTags(varName string, tags []string) { - if s.Tags == nil { - s.Tags = make(map[string][]string) - } - - // Create a union of existing and new tags - existingTags := s.Tags[varName] - unionTags := make([]string, 0, len(existingTags)+len(tags)) - - // Add existing tags to the union - for _, tag := range existingTags { - unionTags = append(unionTags, tag) - } - - // Add new tags if they don't already exist - for _, tag := range tags { - if !slices.Contains(unionTags, tag) { - unionTags = append(unionTags, tag) - } - } - - s.Tags[varName] = unionTags -} - -// RemoveTags removes the specified tags from a variable -func (s *EnvStore) RemoveTags(varName string, tagsToRemove []string) { - if s.Tags == nil || len(s.Tags[varName]) == 0 { - return - } - - existingTags := s.Tags[varName] - remainingTags := make([]string, 0, len(existingTags)) - - // Keep only tags that are not in tagsToRemove - for _, tag := range existingTags { - if !slices.Contains(tagsToRemove, tag) { - remainingTags = append(remainingTags, tag) - } - } - - s.Tags[varName] = remainingTags -} - -// HasAnyTag returns true if the variable has any of the specified tags -func (s *EnvStore) HasAnyTag(varName string, tags []string) bool { - if len(tags) == 0 { - return true // No tags specified, include all variables - } - - varTags := s.GetTags(varName) - for _, tag := range tags { - if slices.Contains(varTags, tag) { - return true - } - } - return false -} - type CLI struct { Init InitCmd `cmd:"" help:"Initialize the vault."` List ListCmd `cmd:"" help:"List all environment variables."` @@ -208,81 +136,84 @@ func main() { } func subcommandInitVault() { // {{{ - internal.Log.Println("Initializing vault") - - if _, err := os.Stat(keyFilePath); os.IsNotExist(err) { - internal.Log.Println("Key file does not exist, creating new vault") - - fmt.Fprint(os.Stderr, "Enter a new master password: ") - password, err := term.ReadPassword(int(os.Stdin.Fd())) - fmt.Fprintln(os.Stderr) - if err != nil { - internal.Log.Printf("Failed to read password: %v", err) - log.Fatal(err) - } - - fmt.Fprint(os.Stderr, "Enter a new master password (again): ") - password2, err := term.ReadPassword(int(os.Stdin.Fd())) - fmt.Fprintln(os.Stderr) - if err != nil { - internal.Log.Printf("Failed to read confirmation password: %v", err) - log.Fatal(err) - } - - if !bytes.Equal(password, password2) { - internal.Log.Println("Passwords do not match") - log.Fatal("Passwords do not match") - } - internal.Log.Println("Passwords match, caching password") - cachedPassword = password - - // Generate a random key - internal.Log.Println("Generating random key") - var key [32]byte - if _, err := rand.Read(key[:]); err != nil { - internal.Log.Printf("Failed to generate random key: %v", err) - log.Fatal(err) - } - - // Encrypt the key with the password - internal.Log.Println("Encrypting key with password") - encryptedKey := encryptKeyWithPassword(key[:], password) - - internal.Log.Printf("Creating key directory: %s", filepath.Dir(keyFilePath)) - if err := os.MkdirAll(filepath.Dir(keyFilePath), 0700); err != nil { - internal.Log.Printf("Failed to create key directory: %v", err) - log.Fatal(err) - } - - internal.Log.Printf("Writing encrypted key to: %s", keyFilePath) - if err := os.WriteFile(keyFilePath, encryptedKey, 0600); err != nil { - internal.Log.Printf("Failed to write key file: %v", err) - log.Fatal(err) - } - - // Initialize the db file - internal.Log.Println("Initializing empty environment store") - emptyStore := &EnvStore{Vars: make(map[string]string)} - saveGlobalEnvStore(emptyStore) - - internal.Log.Println("Vault initialized successfully") - fmt.Fprintln(os.Stderr, "Vault initialized successfully.") - } else { + if exists(keyFilePath) { internal.Log.Println("Vault already initialized") fmt.Fprintln(os.Stderr, "Vault already initialized.") + return } + + internal.Log.Println("Initializing vault") + fmt.Fprint(os.Stderr, "Enter a new master password: ") + password, err := term.ReadPassword(int(os.Stdin.Fd())) + fmt.Fprintln(os.Stderr) + if err != nil { + internal.Log.Printf("Failed to read password: %v", err) + log.Fatal(err) + } + + fmt.Fprint(os.Stderr, "Enter a new master password (again): ") + password2, err := term.ReadPassword(int(os.Stdin.Fd())) + fmt.Fprintln(os.Stderr) + if err != nil { + internal.Log.Printf("Failed to read confirmation password: %v", err) + log.Fatal(err) + } + + if !bytes.Equal(password, password2) { + internal.Log.Println("Passwords do not match") + log.Fatal("Passwords do not match") + } + internal.Log.Println("Passwords match, caching password") + cachedPassword = password + + // Generate a random key + internal.Log.Println("Generating random key") + var key [32]byte + if _, err := rand.Read(key[:]); err != nil { + internal.Log.Printf("Failed to generate random key: %v", err) + log.Fatal(err) + } + + // Encrypt the key with the password + internal.Log.Println("Encrypting key with password") + encryptedKey := encryptKeyWithPassword(key[:], password) + + internal.Log.Printf("Creating key directory: %s", filepath.Dir(keyFilePath)) + if err := os.MkdirAll(filepath.Dir(keyFilePath), 0700); err != nil { + internal.Log.Printf("Failed to create key directory: %v", err) + log.Fatal(err) + } + + internal.Log.Printf("Writing encrypted key to: %s", keyFilePath) + if err := os.WriteFile(keyFilePath, encryptedKey, 0600); err != nil { + internal.Log.Printf("Failed to write key file: %v", err) + log.Fatal(err) + } + + // Initialize the db file + internal.Log.Println("Initializing empty environment store") + emptyStore := &EnvStore{filePath: dbFilePath, Vars: make(map[string]string)} + if err := emptyStore.Save(); err != nil { + internal.Log.Printf("Failed to initialize global environment store: %v", err) + log.Fatal(err) + } + + internal.Log.Println("Vault initialized successfully") + fmt.Fprintln(os.Stderr, "Vault initialized successfully.") } // }}} func subcommandAddEnvVar(cmd AddCmd) { // {{{ - var store *EnvStore + var storeKind StoreKind if cmd.Local { - store = loadLocalEnvStore() - internal.Log.Printf("Adding environment variable '%s' to local vault", cmd.Name) + storeKind = StoreKindLocal } else { - store = loadGlobalEnvStore() - internal.Log.Printf("Adding environment variable '%s' to global vault", cmd.Name) + storeKind = StoreKindGlobal + } + store, err := NewEnvStore(storeKind) + if err != nil { + log.Fatal(err) } if cmd.Value == "" { @@ -296,28 +227,23 @@ func subcommandAddEnvVar(cmd AddCmd) { // {{{ } store.Vars[cmd.Name] = cmd.Value - if cmd.Local { - saveLocalEnvStore(store) - fmt.Fprintf(os.Stderr, "Environment variable '%s' added to local vault.\n", cmd.Name) - } else { - saveGlobalEnvStore(store) - fmt.Fprintf(os.Stderr, "Environment variable '%s' added to global vault.\n", cmd.Name) + if err := store.Save(); err != nil { + log.Fatal(err) } + fmt.Fprintf(os.Stderr, "Environment variable '%s' added to vault.\n", cmd.Name) } // }}} func subcommandRmEnvVar(name string, localOnly bool) { // {{{ - localExists := false - // Check local store if it exists - if _, err := os.Stat(localDbFilePath); err == nil { - localStore := loadLocalEnvStore() - if _, exists := localStore.Vars[name]; exists { - delete(localStore.Vars, name) - saveLocalEnvStore(localStore) - localExists = true - fmt.Fprintf(os.Stderr, "Environment variable '%s' removed from local vault.\n", name) - } else if localOnly { - fmt.Fprintf(os.Stderr, "Environment variable '%s' not found in local vault.\n", name) + if exists(localDbFilePath) { + localStore, err := NewEnvStoreFromPath(localDbFilePath) + if err != nil { + log.Fatal(err) + } + + localStore.RemoveEnvVar(name) + if err := localStore.Save(); err != nil { + log.Fatal(err) } } else if localOnly { fmt.Fprintf(os.Stderr, "Local vault does not exist.\n") @@ -325,31 +251,28 @@ func subcommandRmEnvVar(name string, localOnly bool) { // {{{ // Check global store if not local-only if !localOnly { - globalStore := loadGlobalEnvStore() - if _, exists := globalStore.Vars[name]; exists { - delete(globalStore.Vars, name) - saveGlobalEnvStore(globalStore) - fmt.Fprintf(os.Stderr, "Environment variable '%s' removed from global vault.\n", name) - } else if !localExists { - fmt.Fprintf(os.Stderr, "Environment variable '%s' not found in global vault.\n", name) + globalStore, err := NewEnvStoreFromPath(dbFilePath) + if err != nil { + log.Fatal(err) + } + + globalStore.RemoveEnvVar(name) + if err := globalStore.Save(); err != nil { + log.Fatal(err) } } } // }}} func subcommandTagEnvVar(cmd TagCmd) { // {{{ - var store *EnvStore - - // Determine which store to use + var storeKind StoreKind if cmd.Local { - if _, err := os.Stat(localDbFilePath); os.IsNotExist(err) { - fmt.Fprintf(os.Stderr, "Local vault does not exist.\n") - os.Exit(1) - } - store = loadLocalEnvStore() - internal.Log.Printf("Using local vault") + storeKind = StoreKindLocal } else { - store = loadGlobalEnvStore() - internal.Log.Printf("Using global vault") + storeKind = StoreKindGlobal + } + store, err := NewEnvStore(storeKind) + if err != nil { + log.Fatal(err) } // Check if the variable exists @@ -387,12 +310,6 @@ func subcommandTagEnvVar(cmd TagCmd) { // {{{ if cmd.Remove { internal.Log.Printf("Removing tags %v from variable '%s'", cmd.Tags, cmd.Name) store.RemoveTags(cmd.Name, cmd.Tags) - fmt.Fprintf( - os.Stderr, - "Removed tags from '%s': %s\n", - cmd.Name, - strings.Join(cmd.Tags, ", "), - ) } else { internal.Log.Printf("Adding tags %v to variable '%s'", cmd.Tags, cmd.Name) store.SetTags(cmd.Name, cmd.Tags) @@ -400,26 +317,21 @@ func subcommandTagEnvVar(cmd TagCmd) { // {{{ } // Save the store - if cmd.Local { - saveLocalEnvStore(store) - } else { - saveGlobalEnvStore(store) + if err := store.Save(); err != nil { + log.Fatal(err) } } // }}} func subcommandListEnvVars(cmdArgs ListCmd) { // {{{ - var store *EnvStore - - // Determine which store to use + var storeKind StoreKind if cmdArgs.Local { - if _, err := os.Stat(localDbFilePath); os.IsNotExist(err) { - fmt.Fprintf(os.Stderr, "Local vault does not exist.\n") - return - } - store = loadLocalEnvStore() + storeKind = StoreKindLocal } else { - // Default: load and merge stores - store = loadEnvStore() + storeKind = StoreKindGlobal + } + store, err := NewEnvStore(storeKind) + if err != nil { + log.Fatal(err) } // Sort keys for consistent output @@ -452,21 +364,17 @@ func subcommandListEnvVars(cmdArgs ListCmd) { // {{{ } // }}} func subcommandExecCommand(cmdArgs ExecCmd) { // {{{ - var store *EnvStore - - // Determine which store to use + var storeKind StoreKind if cmdArgs.Local { - if _, err := os.Stat(localDbFilePath); os.IsNotExist(err) { - fmt.Fprintf(os.Stderr, "Local vault does not exist.\n") - os.Exit(1) - } - store = loadLocalEnvStore() - internal.Log.Println("Using local vault only for execution") + storeKind = StoreKindLocal } else { - // Default: load and merge stores - store = loadEnvStore() - internal.Log.Println("Using merged vaults for execution") + storeKind = StoreKindGlobal } + store, err := NewEnvStore(storeKind) + if err != nil { + log.Fatal(err) + } + envVars := os.Environ() for candidateEnvName, candidateEnvValue := range store.Vars { // Skip if not matching tag filter @@ -628,6 +536,191 @@ func subcommandRekeyVault() { // {{{ // //////////////////////////////////////////////////////////////////////////////// +func exists(path string) bool { + _, err := os.Stat(path) + if err != nil && os.IsNotExist(err) { + return false + } + return err == nil +} + +type EnvStore struct { // {{{ + filePath string + + Vars map[string]string `json:"vars"` + Tags map[string][]string `json:"tags,omitempty"` +} + +func NewEnvStoreFromPath(filePath string) (*EnvStore, error) { + key := loadKey() + + store := EnvStore{ + filePath: filePath, + Vars: make(map[string]string), + Tags: make(map[string][]string), + } + + data, err := os.ReadFile(filePath) + if err != nil { + if os.IsNotExist(err) { + return &store, nil + } + return nil, err + } + + if len(data) == 0 { + return &store, nil + } + + var nonce [24]byte + copy(nonce[:], data[:24]) + decrypted, ok := secretbox.Open(nil, data[24:], &nonce, &key) + if !ok { + log.Fatal("Decryption failed") + } + if err := json.Unmarshal(decrypted, &store); err != nil { + log.Fatal(err) + } + // Initialize Tags map if it's nil + if store.Tags == nil { + store.Tags = make(map[string][]string) + } + return &store, nil +} + +type StoreKind int + +const ( + StoreKindGlobal = iota + StoreKindLocal + StoreKindMerged +) + +func NewEnvStore(kind StoreKind) (*EnvStore, error) { + globalStore, globalErr := NewEnvStoreFromPath(dbFilePath) + localStore, localErr := NewEnvStoreFromPath(localDbFilePath) + + if kind == StoreKindGlobal { + if globalErr != nil { + return nil, globalErr + } + return globalStore, nil + } + if kind == StoreKindLocal { + if localErr != nil { + return nil, localErr + } + return localStore, nil + } + + if globalErr != nil { + return nil, globalErr + } + if localErr != nil { + return nil, localErr + } + + // Override/add local vars and tags + maps.Copy(globalStore.Vars, localStore.Vars) + maps.Copy(globalStore.Tags, localStore.Tags) + return globalStore, nil +} + +func (s *EnvStore) Save() error { + key := loadKey() + data, err := json.Marshal(s) + if err != nil { + return err + } + + var nonce [24]byte + if _, err := rand.Read(nonce[:]); err != nil { + return err + } + encrypted := secretbox.Seal(nonce[:], data, &nonce, &key) + + if err := os.MkdirAll(filepath.Dir(s.filePath), 0700); err != nil { + return err + } + if err := os.WriteFile(s.filePath, encrypted, 0600); err != nil { + return err + } + + return nil +} + +// GetTags returns all tags for a given variable name +func (s *EnvStore) GetTags(varName string) []string { + if s.Tags == nil { + return []string{} + } + return s.Tags[varName] +} + +// SetTags sets the tags for a given variable name +func (s *EnvStore) SetTags(varName string, tags []string) { + if s.Tags == nil { + s.Tags = make(map[string][]string) + } + + // Create a union of existing and new tags + existingTags := s.Tags[varName] + unionTags := make([]string, 0, len(existingTags)+len(tags)) + + // Add existing tags to the union + for _, tag := range existingTags { + unionTags = append(unionTags, tag) + } + + // Add new tags if they don't already exist + for _, tag := range tags { + if !slices.Contains(unionTags, tag) { + unionTags = append(unionTags, tag) + } + } + + s.Tags[varName] = unionTags +} + +// RemoveTags removes the specified tags from a variable +func (s *EnvStore) RemoveTags(varName string, tagsToRemove []string) { + if s.Tags == nil || len(s.Tags[varName]) == 0 { + return + } + + existingTags := s.Tags[varName] + remainingTags := make([]string, 0, len(existingTags)) + + // Keep only tags that are not in tagsToRemove + for _, tag := range existingTags { + if !slices.Contains(tagsToRemove, tag) { + remainingTags = append(remainingTags, tag) + } + } + + s.Tags[varName] = remainingTags +} + +// HasAnyTag returns true if the variable has any of the specified tags +func (s *EnvStore) HasAnyTag(varName string, tags []string) bool { + if len(tags) == 0 { + return true // No tags specified, include all variables + } + + varTags := s.GetTags(varName) + for _, tag := range tags { + if slices.Contains(varTags, tag) { + return true + } + } + return false +} + +func (s *EnvStore) RemoveEnvVar(name string) { + delete(s.Vars, name) + delete(s.Tags, name) +} // }}} + func loadPassword() []byte { // {{{ internal.Log.Println("Getting password without caching") @@ -673,14 +766,14 @@ func loadPassword() []byte { // {{{ } // }}} func loadKey() [32]byte { // {{{ - // Get password but don't cache it yet - password := loadPassword() - encryptedKey, err := os.ReadFile(keyFilePath) if err != nil { log.Fatal(err) } + // Get password but don't cache it yet + password := loadPassword() + decryptedKey, err := decryptKeyWithPassword(encryptedKey, password) if err != nil { log.Fatal("Invalid password or corrupted key file") @@ -753,118 +846,4 @@ func decryptKeyWithPassword(encryptedKey, password []byte) ([]byte, error) { // return decrypted, nil } // }}} - -func loadStoreFile(filePath string) *EnvStore { // {{{ - key := loadKey() - - data, err := os.ReadFile(filePath) - if err != nil { - if os.IsNotExist(err) { - return &EnvStore{ - Vars: make(map[string]string), - Tags: make(map[string][]string), - } - } - log.Fatal(err) - } - - var store EnvStore - if len(data) > 0 { - var nonce [24]byte - copy(nonce[:], data[:24]) - decrypted, ok := secretbox.Open(nil, data[24:], &nonce, &key) - if !ok { - log.Fatal("Decryption failed") - } - if err := json.Unmarshal(decrypted, &store); err != nil { - log.Fatal(err) - } - // Initialize Tags map if it's nil - if store.Tags == nil { - store.Tags = make(map[string][]string) - } - } else { - return &EnvStore{ - Vars: make(map[string]string), - Tags: make(map[string][]string), - } - } - return &store -} // }}} - -func loadGlobalEnvStore() *EnvStore { // {{{ - if err := os.MkdirAll(filepath.Dir(dbFilePath), 0700); err != nil { - log.Fatal(err) - } - return loadStoreFile(dbFilePath) -} // }}} - -func loadLocalEnvStore() *EnvStore { // {{{ - return loadStoreFile(localDbFilePath) -} // }}} - -func loadEnvStore() *EnvStore { // {{{ - // Load global store first - globalStore := loadGlobalEnvStore() - - // Check if local store exists - if _, err := os.Stat(localDbFilePath); os.IsNotExist(err) { - // No local store, just return global - return globalStore - } - - // Load local store - localStore := loadLocalEnvStore() - - // Merge stores (local takes precedence) - mergedStore := &EnvStore{ - Vars: make(map[string]string), - Tags: make(map[string][]string), - } - - // Copy all global vars and tags - maps.Copy(mergedStore.Vars, globalStore.Vars) - maps.Copy(mergedStore.Tags, globalStore.Tags) - - // Override/add local vars and tags - maps.Copy(mergedStore.Vars, localStore.Vars) - maps.Copy(mergedStore.Tags, localStore.Tags) - - return mergedStore -} // }}} - -func saveGlobalEnvStore(store *EnvStore) { // {{{ - key := loadKey() - data, err := json.Marshal(store) - if err != nil { - log.Fatal(err) - } - - var nonce [24]byte - if _, err := rand.Read(nonce[:]); err != nil { - log.Fatal(err) - } - encrypted := secretbox.Seal(nonce[:], data, &nonce, &key) - - if err := os.WriteFile(dbFilePath, encrypted, 0600); err != nil { - log.Fatal(err) - } -} // }}} - -func saveLocalEnvStore(store *EnvStore) { // {{{ - key := loadKey() - data, err := json.Marshal(store) - if err != nil { - log.Fatal(err) - } - - var nonce [24]byte - if _, err := rand.Read(nonce[:]); err != nil { - log.Fatal(err) - } - encrypted := secretbox.Seal(nonce[:], data, &nonce, &key) - - if err := os.WriteFile(localDbFilePath, encrypted, 0600); err != nil { - log.Fatal(err) - } -} // }}} +// }}}