logical: put structs here, vault uses them

This commit is contained in:
Mitchell Hashimoto 2015-03-15 13:52:43 -07:00
parent 1837991454
commit 63a9eb321a
9 changed files with 237 additions and 45 deletions

24
logical/logical.go Normal file
View File

@ -0,0 +1,24 @@
package logical
// Backend 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,
// and this is what allows materialized keys to function. There can be specialized
// logical backends for various upstreams (Consul, PostgreSQL, MySQL, etc) that can
// interact with remote APIs to generate keys dynamically. This interface also
// allows for a "procfs" like interaction, as internal state can be exposed by
// acting like a logical backend and being mounted.
type Backend interface {
// HandleRequest is used to handle a request and generate a response.
// The backends must check the operation type and handle appropriately.
HandleRequest(*Request) (*Response, error)
// RootPaths is a list of paths that require root level privileges.
// These paths will be enforced by the router so that backends do
// not need to handle the authorization. Paths are enforced exactly
// or using a prefix match if they end in '*'
RootPaths() []string
}
// Factory is the factory function to create a logical backend.
type Factory func(map[string]string) (Backend, error)

49
logical/request.go Normal file
View File

@ -0,0 +1,49 @@
package logical
// Request is a struct that stores the parameters and context
// of a request being made to Vault. It is used to abstract
// the details of the higher level request protocol from the handlers.
type Request struct {
// Operation is the requested operation type
Operation Operation
// Path is the part of the request path not consumed by the
// routing. As an example, if the original request path is "prod/aws/foo"
// and the AWS logical backend is mounted at "prod/aws/", then the
// 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{}
// Storage can be used to durably store and retrieve state.
Storage Storage
}
// 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
}
// Operation is an enum that is used to specify the type
// of request being made
type Operation string
const (
ReadOperation Operation = "read"
WriteOperation = "write"
DeleteOperation = "delete"
ListOperation = "list"
RevokeOperation = "revoke"
HelpOperation = "help"
)

68
logical/response.go Normal file
View File

@ -0,0 +1,68 @@
package logical
import (
"fmt"
"time"
)
// 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 {
// IsSecret is used to indicate this is secret material instead of policy or configuration.
// Non-secrets never have a VaultID or renewable properties.
IsSecret bool
// The lease settings if applicable.
Lease *Lease
// Response data is an opaque map that must have string keys.
Data map[string]interface{}
}
// Lease is used to provide more information about the lease
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
}
// Validate is used to sanity check a lease
func (l *Lease) Validate() error {
if l.Duration <= 0 {
return fmt.Errorf("lease duration must be greater than zero")
}
if l.MaxDuration <= 0 {
return fmt.Errorf("maximum lease duration must be greater than zero")
}
if l.Duration > l.MaxDuration {
return fmt.Errorf("lease duration cannot be greater than maximum lease duration")
}
if l.MaxIncrement < 0 {
return fmt.Errorf("maximum lease increment cannot be negative")
}
return nil
}
// HelpResponse is used to format a help response
func HelpResponse(text string, seeAlso []string) *Response {
return &Response{
IsSecret: false,
Data: map[string]interface{}{
"help": text,
"see_also": seeAlso,
},
}
}
// ErrorResponse is used to format an error response
func ErrorResponse(text string) *Response {
return &Response{
IsSecret: false,
Data: map[string]interface{}{
"error": text,
},
}
}

15
logical/storage.go Normal file
View File

@ -0,0 +1,15 @@
package logical
// Storage is the way that logical backends are able read/write data.
type Storage interface {
List(prefix string) ([]string, error)
Get(string) (*StorageEntry, error)
Put(*StorageEntry) error
Delete(string) error
}
// StorageEntry is the entry for an item in a Storage implementation.
type StorageEntry struct {
Key string
Value []byte
}

View File

