Skip to content

Commit

Permalink
Merge pull request #4 from jacobbednarz/no-args-means-new-shell
Browse files Browse the repository at this point in the history
exec: spawn new shell on no arguments
  • Loading branch information
jacobbednarz committed Sep 16, 2020
2 parents 568f949 + 66c6a90 commit 55d4d60
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 35 deletions.
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,22 @@ $ env | grep -i cloudflare
# => no results

$ cf-vault exec work -- env | grep -i cloudflare
CLOUDFLARE_VAULT_SESSION=work
[email protected]
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
[email protected]
CLOUDFLARE_API_KEY=s3cr3t

$ exit
$ env | grep -i cloudflare
# => no results
```
11 changes: 6 additions & 5 deletions cmd/add.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -65,11 +70,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),
Expand Down
72 changes: 46 additions & 26 deletions cmd/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"os"
"strings"
"syscall"

"os/exec"

Expand All @@ -19,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
[email protected]
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
[email protected]
CLOUDFLARE_API_KEY=s3cr3t
`,
Args: func(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return errors.New("requires a profile argument")
Expand All @@ -32,6 +49,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()
Expand All @@ -45,42 +67,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)
}

ring, _ := keyring.Open(keyring.Config{
FileDir: "~/.cf-vault/keys/",
ServiceName: projectName,
KeychainName: projectName,
})

keychain, _ := ring.Get(fmt.Sprintf("%s-%s", profileName, profileSection.Key("auth_type").String()))

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")
}

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]
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)
},
}
23 changes: 19 additions & 4 deletions cmd/root.go
Original file line number Diff line number Diff line change
@@ -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",
Expand Down

0 comments on commit 55d4d60

Please sign in to comment.