From dfc754951ea687602cc8c28a049cdb3c59e5f4ea Mon Sep 17 00:00:00 2001 From: Jacob Bednarz Date: Thu, 17 Sep 2020 08:00:58 +1000 Subject: [PATCH 1/6] Use variable for keychain defaults --- cmd/add.go | 6 +----- cmd/exec.go | 6 +----- cmd/root.go | 23 +++++++++++++++++++---- 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/cmd/add.go b/cmd/add.go index 6e0af8b..f6eb68e 100644 --- a/cmd/add.go +++ b/cmd/add.go @@ -65,11 +65,7 @@ var addCmd = &cobra.Command{ cfg.Section(fmt.Sprintf("profile %s", profileName)).NewKey("auth_type", authType) cfg.SaveTo(home + defaultFullConfigPath) - ring, _ := keyring.Open(keyring.Config{ - FileDir: "~/.cf-vault/keys/", - ServiceName: projectName, - KeychainName: projectName, - }) + ring, _ := keyring.Open(keyringDefaults) _ = ring.Set(keyring.Item{ Key: fmt.Sprintf("%s-%s", profileName, authType), diff --git a/cmd/exec.go b/cmd/exec.go index 4736b6e..2cd2518 100644 --- a/cmd/exec.go +++ b/cmd/exec.go @@ -48,11 +48,7 @@ var execCmd = &cobra.Command{ log.Fatal(fmt.Sprintf("no profile matching %q found in the configuration file at %s", profileName, defaultFullConfigPath)) } - ring, _ := keyring.Open(keyring.Config{ - FileDir: "~/.cf-vault/keys/", - ServiceName: projectName, - KeychainName: projectName, - }) + ring, _ := keyring.Open(keyringDefaults) keychain, _ := ring.Get(fmt.Sprintf("%s-%s", profileName, profileSection.Key("auth_type").String())) diff --git a/cmd/root.go b/cmd/root.go index 06ab19e..a68bfde 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -1,17 +1,32 @@ package cmd import ( + "fmt" + + "github.com/99designs/keyring" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) var ( - verbose bool - projectName = "cf-vault" - defaultConfigDirectory = "/." + projectName - defaultFullConfigPath = defaultConfigDirectory + "/config" + verbose bool + projectName = "cf-vault" + projectNameWithoutHyphen = "cfvault" + defaultConfigDirectory = "/." + projectName + defaultFullConfigPath = defaultConfigDirectory + "/config" ) +var keyringDefaults = keyring.Config{ + FileDir: fmt.Sprintf("~/.%s/keys/", projectName), + ServiceName: projectName, + KeychainName: projectName, + LibSecretCollectionName: projectNameWithoutHyphen, + KWalletAppID: projectName, + KWalletFolder: projectName, + KeychainTrustApplication: true, + WinCredPrefix: projectName, +} + var rootCmd = &cobra.Command{ Use: projectName, Long: "Manage your Cloudflare credentials, securely", From 038f77535f53109e65cb717456b15695edbb57ce Mon Sep 17 00:00:00 2001 From: Jacob Bednarz Date: Thu, 17 Sep 2020 09:22:50 +1000 Subject: [PATCH 2/6] tidy up `args` handling and duplication --- cmd/exec.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/cmd/exec.go b/cmd/exec.go index 2cd2518..8ee5d85 100644 --- a/cmd/exec.go +++ b/cmd/exec.go @@ -32,6 +32,11 @@ var execCmd = &cobra.Command{ }, Run: func(cmd *cobra.Command, args []string) { profileName := args[0] + // Remove the extra executable name at the beginning of the slice. + copy(args[0:], args[0+1:]) + args[len(args)-1] = "" + args = args[:len(args)-1] + log.Debug("using profile: ", profileName) home, err := homedir.Dir() @@ -59,12 +64,6 @@ var execCmd = &cobra.Command{ log.Fatalf("couldn't find the executable '%s': %w", executable, err) } - log.Printf("found executable %s", argv0) - - // Remove the extra executable name at the beginning of the slice. - copy(args[0:], args[0+1:]) - args[len(args)-1] = "" - args = args[:len(args)-1] runningCommand := exec.Command(executable, args...) runningCommand.Env = append(os.Environ(), From e83887aac77e29d19bb61d754c0056c17418938c Mon Sep 17 00:00:00 2001 From: Jacob Bednarz Date: Thu, 17 Sep 2020 09:24:15 +1000 Subject: [PATCH 3/6] use default keychain configuration --- cmd/exec.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/cmd/exec.go b/cmd/exec.go index 8ee5d85..2dd8c98 100644 --- a/cmd/exec.go +++ b/cmd/exec.go @@ -53,9 +53,6 @@ var execCmd = &cobra.Command{ log.Fatal(fmt.Sprintf("no profile matching %q found in the configuration file at %s", profileName, defaultFullConfigPath)) } - ring, _ := keyring.Open(keyringDefaults) - - keychain, _ := ring.Get(fmt.Sprintf("%s-%s", profileName, profileSection.Key("auth_type").String())) command := strings.Split(args[1], " ") executable := command[0] @@ -64,6 +61,8 @@ var execCmd = &cobra.Command{ log.Fatalf("couldn't find the executable '%s': %w", executable, err) } + ring, _ := keyring.Open(keyringDefaults) + keychain, _ := ring.Get(fmt.Sprintf("%s-%s", profileName, profileSection.Key("auth_type").String())) runningCommand := exec.Command(executable, args...) runningCommand.Env = append(os.Environ(), From 47eefa1faf8b00c7c1888469edb5f2f3baf6e400 Mon Sep 17 00:00:00 2001 From: Jacob Bednarz Date: Thu, 17 Sep 2020 09:26:40 +1000 Subject: [PATCH 4/6] exec: spawn new shell on no arguments When no arguments are provided, spawn a fresh shell with the current environment and the credentials populated. --- cmd/exec.go | 36 +++++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/cmd/exec.go b/cmd/exec.go index 2dd8c98..d2c19b5 100644 --- a/cmd/exec.go +++ b/cmd/exec.go @@ -5,6 +5,7 @@ import ( "fmt" "os" "strings" + "syscall" "os/exec" @@ -50,31 +51,40 @@ var execCmd = &cobra.Command{ } if profileSection.Key("email").String() == "" { - log.Fatal(fmt.Sprintf("no profile matching %q found in the configuration file at %s", profileName, defaultFullConfigPath)) + log.Fatalf("no profile matching %q found in the configuration file at %s", profileName, defaultFullConfigPath) } - - command := strings.Split(args[1], " ") - executable := command[0] - argv0, err := exec.LookPath(executable) - if err != nil { - log.Fatalf("couldn't find the executable '%s': %w", executable, err) + // Don't allow nesting of cf-vault sessions, it gets messy. + if os.Getenv("CLOUDFLARE_VAULT_SESSION") != "" { + log.Fatal("cf-vault sessions shouldn't be nested, unset CLOUDFLARE_VAULT_SESSION to continue or open a new shell session") } ring, _ := keyring.Open(keyringDefaults) keychain, _ := ring.Get(fmt.Sprintf("%s-%s", profileName, profileSection.Key("auth_type").String())) - runningCommand := exec.Command(executable, args...) - runningCommand.Env = append(os.Environ(), + cloudflareCreds := []string{ + fmt.Sprintf("CLOUDFLARE_VAULT_SESSION=%s", profileName), fmt.Sprintf("CLOUDFLARE_EMAIL=%s", profileSection.Key("email").String()), fmt.Sprintf("CLOUDFLARE_%s=%s", strings.ToUpper(profileSection.Key("auth_type").String()), string(keychain.Data)), - ) - stdoutStderr, err := runningCommand.CombinedOutput() + } + // Should a command not be provided, drop into a fresh shell with the + // credentials populated alongside the existing env. + if len(args) == 0 { + log.Debug("launching new shell with credentials populated") + envVars := append(syscall.Environ(), cloudflareCreds...) + syscall.Exec(os.Getenv("SHELL"), []string{os.Getenv("SHELL")}, envVars) + } + + executable := args[0] + pathtoExec, err := exec.LookPath(executable) if err != nil { - log.Fatal(err) + log.Fatalf("couldn't find the executable '%s': %w", pathtoExec, err) } - fmt.Printf("%s\n", stdoutStderr) + log.Debugf("found executable %s", pathtoExec) + log.Debugf("executing command: %s", strings.Join(args, " ")) + + syscall.Exec(pathtoExec, args, cloudflareCreds) }, } From df94c80c42fb7bb9971dc9cca5a2e5b956ff1bed Mon Sep 17 00:00:00 2001 From: Jacob Bednarz Date: Thu, 17 Sep 2020 09:32:36 +1000 Subject: [PATCH 5/6] add new shell example to README --- README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/README.md b/README.md index b82cdfe..bf9bc69 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,22 @@ $ env | grep -i cloudflare # => no results $ cf-vault exec work -- env | grep -i cloudflare +CLOUDFLARE_VAULT_SESSION=work CLOUDFLARE_EMAIL=jacob@example.com CLOUDFLARE_API_KEY=s3cr3t ``` + +If you don't provide a command, you will be dropped into a new shell with the +credentials populated. + +```shell +$ cf-vault exec work +$ env | grep -i cloudflare +CLOUDFLARE_VAULT_SESSION=work +CLOUDFLARE_EMAIL=jacob@example.com +CLOUDFLARE_API_KEY=s3cr3t + +$ exit +$ env | grep -i cloudflare +# => no results +``` From 66c6a907080ec6e7faeaa441f00a8f90c3659fb6 Mon Sep 17 00:00:00 2001 From: Jacob Bednarz Date: Thu, 17 Sep 2020 09:41:11 +1000 Subject: [PATCH 6/6] add example help output --- cmd/add.go | 5 +++++ cmd/exec.go | 16 ++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/cmd/add.go b/cmd/add.go index f6eb68e..f2a6d45 100644 --- a/cmd/add.go +++ b/cmd/add.go @@ -19,6 +19,11 @@ var addCmd = &cobra.Command{ Use: "add [profile]", Short: "Add a new profile to your configuration and keychain", Long: "", + Example: ` + Add a new profile (you will be prompted for credentials) + + $ cf-vault add example-profile +`, Args: func(cmd *cobra.Command, args []string) error { if len(args) < 1 { return errors.New("requires a profile argument") diff --git a/cmd/exec.go b/cmd/exec.go index d2c19b5..9a7c19b 100644 --- a/cmd/exec.go +++ b/cmd/exec.go @@ -20,6 +20,22 @@ var execCmd = &cobra.Command{ Use: "exec [profile]", Short: "Execute a command with Cloudflare credentials populated", Long: "", + Example: ` + Execute a single command with credentials populated + + $ cf-vault exec example-profile -- env | grep -i cloudflare + CLOUDFLARE_VAULT_SESSION=example-profile + CLOUDFLARE_EMAIL=jacob@example.com + CLOUDFLARE_API_KEY=s3cr3t + + Spawn a new shell with credentials populated + + $ cf-vault exec example-profile -- + $ env | grep -i cloudflare + CLOUDFLARE_VAULT_SESSION=example-profile + CLOUDFLARE_EMAIL=jacob@example.com + CLOUDFLARE_API_KEY=s3cr3t +`, Args: func(cmd *cobra.Command, args []string) error { if len(args) < 1 { return errors.New("requires a profile argument")