vault: First pass at mount/unmount
This commit is contained in:
parent
59052069bc
commit
0ca093fb2d
|
@ -14,6 +14,9 @@ var (
|
|||
// ErrUnsupportedPath is returned if the path is not supported
|
||||
// by the logical backend.
|
||||
ErrUnsupportedPath = errors.New("unsupported path")
|
||||
|
||||
// ErrInvalidRequest is returned if the request is invalid
|
||||
ErrInvalidRequest = errors.New("invalid request")
|
||||
)
|
||||
|
||||
// LogicalBackend interface must be implemented to be "mountable" at
|
||||
|
@ -70,6 +73,21 @@ type Request struct {
|
|||
View *BarrierView
|
||||
}
|
||||
|
||||
// Get returns a data field and guards for nil Data
|
||||
func (r *Request) Get(key string) interface{} {
|
||||
if r.Data == nil {
|
||||
return nil
|
||||
}
|
||||
return r.Data[key]
|
||||
}
|
||||
|
||||
// GetString returns a data field as a string
|
||||
func (r *Request) GetString(key string) string {
|
||||
raw := r.Get(key)
|
||||
s, _ := raw.(string)
|
||||
return s
|
||||
}
|
||||
|
||||
// Response is a struct that stores the response of a request.
|
||||
// It is used to abstract the details of the higher level request protocol.
|
||||
type Response struct {
|
||||
|
@ -122,3 +140,13 @@ func HelpResponse(text string, seeAlso []string) *Response {
|
|||
},
|
||||
}
|
||||
}
|
||||
|
||||
// ErrorResponse is used to format an error response
|
||||
func ErrorResponse(text string) *Response {
|
||||
return &Response{
|
||||
IsSecret: false,
|
||||
Data: map[string]interface{}{
|
||||
"error": text,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
113
vault/mount.go
113
vault/mount.go
|
@ -3,6 +3,8 @@ package vault
|
|||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -26,6 +28,17 @@ 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
|
||||
|
@ -34,6 +47,16 @@ type MountEntry struct {
|
|||
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,
|
||||
}
|
||||
}
|
||||
|
||||
// loadMounts is invoked as part of postUnseal to load the mount table
|
||||
func (c *Core) loadMounts() error {
|
||||
// Load the existing mount table
|
||||
|
@ -57,16 +80,16 @@ func (c *Core) loadMounts() error {
|
|||
|
||||
// Create and persist the default mount table
|
||||
c.mounts = defaultMountTable()
|
||||
if err := c.persistMounts(); err != nil {
|
||||
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() error {
|
||||
func (c *Core) persistMounts(table *MountTable) error {
|
||||
// Marshal the table
|
||||
raw, err := json.Marshal(c.mounts)
|
||||
raw, err := json.Marshal(table)
|
||||
if err != nil {
|
||||
c.logger.Printf("[ERR] core: failed to encode mount table: %v", err)
|
||||
return err
|
||||
|
@ -115,6 +138,90 @@ func (c *Core) setupMounts() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// mountEntry is used to create a new mount entry
|
||||
func (c *Core) mountEntry(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
|
||||
}
|
||||
|
||||
// unmountPath is used to unmount a path
|
||||
func (c *Core) unmountPath(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]
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// defaultMountTable creates a default mount table
|
||||
func defaultMountTable() *MountTable {
|
||||
table := &MountTable{}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package vault
|
||||
|
||||
import "strings"
|
||||
|
||||
// SystemBackend implements the LogicalBackend interface but is used
|
||||
// to interact with the core of the system. It acts like a "procfs"
|
||||
// to provide a uniform interface to vault.
|
||||
|
@ -12,13 +14,17 @@ func (s *SystemBackend) HandleRequest(req *Request) (*Response, error) {
|
|||
switch {
|
||||
case req.Path == "mounts":
|
||||
return s.handleMountTable(req)
|
||||
case strings.HasPrefix(req.Path, "mount/"):
|
||||
return s.handleMountOperation(req)
|
||||
default:
|
||||
return nil, ErrUnsupportedPath
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SystemBackend) RootPaths() []string {
|
||||
return []string{}
|
||||
return []string{
|
||||
"mount/*",
|
||||
}
|
||||
}
|
||||
|
||||
// handleMountTable handles the "mounts" endpoint to provide the mount table
|
||||
|
@ -26,7 +32,7 @@ func (s *SystemBackend) handleMountTable(req *Request) (*Response, error) {
|
|||
switch req.Operation {
|
||||
case ReadOperation:
|
||||
case HelpOperation:
|
||||
return HelpResponse("logical backend mount table", nil), nil
|
||||
return HelpResponse("logical backend mount table", []string{"sys/mount/"}), nil
|
||||
default:
|
||||
return nil, ErrUnsupportedOperation
|
||||
}
|
||||
|
@ -47,3 +53,59 @@ func (s *SystemBackend) handleMountTable(req *Request) (*Response, error) {
|
|||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// handleMountOperation is used to mount or unmount a path
|
||||
func (s *SystemBackend) handleMountOperation(req *Request) (*Response, error) {
|
||||
switch req.Operation {
|
||||
case WriteOperation:
|
||||
return s.handleMount(req)
|
||||
case DeleteOperation:
|
||||
return s.handleUnmount(req)
|
||||
case HelpOperation:
|
||||
return HelpResponse("used to mount or unmount a path", []string{"sys/mounts"}), nil
|
||||
default:
|
||||
return nil, ErrUnsupportedOperation
|
||||
}
|
||||
}
|
||||
|
||||
// handleMount is used to mount a new path
|
||||
func (s *SystemBackend) handleMount(req *Request) (*Response, error) {
|
||||
suffix := strings.TrimPrefix(req.Path, "mount/")
|
||||
if len(suffix) == 0 {
|
||||
return ErrorResponse("path cannot be blank"), ErrInvalidRequest
|
||||
}
|
||||
|
||||
// Get the type and description (optionally)
|
||||
logicalType := req.GetString("type")
|
||||
if logicalType == "" {
|
||||
return ErrorResponse("backend type must be specified as a string"), ErrInvalidRequest
|
||||
}
|
||||
description := req.GetString("description")
|
||||
|
||||
// Create the mount entry
|
||||
me := &MountEntry{
|
||||
Path: suffix,
|
||||
Type: logicalType,
|
||||
Description: description,
|
||||
}
|
||||
|
||||
// Attempt mount
|
||||
if err := s.core.mountEntry(me); err != nil {
|
||||
return ErrorResponse(err.Error()), ErrInvalidRequest
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// handleUnmount is used to unmount a path
|
||||
func (s *SystemBackend) handleUnmount(req *Request) (*Response, error) {
|
||||
suffix := strings.TrimPrefix(req.Path, "mount/")
|
||||
if len(suffix) == 0 {
|
||||
return ErrorResponse("path cannot be blank"), ErrInvalidRequest
|
||||
}
|
||||
|
||||
// Attempt unmount
|
||||
if err := s.core.unmountPath(suffix); err != nil {
|
||||
return ErrorResponse(err.Error()), ErrInvalidRequest
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue