logical: Adding generic backend
This commit is contained in:
parent
e997fd31ea
commit
51cc19e92f
|
@ -0,0 +1,157 @@
|
|||
package logical
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/vault/vault"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Register the generic backend
|
||||
vault.BuiltinBackends["generic"] = newGenericBackend
|
||||
}
|
||||
|
||||
// GenericBackend is used for the storing generic secrets. These are not
|
||||
// materialized in any way. The value that is written to this backend
|
||||
// is the same value that is always returned. Leasing can be configured on
|
||||
// a per-key basis.
|
||||
type GenericBackend struct{}
|
||||
|
||||
// newGenericBackend is a factory constructor for the generic backend
|
||||
func newGenericBackend(map[string]string) (vault.LogicalBackend, error) {
|
||||
b := &GenericBackend{}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// HandleRequest is used to handle a request and generate a response.
|
||||
// The backends must check the operation type and handle appropriately.
|
||||
func (g *GenericBackend) HandleRequest(req *vault.Request) (*vault.Response, error) {
|
||||
switch req.Operation {
|
||||
case vault.ReadOperation:
|
||||
return g.handleRead(req)
|
||||
case vault.WriteOperation:
|
||||
return g.handleWrite(req)
|
||||
case vault.ListOperation:
|
||||
return g.handleList(req)
|
||||
case vault.DeleteOperation:
|
||||
return g.handleDelete(req)
|
||||
case vault.HelpOperation:
|
||||
return g.handleHelp(req)
|
||||
default:
|
||||
return nil, vault.ErrUnsupportedOperation
|
||||
}
|
||||
}
|
||||
|
||||
// RootPaths is a list of paths that require root level privileges,
|
||||
// which do not exist for the geneirc backend.
|
||||
func (g *GenericBackend) RootPaths() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *GenericBackend) handleRead(req *vault.Request) (*vault.Response, error) {
|
||||
// Read the path
|
||||
out, err := req.View.Get(req.Path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read failed: %v", err)
|
||||
}
|
||||
|
||||
// Fast-path the no data case
|
||||
if out == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Decode the data
|
||||
var raw map[string]interface{}
|
||||
if err := json.Unmarshal(out.Value, &raw); err != nil {
|
||||
return nil, fmt.Errorf("json decoding failed: %v", err)
|
||||
}
|
||||
|
||||
// Check if there is a lease key
|
||||
leaseVal, ok := raw["lease"].(string)
|
||||
var lease *vault.Lease
|
||||
if ok {
|
||||
leaseDuration, err := time.ParseDuration(leaseVal)
|
||||
if err == nil {
|
||||
lease = &vault.Lease{
|
||||
Renewable: false,
|
||||
Revokable: false,
|
||||
Duration: leaseDuration,
|
||||
MaxDuration: leaseDuration,
|
||||
MaxIncrement: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Generate the response
|
||||
resp := &vault.Response{
|
||||
IsSecret: true,
|
||||
Lease: lease,
|
||||
Data: raw,
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (g *GenericBackend) handleWrite(req *vault.Request) (*vault.Response, error) {
|
||||
// Check that some fields are given
|
||||
if len(req.Data) == 0 {
|
||||
return nil, fmt.Errorf("missing data fields")
|
||||
}
|
||||
|
||||
// JSON encode the data
|
||||
buf, err := json.Marshal(req.Data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("json encoding failed: %v", err)
|
||||
}
|
||||
|
||||
// Write out a new key
|
||||
entry := &vault.Entry{
|
||||
Key: req.Path,
|
||||
Value: buf,
|
||||
}
|
||||
if err := req.View.Put(entry); err != nil {
|
||||
return nil, fmt.Errorf("failed to write: %v", err)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (g *GenericBackend) handleDelete(req *vault.Request) (*vault.Response, error) {
|
||||
// Delete the key at the request path
|
||||
if err := req.View.Delete(req.Path); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (g *GenericBackend) handleList(req *vault.Request) (*vault.Response, error) {
|
||||
// List the keys at the prefix given by the request
|
||||
keys, err := req.View.List(req.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Generate the response
|
||||
resp := &vault.Response{
|
||||
IsSecret: false,
|
||||
Lease: nil,
|
||||
Data: map[string]interface{}{
|
||||
"keys": keys,
|
||||
},
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (g *GenericBackend) handleHelp(req *vault.Request) (*vault.Response, error) {
|
||||
resp := &vault.Response{
|
||||
IsSecret: false,
|
||||
Lease: nil,
|
||||
Data: map[string]interface{}{
|
||||
"help": genericHelpText,
|
||||
},
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// genericHelpText is the help information we return
|
||||
const genericHelpText = "Generic backend for storing and retreiving raw keys with user-defined fields"
|
|
@ -0,0 +1,223 @@
|
|||
package logical
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/vault/physical"
|
||||
"github.com/hashicorp/vault/vault"
|
||||
)
|
||||
|
||||
// mockRequest returns a request with a real view attached
|
||||
func mockRequest(t *testing.T, op vault.Operation, path string) *vault.Request {
|
||||
inm := physical.NewInmem()
|
||||
b, err := vault.NewAESGCMBarrier(inm)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
// Initialize and unseal
|
||||
key, _ := b.GenerateKey()
|
||||
b.Initialize(key)
|
||||
b.Unseal(key)
|
||||
|
||||
// Create the barrier view
|
||||
view := vault.NewBarrierView(b, "logical/")
|
||||
|
||||
// Create the request
|
||||
req := &vault.Request{
|
||||
Operation: op,
|
||||
Path: path,
|
||||
Data: make(map[string]interface{}),
|
||||
View: view,
|
||||
}
|
||||
return req
|
||||
}
|
||||
|
||||
func TestGenericBackend_RootPaths(t *testing.T) {
|
||||
b, err := newGenericBackend(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
root := b.RootPaths()
|
||||
if len(root) != 0 {
|
||||
t.Fatalf("unexpected: %v", root)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenericBackend_Write(t *testing.T) {
|
||||
b, err := newGenericBackend(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
req := mockRequest(t, vault.WriteOperation, "foo")
|
||||
req.Data["raw"] = "test"
|
||||
|
||||
resp, err := b.HandleRequest(req)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if resp != nil {
|
||||
t.Fatalf("bad: %v", resp)
|
||||
}
|
||||
|
||||
out, err := req.View.Get("foo")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if out == nil {
|
||||
t.Fatalf("failed to write to view")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenericBackend_Read(t *testing.T) {
|
||||
b, err := newGenericBackend(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
req := mockRequest(t, vault.WriteOperation, "foo")
|
||||
req.Data["raw"] = "test"
|
||||
req.Data["lease"] = "1h"
|
||||
|
||||
if _, err := b.HandleRequest(req); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
req2 := mockRequest(t, vault.ReadOperation, "foo")
|
||||
req2.View = req.View
|
||||
|
||||
resp, err := b.HandleRequest(req2)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
if !resp.IsSecret {
|
||||
t.Fatalf("should be secret: %#v", resp)
|
||||
}
|
||||
|
||||
if resp.Lease == nil {
|
||||
t.Fatalf("should have lease: %#v", resp)
|
||||
}
|
||||
if resp.Lease.Renewable {
|
||||
t.Fatalf("bad lease: %#v", resp.Lease)
|
||||
}
|
||||
if resp.Lease.Revokable {
|
||||
t.Fatalf("bad lease: %#v", resp.Lease)
|
||||
}
|
||||
if resp.Lease.Duration != time.Hour {
|
||||
t.Fatalf("bad lease: %#v", resp.Lease)
|
||||
}
|
||||
if resp.Lease.MaxDuration != time.Hour {
|
||||
t.Fatalf("bad lease: %#v", resp.Lease)
|
||||
}
|
||||
if resp.Lease.MaxIncrement != 0 {
|
||||
t.Fatalf("bad lease: %#v", resp.Lease)
|
||||
}
|
||||
|
||||
if resp.Data["raw"] != "test" {
|
||||
t.Fatalf("bad data: %#v", resp.Data)
|
||||
}
|
||||
if resp.Data["lease"] != "1h" {
|
||||
t.Fatalf("bad data: %#v", resp.Data)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenericBackend_Delete(t *testing.T) {
|
||||
b, err := newGenericBackend(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
req := mockRequest(t, vault.WriteOperation, "foo")
|
||||
req.Data["raw"] = "test"
|
||||
req.Data["lease"] = "1h"
|
||||
|
||||
if _, err := b.HandleRequest(req); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
req2 := mockRequest(t, vault.DeleteOperation, "foo")
|
||||
req2.View = req.View
|
||||
|
||||
resp, err := b.HandleRequest(req2)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if resp != nil {
|
||||
t.Fatalf("bad: %v", resp)
|
||||
}
|
||||
|
||||
req3 := mockRequest(t, vault.ReadOperation, "foo")
|
||||
req3.View = req.View
|
||||
|
||||
resp, err = b.HandleRequest(req3)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if resp != nil {
|
||||
t.Fatalf("bad: %v", resp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenericBackend_List(t *testing.T) {
|
||||
b, err := newGenericBackend(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
req := mockRequest(t, vault.WriteOperation, "foo/bar")
|
||||
req.Data["raw"] = "test"
|
||||
req.Data["lease"] = "1h"
|
||||
|
||||
if _, err := b.HandleRequest(req); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
req2 := mockRequest(t, vault.ListOperation, "")
|
||||
req2.View = req.View
|
||||
|
||||
resp, err := b.HandleRequest(req2)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
if resp.IsSecret {
|
||||
t.Fatalf("bad: %v", resp)
|
||||
}
|
||||
if resp.Lease != nil {
|
||||
t.Fatalf("bad: %v", resp)
|
||||
}
|
||||
if resp.Data["keys"] == nil {
|
||||
t.Fatalf("bad: %v", resp)
|
||||
}
|
||||
keys := resp.Data["keys"].([]string)
|
||||
if len(keys) != 1 || keys[0] != "foo/" {
|
||||
t.Fatalf("keys: %v", keys)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenericBackend_Help(t *testing.T) {
|
||||
b, err := newGenericBackend(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
req := mockRequest(t, vault.HelpOperation, "foo")
|
||||
resp, err := b.HandleRequest(req)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
if resp.IsSecret {
|
||||
t.Fatalf("bad: %v", resp)
|
||||
}
|
||||
if resp.Lease != nil {
|
||||
t.Fatalf("bad: %v", resp)
|
||||
}
|
||||
if resp.Data["help"] != genericHelpText {
|
||||
t.Fatalf("bad: %v", resp)
|
||||
}
|
||||
}
|
|
@ -1,10 +1,17 @@
|
|||
package vault
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrUnsupportedOperation is returned if the operation is not supported
|
||||
// by the logical backend.
|
||||
ErrUnsupportedOperation = errors.New("unsupported operation")
|
||||
)
|
||||
|
||||
// LogicalBackend interface must be implemented to be "mountable" at
|
||||
// a given path. Requests flow through a router which has various mount
|
||||
// points that flow to a logical backend. The logic of each backend is flexible,
|
||||
|
@ -34,6 +41,7 @@ const (
|
|||
WriteOperation = "write"
|
||||
DeleteOperation = "delete"
|
||||
ListOperation = "list"
|
||||
RevokeOperation = "revoke"
|
||||
HelpOperation = "help"
|
||||
)
|
||||
|
||||
|
@ -50,6 +58,9 @@ type Request struct {
|
|||
// final path is "foo" since the mount prefix is trimmed.
|
||||
Path string
|
||||
|
||||
// Request data is an opaque map that must have string keys.
|
||||
Data map[string]interface{}
|
||||
|
||||
// View is the storage view of this logical backend. It can be used
|
||||
// to durably store and retrieve state from the backend.
|
||||
View *BarrierView
|
||||
|
@ -73,6 +84,7 @@ type Response struct {
|
|||
type Lease struct {
|
||||
VaultID string // VaultID is the unique identifier used for renewal and revocation
|
||||
Renewable bool // Is the VaultID renewable
|
||||
Revokable bool // Is the secret revokable. Must support 'Revoke' operation.
|
||||
Duration time.Duration // Current lease duration
|
||||
MaxDuration time.Duration // Maximum lease duration
|
||||
MaxIncrement time.Duration // Maximum increment to lease duration
|
||||
|
|
Loading…
Reference in New Issue