diff --git a/cmd/exec.go b/cmd/exec.go new file mode 100644 index 0000000..592fd60 --- /dev/null +++ b/cmd/exec.go @@ -0,0 +1,98 @@ +package cmd + +import ( + "context" + "fmt" + "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" + "os" + "os/exec" + "strings" +) + +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("error creating client: %w", err) + } + + envVars, err := resolveEnvironmentSecrets(ctx, client) + if err != nil { + return fmt.Errorf("error resolving secrets: %w", err) + } + + if err = client.Logout(ctx); err != nil { + return fmt.Errorf("error 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("error 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("error getting resource: %w", err) + } + + envVars[i] = key + "=" + secret + } + + return envVars, nil +}