diff options
Diffstat (limited to 'server')
-rw-r--r-- | server/config/config.go | 70 | ||||
-rw-r--r-- | server/helpers/vault/vault.go | 55 | ||||
-rw-r--r-- | server/store/store.go | 4 | ||||
-rw-r--r-- | server/util/util.go (renamed from server/certutil/util.go) | 2 | ||||
-rw-r--r-- | server/util/util_test.go (renamed from server/certutil/util_test.go) | 2 | ||||
-rw-r--r-- | server/wkfs/s3fs/s3.go (renamed from server/fs/s3.go) | 2 | ||||
-rw-r--r-- | server/wkfs/vaultfs/vault.go | 91 |
7 files changed, 218 insertions, 8 deletions
diff --git a/server/config/config.go b/server/config/config.go index 3587e9f..9678f6d 100644 --- a/server/config/config.go +++ b/server/config/config.go @@ -7,6 +7,7 @@ import ( "strconv" "github.com/hashicorp/go-multierror" + "github.com/nsheridan/cashier/server/helpers/vault" "github.com/spf13/viper" ) @@ -16,6 +17,7 @@ type Config struct { Auth *Auth `mapstructure:"auth"` SSH *SSH `mapstructure:"ssh"` AWS *AWS `mapstructure:"aws"` + Vault *Vault `mapstructure:"vault"` } // unmarshalled holds the raw config. @@ -24,6 +26,7 @@ type unmarshalled struct { Auth []Auth `mapstructure:"auth"` SSH []SSH `mapstructure:"ssh"` AWS []AWS `mapstructure:"aws"` + Vault []Vault `mapstructure:"vault"` } // Server holds the configuration specific to the web server and sessions. @@ -66,21 +69,31 @@ type AWS struct { SecretKey string `mapstructure:"secret_key"` } +// Vault holds Hashicorp Vault configuration. +type Vault struct { + Address string `mapstructure:"address"` + Token string `mapstructure:"token"` +} + func verifyConfig(u *unmarshalled) error { var err error if len(u.SSH) == 0 { - err = multierror.Append(errors.New("missing ssh config block")) + err = multierror.Append(errors.New("missing ssh config section")) } if len(u.Auth) == 0 { - err = multierror.Append(errors.New("missing auth config block")) + err = multierror.Append(errors.New("missing auth config section")) } if len(u.Server) == 0 { - err = multierror.Append(errors.New("missing server config block")) + err = multierror.Append(errors.New("missing server config section")) } if len(u.AWS) == 0 { // AWS config is optional u.AWS = append(u.AWS, AWS{}) } + if len(u.Vault) == 0 { + // Vault config is optional + u.Vault = append(u.Vault, Vault{}) + } return err } @@ -106,6 +119,53 @@ func setFromEnv(u *unmarshalled) { } } +func setFromVault(u *unmarshalled) error { + if len(u.Vault) == 0 || u.Vault[0].Token == "" || u.Vault[0].Address == "" { + return nil + } + v, err := vault.NewClient(u.Vault[0].Address, u.Vault[0].Token) + if err != nil { + return err + } + get := func(value string) (string, error) { + if value[:7] == "/vault/" { + return v.Read(value) + } + return value, nil + } + if len(u.Auth) > 0 { + u.Auth[0].OauthClientID, err = get(u.Auth[0].OauthClientID) + if err != nil { + err = multierror.Append(err) + } + u.Auth[0].OauthClientSecret, err = get(u.Auth[0].OauthClientSecret) + if err != nil { + err = multierror.Append(err) + } + } + if len(u.Server) > 0 { + u.Server[0].CSRFSecret, err = get(u.Server[0].CSRFSecret) + if err != nil { + err = multierror.Append(err) + } + u.Server[0].CookieSecret, err = get(u.Server[0].CookieSecret) + if err != nil { + err = multierror.Append(err) + } + } + if len(u.AWS) > 0 { + u.AWS[0].AccessKey, err = get(u.AWS[0].AccessKey) + if err != nil { + err = multierror.Append(err) + } + u.AWS[0].SecretKey, err = get(u.AWS[0].SecretKey) + if err != nil { + err = multierror.Append(err) + } + } + return err +} + // ReadConfig parses a JSON configuration file into a Config struct. func ReadConfig(r io.Reader) (*Config, error) { u := &unmarshalled{} @@ -118,6 +178,9 @@ func ReadConfig(r io.Reader) (*Config, error) { return nil, err } setFromEnv(u) + if err := setFromVault(u); err != nil { + return nil, err + } if err := verifyConfig(u); err != nil { return nil, err } @@ -126,5 +189,6 @@ func ReadConfig(r io.Reader) (*Config, error) { Auth: &u.Auth[0], SSH: &u.SSH[0], AWS: &u.AWS[0], + Vault: &u.Vault[0], }, nil } diff --git a/server/helpers/vault/vault.go b/server/helpers/vault/vault.go new file mode 100644 index 0000000..bec18b9 --- /dev/null +++ b/server/helpers/vault/vault.go @@ -0,0 +1,55 @@ +package vault + +import ( + "fmt" + "strings" + + "github.com/hashicorp/vault/api" +) + +// NewClient returns a new vault client. +func NewClient(address, token string) (*Client, error) { + config := &api.Config{ + Address: address, + } + client, err := api.NewClient(config) + if err != nil { + return nil, err + } + client.SetToken(token) + return &Client{ + vault: client, + }, nil +} + +func parseName(name string) (path, key string) { + name = strings.TrimPrefix(name, "/vault/") + i := strings.LastIndex(name, "/") + if i < 0 { + return name, "" + } + return name[:i], name[i+1:] +} + +// Client is a simple client for vault. +type Client struct { + vault *api.Client +} + +// Read returns a secret for a given path and key of the form `/vault/secret/path/key`. +// If the requested key cannot be read the original string is returned along with an error. +func (c *Client) Read(value string) (string, error) { + p, k := parseName(value) + data, err := c.vault.Logical().Read(p) + if err != nil { + return value, err + } + if data == nil { + return value, fmt.Errorf("no such key %s", k) + } + secret, ok := data.Data[k] + if !ok { + return value, fmt.Errorf("no such key %s", k) + } + return secret.(string), nil +} diff --git a/server/store/store.go b/server/store/store.go index a846bda..c039d3c 100644 --- a/server/store/store.go +++ b/server/store/store.go @@ -5,7 +5,7 @@ import ( "golang.org/x/crypto/ssh" - "github.com/nsheridan/cashier/server/certutil" + "github.com/nsheridan/cashier/server/util" ) // CertStorer records issued certs in a persistent store for audit and @@ -40,6 +40,6 @@ func parseCertificate(cert *ssh.Certificate) *CertRecord { Principals: cert.ValidPrincipals, CreatedAt: parseTime(cert.ValidAfter), Expires: parseTime(cert.ValidBefore), - Raw: certutil.GetPublicKey(cert), + Raw: util.GetPublicKey(cert), } } diff --git a/server/certutil/util.go b/server/util/util.go index eb1900b..10f5eca 100644 --- a/server/certutil/util.go +++ b/server/util/util.go @@ -1,4 +1,4 @@ -package certutil +package util import "golang.org/x/crypto/ssh" diff --git a/server/certutil/util_test.go b/server/util/util_test.go index df42b90..d294d86 100644 --- a/server/certutil/util_test.go +++ b/server/util/util_test.go @@ -1,4 +1,4 @@ -package certutil +package util import ( "testing" diff --git a/server/fs/s3.go b/server/wkfs/s3fs/s3.go index e16e7d6..a71d874 100644 --- a/server/fs/s3.go +++ b/server/wkfs/s3fs/s3.go @@ -1,4 +1,4 @@ -package fs +package s3fs import ( "bytes" diff --git a/server/wkfs/vaultfs/vault.go b/server/wkfs/vaultfs/vault.go new file mode 100644 index 0000000..6f11057 --- /dev/null +++ b/server/wkfs/vaultfs/vault.go @@ -0,0 +1,91 @@ +package vaultfs + +import ( + "bytes" + "errors" + "os" + "path" + "time" + + "github.com/nsheridan/cashier/server/config" + "github.com/nsheridan/cashier/server/helpers/vault" + "go4.org/wkfs" +) + +// Register the /vault/ filesystem as a well-known filesystem. +func Register(vc *config.Vault) { + client, err := vault.NewClient(vc.Address, vc.Token) + if err != nil { + registerBrokenFS(err) + return + } + wkfs.RegisterFS("/vault/", &vaultFS{ + client: client, + }) +} + +func registerBrokenFS(err error) { + wkfs.RegisterFS("/vault/", &vaultFS{ + err: err, + }) +} + +type vaultFS struct { + err error + client *vault.Client +} + +// Open opens the named file for reading. +func (fs *vaultFS) Open(name string) (wkfs.File, error) { + secret, err := fs.client.Read(name) + if err != nil { + return nil, err + } + return &file{ + name: name, + Reader: bytes.NewReader([]byte(secret)), + }, nil +} + +func (fs *vaultFS) Stat(name string) (os.FileInfo, error) { return fs.Lstat(name) } +func (fs *vaultFS) Lstat(name string) (os.FileInfo, error) { + secret, err := fs.client.Read(name) + if err != nil { + return nil, err + } + return &statInfo{ + name: path.Base(name), + size: int64(len(secret)), + }, nil +} + +func (fs *vaultFS) MkdirAll(path string, perm os.FileMode) error { return nil } + +func (fs *vaultFS) OpenFile(name string, flag int, perm os.FileMode) (wkfs.FileWriter, error) { + return nil, errors.New("not implemented") +} + +type statInfo struct { + name string + size int64 + isDir bool + modtime time.Time +} + +func (si *statInfo) IsDir() bool { return si.isDir } +func (si *statInfo) ModTime() time.Time { return si.modtime } +func (si *statInfo) Mode() os.FileMode { return 0644 } +func (si *statInfo) Name() string { return path.Base(si.name) } +func (si *statInfo) Size() int64 { return si.size } +func (si *statInfo) Sys() interface{} { return nil } + +type file struct { + name string + *bytes.Reader +} + +func (*file) Close() error { return nil } +func (f *file) Name() string { return path.Base(f.name) } +func (f *file) Stat() (os.FileInfo, error) { + return nil, errors.New("Stat not implemented on /vault/ files") +} |