open-vault/vault/mount.go
Mitchell Hashimoto b2af154fb4 vault: make Mount related core functions public
/cc @armon - So I know the conversation we had related to this about
auth, but I think we still need to export these and do auth only at the
external API layer. If you're writing to the internal API, then all bets
are off.

The reason is simply that if you have access to the code, you can
already work around it anyways (you can disable auth or w/e), so a
compromised Vault source/binary is already a failure, and that is the
only thing that our previous unexported methods were protecting against.

If you write an external tool to access a Vault, it still needs to be
unsealed so _that_ is the primary security mechanism from an API
perspective. Once it is unsealed then the core API has full access to
the Vault, and identity/auth is only done at the external API layer, not
at the internal API layer.

The benefits of this approach is that it lets us still treat the "sys"
mount specially but at least have sys adopt helper/backend and use that
machinery and it can still be the only backend which actually has a
reference to *vault.Core to do core things (a key difference). So, an
AWS backend still will never be able to muck with things it can't, but
we're explicitly giving Sys (via struct initialization in Go itself)
a reference to *vault.Core.
2015-03-14 17:26:59 -07:00

323 lines
8.1 KiB
Go

package vault
import (
"encoding/json"
"errors"
"fmt"
"strings"
)
const (
// coreMountConfigPath is used to store the mount configuration.
// Mounts are protected within the Vault itself, which means they
// can only be viewed or modified after an unseal.
coreMountConfigPath = "core/mounts"
// backendBarrierPrefix is the prefix to the UUID used in the
// barrier view for the backends.
backendBarrierPrefix = "logical/"
// systemBarrierPrefix is sthe prefix used for the
// system logical backend.
systemBarrierPrefix = "sys/"
)
var (
// loadMountsFailed if loadMounts encounters an error
loadMountsFailed = errors.New("failed to setup mount table")
// protectedMounts cannot be remounted
protectedMounts = []string{
"sys/",
}
)
// MountTable is used to represent the internal mount table
type MountTable struct {
Entries []*MountEntry `json:"entries"`
}
// Returns a deep copy of the mount table
func (t *MountTable) Clone() *MountTable {
mt := &MountTable{
Entries: make([]*MountEntry, len(t.Entries)),
}
for i, e := range t.Entries {
mt.Entries[i] = e.Clone()
}
return mt
}
// MountEntry is used to represent a mount table entry
type MountEntry struct {
Path string `json:"path"` // Mount Path
Type string `json:"type"` // Logical backend Type
Description string `json:"description"` // User-provided description
UUID string `json:"uuid"` // Barrier view UUID
}
// Returns a deep copy of the mount entry
func (e *MountEntry) Clone() *MountEntry {
return &MountEntry{
Path: e.Path,
Type: e.Type,
Description: e.Description,
UUID: e.UUID,
}
}
// Mount is used to mount a new backend to the mount table.
func (c *Core) Mount(me *MountEntry) error {
c.mountsLock.Lock()
defer c.mountsLock.Unlock()
// Ensure we end the path in a slash
if !strings.HasSuffix(me.Path, "/") {
me.Path += "/"
}
// Verify there is no conflicting mount
if match := c.router.MatchingMount(me.Path); match != "" {
return fmt.Errorf("existing mount at '%s'", match)
}
// Lookup the new backend
backend, err := NewBackend(me.Type, nil)
if err != nil {
return err
}
// Generate a new UUID and view
me.UUID = generateUUID()
view := NewBarrierView(c.barrier, backendBarrierPrefix+me.UUID+"/")
// Update the mount table
newTable := c.mounts.Clone()
newTable.Entries = append(newTable.Entries, me)
if err := c.persistMounts(newTable); err != nil {
return errors.New("failed to update mount table")
}
c.mounts = newTable
// Mount the backend
if err := c.router.Mount(backend, me.Type, me.Path, view); err != nil {
return err
}
c.logger.Printf("[INFO] core: mounted '%s' type: %s", me.Path, me.Type)
return nil
}
// Unmount is used to unmount a path.
//
// TODO: document what happens to all secrets currently out for this path.
func (c *Core) Unmount(path string) error {
c.mountsLock.Lock()
defer c.mountsLock.Unlock()
// Ensure we end the path in a slash
if !strings.HasSuffix(path, "/") {
path += "/"
}
// Verify exact match of the route
match := c.router.MatchingMount(path)
if match == "" || path != match {
return fmt.Errorf("no matching mount")
}
// Remove the entry from the mount table
newTable := c.mounts.Clone()
n := len(newTable.Entries)
for i := 0; i < n; i++ {
if newTable.Entries[i].Path == path {
newTable.Entries[i], newTable.Entries[n-1] = newTable.Entries[n-1], nil
newTable.Entries = newTable.Entries[:n-1]
break
}
}
// Update the mount table
if err := c.persistMounts(newTable); err != nil {
return errors.New("failed to update mount table")
}
c.mounts = newTable
// Unmount the backend
if err := c.router.Unmount(path); err != nil {
return err
}
// TODO: Delete data in view?
// TODO: Handle revocation?
c.logger.Printf("[INFO] core: unmounted '%s'", path)
return nil
}
// Remount is used to remount a path at a new mount point.
func (c *Core) Remount(src, dst string) error {
c.mountsLock.Lock()
defer c.mountsLock.Unlock()
// Ensure we end the path in a slash
if !strings.HasSuffix(src, "/") {
src += "/"
}
if !strings.HasSuffix(dst, "/") {
dst += "/"
}
// Prevent sys/ from being remounted
for _, p := range protectedMounts {
if src == p {
return fmt.Errorf("cannot remount '%s'", p)
}
}
// Verify exact match of the route
match := c.router.MatchingMount(src)
if match == "" || src != match {
return fmt.Errorf("no matching mount at '%s'", src)
}
// Verify there is no conflicting mount
if match := c.router.MatchingMount(dst); match != "" {
return fmt.Errorf("existing mount at '%s'", match)
}
// Update the entry in the mount table
newTable := c.mounts.Clone()
for _, ent := range newTable.Entries {
if ent.Path == src {
ent.Path = dst
break
}
}
// Update the mount table
if err := c.persistMounts(newTable); err != nil {
return errors.New("failed to update mount table")
}
c.mounts = newTable
// Remount the backend
if err := c.router.Remount(src, dst); err != nil {
return err
}
c.logger.Printf("[INFO] core: remounted '%s' to '%s'", src, dst)
return nil
}
// loadMounts is invoked as part of postUnseal to load the mount table
func (c *Core) loadMounts() error {
// Load the existing mount table
raw, err := c.barrier.Get(coreMountConfigPath)
if err != nil {
c.logger.Printf("[ERR] core: failed to read mount table: %v", err)
return loadMountsFailed
}
if raw != nil {
c.mounts = &MountTable{}
if err := json.Unmarshal(raw.Value, c.mounts); err != nil {
c.logger.Printf("[ERR] core: failed to decode mount table: %v", err)
return loadMountsFailed
}
}
// Done if we have restored the mount table
if c.mounts != nil {
return nil
}
// Create and persist the default mount table
c.mounts = defaultMountTable()
if err := c.persistMounts(c.mounts); err != nil {
return loadMountsFailed
}
return nil
}
// persistMounts is used to persist the mount table after modification
func (c *Core) persistMounts(table *MountTable) error {
// Marshal the table
raw, err := json.Marshal(table)
if err != nil {
c.logger.Printf("[ERR] core: failed to encode mount table: %v", err)
return err
}
// Create an entry
entry := &Entry{
Key: coreMountConfigPath,
Value: raw,
}
// Write to the physical backend
if err := c.barrier.Put(entry); err != nil {
c.logger.Printf("[ERR] core: failed to persist mount table: %v", err)
return err
}
return nil
}
// setupMounts is invoked after we've loaded the mount table to
// initialize the logical backends and setup the router
func (c *Core) setupMounts() error {
var backend LogicalBackend
var view *BarrierView
var err error
for _, entry := range c.mounts.Entries {
// Initialize the backend, special casing for system
if entry.Type == "system" {
backend = &SystemBackend{core: c}
view = NewBarrierView(c.barrier, systemBarrierPrefix+entry.UUID+"/")
c.systemView = view
} else {
backend, err = NewBackend(entry.Type, nil)
if err != nil {
c.logger.Printf("[ERR] core: failed to create mount entry %#v: %v", entry, err)
return loadMountsFailed
}
// Create a barrier view using the UUID
view = NewBarrierView(c.barrier, backendBarrierPrefix+entry.UUID+"/")
}
// Mount the backend
if err := c.router.Mount(backend, entry.Type, entry.Path, view); err != nil {
c.logger.Printf("[ERR] core: failed to mount entry %#v: %v", entry, err)
return loadMountsFailed
}
}
return nil
}
// unloadMounts is used before we seal the vault to reset the mounts to
// their unloaded state. This is reversed by load and setup mounts.
func (c *Core) unloadMounts() error {
c.mounts = nil
c.router = NewRouter()
c.systemView = nil
return nil
}
// defaultMountTable creates a default mount table
func defaultMountTable() *MountTable {
table := &MountTable{}
genericMount := &MountEntry{
Path: "secret/",
Type: "generic",
Description: "generic secret storage",
UUID: generateUUID(),
}
sysMount := &MountEntry{
Path: "sys/",
Type: "system",
Description: "system endpoints used for control, policy and debugging",
UUID: generateUUID(),
}
table.Entries = append(table.Entries, genericMount)
table.Entries = append(table.Entries, sysMount)
return table
}