diff --git a/logical/framework/backend.go b/logical/framework/backend.go index 88ffe4853f..7aad4eda2c 100644 --- a/logical/framework/backend.go +++ b/logical/framework/backend.go @@ -24,6 +24,12 @@ type Backend struct { // backend is in use). Paths []*Path + // PathsRoot is the list of path patterns that denote the + // paths above that require root-level privileges. These can't be + // regular expressions, it is either exact match or prefix match. + // For prefix match, append '*' as a suffix. + PathsRoot []string + once sync.Once pathsRe []*regexp.Regexp } @@ -46,12 +52,6 @@ type Path struct { // whereas all fields are avaiable in the Write operation. Fields map[string]*FieldSchema - // Root if not blank, denotes that this path requires root - // privileges and the path pattern that is the root path. This can't - // be a regular expression and must be an exact path. It may have a - // trailing '*' to denote that it is a prefix, and not an exact match. - Root string - // Callbacks are the set of callbacks that are called for a given // operation. If a callback for a specific operation is not present, // then logical.ErrUnsupportedOperation is automatically generated. @@ -123,8 +123,7 @@ func (b *Backend) HandleRequest(req *logical.Request) (*logical.Response, error) // logical.Backend impl. func (b *Backend) RootPaths() []string { - // TODO - return nil + return b.PathsRoot } // Route looks up the path that would be used for a given path string. diff --git a/vault/core.go b/vault/core.go index ebcabc9e6e..778746c7c5 100644 --- a/vault/core.go +++ b/vault/core.go @@ -158,7 +158,7 @@ func NewCore(conf *CoreConfig) (*Core, error) { } backends["generic"] = PassthroughBackendFactory backends["system"] = func(map[string]string) (logical.Backend, error) { - return &SystemBackend{Core: c}, nil + return NewSystemBackend(c), nil } c.backends = backends diff --git a/vault/logical_system.go b/vault/logical_system.go index 937e17e9f3..60e3186edb 100644 --- a/vault/logical_system.go +++ b/vault/logical_system.go @@ -4,8 +4,72 @@ import ( "strings" "github.com/hashicorp/vault/logical" + "github.com/hashicorp/vault/logical/framework" ) +func NewSystemBackend(core *Core) logical.Backend { + b := &SystemBackend{Core: core} + + return &framework.Backend{ + PathsRoot: []string{ + "mount/*", + "remount", + }, + + Paths: []*framework.Path{ + &framework.Path{ + Pattern: "mounts", + + Callbacks: map[logical.Operation]framework.OperationFunc{ + logical.ReadOperation: b.handleMountTable, + }, + + HelpSynopsis: strings.TrimSpace(sysHelp["mounts"][0]), + HelpDescription: strings.TrimSpace(sysHelp["mounts"][1]), + }, + + &framework.Path{ + Pattern: "mount/(?P.+?)", + + Fields: map[string]*framework.FieldSchema{ + "path": &framework.FieldSchema{ + Type: framework.TypeString, + Description: strings.TrimSpace(sysHelp["mount_path"][0]), + }, + + "type": &framework.FieldSchema{ + Type: framework.TypeString, + Description: strings.TrimSpace(sysHelp["mount_type"][0]), + }, + "description": &framework.FieldSchema{ + Type: framework.TypeString, + Description: strings.TrimSpace(sysHelp["mount_desc"][0]), + }, + }, + + Callbacks: map[logical.Operation]framework.OperationFunc{ + logical.WriteOperation: b.handleMount, + logical.DeleteOperation: b.handleUnmount, + }, + + HelpSynopsis: strings.TrimSpace(sysHelp["mount"][0]), + HelpDescription: strings.TrimSpace(sysHelp["mount"][1]), + }, + + &framework.Path{ + Pattern: "remount", + + Callbacks: map[logical.Operation]framework.OperationFunc{ + logical.WriteOperation: b.handleRemount, + }, + + HelpSynopsis: strings.TrimSpace(sysHelp["remount"][0]), + HelpDescription: strings.TrimSpace(sysHelp["remount"][1]), + }, + }, + } +} + // SystemBackend implements logical.Backend and is used to interact with // the core of the system. This backend is hardcoded to exist at the "sys" // prefix. Conceptually it is similar to procfs on Linux. @@ -13,35 +77,9 @@ type SystemBackend struct { Core *Core } -func (b *SystemBackend) HandleRequest(req *logical.Request) (*logical.Response, error) { - // Switch on the path to route to the appropriate handler - switch { - case req.Path == "mounts": - return b.handleMountTable(req) - case strings.HasPrefix(req.Path, "mount/"): - return b.handleMountOperation(req) - case req.Path == "remount": - return b.handleRemount(req) - default: - return nil, logical.ErrUnsupportedPath - } -} - -func (b *SystemBackend) RootPaths() []string { - return []string{ - "mount/*", - "remount", - } -} - // handleMountTable handles the "mounts" endpoint to provide the mount table -func (b *SystemBackend) handleMountTable(req *logical.Request) (*logical.Response, error) { - switch req.Operation { - case logical.ReadOperation: - default: - return nil, logical.ErrUnsupportedOperation - } - +func (b *SystemBackend) handleMountTable( + req *logical.Request, data *framework.FieldData) (*logical.Response, error) { b.Core.mountsLock.RLock() defer b.Core.mountsLock.RUnlock() @@ -60,35 +98,23 @@ func (b *SystemBackend) handleMountTable(req *logical.Request) (*logical.Respons return resp, nil } -// handleMountOperation is used to mount or unmount a path -func (b *SystemBackend) handleMountOperation(req *logical.Request) (*logical.Response, error) { - switch req.Operation { - case logical.WriteOperation: - return b.handleMount(req) - case logical.DeleteOperation: - return b.handleUnmount(req) - default: - return nil, logical.ErrUnsupportedOperation - } -} - // handleMount is used to mount a new path -func (b *SystemBackend) handleMount(req *logical.Request) (*logical.Response, error) { - suffix := strings.TrimPrefix(req.Path, "mount/") - if len(suffix) == 0 { - return logical.ErrorResponse("path cannot be blank"), logical.ErrInvalidRequest - } +func (b *SystemBackend) handleMount( + req *logical.Request, data *framework.FieldData) (*logical.Response, error) { + // Get all the options + path := data.Get("path").(string) + logicalType := data.Get("type").(string) + description := data.Get("description").(string) - // Get the type and description (optionally) - logicalType := req.GetString("type") if logicalType == "" { - return logical.ErrorResponse("backend type must be specified as a string"), logical.ErrInvalidRequest + return logical.ErrorResponse( + "backend type must be specified as a string"), + logical.ErrInvalidRequest } - description := req.GetString("description") // Create the mount entry me := &MountEntry{ - Path: suffix, + Path: path, Type: logicalType, Description: description, } @@ -101,7 +127,8 @@ func (b *SystemBackend) handleMount(req *logical.Request) (*logical.Response, er } // handleUnmount is used to unmount a path -func (b *SystemBackend) handleUnmount(req *logical.Request) (*logical.Response, error) { +func (b *SystemBackend) handleUnmount( + req *logical.Request, data *framework.FieldData) (*logical.Response, error) { suffix := strings.TrimPrefix(req.Path, "mount/") if len(suffix) == 0 { return logical.ErrorResponse("path cannot be blank"), logical.ErrInvalidRequest @@ -116,7 +143,8 @@ func (b *SystemBackend) handleUnmount(req *logical.Request) (*logical.Response, } // handleRemount is used to remount a path -func (b *SystemBackend) handleRemount(req *logical.Request) (*logical.Response, error) { +func (b *SystemBackend) handleRemount( + req *logical.Request, data *framework.FieldData) (*logical.Response, error) { // Only accept write operations switch req.Operation { case logical.WriteOperation: @@ -140,3 +168,46 @@ func (b *SystemBackend) handleRemount(req *logical.Request) (*logical.Response, return nil, nil } + +// sysHelp is all the help text for the sys backend. +var sysHelp = map[string][2]string{ + "mounts": { + "List the currently mounted backends.", + ` +List the currently mounted backends: the mount path, the type of the backend, +and a user friendly description of the purpose for the mount. + `, + }, + + "mount": { + `Mount a new backend at a new path.`, + ` +Mount a backend at a new path. A backend can be mounted multiple times at +multiple paths in order to configure multiple separately configured backends. +Example: you might have an AWS backend for the east coast, and one for the +west coast. + `, + }, + + "mount_path": { + `The path to mount to. Example: "aws/east"`, + "", + }, + + "mount_type": { + `The type of the backend. Example: "passthrough"`, + "", + }, + + "mount_desc": { + `User-friendly description for this mount.`, + "", + }, + + "remount": { + "Move the mount point of an already-mounted backend.", + ` +Change the mount point of an already-mounted backend. + `, + }, +} diff --git a/vault/logical_system_test.go b/vault/logical_system_test.go index 28221057ff..a76b84c484 100644 --- a/vault/logical_system_test.go +++ b/vault/logical_system_test.go @@ -7,10 +7,6 @@ import ( "github.com/hashicorp/vault/logical" ) -func TestSystemBackend_impl(t *testing.T) { - var _ logical.Backend = new(SystemBackend) -} - func TestSystemBackend_RootPaths(t *testing.T) { expected := []string{ "mount/*", @@ -147,7 +143,7 @@ func TestSystemBackend_remount_system(t *testing.T) { } } -func testSystemBackend(t *testing.T) *SystemBackend { +func testSystemBackend(t *testing.T) logical.Backend { c, _ := TestCoreUnsealed(t) - return &SystemBackend{Core: c} + return NewSystemBackend(c) }