From 1183813dcb9f2a521849f43e703319498a65e031 Mon Sep 17 00:00:00 2001 From: Nelson Isioma Date: Sun, 23 Feb 2025 15:56:48 +0100 Subject: [PATCH] Feature: adding support for exposing secrets to subprocesses (#68) * adding support for exposing secrets to subprocesses * updating readme * wip 3 * wip 4 * wip 56 --- README.md | 13 +++++++ cmd/exec.go | 103 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 116 insertions(+) create mode 100644 cmd/exec.go diff --git a/README.md b/README.md index 89409ba..10ce716 100644 --- a/README.md +++ b/README.md @@ -105,6 +105,19 @@ For Scripting we have a -j or --json flag to convert the Output for the create, Note: The JSON Output does not cover Error Messages, you can detect Errors by checking if the Exitcode is not 0 +# Exposing Secrets to Subprocesses +The `exec` command allows you to execute another command with environment variables that reference secrets stored in Passbolt. +Any environment variables containing `passbolt://` references are automatically resolved to their corresponding secret values +before the specified command is executed. This ensures that secrets are securely injected into the child process's environment +without exposing them to the parent shell. +For example: +```bash +export GITHUB_TOKEN=passbolt:// +passbolt exec -- gh auth login +``` + +This would resolve the passbolt:// reference in GITHUB_TOKEN to its actual secret value and pass it to the gh process. + # Documentation Usage for all Subcommands is [here](https://github.com/passbolt/go-passbolt-cli/wiki/passbolt). And is also available via `man passbolt` diff --git a/cmd/exec.go b/cmd/exec.go new file mode 100644 index 0000000..7000b41 --- /dev/null +++ b/cmd/exec.go @@ -0,0 +1,103 @@ +package cmd + +import ( + "context" + "fmt" + "os" + "os/exec" + "strings" + + "github.com/passbolt/go-passbolt-cli/util" + "github.com/passbolt/go-passbolt/api" + "github.com/passbolt/go-passbolt/helper" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +const PassboltPrefix = "passbolt://" + +// execCmd represents the exec command +var execCmd = &cobra.Command{ + Use: "exec -- command [args...]", + Short: "Run a command with secrets injected into the environment.", + Long: `The command allows you to execute another command with environment variables that reference secrets stored in Passbolt. +Any environment variables containing passbolt:// references are automatically resolved to their corresponding secret values + before the specified command is executed. This ensures that secrets are securely injected into the child process's environment + without exposing them to the parent shell. + + For example: + export GITHUB_TOKEN=passbolt:// + passbolt exec -- gh auth login + + This would resolve the passbolt:// reference in GITHUB_TOKEN to its actual secret value and pass it to the gh process. +`, + Args: cobra.MinimumNArgs(1), + RunE: execAction, +} + +func init() { + rootCmd.AddCommand(execCmd) +} + +func execAction(_ *cobra.Command, args []string) error { + ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout")) + defer cancel() + + client, err := util.GetClient(ctx) + if err != nil { + return fmt.Errorf("Creating client: %w", err) + } + + envVars, err := resolveEnvironmentSecrets(ctx, client) + if err != nil { + return fmt.Errorf("Resolving secrets: %w", err) + } + + if err = client.Logout(ctx); err != nil { + return fmt.Errorf("Logging out client: %w", err) + } + + subCmd := exec.Command(args[0], args[1:]...) + subCmd.Stdin = os.Stdin + subCmd.Stdout = os.Stdout + subCmd.Stderr = os.Stderr + subCmd.Env = envVars + + if err = subCmd.Run(); err != nil { + return fmt.Errorf("Running command: %w", err) + } + + return nil +} + +func resolveEnvironmentSecrets(ctx context.Context, client *api.Client) ([]string, error) { + envVars := os.Environ() + + for i, envVar := range envVars { + splitIndex := strings.Index(envVar, "=") + if splitIndex == -1 { + continue + } + + key := envVar[:splitIndex] + value := envVar[splitIndex+1:] + + if !strings.HasPrefix(value, PassboltPrefix) { + continue + } + + resourceId := strings.TrimPrefix(value, PassboltPrefix) + _, _, _, _, secret, _, err := helper.GetResource(ctx, client, resourceId) + if err != nil { + return nil, fmt.Errorf("Getting resource: %w", err) + } + + envVars[i] = key + "=" + secret + + if viper.GetBool("debug") { + fmt.Fprintf(os.Stdout, "%v env var populated with resource id %v\n", key, resourceId) + } + } + + return envVars, nil +}