Skip to content

Commit

Permalink
Add keychain support for credentials.
Browse files Browse the repository at this point in the history
  • Loading branch information
ejcx committed Jul 25, 2018
1 parent 14a76ba commit 2556db4
Show file tree
Hide file tree
Showing 2 changed files with 156 additions and 7 deletions.
112 changes: 112 additions & 0 deletions cmd/configure.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package cmd

import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"os"

"github.com/99designs/keyring"
cloudflare "github.com/cloudflare/cloudflare-go"
cflib "github.com/ejcx/cf/lib"
"github.com/segmentio/aws-okta/lib"
"github.com/spf13/cobra"
)

var ConfigureCmd = &cobra.Command{
Use: "configure",
Short: "A command for configuring your cloudflare api credentials",
Run: func(cmd *cobra.Command, args []string) {
err := Configure(cmd, args)
if err != nil {
log.Fatalf("Could not configure cf cli: %s", err)
}
},
}

func init() {
RootCmd.AddCommand(ConfigureCmd)
}

func Configure(cmd *cobra.Command, args []string) error {
email, err := lib.Prompt("Cloudflare Email", false)
if err != nil {
return err
}

apiKey, err := lib.Prompt("Cloudflare APIKey", true)
if err != nil {
return err
}

// Add a newline at the beginning and end because sensitive
// prompts all end up on one line.
serviceKey, err := lib.Prompt("\nService APIKey", true)
if err != nil {
return err
}
fmt.Println("")

// Now that we have the credentials, validate that the apikey and the email
// are real by calling the User Details API.
creds := &cflib.Credentials{
Email: email,
Key: apiKey,
UserServiceKey: serviceKey,
}
creds.SetEnv()

api, err := cloudflare.New(os.Getenv("CF_API_KEY"), os.Getenv("CF_API_EMAIL"))
if err != nil {
return fmt.Errorf("Could not initialize api object: %s", err)
}

_, err = api.UserDetails()
if err != nil {
return fmt.Errorf("Invalid user credentials: %s", err)
}

// Now marshal the data to store in the keychain and set a cloudflare creds
// file that has keychain set to true and nothing else
encoded, err := json.Marshal(creds)
if err != nil {
return err
}
var allowedBackends []keyring.BackendType
kr, err := keyring.Open(keyring.Config{
AllowedBackends: allowedBackends,
KeychainTrustApplication: true,
ServiceName: "cloudflare-credentials",
LibSecretCollectionName: "cloudflare",
FileDir: "~/.cf/",
FilePasswordFunc: func(prompt string) (string, error) {
return lib.Prompt("\n"+prompt, true)
},
})
err = kr.Set(keyring.Item{
Key: "cloudflare-creds",
Data: encoded,
Label: "cloudflare credentials",
KeychainNotTrustApplication: false,
})
if err != nil {
return err
}

// Write a dumby creds file that points to the keychain.
buf, err := json.Marshal(cflib.Credentials{
Keychain: true,
})
if err != nil {
return err
}
home, err := cflib.GetHomeDir()
if err != nil {
return err
}
outfile := home + "/.cf/credentials"
err = ioutil.WriteFile(outfile, buf, 0600)

return err
}
51 changes: 44 additions & 7 deletions lib/creds.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import (
"io/ioutil"
"os"
"os/user"

"github.com/99designs/keyring"
"github.com/segmentio/aws-okta/lib"
)

var (
Expand All @@ -19,9 +22,10 @@ type Credentials struct {
Email string `json:"Email"`
Key string `json:"Key"`
UserServiceKey string `json:"UserServiceKey"`
Keychain bool `json:"Keychain"`
}

func getHomeDir() (string, error) {
func GetHomeDir() (string, error) {
usr, err := user.Current()
if err != nil {
return "", err
Expand All @@ -40,32 +44,65 @@ func readConfigFile(homedir string) (*Credentials, error) {
return c, err
}

func (c *CredProvider) ConfigureEnvironment() error {
func (c *Credentials) SetEnv() {
os.Setenv("CF_API_KEY", c.Key)
os.Setenv("CF_API_EMAIL", c.Email)
os.Setenv("CF_USER_SERVICE_KEY", c.UserServiceKey)
}

func isEnvSet() bool {
// If we already have the cloudflare environment variables set that
// are used by the cloudflare-go library then we should just return.
_, keyOk := os.LookupEnv("CF_API_KEY")
_, emailOk := os.LookupEnv("CF_API_EMAIL")
_, serviceOk := os.LookupEnv("CF_USER_SERVICE_KEY")
if keyOk || emailOk || serviceOk {
return keyOk || emailOk || serviceOk
}

func (c *CredProvider) ConfigureEnvironment() error {
// Nothing to do
if isEnvSet() {
return nil
}

homedir := c.HomeDir

// Otherwise, we need to read the ~/.cf/credentials file in the users
// home directory. It would also be nice to store this in the keychain
if c.HomeDir == "" {
h, err := getHomeDir()
h, err := GetHomeDir()
if err != nil {
return err
}
homedir = h
}

creds, err := readConfigFile(homedir)
if err != nil {
return err
}
os.Setenv("CF_API_KEY", creds.Key)
os.Setenv("CF_API_EMAIL", creds.Email)
os.Setenv("CF_USER_SERVICE_KEY", creds.UserServiceKey)

if creds.Keychain {
var allowedBackends []keyring.BackendType
kr, err := keyring.Open(keyring.Config{
AllowedBackends: allowedBackends,
KeychainTrustApplication: true,
ServiceName: "cloudflare-credentials",
LibSecretCollectionName: "cloudflare",
FileDir: "~/.cf/",
FilePasswordFunc: func(prompt string) (string, error) {
return lib.Prompt("\n"+prompt, true)
},
})
keychainCreds, err := kr.Get("cloudflare-creds")
if err != nil {
return err
}
if err = json.Unmarshal(keychainCreds.Data, &creds); err != nil {
return err
}
}

creds.SetEnv()
return nil
}

0 comments on commit 2556db4

Please sign in to comment.