package secret import ( "fmt" "strings" vault "github.com/hashicorp/vault/api" ) type vaultProvider struct{} // Vault provider uses Hashicorp Vault for secrets storage. // // The secretKey is used to func Vault() Provider { return vaultProvider{} } func (p vaultProvider) GetSecret(key string) (value []byte, err error) { var ( config = vault.DefaultConfig() client *vault.Client ) if client, err = vault.NewClient(config); err != nil { return } var mounts map[string]*vault.MountOutput if mounts, err = client.Sys().ListMounts(); err != nil { return } var ( mount *vault.MountOutput location string ) if mount, location, err = p.split(key, mounts); err != nil { return } switch mount.Type { case "generic", "kv": // Supported default: return nil, fmt.Errorf("secret: mount type %q is not supported", mount.Type) } var secret *vault.Secret if secret, err = client.Logical().Read(location); err != nil { return } else if secret == nil || len(secret.Data) == 0 { return nil, NotFound{Key: key} } data, ok := secret.Data["data"].(map[string]interface{}) if !ok { return nil, NotFound{Key: key} } if len(data) == 1 { for _, item := range data { switch data := item.(type) { case []byte: return data, nil case string: return []byte(data), nil default: return nil, fmt.Errorf("secret: unexpected return type %T", data) } } } return nil, AmbiguousKey{Key: key} } func (p vaultProvider) split(key string, mounts map[string]*vault.MountOutput) (mount *vault.MountOutput, location string, err error) { location = strings.TrimPrefix(key, "/") var tree string for tree, mount = range mounts { if strings.HasPrefix(location, tree) { if mount.Type == "kv" && mount.Options["version"] == "2" { location = tree + "data/" + location[len(tree):] } return } } return nil, "", fmt.Errorf("secret: no Vault mount found for secret path %q", key) }