Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: adding support for exposing secrets to subprocesses #68

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 98 additions & 0 deletions cmd/exec.go
Original file line number Diff line number Diff line change
@@ -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_RESOURCE_ID_HERE>
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
}