Skip to content

Commit

Permalink
Merge pull request #63 from 99designs/file-backend
Browse files Browse the repository at this point in the history
Add a basic file backend
  • Loading branch information
lox committed Jun 21, 2016
2 parents d84315c + b215e91 commit 0a93138
Show file tree
Hide file tree
Showing 9 changed files with 243 additions and 44 deletions.
34 changes: 23 additions & 11 deletions glide.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions glide.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@ import:
- aws/awserr
- aws/credentials
- service/iam
- package: github.com/bgentry/speakeasy
version: 200c3052657e041d0b8abab4ad65e0ba64a9f8c4
- package: github.com/skratchdot/open-golang
version: c8748311a7528d0ba7330d302adbc5a677ef9c9e
subpackages:
Expand All @@ -29,3 +27,4 @@ import:
version: a98ad7ee00ec53921f08832bc06ecf7fd600e6a1
- package: gopkg.in/alecthomas/kingpin.v2
version: 639879d6110b1b0409410c7b737ef0bb18325038
- package: github.com/dvsekhvalnov/jose2go
3 changes: 1 addition & 2 deletions keyring/array.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@ type arrayKeyring struct {
func (k *arrayKeyring) Get(key string) (Item, error) {
if i, ok := k.items[key]; ok {
return i, nil
} else {
return Item{}, ErrKeyNotFound
}
return Item{}, ErrKeyNotFound
}

func (k *arrayKeyring) Set(i Item) error {
Expand Down
155 changes: 155 additions & 0 deletions keyring/file.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
package keyring

import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"os/user"
"path/filepath"
"time"

jose "github.com/dvsekhvalnov/jose2go"
"golang.org/x/crypto/ssh/terminal"
)

type passwordFunc func(string) (string, error)

func terminalPrompt(prompt string) (string, error) {
fmt.Printf("%s: ", prompt)
b, err := terminal.ReadPassword(1)
if err != nil {
return "", err
}
fmt.Println()
return string(b), nil
}

func init() {
supportedBackends[FileBackend] = opener(func(name string) (Keyring, error) {
return &fileKeyring{
PasswordFunc: terminalPrompt,
}, nil
})
}

type fileKeyring struct {
Dir string
PasswordFunc passwordFunc
password string
}

func (k *fileKeyring) dir() (string, error) {
dir := k.Dir
if dir == "" {
usr, err := user.Current()
if err != nil {
return dir, err
}
dir = usr.HomeDir + "/.awsvault/keys/"
}

stat, err := os.Stat(dir)
if os.IsNotExist(err) {
os.MkdirAll(dir, 0700)
} else if err != nil && !stat.IsDir() {
err = fmt.Errorf("%s is a file, not a directory", dir)
}

return dir, nil
}

func (k *fileKeyring) unlock() error {
dir, err := k.dir()
if err != nil {
return err
}

if k.password == "" {
pwd, err := k.PasswordFunc(fmt.Sprintf("Enter passphrase to unlock %s", dir))
if err != nil {
return err
}
k.password = pwd
}

return nil
}

func (k *fileKeyring) Get(key string) (Item, error) {
dir, err := k.dir()
if err != nil {
return Item{}, err
}

bytes, err := ioutil.ReadFile(filepath.Join(dir, key))
if os.IsNotExist(err) {
return Item{}, ErrKeyNotFound
} else if err != nil {
return Item{}, err
}

if err = k.unlock(); err != nil {
return Item{}, err
}

payload, _, err := jose.Decode(string(bytes), k.password)
if err != nil {
return Item{}, err
}

var decoded Item
err = json.Unmarshal([]byte(payload), &decoded)

return decoded, err
}

func (k *fileKeyring) Set(i Item) error {
bytes, err := json.Marshal(i)
if err != nil {
return err
}

dir, err := k.dir()
if err != nil {
return err
}

if err = k.unlock(); err != nil {
return err
}

token, err := jose.Encrypt(string(bytes), jose.PBES2_HS256_A128KW, jose.A256GCM, k.password,
jose.Headers(map[string]interface{}{
"created": time.Now().String(),
}))
if err != nil {
return err
}

return ioutil.WriteFile(filepath.Join(dir, i.Key), []byte(token), 0600)
}

func (k *fileKeyring) Remove(key string) error {
dir, err := k.dir()
if err != nil {
return err
}

return os.Remove(filepath.Join(dir, key))
}

func (k *fileKeyring) Keys() ([]string, error) {
dir, err := k.dir()
if err != nil {
return nil, err
}

var keys = []string{}
files, _ := ioutil.ReadDir(dir)
for _, f := range files {
keys = append(keys, f.Name())
}

return keys, nil
}
33 changes: 33 additions & 0 deletions keyring/file_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package keyring

import (
"os"
"testing"
)

func TestFileKeyringSetWhenEmpty(t *testing.T) {
k := &fileKeyring{
Dir: os.TempDir(),
PasswordFunc: passwordFunc(func(string) (string, error) {
return "no more secrets", nil
}),
}
item := Item{Key: "llamas", Data: []byte("llamas are great")}

if err := k.Set(item); err != nil {
t.Fatal(err)
}

foundItem, err := k.Get("llamas")
if err != nil {
t.Fatal(err)
}

if string(foundItem.Data) != "llamas are great" {
t.Fatalf("Value stored was not the value retrieved: %q", foundItem.Data)
}

if foundItem.Key != "llamas" {
t.Fatalf("Key wasn't persisted: %q", foundItem.Key)
}
}
2 changes: 2 additions & 0 deletions keyring/keychain.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ func init() {

return &keychain{Path: usr.HomeDir + "/Library/Keychains/" + name + ".keychain", Service: name}, nil
})