@ -1,6 +1,10 @@
package vault
import "errors"
import (
"errors"
"github.com/hashicorp/vault/logical"
)
var (
// ErrBarrierSealed is returned if an operation is performed on
@ -76,3 +80,11 @@ type Entry struct {
Key string
Value []byte
}
// Logical turns the Entry into a logical storage entry.
func (e *Entry) Logical() *logical.StorageEntry {
return &logical.StorageEntry{
Key: e.Key,
Value: e.Value,
}
}

View File

@ -1,12 +1,18 @@
package vault
import "strings"
import (
"strings"
// BarrierView is used to wrap a barrier and ensure all access is automatically
// prefixed. This means that nothing outside of the given prefix can be
// accessed through the view, which is an additional layer of security when
// interacting with the security barrier. Conceptually this is like a
// "chroot" into the barrier.
"github.com/hashicorp/vault/logical"
)
// BarrierView wraps a SecurityBarrier and ensures all access is automatically
// prefixed. This is used to prevent anyone with access to the view to access
// any data in the durable storage outside of their prefix. Conceptually this
// is like a "chroot" into the barrier.
//
// BarrierView implements logical.Storage so it can be passed in as the
// durable storage mechanism for logical views.
type BarrierView struct {
barrier SecurityBarrier
prefix string
@ -21,14 +27,32 @@ func NewBarrierView(barrier SecurityBarrier, prefix string) *BarrierView {
}
}
// SubView constructs a nested sub-view using the given prefix
func (v *BarrierView) SubView(prefix string) *BarrierView {
sub := v.expandKey(prefix)
return &BarrierView{barrier: v.barrier, prefix: sub}
// logical.Storage impl.
func (v *BarrierView) List(prefix string) ([]string, error) {
return v.barrier.List(v.expandKey(prefix))
}
// Put is used to insert or update an entry
func (v *BarrierView) Put(entry *Entry) error {
// logical.Storage impl.
func (v *BarrierView) Get(key string) (*logical.StorageEntry, error) {
entry, err := v.barrier.Get(v.expandKey(key))
if err != nil {
return nil, err
}
if entry == nil {
return nil, nil
}
if entry != nil {
entry.Key = v.truncateKey(entry.Key)
}
return &logical.StorageEntry{
Key: entry.Key,
Value: entry.Value,
}, nil
}
// logical.Storage impl.
func (v *BarrierView) Put(entry *logical.StorageEntry) error {
nested := &Entry{
Key: v.expandKey(entry.Key),
Value: entry.Value,
@ -36,27 +60,15 @@ func (v *BarrierView) Put(entry *Entry) error {
return v.barrier.Put(nested)
}
// Get is used to fetch an entry
func (v *BarrierView) Get(key string) (*Entry, error) {
entry, err := v.barrier.Get(v.expandKey(key))
if err != nil {
return nil, err
}
if entry != nil {
entry.Key = v.truncateKey(entry.Key)
}
return entry, nil
}
// Delete is used to permanently delete an entry
// logical.Storage impl.
func (v *BarrierView) Delete(key string) error {
return v.barrier.Delete(v.expandKey(key))
}
// List is used ot list all the keys under a given
// prefix, up to the next prefix.
func (v *BarrierView) List(prefix string) ([]string, error) {
return v.barrier.List(v.expandKey(prefix))
// SubView constructs a nested sub-view using the given prefix
func (v *BarrierView) SubView(prefix string) *BarrierView {
sub := v.expandKey(prefix)
return &BarrierView{barrier: v.barrier, prefix: sub}
}
// expandKey is used to expand to the full key path with the prefix

View File

@ -1,6 +1,14 @@
package vault
import "testing"
import (
"testing"
"github.com/hashicorp/vault/logical"
)
func TestBarrierView_impl(t *testing.T) {
var _ logical.Storage = new(BarrierView)
}
func TestBarrierView(t *testing.T) {
_, barrier, _ := mockBarrier(t)
@ -31,16 +39,16 @@ func TestBarrierView(t *testing.T) {
}
// Try to put the same entry via the view
if err := view.Put(entry); err != nil {
if err := view.Put(entry.Logical()); err != nil {
t.Fatalf("err: %v", err)
}
// Check it is nested
out, err = barrier.Get("foo/test")
entry, err = barrier.Get("foo/test")
if err != nil {
t.Fatalf("err: %v", err)
}
if out == nil {
if entry == nil {
t.Fatalf("missing nested foo/test")
}
@ -50,20 +58,20 @@ func TestBarrierView(t *testing.T) {
}
// Check the nested key
out, err = barrier.Get("foo/test")
entry, err = barrier.Get("foo/test")
if err != nil {
t.Fatalf("err: %v", err)
}
if out != nil {
if entry != nil {
t.Fatalf("nested foo/test should be gone")
}
// Check the non-nested key
out, err = barrier.Get("test")
entry, err = barrier.Get("test")
if err != nil {
t.Fatalf("err: %v", err)
}
if out == nil {
if entry == nil {
t.Fatalf("root test missing")
}
}
@ -92,17 +100,17 @@ func TestBarrierView_SubView(t *testing.T) {
}
// Try to put the same entry via the view
entry := &Entry{Key: "test", Value: []byte("test")}
entry := &logical.StorageEntry{Key: "test", Value: []byte("test")}
if err := view.Put(entry); err != nil {
t.Fatalf("err: %v", err)
}
// Check it is nested
out, err = barrier.Get("foo/bar/test")
bout, err := barrier.Get("foo/bar/test")
if err != nil {
t.Fatalf("err: %v", err)
}
if out == nil {
if bout == nil {
t.Fatalf("missing nested foo/bar/test")
}
@ -121,11 +129,11 @@ func TestBarrierView_SubView(t *testing.T) {
}
// Check the nested key
out, err = barrier.Get("foo/bar/test")
bout, err = barrier.Get("foo/bar/test")
if err != nil {
t.Fatalf("err: %v", err)
}
if out != nil {
if bout != nil {
t.Fatalf("nested foo/bar/test should be gone")
}
}

View File

@ -6,6 +6,8 @@ import (
"path"
"sync"
"time"
"github.com/hashicorp/vault/logical"
)
const (
@ -173,7 +175,7 @@ func (m *ExpirationManager) Register(req *Request, resp *Response) (string, erro
}
// Write out to the view
ent := Entry{
ent := logical.StorageEntry{
Key: le.VaultID,
Value: buf,
}

View File

@ -4,6 +4,8 @@ import (
"encoding/json"
"fmt"
"time"
"github.com/hashicorp/vault/logical"
)
// GenericBackend is used for the storing generic secrets. These are not
@ -99,7 +101,7 @@ func (g *GenericBackend) handleWrite(req *Request) (*Response, error) {
}
// Write out a new key
entry := &Entry{
entry := &logical.StorageEntry{
Key: req.Path,
Value: buf,
}