diff --git a/README.md b/README.md index 9a2eac2..9cf4176 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ # Overview -Vaultify is a powerful CLI tool developed in Go, designed to enhance productivity and security by encrypting the state-files in `base64` for managing Terraform state files. It streamlines the encryption and storage of state files across multiple platforms, including `HashiCorp Vault`, `Azure Storage Account`, and soon, `AWS S3 buckets`. By automating the encryption and push/pull processes, Vaultify ensures your Terraform state files are securely managed and easily accessible. +Vaultify is a powerful CLI tool developed in Go, designed to enhance productivity and security by storing state-files as `base64` encoded strings in a secure location. It streamlines the encryption and storage of state files across multiple platforms, including `HashiCorp Vault`, `Azure Storage Account`, and soon, `AWS S3 buckets`. By automating the encryption and push/pull processes, Vaultify ensures your Terraform state files are securely managed and easily accessible. > NOTE: You can also refer to vaultify documentation at [Vaultify](https://vaultify.buungroup.com) to learn more. @@ -382,4 +382,4 @@ This section covers how to contrinute to this project see the [CONTRIBUTING](CO # 📃 License -This project is licensed under the `GNU General Public License, Version 3 (GPL-3.0)` - see the [LICENSE](LICENSE) file for details. \ No newline at end of file +This project is licensed under the `GNU General Public License, Version 3 (GPL-3.0)` - see the [LICENSE](LICENSE) file for details. diff --git a/cmd/common.go b/cmd/common.go index 18844ae..a8715f0 100644 --- a/cmd/common.go +++ b/cmd/common.go @@ -14,19 +14,19 @@ package cmd import ( "bytes" + "crypto/hmac" + "crypto/sha256" + "encoding/base64" "encoding/json" "fmt" + "github.com/Azure/azure-sdk-for-go/storage" + "io" "log" + "net/http" "os" "os/exec" "path/filepath" "strings" - "net/http" - "io" - "github.com/Azure/azure-sdk-for-go/storage" - "crypto/hmac" - "crypto/sha256" - "encoding/base64" ) // ############################### @@ -71,24 +71,24 @@ func getCurrentWorkspace() (string, error) { // ######################### func readSettings() (*Configuration, error) { - homeDir, err := os.UserHomeDir() - if err != nil { - return nil, fmt.Errorf("❌ Error getting user home directory: %v", err) - } - - settingsFilePath := filepath.Join(homeDir, ".vaultify", "settings.json") - content, err := os.ReadFile(settingsFilePath) - if err != nil { - return nil, fmt.Errorf("❌ Error reading settings file: \033[33m%v\033[0m", err) - } - - var config Configuration - err = json.Unmarshal(content, &config) - if err != nil { - return nil, fmt.Errorf("❌ Error unmarshalling settings JSON: \033[33m%v\033[0m", err) - } - - return &config, nil + homeDir, err := os.UserHomeDir() + if err != nil { + return nil, fmt.Errorf("❌ Error getting user home directory: %v", err) + } + + settingsFilePath := filepath.Join(homeDir, ".vaultify", "settings.json") + content, err := os.ReadFile(settingsFilePath) + if err != nil { + return nil, fmt.Errorf("❌ Error reading settings file: \033[33m%v\033[0m", err) + } + + var config Configuration + err = json.Unmarshal(content, &config) + if err != nil { + return nil, fmt.Errorf("❌ Error unmarshalling settings JSON: \033[33m%v\033[0m", err) + } + + return &config, nil } // ##################################### @@ -252,10 +252,9 @@ func deleteVolume(volumeName string) { // #################### type OAuthResponse struct { - AccessToken string `json:"access_token"` + AccessToken string `json:"access_token"` } - type Subscription struct { ID string `json:"id"` DisplayName string `json:"displayName"` @@ -265,7 +264,6 @@ type SubscriptionsResponse struct { Subscriptions []Subscription `json:"value"` } - func AuthenticateWithAzureAD() (string, error) { tenantID := os.Getenv("ARM_TENANT_ID") clientID := os.Getenv("ARM_CLIENT_ID") @@ -303,75 +301,75 @@ func AuthenticateWithAzureAD() (string, error) { } func checkAzureStorageAccountExists() (bool, error) { - accessToken, err := AuthenticateWithAzureAD() - if err != nil { - return false, fmt.Errorf("error obtaining access token: %v", err) - } - - config, err := readConfiguration() - if err != nil { - return false, fmt.Errorf("error reading configuration: %v", err) - } - - accountName := config.Settings.Azure.StorageAccountName - resourceGroup := config.Settings.Azure.StorageAccountResourceGroupName - subscriptionID := os.Getenv("ARM_SUBSCRIPTION_ID") - - if subscriptionID == "" { - return false, fmt.Errorf("subscription ID is missing") - } - - url := fmt.Sprintf("https://management.azure.com/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Storage/storageAccounts/%s?api-version=2019-06-01", subscriptionID, resourceGroup, accountName) - - req, err := http.NewRequest("GET", url, nil) - if err != nil { - return false, err - } - - req.Header.Set("Authorization", "Bearer "+accessToken) - - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - return false, err - } - defer resp.Body.Close() - - if resp.StatusCode == http.StatusOK { - return true, nil - } else { - bodyBytes, _ := io.ReadAll(resp.Body) - return false, fmt.Errorf("storage account check failed with status %d: %s", resp.StatusCode, string(bodyBytes)) - } + accessToken, err := AuthenticateWithAzureAD() + if err != nil { + return false, fmt.Errorf("error obtaining access token: %v", err) + } + + config, err := readConfiguration() + if err != nil { + return false, fmt.Errorf("error reading configuration: %v", err) + } + + accountName := config.Settings.Azure.StorageAccountName + resourceGroup := config.Settings.Azure.StorageAccountResourceGroupName + subscriptionID := os.Getenv("ARM_SUBSCRIPTION_ID") + + if subscriptionID == "" { + return false, fmt.Errorf("subscription ID is missing") + } + + url := fmt.Sprintf("https://management.azure.com/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Storage/storageAccounts/%s?api-version=2019-06-01", subscriptionID, resourceGroup, accountName) + + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return false, err + } + + req.Header.Set("Authorization", "Bearer "+accessToken) + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return false, err + } + defer resp.Body.Close() + + if resp.StatusCode == http.StatusOK { + return true, nil + } else { + bodyBytes, _ := io.ReadAll(resp.Body) + return false, fmt.Errorf("storage account check failed with status %d: %s", resp.StatusCode, string(bodyBytes)) + } } func CheckAzureEnvVars() error { - config, err := readConfiguration() - if err != nil { - return fmt.Errorf("error reading configuration: %v", err) - } - - if config.Settings.DefaultSecretStorage == "azure_storage" { - requiredEnvVars := []string{ - "ARM_SUBSCRIPTION_ID", - "ARM_CLIENT_ID", - "ARM_CLIENT_SECRET", - "ARM_TENANT_ID", - } - - var missingVars []string - for _, envVar := range requiredEnvVars { - if os.Getenv(envVar) == "" { - missingVars = append(missingVars, envVar) - } - } - - if len(missingVars) > 0 { - return fmt.Errorf("missing required environment variables for Azure storage: \033[33m%v\033[0m", missingVars) - } - } - - return nil + config, err := readConfiguration() + if err != nil { + return fmt.Errorf("error reading configuration: %v", err) + } + + if config.Settings.DefaultSecretStorage == "azure_storage" { + requiredEnvVars := []string{ + "ARM_SUBSCRIPTION_ID", + "ARM_CLIENT_ID", + "ARM_CLIENT_SECRET", + "ARM_TENANT_ID", + } + + var missingVars []string + for _, envVar := range requiredEnvVars { + if os.Getenv(envVar) == "" { + missingVars = append(missingVars, envVar) + } + } + + if len(missingVars) > 0 { + return fmt.Errorf("missing required environment variables for Azure storage: \033[33m%v\033[0m", missingVars) + } + } + + return nil } // #################################################### @@ -379,62 +377,62 @@ func CheckAzureEnvVars() error { // #################################################### func createContainer(accountName, key string) { - containerName := "vaultify" + containerName := "vaultify" - client, err := storage.NewBasicClient(accountName, key) - if err != nil { - fmt.Println("Error creating storage client:\033[33m", err) - return - } + client, err := storage.NewBasicClient(accountName, key) + if err != nil { + fmt.Println("Error creating storage client:\033[33m", err) + return + } - blobClient := client.GetBlobService() + blobClient := client.GetBlobService() - container := blobClient.GetContainerReference(containerName) + container := blobClient.GetContainerReference(containerName) - exists, err := container.Exists() - if err != nil { - fmt.Println("Error checking container existence:\033[33m", err) - return - } + exists, err := container.Exists() + if err != nil { + fmt.Println("Error checking container existence:\033[33m", err) + return + } - if exists { + if exists { - } else { - err := container.Create(nil) - if err != nil { - fmt.Println("Failed to create container:\033[33m", err) - return - } + } else { + err := container.Create(nil) + if err != nil { + fmt.Println("Failed to create container:\033[33m", err) + return + } - fmt.Println("Container \033[33m'vaultify'\033[0m created successfully.") - } + fmt.Println("Container \033[33m'vaultify'\033[0m created successfully.") + } } func generateSignature(accountName, accountKey, method, contentLength, contentType, date, blobType, containerName, blobName string) (string, error) { - urlPath := fmt.Sprintf("/%s/%s/%s", accountName, containerName, blobName) + urlPath := fmt.Sprintf("/%s/%s/%s", accountName, containerName, blobName) - stringToSign := method + "\n" + stringToSign := method + "\n" - if method == "PUT" { - stringToSign += "\n\n" + contentLength + "\n\n" + contentType + "\n\n\n\n\n\n\n" - } else { - stringToSign += "\n\n\n\n\n\n\n\n\n\n\n" - } + if method == "PUT" { + stringToSign += "\n\n" + contentLength + "\n\n" + contentType + "\n\n\n\n\n\n\n" + } else { + stringToSign += "\n\n\n\n\n\n\n\n\n\n\n" + } - if method == "PUT" { - stringToSign += "x-ms-blob-type:" + blobType + "\n" - } - stringToSign += "x-ms-date:" + date + "\n" + "x-ms-version:2019-12-12\n" + urlPath + if method == "PUT" { + stringToSign += "x-ms-blob-type:" + blobType + "\n" + } + stringToSign += "x-ms-date:" + date + "\n" + "x-ms-version:2019-12-12\n" + urlPath - key, err := base64.StdEncoding.DecodeString(accountKey) - if err != nil { - return "", fmt.Errorf("error decoding storage account access key: %v", err) - } + key, err := base64.StdEncoding.DecodeString(accountKey) + if err != nil { + return "", fmt.Errorf("error decoding storage account access key: %v", err) + } - hasher := hmac.New(sha256.New, key) - hasher.Write([]byte(stringToSign)) - signature := base64.StdEncoding.EncodeToString(hasher.Sum(nil)) + hasher := hmac.New(sha256.New, key) + hasher.Write([]byte(stringToSign)) + signature := base64.StdEncoding.EncodeToString(hasher.Sum(nil)) - authHeader := fmt.Sprintf("SharedKey %s:%s", accountName, signature) - return authHeader, nil -} \ No newline at end of file + authHeader := fmt.Sprintf("SharedKey %s:%s", accountName, signature) + return authHeader, nil +} diff --git a/cmd/configure.go b/cmd/configure.go index ba7fc7a..569183d 100644 --- a/cmd/configure.go +++ b/cmd/configure.go @@ -13,160 +13,160 @@ package cmd import ( - "encoding/json" - "fmt" - "os" - "path/filepath" + "encoding/json" + "fmt" + "os" + "path/filepath" ) func readConfiguration() (*Configuration, error) { - homeDir, _ := os.UserHomeDir() - configFile := filepath.Join(homeDir, ".vaultify", "settings.json") + homeDir, _ := os.UserHomeDir() + configFile := filepath.Join(homeDir, ".vaultify", "settings.json") - file, err := os.ReadFile(configFile) - if err != nil { - return nil, err - } + file, err := os.ReadFile(configFile) + if err != nil { + return nil, err + } - var config Configuration - if err := json.Unmarshal(file, &config); err != nil { - return nil, err - } + var config Configuration + if err := json.Unmarshal(file, &config); err != nil { + return nil, err + } - return &config, nil + return &config, nil } func writeConfiguration(config *Configuration) error { - homeDir, _ := os.UserHomeDir() - configFile := filepath.Join(homeDir, ".vaultify", "settings.json") + homeDir, _ := os.UserHomeDir() + configFile := filepath.Join(homeDir, ".vaultify", "settings.json") - data, err := json.MarshalIndent(config, "", " ") - if err != nil { - return err - } + data, err := json.MarshalIndent(config, "", " ") + if err != nil { + return err + } - return os.WriteFile(configFile, data, 0644) + return os.WriteFile(configFile, data, 0644) } func isValidEngineName(name string) bool { - // TODO: Add logic to validate engine name - return true + // TODO: Add logic to validate engine name + return true } func Configure() { - if err := checkVaultifySetup(); err != nil { - fmt.Println(err) - fmt.Println("Please run \033[33m'vaultify init'\033[0m to set up \033[33mVaultify\033[0m.") - return - } - - config, err := readConfiguration() - if err != nil { - fmt.Println("❌ Error reading configuration:", err) - return - } - - for { - fmt.Println("\n\033[33mVaultify Settings\033[0m") - fmt.Println("-------------------------------------------") - fmt.Printf("%-20s \033[33m%s\033[0m\n", "\033[33m1\033[0m. Default Engine Name:", config.Settings.DefaultEngineName) - fmt.Printf("%-20s \033[33m%t\033[0m\n", "\033[33m2\033[0m. Use Terraform Workspaces:", config.Settings.TerraformWorkspace) - fmt.Printf("%-20s \033[33m%s\033[0m\n", "\033[33m3\033[0m. Default Secret Storage:", config.Settings.DefaultSecretStorage) - fmt.Printf("%-20s \033[33m%s\033[0m\n", "\033[33m4\033[0m. View Secret Storage Settings", "\033[97m(\033[33mAmazon\033[0m \033[97mor\033[0m \033[33mAzure\033[0m\033[97m)\033[0m") - fmt.Println("-------------------------------------------") - fmt.Println("Enter the number of the option you want to change (or \033[33m0\033[0m to exit):") - - var choice int - fmt.Print("\033[33mChoice\033[0m: ") - _, err := fmt.Scanln(&choice) - if err != nil { - fmt.Println("Invalid input. Please enter a \033[33mnumber\033[0m.") - continue - } - - switch choice { - case 0: - fmt.Println("\nExiting configuration.") - return - case 1: - fmt.Print("\n\033[33mEnter new Default Engine Name\033[0m: ") - var engineName string - fmt.Scanln(&engineName) - if !isValidEngineName(engineName) { - fmt.Println("Invalid engine name. Please enter a valid \033[33mengine name\033[0m.") - continue - } - config.Settings.DefaultEngineName = engineName - case 2: - fmt.Println("\nEnable \033[33mTerraform\033[0m Workspaces:") - fmt.Println("\033[33m1\033[0m. True") - fmt.Println("\033[33m2\033[0m. False") - var workspaceOption int - fmt.Print("\033[97m------------------\033[0m\n") - fmt.Print("\033[33mChoice\033[0m: ") - fmt.Scanln(&workspaceOption) - switch workspaceOption { - case 1: - config.Settings.TerraformWorkspace = true - case 2: - config.Settings.TerraformWorkspace = false - default: - fmt.Println("Invalid option. Please select \033[33m1\033[0m for True or \033[33m2\033[0m for False.") - continue - } - case 3: - fmt.Println("\n\033[33mSelect Default Secret Storage:\033[0m") - fmt.Println("\033[33m1\033[0m. Hashicorp Vault") - fmt.Println("\033[33m2\033[0m. AWS S3 Bucket") - fmt.Println("\033[33m3\033[0m. Azure Storage") - var storageOption int + if err := checkVaultifySetup(); err != nil { + fmt.Println(err) + fmt.Println("Please run \033[33m'vaultify init'\033[0m to set up \033[33mVaultify\033[0m.") + return + } + + config, err := readConfiguration() + if err != nil { + fmt.Println("❌ Error reading configuration:", err) + return + } + + for { + fmt.Println("\n\033[33mVaultify Settings\033[0m") + fmt.Println("-------------------------------------------") + fmt.Printf("%-20s \033[33m%s\033[0m\n", "\033[33m1\033[0m. Default Engine Name:", config.Settings.DefaultEngineName) + fmt.Printf("%-20s \033[33m%t\033[0m\n", "\033[33m2\033[0m. Use Terraform Workspaces:", config.Settings.TerraformWorkspace) + fmt.Printf("%-20s \033[33m%s\033[0m\n", "\033[33m3\033[0m. Default Secret Storage:", config.Settings.DefaultSecretStorage) + fmt.Printf("%-20s \033[33m%s\033[0m\n", "\033[33m4\033[0m. View Secret Storage Settings", "\033[97m(\033[33mAmazon\033[0m \033[97mor\033[0m \033[33mAzure\033[0m\033[97m)\033[0m") + fmt.Println("-------------------------------------------") + fmt.Println("Enter the number of the option you want to change (or \033[33m0\033[0m to exit):") + + var choice int + fmt.Print("\033[33mChoice\033[0m: ") + _, err := fmt.Scanln(&choice) + if err != nil { + fmt.Println("Invalid input. Please enter a \033[33mnumber\033[0m.") + continue + } + + switch choice { + case 0: + fmt.Println("\nExiting configuration.") + return + case 1: + fmt.Print("\n\033[33mEnter new Default Engine Name\033[0m: ") + var engineName string + fmt.Scanln(&engineName) + if !isValidEngineName(engineName) { + fmt.Println("Invalid engine name. Please enter a valid \033[33mengine name\033[0m.") + continue + } + config.Settings.DefaultEngineName = engineName + case 2: + fmt.Println("\nEnable \033[33mTerraform\033[0m Workspaces:") + fmt.Println("\033[33m1\033[0m. True") + fmt.Println("\033[33m2\033[0m. False") + var workspaceOption int fmt.Print("\033[97m------------------\033[0m\n") fmt.Print("\033[33mChoice\033[0m: ") - fmt.Scanln(&storageOption) - switch storageOption { - case 1: - config.Settings.DefaultSecretStorage = "vault" - case 2: - config.Settings.DefaultSecretStorage = "s3" - case 3: - config.Settings.DefaultSecretStorage = "azure_storage" - default: - fmt.Println("Invalid option. Please select \033[33m1\033[0m, \033[33m2\033[0m, or \033[33m3\033[0m.") - continue - } - case 4: - fmt.Println("\n\033[33mSecret Storage Settings:\033[0m") - fmt.Print("\033[97m----------------------\033[0m\n") - fmt.Println("\033[33m1\033[0m. Amazon Web Services") - fmt.Println("\033[33m2\033[0m. Azure") - fmt.Println("") - fmt.Print("\033[33mSelect the platform to view settings\033[0m: ") - var platformChoice int - fmt.Scanln(&platformChoice) - switch platformChoice { - case 1: - fmt.Println("\n\033[33mAWS Settings:\033[0m") - fmt.Print("\033[97m----------------------\033[0m\n") - awsSettings, _ := json.MarshalIndent(config.Settings.AWS, "", " ") - fmt.Println(string(awsSettings)) - case 2: - fmt.Println("\n\033[33mAzure Settings:\033[0m") - fmt.Print("\033[97m----------------------\033[0m\n") - azureSettings, _ := json.MarshalIndent(config.Settings.Azure, "", " ") - fmt.Println(string(azureSettings)) - default: - fmt.Println("\033[33mInvalid option\033[0m. Please select a valid platform.") - } - default: - fmt.Println("\033[33mInvalid option\033[0m. Please enter a valid \033[33mnumber\033[0m.") - } - - if err := writeConfiguration(config); err != nil { - fmt.Println("❌ Error saving \033[33mconfiguration\033[0m:", err) - return - } - - fmt.Println("\nConfiguration in \033[33m`settings.json`\033[0m updated/viewed successfully.") - } + fmt.Scanln(&workspaceOption) + switch workspaceOption { + case 1: + config.Settings.TerraformWorkspace = true + case 2: + config.Settings.TerraformWorkspace = false + default: + fmt.Println("Invalid option. Please select \033[33m1\033[0m for True or \033[33m2\033[0m for False.") + continue + } + case 3: + fmt.Println("\n\033[33mSelect Default Secret Storage:\033[0m") + fmt.Println("\033[33m1\033[0m. Hashicorp Vault") + fmt.Println("\033[33m2\033[0m. AWS S3 Bucket") + fmt.Println("\033[33m3\033[0m. Azure Storage") + var storageOption int + fmt.Print("\033[97m------------------\033[0m\n") + fmt.Print("\033[33mChoice\033[0m: ") + fmt.Scanln(&storageOption) + switch storageOption { + case 1: + config.Settings.DefaultSecretStorage = "vault" + case 2: + config.Settings.DefaultSecretStorage = "s3" + case 3: + config.Settings.DefaultSecretStorage = "azure_storage" + default: + fmt.Println("Invalid option. Please select \033[33m1\033[0m, \033[33m2\033[0m, or \033[33m3\033[0m.") + continue + } + case 4: + fmt.Println("\n\033[33mSecret Storage Settings:\033[0m") + fmt.Print("\033[97m----------------------\033[0m\n") + fmt.Println("\033[33m1\033[0m. Amazon Web Services") + fmt.Println("\033[33m2\033[0m. Azure") + fmt.Println("") + fmt.Print("\033[33mSelect the platform to view settings\033[0m: ") + var platformChoice int + fmt.Scanln(&platformChoice) + switch platformChoice { + case 1: + fmt.Println("\n\033[33mAWS Settings:\033[0m") + fmt.Print("\033[97m----------------------\033[0m\n") + awsSettings, _ := json.MarshalIndent(config.Settings.AWS, "", " ") + fmt.Println(string(awsSettings)) + case 2: + fmt.Println("\n\033[33mAzure Settings:\033[0m") + fmt.Print("\033[97m----------------------\033[0m\n") + azureSettings, _ := json.MarshalIndent(config.Settings.Azure, "", " ") + fmt.Println(string(azureSettings)) + default: + fmt.Println("\033[33mInvalid option\033[0m. Please select a valid platform.") + } + default: + fmt.Println("\033[33mInvalid option\033[0m. Please enter a valid \033[33mnumber\033[0m.") + } + + if err := writeConfiguration(config); err != nil { + fmt.Println("❌ Error saving \033[33mconfiguration\033[0m:", err) + return + } + + fmt.Println("\nConfiguration in \033[33m`settings.json`\033[0m updated/viewed successfully.") + } } diff --git a/cmd/delete-vault.go b/cmd/delete-vault.go index 83b32ba..20c108c 100644 --- a/cmd/delete-vault.go +++ b/cmd/delete-vault.go @@ -13,13 +13,13 @@ package cmd import ( + "bufio" "bytes" + "fmt" "log" + "os" "os/exec" "strings" - "bufio" - "os" - "fmt" ) func containerExists(containerName string) bool { @@ -33,58 +33,58 @@ func containerExists(containerName string) bool { } func promptForConfirmation(prompt string, autoConfirm bool) bool { - if autoConfirm { - fmt.Println(prompt + " \033[33mAuto-confirmed\033[0m due to \033[33m-y\033[0m flag.") - return true - } + if autoConfirm { + fmt.Println(prompt + " \033[33mAuto-confirmed\033[0m due to \033[33m-y\033[0m flag.") + return true + } - reader := bufio.NewReader(os.Stdin) - fmt.Printf("%s [y/n]: ", prompt) + reader := bufio.NewReader(os.Stdin) + fmt.Printf("%s [y/n]: ", prompt) - response, err := reader.ReadString('\n') - if err != nil { - log.Fatalf("Failed to read input: \033[33m%v\033[0m", err) - } + response, err := reader.ReadString('\n') + if err != nil { + log.Fatalf("Failed to read input: \033[33m%v\033[0m", err) + } - response = strings.TrimSpace(response) - return strings.ToLower(response) == "y" || strings.ToLower(response) == "yes" + response = strings.TrimSpace(response) + return strings.ToLower(response) == "y" || strings.ToLower(response) == "yes" } func DeleteVault(autoConfirm bool) { - if !isDockerInstalled() { - log.Fatal("\033[33mDocker\033[0m is not installed.") - } + if !isDockerInstalled() { + log.Fatal("\033[33mDocker\033[0m is not installed.") + } - if !isDockerRunning() { - log.Fatal("\033[33mDocker\033[0m is not running. Please start the \033[33mDocker daemon\033[0m.") - } + if !isDockerRunning() { + log.Fatal("\033[33mDocker\033[0m is not running. Please start the \033[33mDocker daemon\033[0m.") + } - const containerName = "vault-raft-backend" - - confirmationPrompt := "\033[0mAre you sure you want to delete the docker container: \033[33m" + containerName + "\033[0m and all its \033[33mdata/volumes\033[0m and \033[33msecrets\033[0m? This action cannot be \033[33mundone\033[0m." - if !promptForConfirmation(confirmationPrompt, autoConfirm) { - log.Println("Deletion canceled by the user.") - return - } + const containerName = "vault-raft-backend" - if containerExists(containerName) { - if containerIsRunning(containerName) { - stopCmd := exec.Command("docker", "stop", containerName) - if err := stopCmd.Run(); err != nil { - log.Fatalf("Failed to stop Vault Docker container: \033[33m%v\033[0m", err) - } - } + confirmationPrompt := "\033[0mAre you sure you want to delete the docker container: \033[33m" + containerName + "\033[0m and all its \033[33mdata/volumes\033[0m and \033[33msecrets\033[0m? This action cannot be \033[33mundone\033[0m." + if !promptForConfirmation(confirmationPrompt, autoConfirm) { + log.Println("Deletion canceled by the user.") + return + } - rmCmd := exec.Command("docker", "rm", containerName) - if err := rmCmd.Run(); err != nil { - log.Fatalf("Failed to remove Vault Docker container: \033[33m%v\033[0m", err) - } - } else { - log.Printf("\033[33mNo\033[0m Docker container with the name \033[33m'%s'\033[0m found. Skipping stop and remove commands.", containerName) - } + if containerExists(containerName) { + if containerIsRunning(containerName) { + stopCmd := exec.Command("docker", "stop", containerName) + if err := stopCmd.Run(); err != nil { + log.Fatalf("Failed to stop Vault Docker container: \033[33m%v\033[0m", err) + } + } - deleteVolume("docker_vault_data") - deleteVolume("vault_data") + rmCmd := exec.Command("docker", "rm", containerName) + if err := rmCmd.Run(); err != nil { + log.Fatalf("Failed to remove Vault Docker container: \033[33m%v\033[0m", err) + } + } else { + log.Printf("\033[33mNo\033[0m Docker container with the name \033[33m'%s'\033[0m found. Skipping stop and remove commands.", containerName) + } + + deleteVolume("docker_vault_data") + deleteVolume("vault_data") - log.Println("Vault Docker \033[33mcontainer\033[0m and \033[33mvolume\033[0m have been cleaned up.") -} \ No newline at end of file + log.Println("Vault Docker \033[33mcontainer\033[0m and \033[33mvolume\033[0m have been cleaned up.") +} diff --git a/cmd/delete.go b/cmd/delete.go index 54f29c6..3af451e 100644 --- a/cmd/delete.go +++ b/cmd/delete.go @@ -14,10 +14,9 @@ package cmd import ( "bufio" - "encoding/json" + "context" "fmt" "os" - "os/exec" "path/filepath" "strings" ) @@ -31,14 +30,18 @@ func Delete() { return } + vaultClient, initStat := initVaultClientWithStatus() + if !initStat { + fmt.Println("❌ Error: Vault is not initialized!") + return + } + settings, err := readSettings() if err != nil { fmt.Println("❌ Error reading settings:", err) return } - curlCommand := "curl" - vaultURL := os.Getenv("VAULT_ADDR") engineName := settings.Settings.DefaultEngineName dataPath := "vaultify" @@ -58,38 +61,17 @@ func Delete() { workingDirName := filepath.Base(workingDir) secretPath := fmt.Sprintf("%s/%s/%s_%s", dataPath, workingDirName, workspaceName, "terraform.tfstate") - metadataCmd := exec.Command( - curlCommand, - "--header", "X-Vault-Token: "+os.Getenv("VAULT_TOKEN"), - "--request", "GET", - "--silent", - vaultURL+"/v1/"+engineName+"/metadata/"+secretPath, - ) - - metadataOutput, err := metadataCmd.Output() - if err != nil { - fmt.Println("❌ Error retrieving metadata:", err) - return - } - - var metadata struct { - Data struct { - CurrentVersion int `json:"current_version"` - Versions map[string]struct { - DeletionTime string `json:"deletion_time"` - } `json:"versions"` - } `json:"data"` - } - - if err := json.Unmarshal(metadataOutput, &metadata); err != nil { - fmt.Println("❌ Error parsing metadata JSON:", err) - return - } - - latestVersion := fmt.Sprintf("%d", metadata.Data.CurrentVersion) - if versionData, exists := metadata.Data.Versions[latestVersion]; exists && versionData.DeletionTime != "" { - fmt.Println("✅ The latest version of the secret has already been deleted.") - return + metadataValue, err := vaultClient.KVv2(engineName).GetMetadata(context.Background(), secretPath) + if err == nil { + if metadataValue == nil { + fmt.Println("❌ Error retrieving metadata:", err) + return + } + latestVersion := fmt.Sprintf("%d", metadataValue.CurrentVersion) + if versionData, exists := metadataValue.Versions[latestVersion]; exists && versionData.DeletionTime.String() != "0001-01-01 00:00:00 +0000 UTC" { + fmt.Println("✅ The latest version of the secret has already been deleted.") + return + } } fmt.Printf("Are you sure you want to delete the secret at '%s'? [y/N]: ", secretPath) @@ -102,15 +84,7 @@ func Delete() { return } - deleteCmd := exec.Command( - curlCommand, - "--silent", "--show-error", - "--header", "X-Vault-Token: "+os.Getenv("VAULT_TOKEN"), - "--request", "DELETE", - vaultURL+"/v1/"+engineName+"/data/"+secretPath, - ) - - _, err = deleteCmd.CombinedOutput() + err = vaultClient.KVv2(engineName+"/").Delete(context.Background(), secretPath) if err != nil { fmt.Println("❌ Error deleting secret from Vault:", err) return diff --git a/cmd/help.go b/cmd/help.go index ea10d52..d0d0ce9 100644 --- a/cmd/help.go +++ b/cmd/help.go @@ -30,6 +30,8 @@ func Help() { fmt.Println(" \033[33mdelete-vault\033[0m Delete a local developer Hashicorp Vault server, auto delete with `-y`.") fmt.Println(" \033[33mpermissions\033[0m Validate your roles and permissions on your used Hashicorp Vault token") fmt.Println(" \033[33mpush\033[0m Push state to remote Hashicorp Vault server afer you have wrapped your statefile") + fmt.Println(" \033[33mpublish\033[0m Wrap a secret to base64 and push") + fmt.Println(" \033[33mretrieve\033[0m Pull and Unwrap a secret from base64") fmt.Println(" \033[33mstatus\033[0m Checks if Vaultify is still authenticated to Hashicorp Vault.") fmt.Println(" \033[33mconfigure\033[0m Configures the Vaultify project, allowing customization of settings such as the Vault address, authentication method, and data paths") fmt.Println(" \033[33m-v\033[0m, \033[33m--version\033[0m Show the Vaultify version") diff --git a/cmd/init.go b/cmd/init.go index c8009cc..f97c858 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -20,106 +20,105 @@ import ( ) type AzureSettings struct { - StorageAccountName string `json:"storage_account_name"` - StorageAccountResourceGroupName string `json:"storage_account_resource_group_name"` + StorageAccountName string `json:"storage_account_name"` + StorageAccountResourceGroupName string `json:"storage_account_resource_group_name"` } type AwsSettings struct { - S3BucketName string `json:"s3_bucket_name"` + S3BucketName string `json:"s3_bucket_name"` } type Configuration struct { - Settings struct { - TerraformWorkspace bool `json:"terraform_workspace"` - DefaultEngineName string `json:"default_engine_name"` - DefaultSecretStorage string `json:"default_secret_storage"` - Azure AzureSettings `json:"azure"` - AWS AwsSettings `json:"aws"` - } `json:"settings"` + Settings struct { + TerraformWorkspace bool `json:"terraform_workspace"` + DefaultEngineName string `json:"default_engine_name"` + DefaultSecretStorage string `json:"default_secret_storage"` + Azure AzureSettings `json:"azure"` + AWS AwsSettings `json:"aws"` + } `json:"settings"` } func Init() { - vaultifyDir, err := ensureVaultifyFolder() - if err != nil { - fmt.Printf("❌ Error: %v\n", err) - os.Exit(1) - } - - if err := createSettingsFile(vaultifyDir); err != nil { - fmt.Printf("❌ Error creating \033[33msettings.json\033[0m: %v\n", err) - os.Exit(1) - } - - vaultToken := os.Getenv("VAULT_TOKEN") - vaultAddr := os.Getenv("VAULT_ADDR") - if vaultToken == "" || vaultAddr == "" { - fmt.Println("❌ Error: \033[33mVAULT_TOKEN\033[0m and \033[33mVAULT_ADDR\033[0m environment variables must be set.\033[0m") - os.Exit(1) - } - - fmt.Println("✅ \033[33mVaultify\033[0m initialized successfully.\033[0m") + vaultifyDir, err := ensureVaultifyFolder() + if err != nil { + fmt.Printf("❌ Error: %v\n", err) + os.Exit(1) + } + + if err := createSettingsFile(vaultifyDir); err != nil { + fmt.Printf("❌ Error creating \033[33msettings.json\033[0m: %v\n", err) + os.Exit(1) + } + + vaultToken := os.Getenv("VAULT_TOKEN") + vaultAddr := os.Getenv("VAULT_ADDR") + if vaultToken == "" || vaultAddr == "" { + fmt.Println("❌ Error: \033[33mVAULT_TOKEN\033[0m and \033[33mVAULT_ADDR\033[0m environment variables must be set.\033[0m") + os.Exit(1) + } + + fmt.Println("✅ \033[33mVaultify\033[0m initialized successfully.\033[0m") } func ensureVaultifyFolder() (string, error) { - homeDir, err := os.UserHomeDir() - if err != nil { - return "", fmt.Errorf("failed to get user home directory: %w", err) - } - - vaultifyDir := filepath.Join(homeDir, ".vaultify") - if _, err := os.Stat(vaultifyDir); os.IsNotExist(err) { - if err := os.Mkdir(vaultifyDir, 0700); err != nil { - return "", fmt.Errorf("failed to create \033[33m.vaultify\033[0m directory: %w", err) - } - fmt.Println("✅ Created \033[33m.vaultify\033[0m folder.\033[0m") - } else if err != nil { - return "", fmt.Errorf("error checking \033[33m.vaultify\033[0m directory: %w", err) - } - - return vaultifyDir, nil + homeDir, err := os.UserHomeDir() + if err != nil { + return "", fmt.Errorf("failed to get user home directory: %w", err) + } + + vaultifyDir := filepath.Join(homeDir, ".vaultify") + if _, err := os.Stat(vaultifyDir); os.IsNotExist(err) { + if err := os.Mkdir(vaultifyDir, 0700); err != nil { + return "", fmt.Errorf("failed to create \033[33m.vaultify\033[0m directory: %w", err) + } + fmt.Println("✅ Created \033[33m.vaultify\033[0m folder.\033[0m") + } else if err != nil { + return "", fmt.Errorf("error checking \033[33m.vaultify\033[0m directory: %w", err) + } + + return vaultifyDir, nil } func createSettingsFile(vaultifyDir string) error { - settingsFilePath := filepath.Join(vaultifyDir, "settings.json") - - if _, err := os.Stat(settingsFilePath); err == nil { - fmt.Println("✅ \033[33msettings.json\033[0m already exists.\033[0m") - return nil - } else if !os.IsNotExist(err) { - return fmt.Errorf("error checking \033[33msettings.json\033[0m: \033[33m%w\033[0m", err) - } - - settings := Configuration{ - Settings: struct{ - TerraformWorkspace bool `json:"terraform_workspace"` - DefaultEngineName string `json:"default_engine_name"` - DefaultSecretStorage string `json:"default_secret_storage"` - Azure AzureSettings `json:"azure"` - AWS AwsSettings `json:"aws"` - }{ - TerraformWorkspace: true, - DefaultEngineName: "kv", - DefaultSecretStorage: "vault", - Azure: AzureSettings{ - StorageAccountName: "", - StorageAccountResourceGroupName: "", - }, - AWS: AwsSettings{ - S3BucketName: "", - }, - }, - } - - - jsonData, err := json.MarshalIndent(settings, "", " ") - if err != nil { - return fmt.Errorf("failed to marshal settings to JSON: \033[33m%w\033[0m", err) - } - - if err := os.WriteFile(settingsFilePath, jsonData, 0644); err != nil { - return fmt.Errorf("failed to write \033[33msettings.json\033[0m: %w", err) - } - - fmt.Println("✅ Generated \033[33msettings.json\033[0m successfully.\033[0m") - return nil -} \ No newline at end of file + settingsFilePath := filepath.Join(vaultifyDir, "settings.json") + + if _, err := os.Stat(settingsFilePath); err == nil { + fmt.Println("✅ \033[33msettings.json\033[0m already exists.\033[0m") + return nil + } else if !os.IsNotExist(err) { + return fmt.Errorf("error checking \033[33msettings.json\033[0m: \033[33m%w\033[0m", err) + } + + settings := Configuration{ + Settings: struct { + TerraformWorkspace bool `json:"terraform_workspace"` + DefaultEngineName string `json:"default_engine_name"` + DefaultSecretStorage string `json:"default_secret_storage"` + Azure AzureSettings `json:"azure"` + AWS AwsSettings `json:"aws"` + }{ + TerraformWorkspace: true, + DefaultEngineName: "kv", + DefaultSecretStorage: "vault", + Azure: AzureSettings{ + StorageAccountName: "", + StorageAccountResourceGroupName: "", + }, + AWS: AwsSettings{ + S3BucketName: "", + }, + }, + } + + jsonData, err := json.MarshalIndent(settings, "", " ") + if err != nil { + return fmt.Errorf("failed to marshal settings to JSON: \033[33m%w\033[0m", err) + } + + if err := os.WriteFile(settingsFilePath, jsonData, 0644); err != nil { + return fmt.Errorf("failed to write \033[33msettings.json\033[0m: %w", err) + } + + fmt.Println("✅ Generated \033[33msettings.json\033[0m successfully.\033[0m") + return nil +} diff --git a/cmd/install-vault.go b/cmd/install-vault.go index e84e999..c1f9366 100644 --- a/cmd/install-vault.go +++ b/cmd/install-vault.go @@ -13,16 +13,16 @@ package cmd import ( + "bufio" "bytes" "encoding/json" + "fmt" "io" "log" "net/http" - "os/exec" "os" + "os/exec" "strings" - "fmt" - "bufio" ) type DockerTag struct { @@ -172,56 +172,56 @@ func InstallVault() { } } } - + log.Printf("\033[33m%s\033[0m", out.String()) // log.Printf("Vault Address: \033[33m%s\033[0m", vaultAddr) // log.Printf("Vault Token: \033[33m%s\033[0m", vaultToken) - + if promptUserForVaultEnvVariables() { exportEnvVariables(vaultAddr, vaultToken) } } func exportEnvVariables(vaultAddr, vaultToken string) { - shellConfigFile := determineShellConfigFile() + shellConfigFile := determineShellConfigFile() - if shellConfigFile == "" { - fmt.Println("Could not determine your shell configuration file (\033[33m.bashrc\033[0m or \033[33m.zshrc\033[0m).") - return - } + if shellConfigFile == "" { + fmt.Println("Could not determine your shell configuration file (\033[33m.bashrc\033[0m or \033[33m.zshrc\033[0m).") + return + } - appendCmds := fmt.Sprintf("echo 'export VAULT_ADDR=\"%s\"' >> %s && echo 'export VAULT_TOKEN=\"%s\"' >> %s", - vaultAddr, shellConfigFile, vaultToken, shellConfigFile) + appendCmds := fmt.Sprintf("echo 'export VAULT_ADDR=\"%s\"' >> %s && echo 'export VAULT_TOKEN=\"%s\"' >> %s", + vaultAddr, shellConfigFile, vaultToken, shellConfigFile) - execCmd := exec.Command("/bin/sh", "-c", appendCmds) - if err := execCmd.Run(); err != nil { - fmt.Fprintf(os.Stderr, "Failed to append environment variables to %s: %v\n", shellConfigFile, err) - return - } + execCmd := exec.Command("/bin/sh", "-c", appendCmds) + if err := execCmd.Run(); err != nil { + fmt.Fprintf(os.Stderr, "Failed to append environment variables to %s: %v\n", shellConfigFile, err) + return + } - fmt.Printf("Appended \033[33mVAULT_ADDR\033[0m and \033[33mVAULT_TOKEN\033[0m to %s. Please run \033[33m'source %s'\033[0m to apply changes.\n", shellConfigFile, shellConfigFile) + fmt.Printf("Appended \033[33mVAULT_ADDR\033[0m and \033[33mVAULT_TOKEN\033[0m to %s. Please run \033[33m'source %s'\033[0m to apply changes.\n", shellConfigFile, shellConfigFile) } func determineShellConfigFile() string { - shell := os.Getenv("SHELL") - if strings.Contains(shell, "zsh") { - return os.Getenv("HOME") + "/.zshrc" - } else if strings.Contains(shell, "bash") { - return os.Getenv("HOME") + "/.bashrc" - } - return "" + shell := os.Getenv("SHELL") + if strings.Contains(shell, "zsh") { + return os.Getenv("HOME") + "/.zshrc" + } else if strings.Contains(shell, "bash") { + return os.Getenv("HOME") + "/.bashrc" + } + return "" } func promptUserForVaultEnvVariables() bool { - reader := bufio.NewReader(os.Stdin) - fmt.Println("Do you want to export \033[33mVAULT_ADDR\033[0m and \033[33mVAULT_TOKEN\033[0m to your host's environment variables? This will modify your \033[33m.bashrc\033[0m or \033[33m.zshrc\033[0m file. [y/n]") + reader := bufio.NewReader(os.Stdin) + fmt.Println("Do you want to export \033[33mVAULT_ADDR\033[0m and \033[33mVAULT_TOKEN\033[0m to your host's environment variables? This will modify your \033[33m.bashrc\033[0m or \033[33m.zshrc\033[0m file. [y/n]") fmt.Print("\033[33mChoice\033[0m: ") - response, err := reader.ReadString('\n') - if err != nil { - fmt.Fprintf(os.Stderr, "Failed to read input: \033[33m%v\033[0m\n", err) - return false - } - - response = strings.TrimSpace(strings.ToLower(response)) - return response == "y" || response == "yes" -} \ No newline at end of file + response, err := reader.ReadString('\n') + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to read input: \033[33m%v\033[0m\n", err) + return false + } + + response = strings.TrimSpace(strings.ToLower(response)) + return response == "y" || response == "yes" +} diff --git a/cmd/permissions.go b/cmd/permissions.go index 66dd953..e4437cb 100644 --- a/cmd/permissions.go +++ b/cmd/permissions.go @@ -13,10 +13,8 @@ package cmd import ( - "encoding/json" "fmt" "os" - "os/exec" ) // TODO: Add a case switch statement depending on, the default secret storage type. @@ -30,6 +28,12 @@ func TokenPermissions() { return } + vaultClient, initStat := initVaultClientWithStatus() + if !initStat { + fmt.Println("❌ Error: Vault is not initialized!") + return + } + settings, err := readSettings() if err != nil { fmt.Println("❌ Error reading settings:", err) @@ -45,48 +49,34 @@ func TokenPermissions() { os.Exit(1) } - checkCmd := exec.Command("curl", "-s", "--header", "X-Vault-Token: "+vaultToken, vaultAddr+"/v1/auth/token/lookup-self") - - output, err := checkCmd.Output() + vaultAuthLookupSelf, err := vaultClient.Auth().Token().LookupSelf() if err != nil { fmt.Printf("❌ Error checking token permissions: \033[33m%v\033[0m\n", err) os.Exit(1) } - var response map[string]interface{} - if err := json.Unmarshal(output, &response); err != nil { - fmt.Printf("❌ Error parsing JSON response: \033[33m%v\033[0m\n", err) - os.Exit(1) - } - fmt.Println("") fmt.Println("\033[33mToken Permissions\033[0m:") fmt.Println("-----------------------------") - fmt.Printf("Policies: \033[33m%v\033[0m\n", response["data"].(map[string]interface{})["policies"]) + fmt.Printf("Policies: \033[33m%v\033[0m\n", vaultAuthLookupSelf.Data["policies"]) - testPath := vaultAddr + "/v1/" + engineName + "/data/vaultify_test_permission" - testCmd := exec.Command("curl", "-s", "--header", "X-Vault-Token: "+vaultToken, "--request", "POST", "--data", "{\"data\": {\"test\": \"value\"}}", testPath) + testPath := engineName + "/data/vaultify_test_permission" - testOutput, err := testCmd.Output() + _, err = vaultClient.Logical().Write(testPath, map[string]interface{}{ + "data": map[string]interface{}{ + "data": "test", + }, + }) if err != nil { - fmt.Println("❌ Error testing write \033[33mpermissions\033[0m"+engineName+" engine:", err) - return - } - - var testResponse map[string]interface{} - if err := json.Unmarshal(testOutput, &testResponse); err != nil { - fmt.Printf("❌ Error parsing test response: %v\n", err) + fmt.Println("❌ Error testing write \033[33mpermissions\033[0m"+engineName+" engine: ", err) return } - if testResponse["errors"] != nil { - fmt.Println("❌ Token does \033[33mnot\033[0m have permission to create secrets in" + engineName + "engine.") - } else { - fmt.Println("✅ Token has \033[33mpermission\033[0m to create secrets in " + engineName + " engine.") - } + fmt.Println("✅ Token has \033[33mpermission\033[0m to create secrets in " + engineName + " engine.") - deleteCmd := exec.Command("curl", "-s", "--header", "X-Vault-Token: "+vaultToken, "--request", "DELETE", testPath) - if _, err := deleteCmd.Output(); err != nil { + _, err = vaultClient.Logical().Delete(testPath) + if err != nil { fmt.Println("❌ Failed to \033[33mclean up\033[0m test secret.") } + fmt.Println("✅ \033[33mclean up\033[0m of test secrets complete.") } diff --git a/cmd/pull.go b/cmd/pull.go index cf71585..fb2692a 100644 --- a/cmd/pull.go +++ b/cmd/pull.go @@ -13,16 +13,19 @@ package cmd import ( - "encoding/json" + "context" + //"encoding/json" "fmt" + "io" + "log" + "net/http" "os" - "os/exec" + //"os/exec" "path/filepath" - "strings" - "log" + //"strings" "time" - "io" - "net/http" + + vault "github.com/hashicorp/vault/api" ) type VaultResponse struct { @@ -32,24 +35,24 @@ type VaultResponse struct { } func Pull() { - if err := checkVaultifySetup(); err != nil { - log.Printf("%v\nPlease run 'vaultify init' to set up Vaultify.\n", err) - return - } - - config, err := readConfiguration() - if err != nil { - fmt.Println("❌ \033[33mError\033[0m loading configuration:", err) - return - } - - defaultSecretStorage := config.Settings.DefaultSecretStorage - accountName := config.Settings.Azure.StorageAccountName - - switch defaultSecretStorage { - case "vault": - pullFromVault() - case "azure_storage": + if err := checkVaultifySetup(); err != nil { + log.Printf("%v\nPlease run 'vaultify init' to set up Vaultify.\n", err) + return + } + + config, err := readConfiguration() + if err != nil { + fmt.Println("❌ \033[33mError\033[0m loading configuration:", err) + return + } + + defaultSecretStorage := config.Settings.DefaultSecretStorage + accountName := config.Settings.Azure.StorageAccountName + + switch defaultSecretStorage { + case "vault": + pullFromVault() + case "azure_storage": key, err := listStorageAccountKeys() if err != nil { log.Fatalf("Failed to list storage account keys: \033[33m%v\033[0m", err) @@ -57,11 +60,11 @@ func Pull() { if err := pullBlobFromAzureStorage(accountName, key); err != nil { fmt.Printf("Error pulling blob from Azure Storage: %v\n", err) } - case "s3": - log.Println("AWS S3 pulling is currently under development.") - default: - log.Println("Unsupported secret storage specified.") - } + case "s3": + log.Println("AWS S3 pulling is currently under development.") + default: + log.Println("Unsupported secret storage specified.") + } } func pullFromVault() { @@ -72,18 +75,19 @@ func pullFromVault() { return } + vaultClient, initStat := initVaultClientWithStatus() + if !initStat { + fmt.Println("❌ Error: Vault is not initialized!") + return + } + settings, err := readSettings() if err != nil { fmt.Println("❌ Error reading settings:", err) return } - curlCommand := "curl" - - vaultURL := os.Getenv("VAULT_ADDR") - engineName := settings.Settings.DefaultEngineName - dataPath := "vaultify" workspaceName, err := getCurrentWorkspace() @@ -102,59 +106,22 @@ func pullFromVault() { secretPath := fmt.Sprintf("%s/%s/%s_%s", dataPath, workingDirName, workspaceName, "terraform.tfstate") - checkPathCmd := exec.Command( - curlCommand, - "--silent", "--show-error", - "--header", "X-Vault-Token: "+os.Getenv("VAULT_TOKEN"), - "--request", "GET", - "--silent", "--write-out", "%{http_code}", - "--output", "/dev/null", - vaultURL+"/v1/"+engineName+"/data/"+secretPath, - ) - - checkPathOutput, err := checkPathCmd.CombinedOutput() - + secretValue, err := vaultClient.KVv2(engineName).Get(context.Background(), secretPath) if err != nil { - fmt.Println("❌ Error checking if secret path exists:", err) + fmt.Printf("❌ Error: %v, %s\n", vault.ErrSecretNotFound, err) return } - pathStatus := strings.TrimSpace(string(checkPathOutput)) - - if pathStatus == "404" { - fmt.Printf("❌ Error: Secret path not found in HashiCorp Vault. Path: \033[33m%s\033[0m\n", vaultURL+"/v1/"+engineName+"/data/"+secretPath) + if secretValue == nil { + fmt.Printf("❌ Error: No secrets at %s, %s\n", secretPath, err) return } fmt.Println("✅ Secret exists in Vault. Retrieving...") - pullCmd := exec.Command( - curlCommand, - "--silent", "--show-error", - "--header", "X-Vault-Token: "+os.Getenv("VAULT_TOKEN"), - "--request", "GET", - vaultURL+"/v1/"+engineName+"/data/"+secretPath, - ) - - pullOutput, err := pullCmd.Output() - if err != nil { - fmt.Println("❌ Error retrieving \033[33msecret\033[0m from Vault:", err) - return - } - - var response VaultResponse - err = json.Unmarshal(pullOutput, &response) - if err != nil { - fmt.Println("❌ Error unmarshalling \033[33mJSON\033[0m:", err) - return - } - - dynamicKey := fmt.Sprintf("%s/%s/%s_%s", dataPath, workingDirName, workspaceName, "terraform.tfstate") - - base64String, ok := response.Data.Data[dynamicKey] + base64String, ok := secretValue.Data[secretPath].(string) if !ok { fmt.Println("❌ Error: Specific \033[33mkey\033[0m not found in the data") - return } targetFilePath := "terraform.tfstate.gz.b64" @@ -175,65 +142,64 @@ func pullFromVault() { } func pullBlobFromAzureStorage(accountName, key string) error { - containerName := "vaultify" - - workspaceName, err := getCurrentWorkspace() - if err != nil { - return fmt.Errorf("❌ Error getting current Terraform workspace: %v", err) - } - - workingDir, err := os.Getwd() - if err != nil { - return fmt.Errorf("❌ Error getting current working directory: %v", err) - } - - workingDirName := filepath.Base(workingDir) - blobName := fmt.Sprintf("%s/%s_%s", workingDirName, workspaceName, "terraform.tfstate") - - method := "GET" - date := time.Now().UTC().Format(http.TimeFormat) - url := fmt.Sprintf("https://%s.blob.core.windows.net/%s/%s", accountName, containerName, blobName) - - authHeader, err := generateSignature(accountName, key, method, "0", "", date, "", containerName, blobName) - if err != nil { - return fmt.Errorf("❌ Error generating authorization signature for download: \033[33m%v\033[0m", err) - } - - req, err := http.NewRequest(method, url, nil) - if err != nil { - return fmt.Errorf("❌ Error creating HTTP request for download: \033[33m%v\033[0m", err) - } - - req.Header.Set("x-ms-date", date) - req.Header.Set("x-ms-version", "2019-12-12") - req.Header.Set("Authorization", authHeader) - - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - return fmt.Errorf("❌ Error making HTTP request for download: \033[33m%v\033[0m", err) - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - responseBody, _ := io.ReadAll(resp.Body) - return fmt.Errorf("❌ Failed to download blob, status code: \033[33m%d\033[0m, response: \033[33m%s\033[0m", resp.StatusCode, string(responseBody)) - } - - - outputFile, err := os.Create("terraform.tfstate.gz.b64") - if err != nil { - return fmt.Errorf("❌ Error creating file to save downloaded blob: \033[33m%v\033[0m", err) - } - defer outputFile.Close() - - _, err = io.Copy(outputFile, resp.Body) - if err != nil { - return fmt.Errorf("❌ Error writing downloaded blob to file: \033[33m%v\033[0m", err) - } - - fmt.Println("✅ Blob downloaded successfully and saved as \033[33mterraform.tfstate.gz.b64\033[0m") - return nil + containerName := "vaultify" + + workspaceName, err := getCurrentWorkspace() + if err != nil { + return fmt.Errorf("❌ Error getting current Terraform workspace: %v", err) + } + + workingDir, err := os.Getwd() + if err != nil { + return fmt.Errorf("❌ Error getting current working directory: %v", err) + } + + workingDirName := filepath.Base(workingDir) + blobName := fmt.Sprintf("%s/%s_%s", workingDirName, workspaceName, "terraform.tfstate") + + method := "GET" + date := time.Now().UTC().Format(http.TimeFormat) + url := fmt.Sprintf("https://%s.blob.core.windows.net/%s/%s", accountName, containerName, blobName) + + authHeader, err := generateSignature(accountName, key, method, "0", "", date, "", containerName, blobName) + if err != nil { + return fmt.Errorf("❌ Error generating authorization signature for download: \033[33m%v\033[0m", err) + } + + req, err := http.NewRequest(method, url, nil) + if err != nil { + return fmt.Errorf("❌ Error creating HTTP request for download: \033[33m%v\033[0m", err) + } + + req.Header.Set("x-ms-date", date) + req.Header.Set("x-ms-version", "2019-12-12") + req.Header.Set("Authorization", authHeader) + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return fmt.Errorf("❌ Error making HTTP request for download: \033[33m%v\033[0m", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + responseBody, _ := io.ReadAll(resp.Body) + return fmt.Errorf("❌ Failed to download blob, status code: \033[33m%d\033[0m, response: \033[33m%s\033[0m", resp.StatusCode, string(responseBody)) + } + + outputFile, err := os.Create("terraform.tfstate.gz.b64") + if err != nil { + return fmt.Errorf("❌ Error creating file to save downloaded blob: \033[33m%v\033[0m", err) + } + defer outputFile.Close() + + _, err = io.Copy(outputFile, resp.Body) + if err != nil { + return fmt.Errorf("❌ Error writing downloaded blob to file: \033[33m%v\033[0m", err) + } + + fmt.Println("✅ Blob downloaded successfully and saved as \033[33mterraform.tfstate.gz.b64\033[0m") + return nil } func saveStateToFile(data []byte, filePath string) error { diff --git a/cmd/push.go b/cmd/push.go index 7460ea8..6a73e1e 100644 --- a/cmd/push.go +++ b/cmd/push.go @@ -13,53 +13,53 @@ package cmd import ( + "bytes" + "encoding/base64" + "encoding/json" "fmt" + "io" + "log" + "net/http" "os" - "os/exec" "path/filepath" - "net/http" - "strings" - "encoding/base64" "time" - "bytes" - "encoding/json" - "log" - "io" + + vault "github.com/hashicorp/vault/api" ) func Push() { - config, err := readConfiguration() - if err != nil { - fmt.Println("❌ \033[33mError\033[0m loading configuration:", err) - return - } + config, err := readConfiguration() + if err != nil { + fmt.Println("❌ \033[33mError\033[0m loading configuration:", err) + return + } - defaultSecretStorage := config.Settings.DefaultSecretStorage - accountName := config.Settings.Azure.StorageAccountName + defaultSecretStorage := config.Settings.DefaultSecretStorage + accountName := config.Settings.Azure.StorageAccountName - switch defaultSecretStorage { - case "vault": - pushToVault() - case "azure_storage": + switch defaultSecretStorage { + case "vault": + pushToVault() + case "azure_storage": key, err := listStorageAccountKeys() if err != nil { log.Fatalf("Failed to list storage account keys: \033[33m%v\033[0m", err) } accountName := accountName - // fmt.Printf("Account Name: %s\n", accountName) - // fmt.Printf("Storage Account Key: %s\n", key) - createContainer(accountName, key) + // fmt.Printf("Account Name: %s\n", accountName) + // fmt.Printf("Storage Account Key: %s\n", key) + createContainer(accountName, key) encodedStateFilePath := "/tmp/.encoded_wrap" if err := uploadBlobWithAccessKey(accountName, key, encodedStateFilePath); err != nil { log.Fatalf("Error uploading blob: \033[33m%v\033[0m", err) } - case "s3": + case "s3": fmt.Println("⚠️ \033[33m AWS S3 Bucket\033[0m is currently under development.") - default: - fmt.Println("Unsupported secret storage specified.") - } + default: + fmt.Println("Unsupported secret storage specified.") + } } func pushToVault() { @@ -70,6 +70,18 @@ func pushToVault() { return } + vaultClient, initStat := initVaultClientWithStatus() + if !initStat { + fmt.Println("❌ Error: Vault is not initialized!") + return + } + + settings, err := readSettings() + if err != nil { + fmt.Println("❌ Error reading settings:", err) + return + } + encodedStateFilePath := "/tmp/.encoded_wrap" if _, err := os.Stat(encodedStateFilePath); os.IsNotExist(err) { @@ -96,11 +108,11 @@ func pushToVault() { } os.Setenv("TERRAFORM_STATE_BASE64", encodedStateFile) - curlCommand := "curl" encodedPayload := encodedStateFile - vaultURL := os.Getenv("VAULT_ADDR") - engineName := "kv" + + engineName := settings.Settings.DefaultEngineName dataPath := "vaultify" + workspaceName, err := getCurrentWorkspace() if err != nil { fmt.Println("❌ Error getting current Terraform workspace:", err) @@ -116,55 +128,36 @@ func pushToVault() { workingDirName := filepath.Base(workingDir) secretPath := fmt.Sprintf("%s/%s/%s_%s", dataPath, workingDirName, workspaceName, "terraform.tfstate") - checkPathCmd := exec.Command(curlCommand, "--silent", "--show-error", "--header", "X-Vault-Token: "+os.Getenv("VAULT_TOKEN"), "--request", "GET", vaultURL+"/v1/"+engineName+"/data/"+dataPath) - checkPathOutput, err := checkPathCmd.CombinedOutput() + err = ensureKVPathExists(vaultClient, engineName, dataPath) if err != nil { - fmt.Println("❌ Error checking if secret path exists:", err) - return + fmt.Println("❌ Error: Unable to perform operation", err) } - pathStatus := strings.TrimSpace(string(checkPathOutput)) - if pathStatus == "404" { - createPathCmd := exec.Command(curlCommand, "--silent", "--show-error", "--header", "X-Vault-Token: "+os.Getenv("VAULT_TOKEN"), "--request", "POST", "--data-raw", `{"type": "kv"}`, vaultURL+"/v1/"+engineName+"/data/"+dataPath) - createPathOutput, err := createPathCmd.CombinedOutput() - if err != nil { - fmt.Println("❌ Error creating secret path:", err) - return - } - if !strings.Contains(string(createPathOutput), "success") { - fmt.Println("❌ Failed to create secret path:", string(createPathOutput)) - return - } + secretData := map[string]interface{}{ + "data": map[string]interface{}{ + secretPath: encodedPayload, + }, } - pushCmd := exec.Command(curlCommand, "--silent", "--show-error", "--header", "X-Vault-Token: "+os.Getenv("VAULT_TOKEN"), "--request", "PUT", "--data-raw", "{\"data\": {\""+secretPath+"\": \""+encodedPayload+"\"}}", "--write-out", "%{http_code}", "--output", "response.json", vaultURL+"/v1/"+engineName+"/data/"+secretPath) - pushOutput, err := pushCmd.CombinedOutput() + _, err = vaultClient.Logical().Write(engineName+"/data/"+secretPath, secretData) if err != nil { fmt.Println("❌ Error pushing secret to Vault:", err) return } - httpStatus := strings.TrimSpace(string(pushOutput)) - if httpStatus == "200" || httpStatus == "204" { - fmt.Printf("✅ Secret written to HashiCorp Vault under: \033[33m%s\033[0m\n", secretPath) - fmt.Printf("💠 The file size uploaded to Hashicorp Vault: \033[33m%.2f\033[0m KB\n", float64(len(encodedStateFile))/1024) - - if _, err := os.Stat("terraform.tfstate"); err == nil { - if err := os.Remove("terraform.tfstate"); err != nil { - fmt.Println("❌ Error: Failed to delete the \033[33mterraform.tfstate\033[0m file.", err) - return - } - //fmt.Println("✅ Deleted the terraform.tfstate file.") - } + fmt.Printf("✅ Secret written to HashiCorp Vault under: \033[33m%s\033[0m\n", secretPath) + fmt.Printf("💠 The file size uploaded to Hashicorp Vault: \033[33m%.2f\033[0m KB\n", float64(len(encodedStateFile))/1024) - if err := os.Remove(encodedStateFilePath); err != nil { - fmt.Println("❌ Error: Failed to delete the \033[33m/tmp/.encoded_wrap\033[0m file.", err) + if _, err := os.Stat("terraform.tfstate"); err == nil { + if err := os.Remove("terraform.tfstate"); err != nil { + fmt.Println("❌ Error: Failed to delete the \033[33mterraform.tfstate\033[0m file.", err) return } - //fmt.Println("✅ Deleted the /tmp/.encoded_wrap file.") - } else { - fmt.Println("❌ Failed to write secret to Hashicorp Vault.") - fmt.Printf("Response code: \033[33m%s\033[0m\n", httpStatus) + } + + if err := os.Remove(encodedStateFilePath); err != nil { + fmt.Println("❌ Error: Failed to delete the \033[33m/tmp/.encoded_wrap\033[0m file.", err) + return } } @@ -174,150 +167,189 @@ func isValidBase64(input string) bool { } func listStorageAccountKeys() (string, error) { - accessToken, err := AuthenticateWithAzureAD() - if err != nil { - return "", err - } - - config, err := readConfiguration() - if err != nil { - return "", fmt.Errorf("error loading configuration: %v", err) - } - - accountName := config.Settings.Azure.StorageAccountName - resourceGroupName := config.Settings.Azure.StorageAccountResourceGroupName - subscriptionId := os.Getenv("ARM_SUBSCRIPTION_ID") - - url := fmt.Sprintf("https://management.azure.com/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Storage/storageAccounts/%s/listKeys?api-version=2019-06-01", subscriptionId, resourceGroupName, accountName) - req, err := http.NewRequest("POST", url, nil) - if err != nil { - return "", err - } - - req.Header.Set("Authorization", "Bearer "+accessToken) - req.Header.Set("Content-Type", "application/json") - - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - return "", err - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return "", fmt.Errorf("failed to list storage account keys, status code: %d", resp.StatusCode) - } - - var result struct { - Keys []struct { - Value string `json:"value"` - } `json:"keys"` - } - if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { - return "", err - } - - if len(result.Keys) > 0 { - return result.Keys[0].Value, nil - } - - return "", fmt.Errorf("no keys found for the storage account") + accessToken, err := AuthenticateWithAzureAD() + if err != nil { + return "", err + } + + config, err := readConfiguration() + if err != nil { + return "", fmt.Errorf("error loading configuration: %v", err) + } + + accountName := config.Settings.Azure.StorageAccountName + resourceGroupName := config.Settings.Azure.StorageAccountResourceGroupName + subscriptionId := os.Getenv("ARM_SUBSCRIPTION_ID") + + url := fmt.Sprintf("https://management.azure.com/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Storage/storageAccounts/%s/listKeys?api-version=2019-06-01", subscriptionId, resourceGroupName, accountName) + req, err := http.NewRequest("POST", url, nil) + if err != nil { + return "", err + } + + req.Header.Set("Authorization", "Bearer "+accessToken) + req.Header.Set("Content-Type", "application/json") + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return "", err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return "", fmt.Errorf("failed to list storage account keys, status code: %d", resp.StatusCode) + } + + var result struct { + Keys []struct { + Value string `json:"value"` + } `json:"keys"` + } + if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { + return "", err + } + + if len(result.Keys) > 0 { + return result.Keys[0].Value, nil + } + + return "", fmt.Errorf("no keys found for the storage account") } func uploadBlobWithAccessKey(accountName, key, encodedStateFilePath string) error { - containerName := "vaultify" + containerName := "vaultify" if err := checkVaultifySetup(); err != nil { errMsg := fmt.Sprintf("%v\nPlease run 'vaultify init' to set up Vaultify.", err) return fmt.Errorf(errMsg) } - if _, err := os.Stat(encodedStateFilePath); os.IsNotExist(err) { - return fmt.Errorf("❌ Error: .encoded_wrap file not found in the /tmp directory. Please run 'vaultify wrap' to create the .encoded_wrap file") - } else if err != nil { - return fmt.Errorf("❌ Error checking .encoded_wrap file: %v", err) - } - - encodedStateFileContents, err := os.ReadFile(encodedStateFilePath) - if err != nil { - return fmt.Errorf("❌ Error reading .encoded_wrap file: %v", err) - } + if _, err := os.Stat(encodedStateFilePath); os.IsNotExist(err) { + return fmt.Errorf("❌ Error: .encoded_wrap file not found in the /tmp directory. Please run 'vaultify wrap' to create the .encoded_wrap file") + } else if err != nil { + return fmt.Errorf("❌ Error checking .encoded_wrap file: %v", err) + } - if len(encodedStateFileContents) == 0 { - return fmt.Errorf("❌ Error: .encoded_wrap file is empty") - } + encodedStateFileContents, err := os.ReadFile(encodedStateFilePath) + if err != nil { + return fmt.Errorf("❌ Error reading .encoded_wrap file: %v", err) + } - workspaceName, err := getCurrentWorkspace() - if err != nil { - return fmt.Errorf("❌ Error getting current Terraform workspace: %v", err) - } + if len(encodedStateFileContents) == 0 { + return fmt.Errorf("❌ Error: .encoded_wrap file is empty") + } + workspaceName, err := getCurrentWorkspace() + if err != nil { + return fmt.Errorf("❌ Error getting current Terraform workspace: %v", err) + } - workingDir, err := os.Getwd() - if err != nil { - return fmt.Errorf("❌ Error getting current working directory: %v", err) - } + workingDir, err := os.Getwd() + if err != nil { + return fmt.Errorf("❌ Error getting current working directory: %v", err) + } workingDirName := filepath.Base(workingDir) blobName := fmt.Sprintf("%s/%s_%s", workingDirName, workspaceName, "terraform.tfstate") - file, err := os.Open(encodedStateFilePath) - if err != nil { - return fmt.Errorf("error opening file: %v", err) - } - defer file.Close() - - fileContents, err := io.ReadAll(file) - if err != nil { - return fmt.Errorf("error reading file contents: %v", err) - } - - method := "PUT" - contentType := "application/octet-stream" - contentLength := fmt.Sprintf("%d", len(fileContents)) - blobType := "BlockBlob" - date := time.Now().UTC().Format(http.TimeFormat) - url := fmt.Sprintf("https://%s.blob.core.windows.net/%s/%s", accountName, containerName, blobName) - - authHeader, err := generateSignature(accountName, key, method, contentLength, contentType, date, blobType, containerName, blobName) - if err != nil { - return fmt.Errorf("error generating authorization signature: %v", err) - } - - req, err := http.NewRequest(method, url, bytes.NewReader(fileContents)) - if err != nil { - return fmt.Errorf("error creating HTTP request: %v", err) - } - - req.Header.Set("Content-Type", contentType) - req.Header.Set("Content-Length", contentLength) - req.Header.Set("x-ms-blob-type", blobType) - req.Header.Set("x-ms-date", date) - req.Header.Set("x-ms-version", "2019-12-12") - req.Header.Set("Authorization", authHeader) - - resp, err := http.DefaultClient.Do(req) - if err != nil { - return fmt.Errorf("error making HTTP request: \033[33m%v\033[0m", err) - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusCreated { - body, _ := io.ReadAll(resp.Body) - return fmt.Errorf("failed to upload blob, status code: \033[33m%d\033[0m, body: \033[33m%s\033[0m", resp.StatusCode, string(body)) - } - - fmt.Println("✅ Blob uploaded successfully to \033[33m" + accountName + "\033[0m, blob name \033[33m" + blobName + "\033[0m.") - fmt.Printf("💠 The file size uploaded to Azure Storage: \033[33m%.2f\033[0m KB\n", float64(len(fileContents))/1024) + file, err := os.Open(encodedStateFilePath) + if err != nil { + return fmt.Errorf("error opening file: %v", err) + } + defer file.Close() + + fileContents, err := io.ReadAll(file) + if err != nil { + return fmt.Errorf("error reading file contents: %v", err) + } + + method := "PUT" + contentType := "application/octet-stream" + contentLength := fmt.Sprintf("%d", len(fileContents)) + blobType := "BlockBlob" + date := time.Now().UTC().Format(http.TimeFormat) + url := fmt.Sprintf("https://%s.blob.core.windows.net/%s/%s", accountName, containerName, blobName) + + authHeader, err := generateSignature(accountName, key, method, contentLength, contentType, date, blobType, containerName, blobName) + if err != nil { + return fmt.Errorf("error generating authorization signature: %v", err) + } + + req, err := http.NewRequest(method, url, bytes.NewReader(fileContents)) + if err != nil { + return fmt.Errorf("error creating HTTP request: %v", err) + } + + req.Header.Set("Content-Type", contentType) + req.Header.Set("Content-Length", contentLength) + req.Header.Set("x-ms-blob-type", blobType) + req.Header.Set("x-ms-date", date) + req.Header.Set("x-ms-version", "2019-12-12") + req.Header.Set("Authorization", authHeader) + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return fmt.Errorf("error making HTTP request: \033[33m%v\033[0m", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusCreated { + body, _ := io.ReadAll(resp.Body) + return fmt.Errorf("failed to upload blob, status code: \033[33m%d\033[0m, body: \033[33m%s\033[0m", resp.StatusCode, string(body)) + } + + fmt.Println("✅ Blob uploaded successfully to \033[33m" + accountName + "\033[0m, blob name \033[33m" + blobName + "\033[0m.") + fmt.Printf("💠 The file size uploaded to Azure Storage: \033[33m%.2f\033[0m KB\n", float64(len(fileContents))/1024) if _, err := os.Stat("terraform.tfstate"); err == nil { if err := os.Remove("terraform.tfstate"); err != nil { return fmt.Errorf("❌ Error: Failed to delete the terraform.tfstate file: %v", err) } } - + if err := os.Remove(encodedStateFilePath); err != nil { return fmt.Errorf("❌ Error: Failed to delete the /tmp/.encoded_wrap file: %v", err) } - return nil -} \ No newline at end of file + return nil +} + +func ensureKVPathExists(client *vault.Client, mountPath string, path string) error { + var secret *vault.Secret + checkKVVersion, err := client.Logical().Read("sys/mounts/" + mountPath) + if err != nil { + return fmt.Errorf("❌ Error: Unable to determine KV version of secrets engine at: %s", mountPath) + } + + if checkKVVersion.Data["options"] != nil { + // KV v2 + secret, err = client.Logical().List(mountPath + "/metadata/" + path) + } else { + // KV v1 + secret, err = client.Logical().Read(mountPath + "/data/" + path) + } + if err != nil { + if respErr, ok := err.(*vault.ResponseError); ok && respErr.StatusCode == 403 { + return fmt.Errorf("❌ Error: Permission denied for path: %s, %w", path, err) + } + return fmt.Errorf("❌ Error reading path: %s, %w", path, err) + } + + if secret != nil { + fmt.Printf("✅ Writing to path %s\n", path) + return nil + } + + _, err = client.Logical().Write(mountPath+"/data/"+path, map[string]interface{}{ + "data": map[string]interface{}{ + "type": "kv", + }, + }) + if err != nil { + return fmt.Errorf("❌ Error creating path: %s, %w", path, err) + } + + fmt.Printf("✅ Path %s created successfully\n", path) + + return nil +} diff --git a/cmd/status.go b/cmd/status.go index f6f4cd1..f1e41da 100644 --- a/cmd/status.go +++ b/cmd/status.go @@ -13,90 +13,123 @@ package cmd import ( - "fmt" - "os" - "os/exec" - "strings" - "encoding/json" + "encoding/json" + "fmt" + "os" + + vault "github.com/hashicorp/vault/api" ) func Status() { - config, err := readConfiguration() - if err != nil { - fmt.Println("❌ \033[33mError\033[0m loading configuration:", err) - return - } + config, err := readConfiguration() + if err != nil { + fmt.Println("❌ \033[33mError\033[0m loading configuration:", err) + return + } - switch config.Settings.DefaultSecretStorage { - case "vault": - checkVaultStatus() - case "s3": + switch config.Settings.DefaultSecretStorage { + case "vault": + checkVaultStatus() + case "s3": fmt.Println("⚠️ \033[33m AWS S3 Bucket\033[0m is currently under development.") - case "azure_storage": - err = CheckAzureEnvVars() - if err != nil { - fmt.Println("❌ Error:", err) - return - } - fmt.Println("✅ \033[33mAzure Storage\033[0m environment variables are set correctly.") - - _, err = AuthenticateWithAzureAD() - if err != nil { - fmt.Println("❌ Error:", err) - return - } - fmt.Println("✅ \033[33mAuthenticated\033[0m to Azure.") - exists, err := checkAzureStorageAccountExists() - if err != nil { - fmt.Println("❌ Error checking \033[33mAzure storage account\033[0m:\033[33m", err) - fmt.Println("⚠️ \033[0mPlease validate your \033[33mresource group name\033[0m and \033[33mstorage account name\033[0m in your \033[33m~/.vaultify/settings.json\033[0m file.") - fmt.Println("\033[33m--------------------------\033[0m") - azureSettings, _ := json.MarshalIndent(config.Settings.Azure, "", " ") - fmt.Println(string(azureSettings)) - fmt.Println("\033[33m--------------------------\033[0m") - } else if exists { - fmt.Println("✅ \033[0mAzure storage account\033[33m " + config.Settings.Azure.StorageAccountName + "\033[0m exists.") - } else { - fmt.Println("❌ \033[0mAzure storage account\033[33m " + config.Settings.Azure.StorageAccountName + "\033[0m does not exist.") // TODO: If storage account give input prompt for vaultify to create it for you automatically and stores its values dynamically for you in the settings.json file. - } - default: - fmt.Println("Unknown DefaultSecretStorage setting:", config.Settings.DefaultSecretStorage) - } + case "azure_storage": + err = CheckAzureEnvVars() + if err != nil { + fmt.Println("❌ Error:", err) + return + } + fmt.Println("✅ \033[33mAzure Storage\033[0m environment variables are set correctly.") + + _, err = AuthenticateWithAzureAD() + if err != nil { + fmt.Println("❌ Error:", err) + return + } + fmt.Println("✅ \033[33mAuthenticated\033[0m to Azure.") + exists, err := checkAzureStorageAccountExists() + if err != nil { + fmt.Println("❌ Error checking \033[33mAzure storage account\033[0m:\033[33m", err) + fmt.Println("⚠️ \033[0mPlease validate your \033[33mresource group name\033[0m and \033[33mstorage account name\033[0m in your \033[33m~/.vaultify/settings.json\033[0m file.") + fmt.Println("\033[33m--------------------------\033[0m") + azureSettings, _ := json.MarshalIndent(config.Settings.Azure, "", " ") + fmt.Println(string(azureSettings)) + fmt.Println("\033[33m--------------------------\033[0m") + } else if exists { + fmt.Println("✅ \033[0mAzure storage account\033[33m " + config.Settings.Azure.StorageAccountName + "\033[0m exists.") + } else { + fmt.Println("❌ \033[0mAzure storage account\033[33m " + config.Settings.Azure.StorageAccountName + "\033[0m does not exist.") // TODO: If storage account give input prompt for vaultify to create it for you automatically and stores its values dynamically for you in the settings.json file. + } + default: + fmt.Println("Unknown DefaultSecretStorage setting:", config.Settings.DefaultSecretStorage) + } } func checkVaultStatus() { - vaultToken := os.Getenv("VAULT_TOKEN") - if vaultToken == "" { - fmt.Println("❌ Error: \033[33mVAULT_TOKEN\033[0m environment variable is not set. Please authenticate to Vault.") - return - } - - vaultAddr := os.Getenv("VAULT_ADDR") - if vaultAddr == "" { - fmt.Println("❌ Error: \033[33mVAULT_ADDR\033[0m environment variable is not set. Please specify the Vault address.") - return - } - - curlCommand := "curl" - vaultAPIEndpoint := vaultAddr + "/v1/sys/init" - - curlCmd := exec.Command( - curlCommand, - "--header", "X-Vault-Token: "+vaultToken, - "--request", "GET", - vaultAPIEndpoint, - ) - - curlOutput, err := curlCmd.CombinedOutput() - if err != nil { - fmt.Println("❌ Error executing 'curl' command:", err) - return - } - - if strings.Contains(string(curlOutput), "initialized\":true") { - fmt.Println("✅ \033[33mVaultify\033[0m is authenticated and connected to Vault at:\033[33m", vaultAddr) - } else { - fmt.Println("❌ Error: \033[33mVaultify\033[0m is not authenticated or unable to connect to Vault.") - } + vaultToken := os.Getenv("VAULT_TOKEN") + if vaultToken == "" { + fmt.Println("❌ Error: \033[33mVAULT_TOKEN\033[0m environment variable is not set. Please authenticate to Vault.") + return + } + + vaultAddr := os.Getenv("VAULT_ADDR") + if vaultAddr == "" { + fmt.Println("❌ Error: \033[33mVAULT_ADDR\033[0m environment variable is not set. Please specify the Vault address.") + return + } + + vaultConfig := vault.DefaultConfig() + vaultConfig.Address = vaultAddr + + vaultClient, err := vault.NewClient(vaultConfig) + if err != nil { + fmt.Printf("❌ Error: Unable to connect to %s: %s", vaultAddr, err.Error()) + } + + vaultClient.SetToken(vaultToken) + + init, err := vaultClient.Sys().InitStatus() + if err != nil { + fmt.Printf("❌ Error: Unable to authenticate with Vault: %s", err.Error()) + } + + if !init { + fmt.Println("❌ Error: Vault is not initialized!") + } else { + fmt.Println("✅ \033[33mVaultify\033[0m is authenticated and connected to Vault at:\033[33m", vaultAddr) + } +} + +func initVaultClientWithStatus() (vaultClient *vault.Client, initStat bool) { + vaultToken := os.Getenv("VAULT_TOKEN") + if vaultToken == "" { + fmt.Println("❌ Error: \033[33mVAULT_TOKEN\033[0m environment variable is not set. Please authenticate to Vault.") + return + } + + vaultAddr := os.Getenv("VAULT_ADDR") + if vaultAddr == "" { + fmt.Println("❌ Error: \033[33mVAULT_ADDR\033[0m environment variable is not set. Please specify the Vault address.") + return + } + + vaultConfig := vault.DefaultConfig() + vaultConfig.Address = vaultAddr + + vaultClient, err := vault.NewClient(vaultConfig) + + if err != nil { + fmt.Printf("❌ Error: Unable to connect to %s: %s", vaultAddr, err.Error()) + } + + vaultClient.SetToken(vaultToken) + + initStat, err = vaultClient.Sys().InitStatus() + + if err != nil { + fmt.Printf("❌ Error: Unable to authenticate with Vault: %s", err.Error()) + } + + return + } diff --git a/cmd/version.go b/cmd/version.go index fe03ee0..63a693f 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -21,7 +21,6 @@ type VersionProvider interface { GetVersion() string } - // This variable, is automatically updated through the release workflow per binary build. const version = "v1.0.18" diff --git a/go.mod b/go.mod index d878ed4..eb6c6c9 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,10 @@ module vaultify go 1.21.4 -require github.com/Azure/azure-sdk-for-go v68.0.0+incompatible +require ( + github.com/Azure/azure-sdk-for-go v68.0.0+incompatible + github.com/hashicorp/vault/api v1.13.0 +) require ( github.com/Azure/go-autorest v14.2.0+incompatible // indirect @@ -12,9 +15,26 @@ require ( github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect github.com/Azure/go-autorest/logger v0.2.1 // indirect github.com/Azure/go-autorest/tracing v0.6.0 // indirect + github.com/cenkalti/backoff/v3 v3.0.0 // indirect github.com/dnaeon/go-vcr v1.2.0 // indirect + github.com/go-jose/go-jose/v4 v4.0.1 // indirect github.com/gofrs/uuid v4.4.0+incompatible // indirect github.com/golang-jwt/jwt/v4 v4.5.0 // indirect - golang.org/x/crypto v0.17.0 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/hashicorp/go-retryablehttp v0.7.1 // indirect + github.com/hashicorp/go-rootcerts v1.0.2 // indirect + github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6 // indirect + github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect + github.com/hashicorp/go-sockaddr v1.0.2 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/ryanuber/go-glob v1.0.0 // indirect + golang.org/x/crypto v0.19.0 // indirect + golang.org/x/net v0.17.0 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/time v0.0.0-20220922220347-f3bd1da661af // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect ) diff --git a/go.sum b/go.sum index bee66d7..7d134f4 100644 --- a/go.sum +++ b/go.sum @@ -17,34 +17,104 @@ github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+Z github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/cenkalti/backoff/v3 v3.0.0 h1:ske+9nBpD9qZsTBoF41nW5L+AIuFBKMeze18XQ3eG1c= +github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI= github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= +github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= +github.com/go-jose/go-jose/v4 v4.0.1 h1:QVEPDE3OluqXBQZDcnNvQrInro2h0e4eqNbnZSWqS6U= +github.com/go-jose/go-jose/v4 v4.0.1/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY= +github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw= +github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-hclog v0.16.2 h1:K4ev2ib4LdQETX5cSZBG0DVLk1jwGqSPXBjdah3veNs= +github.com/hashicorp/go-hclog v0.16.2/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-retryablehttp v0.7.1 h1:sUiuQAnLlbvmExtFQs72iFW/HXeUn8Z1aJLQ4LJJbTQ= +github.com/hashicorp/go-retryablehttp v0.7.1/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= +github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= +github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6 h1:om4Al8Oy7kCm/B86rLCLah4Dt5Aa0Fr5rYBG60OzwHQ= +github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8= +github.com/hashicorp/go-secure-stdlib/strutil v0.1.1/go.mod h1:gKOamz3EwoIoJq7mlMIRBpVTAUn8qPCrEclOKKWhD3U= +github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts= +github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4= +github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc= +github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/vault/api v1.13.0 h1:RTCGpE2Rgkn9jyPcFlc7YmNocomda44k5ck8FKMH41Y= +github.com/hashicorp/vault/api v1.13.0/go.mod h1:0cb/uZUv1w2cVu9DIvuW1SMlXXC6qtATJt+LXJRx+kg= github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= +github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= -golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= -golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -52,6 +122,8 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -60,6 +132,10 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.0.0-20220922220347-f3bd1da661af h1:Yx9k8YCG3dvF87UAn2tu2HQLf2dt/eR1bXxpLMWeH+Y= +golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= @@ -69,3 +145,6 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go index 49ba229..01d9834 100644 --- a/main.go +++ b/main.go @@ -103,4 +103,4 @@ func handleDeleteVaultCommand(args []string) { } cmd.DeleteVault(*autoConfirm) -} \ No newline at end of file +}