DefaultBackend = KeychainBackend
}

func (k *keychain) Get(key string) (Item, error) {
Expand Down
4 changes: 0 additions & 4 deletions keyring/keychain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,6 @@ func TestOSXKeychainKeyringSet(t *testing.T) {
Description: "A freetext description",
Data: []byte("llamas are great"),
TrustSelf: true,
Metadata: map[string]string{
"llamas": "rock",
"alpacas": "rock",
},
}

if err := k.Set(item); err != nil {
Expand Down
40 changes: 20 additions & 20 deletions keyring/keyring.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,35 @@ package keyring

import "errors"

type backend string

const (
KeychainBackend backend = "osxkeychain"
KWalletBackend backend = "kwallet"
KeychainBackend string = "keychain"
KWalletBackend string = "kwallet"
FileBackend string = "file"
)

var supportedBackends = map[backend]opener{}
var DefaultBackend = FileBackend

func Open(name string, prefer ...backend) (Keyring, error) {
if len(prefer) == 0 {
for b := range supportedBackends {
prefer = append(prefer, b)
}
}
var supportedBackends = map[string]opener{}

for _, b := range prefer {
for supported, f := range supportedBackends {
if b == supported {
return f(name)
}
}
func SupportedBackends() []string {
b := []string{}
for k := range supportedBackends {
b = append(b, k)
}

return nil, ErrNoAvailImpl
return b
}

type opener func(name string) (Keyring, error)

func Open(name string, backend string) (Keyring, error) {
op, ok := supportedBackends[backend]
if !ok {
return nil, ErrNoAvailImpl
}

return op(name)
}

type Item struct {
Key string
Data []byte
Expand All @@ -46,5 +46,5 @@ type Keyring interface {
Keys() ([]string, error)
}

var ErrNoAvailImpl = errors.New("No keyring implementation for your platform available.")
var ErrNoAvailImpl = errors.New("Specified keyring backend not available")
var ErrKeyNotFound = errors.New("The specified item could not be found in the keyring.")
13 changes: 8 additions & 5 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,12 @@ func (w logWriter) Write(b []byte) (int, error) {

func main() {
var (
prompts = prompt.Available()
prompts = prompt.Available()
backends = keyring.SupportedBackends()

debug = kingpin.Flag("debug", "Show debugging output").Bool()
promptDriver = kingpin.Flag("prompt", fmt.Sprintf("Prompt driver to use %v", prompts)).Default("terminal").OverrideDefaultFromEnvar("AWS_VAULT_PROMPT").Enum(prompts...)
backend = kingpin.Flag("backend", fmt.Sprintf("Secret backend to use %v", backends)).Default(keyring.DefaultBackend).OverrideDefaultFromEnvar("AWS_VAULT_BACKEND").Enum(backends...)
promptDriver = kingpin.Flag("prompt", fmt.Sprintf("Prompt driver to use %v", prompts)).Default("terminal").OverrideDefaultFromEnvar("AWS_VAULT_PROMPT").Enum(prompts...)
add = kingpin.Command("add", "Adds credentials, prompts if none provided")
addProfile = add.Arg("profile", "Name of the profile").Required().String()
addFromEnv = add.Flag("env", "Read the credentials from the environment").Bool()
Expand Down Expand Up @@ -69,13 +72,13 @@ func main() {
Exit: os.Exit,
}

keyring, err := keyring.Open("aws-vault")
cmd := kingpin.Parse()

keyring, err := keyring.Open("aws-vault", *backend)
if err != nil {
ui.Error.Fatal(err)
}

cmd := kingpin.Parse()

if *debug {
ui.Debug = log.New(os.Stderr, "DEBUG ", log.LstdFlags)
log.SetFlags(0)
Expand Down

0 comments on commit 0a93138

Please sign in to comment.