vault: convert system to logical.Backend

This commit is contained in:
Mitchell Hashimoto 2015-03-15 14:42:05 -07:00
parent c3ae1b59a1
commit 5ffcd02b7a
2 changed files with 295 additions and 0 deletions

142
vault/logical_system.go Normal file
View File

@ -0,0 +1,142 @@
package vault
import (
"strings"
"github.com/hashicorp/vault/logical"
)
// 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.
type SystemBackend2 struct {
Core *Core
}
func (b *SystemBackend2) 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, ErrUnsupportedPath
}
}
func (b *SystemBackend2) RootPaths() []string {
return []string{
"mount/*",
"remount",
}
}
// handleMountTable handles the "mounts" endpoint to provide the mount table
func (b *SystemBackend2) handleMountTable(req *logical.Request) (*logical.Response, error) {
switch req.Operation {
case logical.ReadOperation:
default:
return nil, ErrUnsupportedOperation
}
b.Core.mountsLock.RLock()
defer b.Core.mountsLock.RUnlock()
resp := &logical.Response{
IsSecret: false,
Data: make(map[string]interface{}),
}
for _, entry := range b.Core.mounts.Entries {
info := map[string]string{
"type": entry.Type,
"description": entry.Description,
}
resp.Data[entry.Path] = info
}
return resp, nil
}
// handleMountOperation is used to mount or unmount a path
func (b *SystemBackend2) 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, ErrUnsupportedOperation
}
}
// handleMount is used to mount a new path
func (b *SystemBackend2) handleMount(req *logical.Request) (*logical.Response, error) {
suffix := strings.TrimPrefix(req.Path, "mount/")
if len(suffix) == 0 {
return logical.ErrorResponse("path cannot be blank"), ErrInvalidRequest
}
// Get the type and description (optionally)
logicalType := req.GetString("type")
if logicalType == "" {
return logical.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 := b.Core.mount(me); err != nil {
return logical.ErrorResponse(err.Error()), ErrInvalidRequest
}
return nil, nil
}
// handleUnmount is used to unmount a path
func (b *SystemBackend2) handleUnmount(req *logical.Request) (*logical.Response, error) {
suffix := strings.TrimPrefix(req.Path, "mount/")
if len(suffix) == 0 {
return logical.ErrorResponse("path cannot be blank"), ErrInvalidRequest
}
// Attempt unmount
if err := b.Core.unmount(suffix); err != nil {
return logical.ErrorResponse(err.Error()), ErrInvalidRequest
}
return nil, nil
}
// handleRemount is used to remount a path
func (b *SystemBackend2) handleRemount(req *logical.Request) (*logical.Response, error) {
// Only accept write operations
switch req.Operation {
case WriteOperation:
default:
return nil, ErrUnsupportedOperation
}
// Get the paths
fromPath := req.GetString("from")
toPath := req.GetString("to")
if fromPath == "" || toPath == "" {
return logical.ErrorResponse(
"both 'from' and 'to' path must be specified as a string"),
ErrInvalidRequest
}
// Attempt remount
if err := b.Core.remount(fromPath, toPath); err != nil {
return logical.ErrorResponse(err.Error()), ErrInvalidRequest
}
return nil, nil
}

View File

@ -0,0 +1,153 @@
package vault
import (
"reflect"
"testing"
"github.com/hashicorp/vault/logical"
)
func TestSystemBackend_impl(t *testing.T) {
var _ logical.Backend = new(SystemBackend2)
}
func TestSystemBackend_RootPaths(t *testing.T) {
expected := []string{
"mount/*",
"remount",
}
b := testSystemBackend(t)
actual := b.RootPaths()
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("bad: %#v", actual)
}
}
func TestSystemBackend_mounts(t *testing.T) {
b := testSystemBackend(t)
req := logical.TestRequest(t, logical.ReadOperation, "mounts")
resp, err := b.HandleRequest(req)
if err != nil {
t.Fatalf("err: %v", err)
}
exp := map[string]interface{}{
"secret/": map[string]string{
"type": "generic",
"description": "generic secret storage",
},
"sys/": map[string]string{
"type": "system",
"description": "system endpoints used for control, policy and debugging",
},
}
if !reflect.DeepEqual(resp.Data, exp) {
t.Fatalf("got: %#v expect: %#v", resp.Data, exp)
}
}
func TestSystemBackend_mount(t *testing.T) {
b := testSystemBackend(t)
req := logical.TestRequest(t, logical.WriteOperation, "mount/prod/secret/")
req.Data["type"] = "generic"
resp, err := b.HandleRequest(req)
if err != nil {
t.Fatalf("err: %v", err)
}
if resp != nil {
t.Fatalf("bad: %v", resp)
}
}
func TestSystemBackend_mount_invalid(t *testing.T) {
b := testSystemBackend(t)
req := logical.TestRequest(t, logical.WriteOperation, "mount/prod/secret/")
req.Data["type"] = "nope"
resp, err := b.HandleRequest(req)
if err != ErrInvalidRequest {
t.Fatalf("err: %v", err)
}
if resp.Data["error"] != "unknown logical backend type: nope" {
t.Fatalf("bad: %v", resp)
}
}
func TestSystemBackend_unmount(t *testing.T) {
b := testSystemBackend(t)
req := logical.TestRequest(t, logical.DeleteOperation, "mount/secret/")
resp, err := b.HandleRequest(req)
if err != nil {
t.Fatalf("err: %v", err)
}
if resp != nil {
t.Fatalf("bad: %v", resp)
}
}
func TestSystemBackend_unmount_invalid(t *testing.T) {
b := testSystemBackend(t)
req := logical.TestRequest(t, logical.DeleteOperation, "mount/foo/")
resp, err := b.HandleRequest(req)
if err != ErrInvalidRequest {
t.Fatalf("err: %v", err)
}
if resp.Data["error"] != "no matching mount" {
t.Fatalf("bad: %v", resp)
}
}
func TestSystemBackend_remount(t *testing.T) {
b := testSystemBackend(t)
req := logical.TestRequest(t, logical.WriteOperation, "remount")
req.Data["from"] = "secret"
req.Data["to"] = "foo"
resp, err := b.HandleRequest(req)
if err != nil {
t.Fatalf("err: %v", err)
}
if resp != nil {
t.Fatalf("bad: %v", resp)
}
}
func TestSystemBackend_remount_invalid(t *testing.T) {
b := testSystemBackend(t)
req := logical.TestRequest(t, logical.WriteOperation, "remount")
req.Data["from"] = "unknown"
req.Data["to"] = "foo"
resp, err := b.HandleRequest(req)
if err != ErrInvalidRequest {
t.Fatalf("err: %v", err)
}
if resp.Data["error"] != "no matching mount at 'unknown/'" {
t.Fatalf("bad: %v", resp)
}
}
func TestSystemBackend_remount_system(t *testing.T) {
b := testSystemBackend(t)
req := logical.TestRequest(t, logical.WriteOperation, "remount")
req.Data["from"] = "sys"
req.Data["to"] = "foo"
resp, err := b.HandleRequest(req)
if err != ErrInvalidRequest {
t.Fatalf("err: %v", err)
}
if resp.Data["error"] != "cannot remount 'sys/'" {
t.Fatalf("bad: %v", resp)
}
}
func testSystemBackend(t *testing.T) *SystemBackend2 {
c, _ := TestCoreUnsealed(t)
return &SystemBackend2{Core: c}
}