diff --git a/.envrc b/.envrc deleted file mode 100644 index 3550a30..0000000 --- a/.envrc +++ /dev/null @@ -1 +0,0 @@ -use flake diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index c18c24e..0000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,107 +0,0 @@ -name: CI - -on: - push: - branches: [ main ] - tags: - - 'v*.*.*' - pull_request: - branches: [ main ] - -jobs: - lint: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - name: Set up Go - uses: actions/setup-go@v4 - with: - go-version: '1.21.9' - - name: Cache Go modules - uses: actions/cache@v3 - with: - path: | - ~/.cache/go-build - ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - restore-keys: | - ${{ runner.os }}-go- - - name: Lint - run: | - go mod tidy - make fmt - make lint - - test: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - name: Set up Go - uses: actions/setup-go@v4 - with: - go-version: '1.21.9' - - name: Cache Go modules - uses: actions/cache@v3 - with: - path: | - ~/.cache/go-build - ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - restore-keys: | - ${{ runner.os }}-go- - - name: Test - run: | - go mod tidy - make test - - release: - needs: [lint, test ] - if: startsWith(github.ref, 'refs/tags/v') - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - name: Set up Go - uses: actions/setup-go@v4 - with: - go-version: '1.21.9' - - name: Build for multiple platforms - run: | - go mod tidy - make build - - name: Create Release - id: create_release - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - tag_name: ${{ github.ref }} - release_name: Release ${{ github.ref }} - draft: false - prerelease: false - - name: Upload Linux AMD64 Asset - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: ./bin/zeusctl-linux-amd64 - asset_name: zeusctl-linux-amd64 - asset_content_type: application/octet-stream - - name: Upload Darwin AMD64 Asset - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: ./bin/zeusctl-darwin-amd64 - asset_name: zeusctl-darwin-amd64 - asset_content_type: application/octet-stream - - name: Upload Darwin ARM64 Asset - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: ./bin/zeusctl-darwin-arm64 - asset_name: zeusctl-darwin-arm64 - asset_content_type: application/octet-stream diff --git a/.github/workflows/issues.yml b/.github/workflows/issues.yml deleted file mode 100644 index f2ab6ab..0000000 --- a/.github/workflows/issues.yml +++ /dev/null @@ -1,20 +0,0 @@ -on: - issues: - types: - [opened, closed, reopened] - -jobs: - alert: - runs-on: ubuntu-latest - steps: - - uses: danhellem/github-actions-issue-to-work-item@v2.3 - env: - ado_token: "${{ secrets.ADO_PERSONAL_ACCESS_TOKEN_COREFLUXPROD }}" - github_token: "${{ secrets.GITHUB_TOKEN }}" - ado_organization: "CorefluxProd" - ado_project: "CorefluxTeam" - ado_wit: "Issue" - ado_new_state: "New" - ado_active_state: "Active" - ado_close_state: "Closed" - ado_bypassrules: true diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 78f0afe..0000000 --- a/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -# Builds -bin/* - diff --git a/Makefile b/Makefile deleted file mode 100644 index 6986f83..0000000 --- a/Makefile +++ /dev/null @@ -1,27 +0,0 @@ -.PHONY: fmt lint test build - -TARGETS := linux darwin-amd64 darwin-arm64 - -default: all - -fmt: - go fmt ./... - -lint: - go vet ./... - -test: - go test ./... - -build: $(TARGETS) - -linux: - CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -installsuffix cgo -o bin/zeusctl-linux-amd64 ./main.go - -darwin-amd64: - CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -a -installsuffix cgo -o bin/zeusctl-darwin-amd64 ./main.go - -darwin-arm64: - CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -a -installsuffix cgo -o bin/zeusctl-darwin-arm64 ./main.go - -all: fmt lint test build diff --git a/README.md b/README.md index 4a22b9c..2b2adad 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,154 @@ -# Zeus CLI +# vaultctl -- Simple CLI to facilitate Vault operations, like auth and fetching secrets using context configs +``vaultctl`` is a command-line interface (CLI) tool designed to facilitate operations with HashiCorp Vault by allowing users to fetch and export secrets based on predefined configurations. This guide will cover how to use the vaultctl commands, install it as a Nix package, and understand its features. -## Usage +## Installation -### Auth +``vaultctl`` is packaged as a Nix Flake. To install and use ``vaultctl``, you need to have Nix installed. Follow the [Nix installation guide](https://nix.dev/manual/nix/2.18/installation/multi-user) if you haven't set up Nix yet. +### Using vaultctl with Nix Flakes + +Once Nix is installed, you can enter a shell with ``vaultctl`` ready to use by running the following command: + +```bash +nix develop github:CorefluxCommunity/vaultctl ``` -zeusctl vault auth --host HOST:PORT --ca-path CA_CERT_PATH --user USER --password PASSWORD + +Alternatively, you can add ``vaultctl`` to your own Nix Flake as an input. Here's an example of how to include it in your flake: + +```nix +{ + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05"; + vaultctl.url = "github:CorefluxCommunity/vaultctl"; + }; + + outputs = { self, nixpkgs, vaultctl }: + let + pkgs = import nixpkgs { system = "x86_64-linux"; }; + in { + devShell = pkgs.mkShell { + buildInputs = [ vaultctl.packages."x86_64-linux".default ]; + }; + }; +} ``` -### Load and Export Context +This configuration sets up a development shell with ``vaultctl`` available. + +## Configuration Files +``vaultctl`` uses two main configuration files: + +### Vault Configuration File (config.hcl): Defines the Vault clusters and their servers. + +**Example configuration (~/.vaultctl/config.hcl):** + +```hcl +cluster "prod" { + address = "https://vault.example.com" + servers = ["https://vault1.example.com", "https://vault2.example.com"] +} + +cluster "dev" { + address = "https://vault-dev.example.com" + servers = ["https://vault-dev1.example.com"] +} ``` -eval $(zeusctl vault context CONTEXT_NAME) + +### Secrets Context File (contexts.hcl): Defines the secrets to be fetched and the keys to be exported. + +**Example context file (./contexts.hcl):** + +```hcl +context "app-secrets" { + secret "database" { + path = "secret/data/prod/database" + key { + name = "username" + } + key { + name = "password" + export_name = "DB_PASSWORD" + } + } + + secret "api" { + path = "secret/data/prod/api" + key { + name = "api_key" + base64_decode = true + } + } +} ``` +## Command Overview + +``vaultctl`` provides the following main commands: + +### ``login`` + +Authenticate to Vault using the userpass authentication method. The token generated after authentication is stored locally and used for subsequent requests. + +**Usage:** + +```bash +vaultctl login cluster --method userpass --user --password +``` + +``cluster-name``: The name of the Vault cluster (from the configuration). +``method``: The authentication method (currently only supports userpass. WIP: client certs). +``user``: The username for Vault login. +``password``: The password for Vault login. + +### ``get secrets`` + +Fetch secrets from Vault based on a predefined context from a secrets context file. The secrets can be exported as environment variables using the --export flag. + +**Usage:** + +```bash +vaultctl get secrets --context [--export] +``` + +``cluster-name``: The Vault cluster name. +``context-name``: The context name defined in the context file. +``context``: (Optional) Path to the context file. Defaults to ./contexts.hcl. +``export``: (Optional) Export secrets as environment variables. + +```bash +vaultctl get secrets prod app-secrets --context ./app-contexts.hcl --export +``` + +**Important: Exporting Secrets in Shell** + +When using the --export flag to export secrets as environment variables, the output needs to be wrapped in eval to actually set the variables in your current shell session. + +**Example:** + +```bash +eval $(vaultctl get secrets prod app-secrets --context ./app-contexts.hcl --export) +``` + +This works by generating export commands that need to be evaluated in the current shell context. + +### ``list clusters`` + +Lists the configured Vault clusters from the ``vaultctl`` configuration file. + +**Usage:** + +```bash +vaultctl list clusters +``` + +### ``show cluster`` + +Displays detailed information about a Vault cluster, including its unseal status and the list of servers. + +**Usage:** + +```bash +vaultctl show cluster +``` diff --git a/auth/auth.go b/auth/auth.go deleted file mode 100644 index 4e46ae3..0000000 --- a/auth/auth.go +++ /dev/null @@ -1,69 +0,0 @@ -package auth - -import ( - "fmt" - "os" - - "github.com/hashicorp/vault/api" - "github.com/spf13/cobra" - - "github.com/CorefluxCommunity/zeusctl/config" -) - -var ( - host string - caPath string - user string - password string -) - -func NewAuthCommand() *cobra.Command { - authCmd := &cobra.Command{ - Use: "auth", - Short: "Authenticate with Vault", - Run: authenticate, - } - - authCmd.Flags().StringVar(&host, "host", "", "Vault host URL") - authCmd.Flags().StringVar(&caPath, "ca-path", "", "Path to CA certificate") - authCmd.Flags().StringVar(&user, "user", "", "Username for Vault login") - authCmd.Flags().StringVar(&password, "password", "", "Password for Vault login") - - return authCmd -} - -func authenticate(cmd *cobra.Command, args []string) { - apiConfig := &api.Config{ - Address: host, - } - - if caPath != "" { - apiConfig.ConfigureTLS(&api.TLSConfig{CAPath: caPath}) - } - - client, err := api.NewClient(apiConfig) - if err != nil { - fmt.Fprintf(os.Stderr, "Error creating Vault client: %s\n", err) - os.Exit(1) - } - - options := map[string]interface{}{ - "password": password, - } - path := fmt.Sprintf("auth/userpass/login/%s", user) - - secret, err := client.Logical().Write(path, options) - if err != nil { - fmt.Fprintf(os.Stderr, "Error logging in to Vault: %s\n", err) - os.Exit(1) - } - - // Save the configuration to a file - err = config.SaveConfig(secret.Auth.ClientToken, host, caPath) - if err != nil { - fmt.Fprintf(os.Stderr, "Error saving configuration: %s\n", err) - os.Exit(1) - } - - fmt.Println("Logged in successfully. Configuration saved.") -} diff --git a/cmd/get.go b/cmd/get.go new file mode 100644 index 0000000..f0fb9f6 --- /dev/null +++ b/cmd/get.go @@ -0,0 +1,40 @@ +package cmd + +import ( + "github.com/CorefluxCommunity/vaultctl/pkg/secrets" + "github.com/CorefluxCommunity/vaultctl/pkg/utils" + "github.com/spf13/cobra" +) + +func init() { + getSecretsSubCmd.Flags().StringVarP(&contextFile, "context", "c", "", "secrets context file") + getSecretsSubCmd.Flags().BoolVarP(&exportSecrets, "export", "e", false, "export secrets as environment variables") + + getCmd.AddCommand(getSecretsSubCmd) + + rootCmd.AddCommand(getCmd) +} + +var getCmd = &cobra.Command{ + Use: "get", + Short: "Get Vault secrets", + Long: `Get Vault secrets from a secrets context file.`, +} + +var getSecretsSubCmd = &cobra.Command{ + Use: "secrets ", + Short: "Get Vault secrets", + Long: `Get Vault secrets from a secrets context file.`, + Args: cobra.ExactArgs(2), + Run: func(cmd *cobra.Command, args []string) { + vaultAddr, err := getVaultAddress(args[0]) + if err != nil { + utils.PrintFatal(err.Error(), 1) + } + contextName := args[1] + + if err := secrets.GetSecrets(contextName, contextFile, exportSecrets, vaultAddr); err != nil { + utils.PrintFatal(err.Error(), 1) + } + }, +} diff --git a/cmd/list.go b/cmd/list.go new file mode 100644 index 0000000..9043456 --- /dev/null +++ b/cmd/list.go @@ -0,0 +1,39 @@ +package cmd + +import ( + "fmt" + + "github.com/CorefluxCommunity/vaultctl/pkg/utils" + "github.com/spf13/cobra" +) + +func init() { + listCmd.AddCommand(listClustersSubCmd) + + rootCmd.AddCommand(listCmd) +} + +var listCmd = &cobra.Command{ + Use: "list", + Short: "List configured Vault clusters", + Long: `List configured Vault clusters.`, +} + +var listClustersSubCmd = &cobra.Command{ + Use: "clusters", + Short: "List Vault clusters", + Long: `List Vault clusters in vaultctl configuration file.`, + Run: func(cmd *cobra.Command, args []string) { + i := 0 + for name, cluster := range config.Clusters { + utils.PrintHeader(name) + utils.PrintKVSlice("Server(s)", cluster[0].Servers) + + if i < len(config.Clusters)-1 { + fmt.Println() + } + + i++ + } + }, +} diff --git a/cmd/login.go b/cmd/login.go new file mode 100644 index 0000000..703ac94 --- /dev/null +++ b/cmd/login.go @@ -0,0 +1,40 @@ +package cmd + +import ( + "github.com/CorefluxCommunity/vaultctl/pkg/utils" + "github.com/CorefluxCommunity/vaultctl/pkg/vault" + "github.com/spf13/cobra" +) + +func init() { + loginClusterSubCmd.Flags().StringVarP(&method, "method", "m", "", "Authentication method for Vault") + loginClusterSubCmd.Flags().StringVarP(&user, "user", "u", "", "Username for Vault login") + loginClusterSubCmd.Flags().StringVarP(&password, "password", "p", "", "Password for Vault login") + + loginCmd.AddCommand(loginClusterSubCmd) + + rootCmd.AddCommand(loginCmd) +} + +var loginCmd = &cobra.Command{ + Use: "login", + Short: "Login to Vault", + Long: `Login to Vault using the userpass auth method.`, +} + +var loginClusterSubCmd = &cobra.Command{ + Use: "cluster ", + Short: "Login to Vault", + Long: `Login to Vault using the userpass auth method.`, + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + vaultAddr, err := getVaultAddress(args[0]) + if err != nil { + utils.PrintFatal(err.Error(), 1) + } + + if err := vault.Authenticate(vaultAddr, method, user, password); err != nil { + utils.PrintFatal(err.Error(), 1) + } + }, +} diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 0000000..07e32e9 --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,130 @@ +package cmd + +import ( + "fmt" + "os" + + "github.com/mitchellh/go-homedir" + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "github.com/CorefluxCommunity/vaultctl/pkg/utils" +) + +var ( + // CLI config + config VaultConfig + configFile string + + // login + method string // TODO: Create enum for auth methods + user string + password string + + // get + contextFile string + exportSecrets bool + + rootCmd = &cobra.Command{ + Use: "vaultctl", + } +) + +type VaultConfig struct { + Clusters map[string][]*VaultClusterConfig `hcl:"cluster" mapstructure:"cluster"` +} + +type VaultClusterConfig struct { + Address string `hcl:"address" mapstructure:"address"` + Servers []string `hcl:"servers" mapstructure:"servers"` +} + +// Execute executes the root command. +func Execute() error { + return rootCmd.Execute() +} + +func init() { + cobra.OnInitialize(initConfig) + + rootCmd.PersistentFlags().StringVar(&configFile, "config", "", "config file (default is $HOME/.vaultctl/config.hcl)") +} + +func initConfig() { + if configFile != "" { + // Use config file from the flag. + viper.SetConfigFile(configFile) + } else { + // Find home directory. + home, err := homedir.Dir() + cobra.CheckErr(err) + + // Search config in $HOME/vaultctl directory with name "config" (without extension). + // TODO: Get cli config directory from shell env + viper.AddConfigPath(home + "/.vaultctl") + viper.SetConfigName("config") + viper.SetConfigType("hcl") + } + + viper.AutomaticEnv() + viper.ReadInConfig() + + err := viper.Unmarshal(&config) + if err != nil { + utils.PrintFatal(fmt.Sprintf("unable to decode into struct, %v", err), 1) + } + + // get vaultctl config direction and set as cwd + configDir, err := getConfigDir() + if err != nil { + utils.PrintFatal(err.Error(), 1) + } + + os.Chdir(configDir) +} + +func getVaultClusterConfig(clusterName string) (*VaultClusterConfig, error) { + for name, cluster := range config.Clusters { + if name == clusterName { + return cluster[0], nil + } + } + + return nil, fmt.Errorf("config for Vault cluster '%s' not found", clusterName) +} + +func getVaultAddress(clusterName string) (string, error) { + // Check if the cluster exists in the config + cluster, ok := config.Clusters[clusterName] + if !ok { + return "", fmt.Errorf("cluster '%s' not found in configuration", clusterName) + } + + // Use the first config in the slice (assuming there's only one per cluster name) + clusterConfig := cluster[0] + + // If address is provided, use it + if clusterConfig.Address != "" { + return clusterConfig.Address, nil + } + + // If address is not provided, but servers are, use the first server + if len(clusterConfig.Servers) > 0 { + return clusterConfig.Servers[0], nil + } + + // If neither address nor servers are provided, return an error + return "", fmt.Errorf("no address or servers found for cluster '%s'", clusterName) +} + +func getConfigDir() (string, error) { + home, err := homedir.Dir() + if err != nil { + return "", err + } + + // TODO: Get cli config directory from shell env + path := home + "/.vaultctl" + + return path, nil +} diff --git a/cmd/show.go b/cmd/show.go new file mode 100644 index 0000000..80b18ff --- /dev/null +++ b/cmd/show.go @@ -0,0 +1,45 @@ +package cmd + +import ( + "github.com/CorefluxCommunity/vaultctl/pkg/utils" + "github.com/CorefluxCommunity/vaultctl/pkg/vault" + "github.com/spf13/cobra" +) + +func init() { + showCmd.AddCommand(showClusterSubCmd) + + rootCmd.AddCommand(showCmd) +} + +var showCmd = &cobra.Command{ + Use: "show", + Short: "Show details of Vault clusters", + Long: `Show details of Vault clusters.`, +} + +var showClusterSubCmd = &cobra.Command{ + Use: "cluster ", + Short: "Show Vault cluster status", + Long: `Show overview and unseal status of the specified Vault cluster.`, + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + clusterName := args[0] + + cluster, err := getVaultClusterConfig(clusterName) + if err != nil { + utils.PrintFatal(err.Error(), 1) + } + + if len(cluster.Servers) == 0 { + utils.PrintFatal("no Vault servers in configuration", 1) + } + + utils.PrintHeader("Vault Cluster Status") + utils.PrintKVSlice("Server(s)", cluster.Servers) + + if err := vault.ListVaultStatus(cluster.Servers[0]); err != nil { + utils.PrintFatal(err.Error(), 1) + } + }, +} diff --git a/cmd/vault.go b/cmd/vault.go deleted file mode 100644 index f08774b..0000000 --- a/cmd/vault.go +++ /dev/null @@ -1,20 +0,0 @@ -package cmd - -import ( - "github.com/spf13/cobra" - - "github.com/CorefluxCommunity/zeusctl/auth" - "github.com/CorefluxCommunity/zeusctl/contexts" -) - -func InitVaultCommands(rootCmd *cobra.Command) { - vaultCmd := &cobra.Command{ - Use: "vault", - Short: "Commands related to Vault", - } - - vaultCmd.AddCommand(auth.NewAuthCommand()) - vaultCmd.AddCommand(contexts.NewContextCommand()) - - rootCmd.AddCommand(vaultCmd) -} diff --git a/config/config.go b/config/config.go deleted file mode 100644 index ee4de5f..0000000 --- a/config/config.go +++ /dev/null @@ -1,61 +0,0 @@ -package config - -import ( - "encoding/json" - "os" - "path/filepath" -) - -type VaultConfig struct { - Token string `json:"token"` - Host string `json:"host"` - CAPath string `json:"caPath"` -} - -func SaveConfig(token, host, caPath string) error { - homeDir, err := os.UserHomeDir() - if err != nil { - return err - } - - configFile := filepath.Join(homeDir, ".zeusctl", "vault.json") - - err = os.MkdirAll(filepath.Dir(configFile), 0700) - if err != nil { - return err - } - - config := VaultConfig{ - Token: token, - Host: host, - CAPath: caPath, - } - - data, err := json.Marshal(config) - if err != nil { - return err - } - - return os.WriteFile(configFile, data, 0600) -} - -func LoadConfig() (*VaultConfig, error) { - homeDir, err := os.UserHomeDir() - if err != nil { - return nil, err - } - - configFile := filepath.Join(homeDir, ".zeusctl", "vault.json") - - data, err := os.ReadFile(configFile) - if err != nil { - return nil, err - } - - var config VaultConfig - if err = json.Unmarshal(data, &config); err != nil { - return nil, err - } - - return &config, nil -} diff --git a/contexts/config.go b/contexts/config.go deleted file mode 100644 index 6ef3558..0000000 --- a/contexts/config.go +++ /dev/null @@ -1,49 +0,0 @@ -package contexts - -import ( - "encoding/json" - "os" - "path/filepath" -) - -type SecretInfo struct { - Path string `json:"path"` - Key string `json:"key"` - DecodeBase64 bool `json:"decodeBase64"` - EncodeBase64 bool `json:"encodeBase64"` -} - -type Context struct { - Name string `json:"name"` - Secrets map[string]SecretInfo `json:"secrets"` -} - -type Config struct { - Contexts []Context `json:"contexts"` -} - -func LoadContexts(path string) (*Config, error) { - if path == "" { - // If no path provided, use the default location - homeDir, err := os.UserHomeDir() - if err != nil { - return nil, err - } - path = filepath.Join(homeDir, ".zeusctl", "contexts.json") - } - - file, err := os.Open(path) - if err != nil { - return nil, err - } - defer file.Close() - - var config Config - - decoder := json.NewDecoder(file) - if err := decoder.Decode(&config); err != nil { - return nil, err - } - - return &config, nil -} diff --git a/contexts/context.go b/contexts/context.go deleted file mode 100644 index a01087f..0000000 --- a/contexts/context.go +++ /dev/null @@ -1,108 +0,0 @@ -package contexts - -import ( - "encoding/base64" - "fmt" - "os" - - "github.com/hashicorp/vault/api" - "github.com/spf13/cobra" - - "github.com/CorefluxCommunity/zeusctl/config" -) - -var configFilePath string - -func NewContextCommand() *cobra.Command { - contextCmd := &cobra.Command{ - Use: "context [name]", - Short: "Load a named context and export its secrets as environment variables", - Args: cobra.ExactArgs(1), - Run: loadContext, - } - - contextCmd.Flags().StringVarP(&configFilePath, "config", "c", "", "Path to the contexts configuration file") - - return contextCmd -} - -func loadContext(cmd *cobra.Command, args []string) { - vaultConfig, err := config.LoadConfig() - if err != nil { - fmt.Fprintf(os.Stderr, "Error loading configuration: %s\n", err) - os.Exit(1) - } - - clientConfig := &api.Config{ - Address: vaultConfig.Host, - } - if vaultConfig.CAPath != "" { - clientConfig.ConfigureTLS(&api.TLSConfig{CAPath: vaultConfig.CAPath}) - } - - client, err := api.NewClient(clientConfig) - if err != nil { - fmt.Fprintf(os.Stderr, "Error creating Vault client: %s\n", err) - os.Exit(1) - } - client.SetToken(vaultConfig.Token) - - contextName := args[0] - contexts, err := LoadContexts(configFilePath) - if err != nil { - fmt.Fprintf(os.Stderr, "Error loading contexts: %s\n", err) - os.Exit(1) - } - - found := false - for _, ctx := range contexts.Contexts { - if ctx.Name == contextName { - found = true - for envName, secretInfo := range ctx.Secrets { - secret, err := client.Logical().Read(secretInfo.Path) - if err != nil { - fmt.Printf("Error reading secret from %s: %s\n", secretInfo.Path, err) - continue - - } - - if secret == nil || secret.Data == nil { - fmt.Printf("No data found at path: %s\n", secretInfo.Path) - continue - } - - value, ok := secret.Data["data"].(map[string]interface{})[secretInfo.Key] - if !ok { - - fmt.Printf("Secret key %s not found at path: %s\n", secretInfo.Key, secretInfo.Path) - - continue - } - - var finalValue string - if secretInfo.DecodeBase64 { - decodedBytes, decodeErr := base64.StdEncoding.DecodeString(fmt.Sprintf("%v", value)) - if decodeErr != nil { - fmt.Printf("Error decoding base64 value for %s: %s\n", envName, decodeErr) - continue - } - finalValue = string(decodedBytes) - } else { - finalValue = fmt.Sprintf("%v", value) - } - if secretInfo.EncodeBase64 { - finalValue = base64.StdEncoding.EncodeToString([]byte(finalValue)) - } - - // Output export command instead of setting directly - fmt.Printf("export %s='%s'\n", envName, finalValue) - } - - break - } - } - - if !found { - fmt.Fprintf(os.Stderr, "Context '%s' not found\n", contextName) - } -} diff --git a/flake.lock b/flake.lock index e751083..e7b0489 100644 --- a/flake.lock +++ b/flake.lock @@ -1,24 +1,93 @@ { "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1694529238, + "narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "ff7b65b44d01cf9ba6a71320833626af21126384", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "gomod2nix": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + }, + "locked": { + "lastModified": 1725515722, + "narHash": "sha256-+gljgHaflZhQXtr3WjJrGn8NXv7MruVPAORSufuCFnw=", + "owner": "nix-community", + "repo": "gomod2nix", + "rev": "1c6fd4e862bf2f249c9114ad625c64c6c29a8a08", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "gomod2nix", + "type": "github" + } + }, "nixpkgs": { "locked": { - "lastModified": 1715218190, - "narHash": "sha256-R98WOBHkk8wIi103JUVQF3ei3oui4HvoZcz9tYOAwlk=", + "lastModified": 1658285632, + "narHash": "sha256-zRS5S/hoeDGUbO+L95wXG9vJNwsSYcl93XiD0HQBXLk=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "9a9960b98418f8c385f52de3b09a63f9c561427a", + "rev": "5342fc6fb59d0595d26883c3cadff16ce58e44f3", "type": "github" }, "original": { "owner": "NixOS", - "ref": "nixos-23.11", + "ref": "master", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1727264057, + "narHash": "sha256-KQPI8CTTnB9CrJ7LrmLC4VWbKZfljEPBXOFGZFRpxao=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "759537f06e6999e141588ff1c9be7f3a5c060106", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-24.05", "repo": "nixpkgs", "type": "github" } }, "root": { "inputs": { - "nixpkgs": "nixpkgs" + "gomod2nix": "gomod2nix", + "nixpkgs": "nixpkgs_2" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" } } }, diff --git a/flake.nix b/flake.nix index 8c8bd04..05a73fa 100644 --- a/flake.nix +++ b/flake.nix @@ -1,27 +1,65 @@ { - description = "Go encapsulated development environment"; + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05"; + gomod2nix.url = "github:nix-community/gomod2nix"; + }; - inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.11"; - - outputs = { self, nixpkgs }: + outputs = { self, nixpkgs, gomod2nix }: let - goVersion = "21"; - overlays = [ (final: prev: { go = prev."go_1_${toString goVersion}"; }) ]; - supportedSystems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ]; - forEachSupportedSystem = f: nixpkgs.lib.genAttrs supportedSystems (system: f { - pkgs = import nixpkgs { inherit overlays system; }; - }); - in - { - devShells = forEachSupportedSystem ({ pkgs }: { - default = pkgs.mkShell { - buildInputs = with pkgs; [ - go - gotools - golangci-lint - ]; - }; - }); + # List of supported system architectures + systems = [ + "x86_64-linux" + "aarch64-linux" + "x86_64-darwin" + "aarch64-darwin" + ]; + # Helper function to create attributes for all systems + forAllSystems = f: builtins.listToAttrs (map (system: { + name = system; + value = f system; + }) systems); + overlay = final: prev: { + go = prev.go_1_21; + }; + in { + # Dev shell for all supported systems + devShells = forAllSystems (system: + let + pkgs = import nixpkgs { + system = system; + overlays = [ overlay gomod2nix.overlays.default ]; + }; + in { + default = pkgs.mkShell { + buildInputs = [ + pkgs.go + pkgs.gomod2nix + ]; + shellHook = '' + export GOPRIVATE=github.com/CorefluxCommunity/vaultctl + ''; + }; + } + ); + + # Exporting package for all supported systems + packages = forAllSystems (system: + let + pkgs = import nixpkgs { + system = system; + overlays = [ overlay gomod2nix.overlays.default ]; + }; + in { + default = pkgs.buildGoApplication { + pname = "vaultctl"; + version = "1.0.0"; + src = ./.; + + # Use the generated gomod2nix.toml + # TODO: Automate auto generate mod file during/before package build + modules = ./gomod2nix.toml; + }; + } + ); }; } - diff --git a/go.mod b/go.mod index 6e4336f..6f6c729 100644 --- a/go.mod +++ b/go.mod @@ -1,31 +1,58 @@ -module github.com/CorefluxCommunity/zeusctl +module github.com/CorefluxCommunity/vaultctl -go 1.21.9 +go 1.21.13 require ( - github.com/hashicorp/vault/api v1.13.0 - github.com/spf13/cobra v1.8.0 + github.com/hashicorp/hcl/v2 v2.22.0 + github.com/hashicorp/vault/api v1.15.0 + github.com/logrusorgru/aurora v2.0.3+incompatible + github.com/mitchellh/go-homedir v1.1.0 + github.com/spf13/cobra v1.8.1 + github.com/spf13/viper v1.19.0 ) require ( - github.com/cenkalti/backoff/v3 v3.0.0 // indirect + github.com/agext/levenshtein v1.2.1 // indirect + github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect + github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/go-jose/go-jose/v4 v4.0.1 // indirect + github.com/google/go-cmp v0.6.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.6.6 // indirect + github.com/hashicorp/go-retryablehttp v0.7.7 // 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/inconshreveable/mousetrap v1.1.0 // indirect - github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/magiconair/properties v1.8.7 // indirect + github.com/mitchellh/go-wordwrap v1.0.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect + github.com/sagikazarmark/locafero v0.4.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/afero v1.11.0 // indirect + github.com/spf13/cast v1.6.0 // indirect github.com/spf13/pflag v1.0.5 // 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-20200416051211-89c76fbcd5d1 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + github.com/zclconf/go-cty v1.13.0 // indirect + go.uber.org/atomic v1.9.0 // indirect + go.uber.org/multierr v1.9.0 // indirect + golang.org/x/crypto v0.29.0 // indirect + golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect + golang.org/x/mod v0.17.0 // indirect + golang.org/x/net v0.25.0 // indirect + golang.org/x/sync v0.9.0 // indirect + golang.org/x/sys v0.27.0 // indirect + golang.org/x/text v0.20.0 // indirect + golang.org/x/time v0.5.0 // indirect + golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index daf3d6b..210a582 100644 --- a/go.sum +++ b/go.sum @@ -1,34 +1,43 @@ +github.com/agext/levenshtein v1.2.1 h1:QmvMAjj2aEICytGiWzmxoE0x2KZvE0fvmqMOfy2tjT8= +github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= +github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw= +github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= +github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY= +github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= 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/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 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/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= 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/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= +github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/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-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= +github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= 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.6.6 h1:HJunrbHTDDbBb/ay4kxa1n+dLmttUlnP3V9oNE4hmsM= -github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= +github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= +github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= 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= @@ -40,10 +49,20 @@ github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0S 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/hashicorp/hcl/v2 v2.22.0 h1:hkZ3nCtqeJsDhPRFz5EA9iwcG1hNWGePOTw6oyul12M= +github.com/hashicorp/hcl/v2 v2.22.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA= +github.com/hashicorp/vault/api v1.15.0 h1:O24FYQCWwhwKnF7CuSqP30S51rTV7vz1iACXE/pj5DA= +github.com/hashicorp/vault/api v1.15.0/go.mod h1:+5YTO09JGn0u+b6ySD/LLVf8WkJCPLAL2Vkmrn2+CM8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8= +github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= 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= @@ -53,38 +72,84 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D 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 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4= 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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 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/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= -github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= +github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= +github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= +github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= +github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= +github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= +github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= +github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= +github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= 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/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 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.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -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/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/zclconf/go-cty v1.13.0 h1:It5dfKTTZHe9aeppbNOda3mN7Ag7sg6QkBNm6TkyFa0= +github.com/zclconf/go-cty v1.13.0/go.mod h1:YKQzy/7pZ7iq2jNFzy5go57xdxdWoLLpaEp4u238AE0= +github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo= +github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM= +go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= +go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= +golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= +golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= +golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -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/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-20200416051211-89c76fbcd5d1 h1:NusfzzA6yGQ+ua51ck7E3omNUX/JuqbFSaRGqU8CcLI= -golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= +golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= +golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 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/gomod2nix.toml b/gomod2nix.toml new file mode 100644 index 0000000..2b9d585 --- /dev/null +++ b/gomod2nix.toml @@ -0,0 +1,156 @@ +schema = 3 + +[mod] + [mod."github.com/agext/levenshtein"] + version = "v1.2.1" + hash = "sha256-SuFgpvp7KGO4f2t0KnkhSUv7iRI7oGq40CjRc1tLNr4=" + [mod."github.com/apparentlymart/go-textseg/v13"] + version = "v13.0.0" + hash = "sha256-Hn+zO0J/vnG5U6JvZfRyWm8vsqYdjh4RAreWnZLCQKQ=" + [mod."github.com/apparentlymart/go-textseg/v15"] + version = "v15.0.0" + hash = "sha256-960kVVQSGhx1Gww5cfoNeM3yXaA4dwlv+AFhKh6EGlA=" + [mod."github.com/cenkalti/backoff/v4"] + version = "v4.3.0" + hash = "sha256-wfVjNZsGG1WoNC5aL+kdcy6QXPgZo4THAevZ1787md8=" + [mod."github.com/ebfe/scard"] + version = "v0.0.0-20230420082256-7db3f9b7c8a7" + hash = "sha256-fUJV9ec3CWy+Gh4ChCcWu/szAsEgqU2eVw0NNZu9B4c=" + [mod."github.com/fsnotify/fsnotify"] + version = "v1.7.0" + hash = "sha256-MdT2rQyQHspPJcx6n9ozkLbsktIOJutOqDuKpNAtoZY=" + [mod."github.com/go-jose/go-jose/v4"] + version = "v4.0.1" + hash = "sha256-jscu63JKaJr0j1oBoezhi4yS0FLRS5eupfwJE+++DXw=" + [mod."github.com/google/go-cmp"] + version = "v0.6.0" + hash = "sha256-qgra5jze4iPGP0JSTVeY5qV5AvEnEu39LYAuUCIkMtg=" + [mod."github.com/hashicorp/errwrap"] + version = "v1.1.0" + hash = "sha256-6lwuMQOfBq+McrViN3maJTIeh4f8jbEqvLy2c9FvvFw=" + [mod."github.com/hashicorp/go-cleanhttp"] + version = "v0.5.2" + hash = "sha256-N9GOKYo7tK6XQUFhvhImtL7PZW/mr4C4Manx/yPVvcQ=" + [mod."github.com/hashicorp/go-multierror"] + version = "v1.1.1" + hash = "sha256-ANzPEUJIZIlToxR89Mn7Db73d9LGI51ssy7eNnUgmlA=" + [mod."github.com/hashicorp/go-retryablehttp"] + version = "v0.7.7" + hash = "sha256-XZjxncyLPwy6YBHR3DF5bEl1y72or0JDUncTIsb/eIU=" + [mod."github.com/hashicorp/go-rootcerts"] + version = "v1.0.2" + hash = "sha256-prifkrFs+lawGTig3GwxddR0QM9E1+IpgZWCKoOnS5M=" + [mod."github.com/hashicorp/go-secure-stdlib/parseutil"] + version = "v0.1.6" + hash = "sha256-vnfrdWA2LvxpcerhB3sEX5Uhxx0doiQTrcVth1r8bfU=" + [mod."github.com/hashicorp/go-secure-stdlib/strutil"] + version = "v0.1.2" + hash = "sha256-UmCMzjamCW1d9KNvNzELqKf1ElHOXPz+ZtdJkI+DV0A=" + [mod."github.com/hashicorp/go-sockaddr"] + version = "v1.0.2" + hash = "sha256-bshn2I074/pnQ8gZU5RsfQRTrIvMC459bOfd/O/dHeo=" + [mod."github.com/hashicorp/hcl"] + version = "v1.0.0" + hash = "sha256-xsRCmYyBfglMxeWUvTZqkaRLSW+V2FvNodEDjTGg1WA=" + [mod."github.com/hashicorp/hcl/v2"] + version = "v2.22.0" + hash = "sha256-eim4PQc/SYp6rtAC5qMnhufLV4atOQgWewixHcgIbok=" + [mod."github.com/hashicorp/vault/api"] + version = "v1.15.0" + hash = "sha256-7V/QU9TT+setRdTjS7vKL05wOEd4IEhSMVt9SJr/sQ4=" + [mod."github.com/inconshreveable/mousetrap"] + version = "v1.1.0" + hash = "sha256-XWlYH0c8IcxAwQTnIi6WYqq44nOKUylSWxWO/vi+8pE=" + [mod."github.com/logrusorgru/aurora"] + version = "v2.0.3+incompatible" + hash = "sha256-7o5Fh4jscdYKgXfnNMbcD68Kjw8Z4LcPgHcr4ZyQYrI=" + [mod."github.com/magiconair/properties"] + version = "v1.8.7" + hash = "sha256-XQ2bnc2s7/IH3WxEO4GishZurMyKwEclZy1DXg+2xXc=" + [mod."github.com/mitchellh/go-homedir"] + version = "v1.1.0" + hash = "sha256-oduBKXHAQG8X6aqLEpqZHs5DOKe84u6WkBwi4W6cv3k=" + [mod."github.com/mitchellh/go-wordwrap"] + version = "v1.0.0" + hash = "sha256-bgG/RLuwh8hn34/LMfGX3Yr9+TIFFSYCM9jakRlfzsk=" + [mod."github.com/mitchellh/mapstructure"] + version = "v1.5.0" + hash = "sha256-ztVhGQXs67MF8UadVvG72G3ly0ypQW0IRDdOOkjYwoE=" + [mod."github.com/pelletier/go-toml/v2"] + version = "v2.2.2" + hash = "sha256-ukxk1Cfm6cQW18g/aa19tLcUu5BnF7VmfAvrDHAOl6A=" + [mod."github.com/ryanuber/go-glob"] + version = "v1.0.0" + hash = "sha256-YkMl1utwUhi3E0sHK23ISpAsPyj4+KeXyXKoFYGXGVY=" + [mod."github.com/sagikazarmark/locafero"] + version = "v0.4.0" + hash = "sha256-7I1Oatc7GAaHgAqBFO6Tv4IbzFiYeU9bJAfJhXuWaXk=" + [mod."github.com/sagikazarmark/slog-shim"] + version = "v0.1.0" + hash = "sha256-F92BQXXmn3mCwu3mBaGh+joTRItQDSDhsjU6SofkYdA=" + [mod."github.com/sourcegraph/conc"] + version = "v0.3.0" + hash = "sha256-mIdMs9MLAOBKf3/0quf1iI3v8uNWydy7ae5MFa+F2Ko=" + [mod."github.com/spf13/afero"] + version = "v1.11.0" + hash = "sha256-+rV3cDZr13N8E0rJ7iHmwsKYKH+EhV+IXBut+JbBiIE=" + [mod."github.com/spf13/cast"] + version = "v1.6.0" + hash = "sha256-hxioqRZfXE0AE5099wmn3YG0AZF8Wda2EB4c7zHF6zI=" + [mod."github.com/spf13/cobra"] + version = "v1.8.1" + hash = "sha256-yDF6yAHycV1IZOrt3/hofR+QINe+B2yqkcIaVov3Ky8=" + [mod."github.com/spf13/pflag"] + version = "v1.0.5" + hash = "sha256-w9LLYzxxP74WHT4ouBspH/iQZXjuAh2WQCHsuvyEjAw=" + [mod."github.com/spf13/viper"] + version = "v1.19.0" + hash = "sha256-MZ8EAvdgpGbw6kmUz8UOaAAAMdPPGd14TrCBAY+A1T4=" + [mod."github.com/subosito/gotenv"] + version = "v1.6.0" + hash = "sha256-LspbjTniiq2xAICSXmgqP7carwlNaLqnCTQfw2pa80A=" + [mod."github.com/zclconf/go-cty"] + version = "v1.13.0" + hash = "sha256-2hMDPoptLAB5o2bAyee1jPenOAXnVNGE+dStn0dgQzI=" + [mod."go.uber.org/atomic"] + version = "v1.9.0" + hash = "sha256-D8OtLaViqPShz1w8ijhIHmjw9xVaRu0qD2hXKj63r4Q=" + [mod."go.uber.org/multierr"] + version = "v1.9.0" + hash = "sha256-tlDRooh/V4HDhZohsUrxot/Y6uVInVBtRWCZbj/tPds=" + [mod."golang.org/x/crypto"] + version = "v0.27.0" + hash = "sha256-8HP4+gr4DbXI22GhdgZmCWr1ijtI9HNLsTcE0kltY9o=" + [mod."golang.org/x/exp"] + version = "v0.0.0-20230905200255-921286631fa9" + hash = "sha256-CyeVwjp12Wqh4ptqfi3KHCWPzOFhE8fSrP3sMjMXvec=" + [mod."golang.org/x/mod"] + version = "v0.17.0" + hash = "sha256-CLaPeF6uTFuRDv4oHwOQE6MCMvrzkUjWN3NuyywZjKU=" + [mod."golang.org/x/net"] + version = "v0.25.0" + hash = "sha256-IjFfXLYNj27WLF7vpkZ6mfFXBnp+7QER3OQ0RgjxN54=" + [mod."golang.org/x/sync"] + version = "v0.8.0" + hash = "sha256-usvF0z7gq1vsX58p4orX+8WHlv52pdXgaueXlwj2Wss=" + [mod."golang.org/x/sys"] + version = "v0.25.0" + hash = "sha256-PXZ9EQZ7SFpcL7d3E1+KGTxziYlHEIZPfoXEbnaVD3I=" + [mod."golang.org/x/term"] + version = "v0.24.0" + hash = "sha256-PfC5psjzEWKRm1DlnBXX0ntw9OskJFrq1RRjyBa1lOk=" + [mod."golang.org/x/text"] + version = "v0.18.0" + hash = "sha256-aNvJW4gQs+MTfdz6DZqyyHQS2GJ9W8L8qKPVODPn4+k=" + [mod."golang.org/x/time"] + version = "v0.5.0" + hash = "sha256-W6RgwgdYTO3byIPOFxrP2IpAZdgaGowAaVfYby7AULU=" + [mod."golang.org/x/tools"] + version = "v0.21.1-0.20240508182429-e35e4ccd0d2d" + hash = "sha256-KfnS+3fREPAWQUBoUedPupQp9yLrugxMmmEoHvyzKNE=" + [mod."gopkg.in/ini.v1"] + version = "v1.67.0" + hash = "sha256-V10ahGNGT+NLRdKUyRg1dos5RxLBXBk1xutcnquc/+4=" + [mod."gopkg.in/yaml.v3"] + version = "v3.0.1" + hash = "sha256-FqL9TKYJ0XkNwJFnq9j0VvJ5ZUU1RvH/52h/f5bkYAU=" diff --git a/main.go b/main.go index b430cd2..46e6f90 100644 --- a/main.go +++ b/main.go @@ -1,18 +1,9 @@ package main import ( - "github.com/spf13/cobra" - - "github.com/CorefluxCommunity/zeusctl/cmd" + "github.com/CorefluxCommunity/vaultctl/cmd" ) func main() { - var rootCmd = &cobra.Command{ - Use: "zeusctl", - Short: "zeusctl is a CLI tool for operating Hashicorp Vault", - } - - cmd.InitVaultCommands(rootCmd) - - rootCmd.Execute() + cmd.Execute() } diff --git a/pkg/secrets/kv.go b/pkg/secrets/kv.go new file mode 100644 index 0000000..8304636 --- /dev/null +++ b/pkg/secrets/kv.go @@ -0,0 +1,140 @@ +package secrets + +import ( + "encoding/base64" + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/hashicorp/hcl/v2/hclsimple" + + "github.com/CorefluxCommunity/vaultctl/pkg/vault" +) + +type ContextConfig struct { + Contexts []*Context `hcl:"context,block"` +} + +type Context struct { + Name string `hcl:"name,label"` + Secrets []*Secret `hcl:"secret,block"` +} + +type Secret struct { + Name string `hcl:"name,label"` + Path string `hcl:"path"` + Keys []*Key `hcl:"key,block"` +} + +type Key struct { + Name string `hcl:"name,label"` + ExportName string `hcl:"export_name,optional"` + Base64Decode bool `hcl:"base64_decode,optional"` +} + +func GetSecrets(contextName string, contextFile string, exportSecrets bool, vaultAddr string) error { + if contextFile == "" { + currentDir, err := os.Getwd() + if err != nil { + return fmt.Errorf("error getting current directory: %w", err) + } + contextFile = filepath.Join(currentDir, "contexts.hcl") + } + + if _, err := os.Stat(contextFile); os.IsNotExist(err) { + return fmt.Errorf("contexts file not found: %s", contextFile) + } + + content, err := os.ReadFile(contextFile) + if err != nil { + return fmt.Errorf("error reading contexts file: %w", err) + } + + var contexts ContextConfig + err = hclsimple.Decode(contextFile, content, nil, &contexts) + if err != nil { + return fmt.Errorf("error decoding contexts file: %w", err) + } + + var targetContext *Context + for _, context := range contexts.Contexts { + if context.Name == contextName { + targetContext = context + break + } + } + + if targetContext == nil { + return fmt.Errorf("context '%s' not found in contexts file", contextName) + } + + // Check for saved token + token, err := getSavedToken() + if err != nil || token == "" { + return fmt.Errorf("no valid token found, please authenticate first") + } + + // Create Vault client + vaultClient, err := vault.NewVaultClient(vaultAddr) + if err != nil { + return fmt.Errorf("failed to create Vault client: %w", err) + } + vaultClient.ApiClient.SetToken(token) + + for _, secret := range targetContext.Secrets { + secretData, err := vaultClient.FetchSecret(secret.Path) + if err != nil { + if strings.Contains(err.Error(), "permission denied") { + fmt.Printf("Warning: Not authorized to read secret: %s\n", secret.Path) + continue + } + return fmt.Errorf("failed to fetch secret %s: %w", secret.Path, err) + } + + for _, key := range secret.Keys { + value, ok := secretData[key.Name].(string) + if !ok { + fmt.Printf("Warning: Key %s not found in secret %s\n", key.Name, secret.Path) + continue + } + + if key.Base64Decode { + decodedValue, err := base64.StdEncoding.DecodeString(value) + if err != nil { + fmt.Printf("Warning: Failed to base64 decode %s: %v\n", key.Name, err) + } else { + value = string(decodedValue) + } + } + + exportName := key.Name + if key.ExportName != "" { + exportName = key.ExportName + } + + if exportSecrets { + fmt.Printf("export %s='%s'\n", exportName, value) + } else { + fmt.Printf("%s=%s\n", exportName, value) + } + } + } + + return nil +} + +func getSavedToken() (string, error) { + homeDir, err := os.UserHomeDir() + if err != nil { + return "", err + } + + tokenFile := filepath.Join(homeDir, ".vaultctl", "token") + tokenBytes, err := os.ReadFile(tokenFile) + if err != nil { + return "", err + } + + return strings.TrimSpace(string(tokenBytes)), nil +} diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go new file mode 100644 index 0000000..adfdb98 --- /dev/null +++ b/pkg/utils/utils.go @@ -0,0 +1,72 @@ +package utils + +import ( + "fmt" + "os" + "strings" + + "github.com/logrusorgru/aurora" +) + +const ( + printKVPadWidth int = 30 + printHeaderPadWidth int = 10 +) + +// PrintKV will bold print the key followed by padding to the specified +// total width, then the value. +func PrintKV(key string, value string) { + pad := strings.Repeat(".", printKVPadWidth-len(key)-2) + label := aurora.Bold(fmt.Sprintf("%s %s:", key, pad)) + fmt.Println(label, value) +} + +// PrintKV will bold print the key followed by padding to the specified +// total width, then the first value in the slice. If additional values +// are present in the slice they will be displayed on new line indented +// to match the previous value. +func PrintKVSlice(key string, values []string) { + for i, value := range values { + if i == 0 { + pad := strings.Repeat(".", printKVPadWidth-len(key)-2) + label := aurora.Bold(fmt.Sprintf("%s %s:", key, pad)) + fmt.Println(label, value) + } else { + pad := strings.Repeat(" ", printKVPadWidth) + fmt.Println(pad, value) + } + } +} + +// PrintHeader will print a bolded header label. +func PrintHeader(label string) { + pad := strings.Repeat("=", printHeaderPadWidth) + fmt.Println(aurora.Bold(fmt.Sprintf("%s %s %s", pad, label, pad))) +} + +// PrintInfo will print a formatted info message to stdout. +func PrintInfo(msg string) { + fmt.Println(aurora.Blue(aurora.Bold("[info] ")), msg) +} + +// PrintSuccess will print a formatted success message to stdout. +func PrintSuccess(msg string) { + fmt.Println(aurora.Green(aurora.Bold("[success]")), msg) +} + +// PrintWarning will print a formatted warning message to stdout. +func PrintWarning(msg string) { + fmt.Println(aurora.Yellow(aurora.Bold("[warning]")), msg) +} + +// PrintError will print a formatted error message to stdout. +func PrintError(msg string) { + fmt.Println(aurora.Red(aurora.Bold("[error] ")), msg) +} + +// PrintFatal will print a formatted error message to stdout and exit with +// the provided status. +func PrintFatal(msg string, code int) { + fmt.Println(aurora.Red(aurora.Bold("[fatal] ")), msg) + os.Exit(code) +} diff --git a/pkg/vault/auth.go b/pkg/vault/auth.go new file mode 100644 index 0000000..8737a2f --- /dev/null +++ b/pkg/vault/auth.go @@ -0,0 +1,82 @@ +package vault + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/hashicorp/vault/api" +) + +type AuthMethod interface { + Authenticate(client *VaultClient) (*api.Secret, error) +} + +type UserPassAuth struct { + Username string + Password string +} + +func (u *UserPassAuth) Authenticate(client *VaultClient) (*api.Secret, error) { + return client.ApiClient.Logical().Write("auth/userpass/login/"+u.Username, map[string]interface{}{ + "password": u.Password, + }) +} + +func Authenticate(vaultAddr, method string, user string, password string) error { + vault, err := NewVaultClient(vaultAddr) + if err != nil { + return fmt.Errorf("failed to create Vault client: %w", err) + } + + var authMethod AuthMethod + + switch method { + case "userpass": + authMethod = &UserPassAuth{ + Username: user, + Password: password, + } + // Add more cases here for future auth methods + default: + return fmt.Errorf("unsupported auth method: %s", method) + } + + secret, err := authMethod.Authenticate(vault) + if err != nil { + return fmt.Errorf("authentication failed: %w", err) + } + + if secret == nil || secret.Auth == nil { + return fmt.Errorf("no auth info returned") + } + + // Store the token in the CLI's home config directory + err = storeToken(secret.Auth.ClientToken) + if err != nil { + return fmt.Errorf("failed to store token: %w", err) + } + + return nil +} + +func storeToken(token string) error { + homeDir, err := os.UserHomeDir() + if err != nil { + return fmt.Errorf("failed to get user home directory: %w", err) + } + + configDir := filepath.Join(homeDir, ".vaultctl") + err = os.MkdirAll(configDir, 0700) + if err != nil { + return fmt.Errorf("failed to create config directory: %w", err) + } + + tokenFile := filepath.Join(configDir, "token") + err = os.WriteFile(tokenFile, []byte(token), 0600) + if err != nil { + return fmt.Errorf("failed to write token file: %w", err) + } + + return nil +} diff --git a/pkg/vault/operator.go b/pkg/vault/operator.go new file mode 100644 index 0000000..fab5d2a --- /dev/null +++ b/pkg/vault/operator.go @@ -0,0 +1,40 @@ +package vault + +import ( + "fmt" + + "github.com/CorefluxCommunity/vaultctl/pkg/utils" + "github.com/hashicorp/vault/api" +) + +func printSealStatus(resp *api.SealStatusResponse) { + status := "unsealed" + if resp.Sealed { + status = "sealed" + } else { + utils.PrintKV("Cluster name", resp.ClusterName) + utils.PrintKV("Cluster ID", resp.ClusterID) + } + + utils.PrintKV("Seal status", status) + utils.PrintKV("Key threshold/shares", fmt.Sprintf("%d/%d", resp.T, resp.N)) + utils.PrintKV("Progress", fmt.Sprintf("%d/%d", resp.Progress, resp.T)) + utils.PrintKV("Version", resp.Version) +} + +// ListVaultStatus will output of the status the provided Vault address. +func ListVaultStatus(vaultAddr string) error { + vault, err := NewVaultClient(vaultAddr) + if err != nil { + return err + } + + resp, err := vault.ApiClient.Sys().SealStatus() + if err != nil { + return err + } + + printSealStatus(resp) + + return nil +} diff --git a/pkg/vault/vault.go b/pkg/vault/vault.go new file mode 100644 index 0000000..21ac311 --- /dev/null +++ b/pkg/vault/vault.go @@ -0,0 +1,61 @@ +package vault + +import ( + "fmt" + "net/url" + + "github.com/hashicorp/vault/api" +) + +type VaultClient struct { + ApiClient *api.Client + url *url.URL +} + +func NewVaultClient(addr string) (*VaultClient, error) { + vault := new(VaultClient) + + config := &api.Config{ + Address: addr, + } + + api, err := api.NewClient(config) + if err != nil { + return vault, err + } + + // Disable namespace usage + api.SetNamespace("") + + url, err := url.Parse(addr) + if err != nil { + return vault, err + } + + vault.ApiClient = api + vault.url = url + + return vault, nil +} + +func (v *VaultClient) FetchSecret(path string) (map[string]interface{}, error) { + secret, err := v.ApiClient.Logical().Read(path) + if err != nil { + return nil, err + } + if secret == nil { + return nil, fmt.Errorf("secret not found") + } + + // Check if this is a KV2 secret + if data, ok := secret.Data["data"].(map[string]interface{}); ok { + return data, nil + } + + // If it's not a KV2 secret, return the data as is + if secret.Data != nil { + return secret.Data, nil + } + + return nil, fmt.Errorf("unexpected secret data format") +}