mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-11-02 19:47:54 +00:00
vault: Passthrough backend uses logical.Backend
This commit is contained in:
@@ -66,3 +66,12 @@ func ErrorResponse(text string) *Response {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// ListResponse is used to format a response to a list operation.
|
||||
func ListResponse(keys []string) *Response {
|
||||
return &Response{
|
||||
Data: map[string]interface{}{
|
||||
"keys": keys,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
47
logical/storage_inmem.go
Normal file
47
logical/storage_inmem.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package logical
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// InmemStorage implements Storage and stores all data in memory.
|
||||
type InmemStorage struct {
|
||||
Data map[string]*StorageEntry
|
||||
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
func (s *InmemStorage) List(prefix string) ([]string, error) {
|
||||
s.once.Do(s.init)
|
||||
|
||||
var result []string
|
||||
for k, _ := range s.Data {
|
||||
if strings.HasPrefix(k, prefix) {
|
||||
result = append(result, k)
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *InmemStorage) Get(key string) (*StorageEntry, error) {
|
||||
s.once.Do(s.init)
|
||||
return s.Data[key], nil
|
||||
}
|
||||
|
||||
func (s *InmemStorage) Put(entry *StorageEntry) error {
|
||||
s.once.Do(s.init)
|
||||
s.Data[entry.Key] = entry
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *InmemStorage) Delete(k string) error {
|
||||
s.once.Do(s.init)
|
||||
delete(s.Data, k)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *InmemStorage) init() {
|
||||
s.Data = make(map[string]*StorageEntry)
|
||||
}
|
||||
9
logical/storage_inmem_test.go
Normal file
9
logical/storage_inmem_test.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package logical
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestInmemStorage(t *testing.T) {
|
||||
TestStorage(t, new(InmemStorage))
|
||||
}
|
||||
61
logical/testing.go
Normal file
61
logical/testing.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package logical
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestRequest is a helper to create a purely in-memory Request struct.
|
||||
func TestRequest(t *testing.T, op Operation, path string) *Request {
|
||||
return &Request{
|
||||
Operation: op,
|
||||
Path: path,
|
||||
Data: make(map[string]interface{}),
|
||||
Storage: new(InmemStorage),
|
||||
}
|
||||
}
|
||||
|
||||
// TestStorage is a helper that can be used from unit tests to verify
|
||||
// the behavior of a Storage impl.
|
||||
func TestStorage(t *testing.T, s Storage) {
|
||||
keys, err := s.List("")
|
||||
if err != nil {
|
||||
t.Fatalf("list error: %s", err)
|
||||
}
|
||||
if len(keys) > 0 {
|
||||
t.Fatalf("should have no keys to start: %#v", keys)
|
||||
}
|
||||
|
||||
entry := &StorageEntry{Key: "foo", Value: []byte("bar")}
|
||||
if err := s.Put(entry); err != nil {
|
||||
t.Fatalf("put error: %s", err)
|
||||
}
|
||||
|
||||
actual, err := s.Get("foo")
|
||||
if err != nil {
|
||||
t.Fatalf("get error: %s", err)
|
||||
}
|
||||
if !reflect.DeepEqual(actual, entry) {
|
||||
t.Fatalf("wrong value. Expected: %#v\nGot: %#v", entry, actual)
|
||||
}
|
||||
|
||||
keys, err = s.List("")
|
||||
if err != nil {
|
||||
t.Fatalf("list error: %s", err)
|
||||
}
|
||||
if !reflect.DeepEqual(keys, []string{"foo"}) {
|
||||
t.Fatalf("bad keys: %#v", keys)
|
||||
}
|
||||
|
||||
if err := s.Delete("foo"); err != nil {
|
||||
t.Fatalf("put error: %s", err)
|
||||
}
|
||||
|
||||
keys, err = s.List("")
|
||||
if err != nil {
|
||||
t.Fatalf("list error: %s", err)
|
||||
}
|
||||
if len(keys) > 0 {
|
||||
t.Fatalf("should have no keys to start: %#v", keys)
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,12 @@ func TestBarrierView_impl(t *testing.T) {
|
||||
var _ logical.Storage = new(BarrierView)
|
||||
}
|
||||
|
||||
func TestBarrierView_spec(t *testing.T) {
|
||||
_, barrier, _ := mockBarrier(t)
|
||||
view := NewBarrierView(barrier, "foo/")
|
||||
logical.TestStorage(t, view)
|
||||
}
|
||||
|
||||
func TestBarrierView(t *testing.T) {
|
||||
_, barrier, _ := mockBarrier(t)
|
||||
view := NewBarrierView(barrier, "foo/")
|
||||
|
||||
123
vault/logical_passthrough.go
Normal file
123
vault/logical_passthrough.go
Normal file
@@ -0,0 +1,123 @@
|
||||
package vault
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/vault/logical"
|
||||
)
|
||||
|
||||
// PassthroughBackend is used storing secrets directly into the physical
|
||||
// backend. The secrest are encrypted in the durable storage and custom lease
|
||||
// information can be specified, but otherwise this backend doesn't do anything
|
||||
// fancy.
|
||||
type PassthroughBackend struct{}
|
||||
|
||||
func (b *PassthroughBackend) HandleRequest(req *logical.Request) (*logical.Response, error) {
|
||||
// TODO(mitchellh): help, let's just do it when we migrate to helper/backend
|
||||
|
||||
switch req.Operation {
|
||||
case logical.ReadOperation:
|
||||
return b.handleRead(req)
|
||||
case logical.WriteOperation:
|
||||
return b.handleWrite(req)
|
||||
case logical.DeleteOperation:
|
||||
return b.handleDelete(req)
|
||||
case logical.ListOperation:
|
||||
return b.handleList(req)
|
||||
default:
|
||||
return nil, ErrUnsupportedOperation
|
||||
}
|
||||
}
|
||||
|
||||
func (b *PassthroughBackend) RootPaths() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *PassthroughBackend) handleRead(req *logical.Request) (*logical.Response, error) {
|
||||
// Read the path
|
||||
out, err := req.Storage.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 *logical.Lease
|
||||
if ok {
|
||||
leaseDuration, err := time.ParseDuration(leaseVal)
|
||||
if err == nil {
|
||||
lease = &logical.Lease{
|
||||
Renewable: false,
|
||||
Revokable: false,
|
||||
Duration: leaseDuration,
|
||||
MaxDuration: leaseDuration,
|
||||
MaxIncrement: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Generate the response
|
||||
resp := &logical.Response{
|
||||
IsSecret: true,
|
||||
Lease: lease,
|
||||
Data: raw,
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (b *PassthroughBackend) handleWrite(req *logical.Request) (*logical.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 := &logical.StorageEntry{
|
||||
Key: req.Path,
|
||||
Value: buf,
|
||||
}
|
||||
if err := req.Storage.Put(entry); err != nil {
|
||||
return nil, fmt.Errorf("failed to write: %v", err)
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (b *PassthroughBackend) handleDelete(req *logical.Request) (*logical.Response, error) {
|
||||
// Delete the key at the request path
|
||||
if err := req.Storage.Delete(req.Path); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (b *PassthroughBackend) handleList(req *logical.Request) (*logical.Response, error) {
|
||||
// List the keys at the prefix given by the request
|
||||
keys, err := req.Storage.List(req.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Generate the response
|
||||
return logical.ListResponse(keys), nil
|
||||
}
|
||||
143
vault/logical_passthrough_test.go
Normal file
143
vault/logical_passthrough_test.go
Normal file
@@ -0,0 +1,143 @@
|
||||
package vault
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/vault/logical"
|
||||
)
|
||||
|
||||
func TestPassthroughBackend_impl(t *testing.T) {
|
||||
var _ logical.Backend = new(PassthroughBackend)
|
||||
}
|
||||
|
||||
func TestPassthroughBackend_RootPaths(t *testing.T) {
|
||||
var b PassthroughBackend
|
||||
root := b.RootPaths()
|
||||
if len(root) != 0 {
|
||||
t.Fatalf("unexpected: %v", root)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPassthroughBackend_Write(t *testing.T) {
|
||||
var b PassthroughBackend
|
||||
req := logical.TestRequest(t, logical.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.Storage.Get("foo")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if out == nil {
|
||||
t.Fatalf("failed to write to view")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPassthroughBackend_Read(t *testing.T) {
|
||||
var b PassthroughBackend
|
||||
req := logical.TestRequest(t, logical.WriteOperation, "foo")
|
||||
req.Data["raw"] = "test"
|
||||
req.Data["lease"] = "1h"
|
||||
storage := req.Storage
|
||||
|
||||
if _, err := b.HandleRequest(req); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
req = logical.TestRequest(t, logical.ReadOperation, "foo")
|
||||
req.Storage = storage
|
||||
|
||||
resp, err := b.HandleRequest(req)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
expected := &logical.Response{
|
||||
IsSecret: true,
|
||||
Lease: &logical.Lease{
|
||||
Renewable: false,
|
||||
Revokable: false,
|
||||
Duration: time.Hour,
|
||||
MaxDuration: time.Hour,
|
||||
MaxIncrement: 0,
|
||||
},
|
||||
Data: map[string]interface{}{
|
||||
"raw": "test",
|
||||
"lease": "1h",
|
||||
},
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(resp, expected) {
|
||||
t.Fatalf("bad response.\n\nexpected: %#v\n\nGot: %#v", expected, resp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPassthroughBackend_Delete(t *testing.T) {
|
||||
var b PassthroughBackend
|
||||
req := logical.TestRequest(t, logical.WriteOperation, "foo")
|
||||
req.Data["raw"] = "test"
|
||||
storage := req.Storage
|
||||
|
||||
if _, err := b.HandleRequest(req); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
req = logical.TestRequest(t, logical.DeleteOperation, "foo")
|
||||
req.Storage = storage
|
||||
resp, err := b.HandleRequest(req)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if resp != nil {
|
||||
t.Fatalf("bad: %v", resp)
|
||||
}
|
||||
|
||||
req = logical.TestRequest(t, logical.ReadOperation, "foo")
|
||||
req.Storage = storage
|
||||
resp, err = b.HandleRequest(req)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if resp != nil {
|
||||
t.Fatalf("bad: %v", resp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPassthroughBackend_List(t *testing.T) {
|
||||
var b PassthroughBackend
|
||||
req := logical.TestRequest(t, logical.WriteOperation, "foo")
|
||||
req.Data["raw"] = "test"
|
||||
storage := req.Storage
|
||||
|
||||
if _, err := b.HandleRequest(req); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
req = logical.TestRequest(t, logical.ListOperation, "")
|
||||
req.Storage = storage
|
||||
resp, err := b.HandleRequest(req)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
expected := &logical.Response{
|
||||
IsSecret: false,
|
||||
Lease: nil,
|
||||
Data: map[string]interface{}{
|
||||
"keys": []string{"foo"},
|
||||
},
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(resp, expected) {
|
||||
t.Fatalf("bad response.\n\nexpected: %#v\n\nGot: %#v", expected, resp)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user