Skip to content

Commit

Permalink
public 0.1
Browse files Browse the repository at this point in the history
  • Loading branch information
jacobbednarz committed Sep 16, 2020
1 parent ce7803c commit 9b8eee7
Show file tree
Hide file tree
Showing 7 changed files with 596 additions and 17 deletions.
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,17 @@

Manage your Cloudflare credentials, securely.

The goal of this project is to ensure that when you need to interact with Cloudflare:

- You're not storing credentials unencrypted; and
- You're not exposing your credentials to the entire environment or to all
processes; and
- Your not using long lived credentials

To achieve this, `cf-vault` uses the concept of profiles with associated scopes
to either generate short lived API tokens or retrieve the API key from secure
storage (such as Mac OS keychain).

## Usage

`cf-vault` allows you to manage your Cloudflare credentials in a safe place and
Expand Down
78 changes: 78 additions & 0 deletions cmd/add.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package cmd

import (
"bufio"
"errors"
"fmt"
"os"
"strings"

log "github.com/sirupsen/logrus"

"github.com/99designs/keyring"
"github.com/mitchellh/go-homedir"
"github.com/spf13/cobra"
"gopkg.in/ini.v1"
)

var addCmd = &cobra.Command{
Use: "add [profile]",
Short: "Add a new profile to your configuration and keychain",
Long: "",
Args: func(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return errors.New("requires a profile argument")
}
return nil
},
PreRun: func(cmd *cobra.Command, args []string) {
if verbose {
log.SetLevel(log.DebugLevel)
}
},
Run: func(cmd *cobra.Command, args []string) {
profileName := strings.TrimSpace(args[0])

reader := bufio.NewReader(os.Stdin)
fmt.Print("Email address: ")
emailAddress, _ := reader.ReadString('\n')
emailAddress = strings.TrimSpace(emailAddress)

fmt.Print("Authentication type (api_token or api_key): ")
authType, _ := reader.ReadString('\n')
authType = strings.TrimSpace(authType)

fmt.Print("Authentication value: ")
authValue, _ := reader.ReadString('\n')
authValue = strings.TrimSpace(authValue)

home, err := homedir.Dir()
if err != nil {
log.Fatal("unable to find home directory: ", err)
}

os.MkdirAll(home+defaultConfigDirectory, 0700)
if _, err := os.Stat(home + defaultFullConfigPath); os.IsNotExist(err) {
file, err := os.Create(home + defaultFullConfigPath)
if err != nil {
log.Fatal(err)
}
defer file.Close()
}

cfg, _ := ini.Load(home + defaultFullConfigPath)
cfg.Section(fmt.Sprintf("profile %s", profileName)).NewKey("email", emailAddress)
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,
})

_ = ring.Set(keyring.Item{
Key: fmt.Sprintf("%s-%s", profileName, authType),
Data: []byte(authValue),
})
},
}
71 changes: 65 additions & 6 deletions cmd/exec.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,75 @@
package cmd

import (
"bytes"
"errors"
"fmt"
"os"
"os/exec"
"strings"

"github.com/99designs/keyring"
"github.com/mitchellh/go-homedir"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"gopkg.in/ini.v1"
)

func init() {
rootCmd.AddCommand(execCmd)
}

var execCmd = &cobra.Command{
Use: "exec",
Use: "exec [profile]",
Short: "Execute a command with Cloudflare credentials populated",
Long: "",
Run: func(cmd *cobra.Command, args []string) {},
Args: func(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return errors.New("requires a profile argument")
}
return nil
},
PreRun: func(cmd *cobra.Command, args []string) {
if verbose {
log.SetLevel(log.DebugLevel)
}
},
Run: func(cmd *cobra.Command, args []string) {
profileName := args[0]
log.Debug("using profile: ", profileName)

home, err := homedir.Dir()
if err != nil {
log.Fatal("unable to find home directory: ", err)
}
cfg, err := ini.Load(home + defaultFullConfigPath)
profileSection, err := cfg.GetSection("profile " + profileName)
if err != nil {
log.Fatal("unable to create profile section: ", err)
}

if profileSection.Key("email").String() == "" {
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,
})

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

fmt.Printf("%v", string(i.Data))

log.Debug("environment is populated with credentials")

command := exec.Command(args[1])
command.Env = append(os.Environ(),
fmt.Sprintf("CLOUDFLARE_EMAIL=%s", profileSection.Key("email").String()),
fmt.Sprintf("CLOUDFLARE_%s=%s", strings.ToUpper(profileSection.Key("auth_type").String()), string(i.Data)),
)
commandOutput := &bytes.Buffer{}
command.Stdout = commandOutput
err = command.Run()
if err != nil {
os.Stderr.WriteString(err.Error())
}
fmt.Print(string(commandOutput.Bytes()))
},
}
30 changes: 26 additions & 4 deletions cmd/root.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,36 @@
package cmd

import (
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)

var (
verbose bool
projectName = "cf-vault"
defaultConfigDirectory = "/." + projectName
defaultFullConfigPath = defaultConfigDirectory + "/config"
)

var rootCmd = &cobra.Command{
Use: "cf-vault",
Short: "",
Long: "Manage your Cloudflare credentials, securely",
Run: func(cmd *cobra.Command, args []string) {},
Use: projectName,
Long: "Manage your Cloudflare credentials, securely",
PreRun: func(cmd *cobra.Command, args []string) {
if verbose {
log.SetLevel(log.DebugLevel)
}
},
Run: func(cmd *cobra.Command, args []string) {},
}

func init() {
log.SetLevel(log.WarnLevel)

rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "increase the verbosity of the output")

rootCmd.AddCommand(addCmd)
rootCmd.AddCommand(execCmd)
rootCmd.AddCommand(versionCmd)
}

// Execute is the main entrypoint for the CLI.
Expand Down
14 changes: 8 additions & 6 deletions cmd/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,24 @@ import (
"fmt"
"runtime"

log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)

// Rev is set on build time and should follow the semantic version format for
// versioning strings.
var Rev = "0.0.0-dev"

func init() {
rootCmd.AddCommand(versionCmd)
}

var versionCmd = &cobra.Command{
Use: "version",
Short: "Print the version string of cf-vault",
Short: fmt.Sprintf("Print the version string of %s", projectName),
Long: "",
PreRun: func(cmd *cobra.Command, args []string) {
if verbose {
log.SetLevel(log.DebugLevel)
}
},
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("%s %s (%s,%s-%s)", "cf-vault", Rev, runtime.Version(), runtime.Compiler, runtime.GOARCH)
fmt.Printf("%s %s (%s,%s-%s)", projectName, Rev, runtime.Version(), runtime.Compiler, runtime.GOARCH)
},
}
19 changes: 18 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,21 @@ module github.com/jacobbednarz/cf-vault

go 1.15

require github.com/spf13/cobra v1.0.0
require (
cloud.google.com/go v0.65.0
github.com/99designs/keyring v1.1.5
github.com/BurntSushi/toml v0.3.1
github.com/cosiner/argv v0.1.0 // indirect
github.com/google/martian v2.1.0+incompatible
github.com/google/pprof v0.0.0-20200905233945-acf8798be1f7 // indirect
github.com/googleapis/gax-go v2.0.2+incompatible // indirect
github.com/mitchellh/go-homedir v1.1.0
github.com/sirupsen/logrus v1.2.0
github.com/spf13/cobra v1.0.0
github.com/spf13/viper v1.4.0
golang.org/x/tools v0.0.0-20200915201639-f4cefd1cb5ba
google.golang.org/api v0.32.0 // indirect
gopkg.in/ini.v1 v1.61.0
)

replace github.com/keybase/go-keychain => github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4
Loading

0 comments on commit 9b8eee7

Please sign in to comment.