open-vault/logical/framework/path_map.go

241 lines
5.5 KiB
Go

package framework
import (
"fmt"
"strings"
"sync"
"github.com/hashicorp/vault/helper/salt"
"github.com/hashicorp/vault/logical"
)
// PathMap can be used to generate a path that stores mappings in the
// storage. It is a structure that also exports functions for querying the
// mappings.
//
// The primary use case for this is for credential providers to do their
// mapping to policies.
type PathMap struct {
Prefix string
Name string
Schema map[string]*FieldSchema
CaseSensitive bool
Salt *salt.Salt
SaltFunc func() (*salt.Salt, error)
once sync.Once
}
func (p *PathMap) init() {
if p.Prefix == "" {
p.Prefix = "map"
}
if p.Schema == nil {
p.Schema = map[string]*FieldSchema{
"value": &FieldSchema{
Type: TypeString,
Description: fmt.Sprintf("Value for %s mapping", p.Name),
},
}
}
}
// pathStruct returns the pathStruct for this mapping
func (p *PathMap) pathStruct(s logical.Storage, k string) (*PathStruct, error) {
p.once.Do(p.init)
// If we don't care about casing, store everything lowercase
if !p.CaseSensitive {
k = strings.ToLower(k)
}
// The original key before any salting
origKey := k
// If we have a salt, apply it before lookup
salt := p.Salt
var err error
if p.SaltFunc != nil {
salt, err = p.SaltFunc()
if err != nil {
return nil, err
}
}
if salt != nil {
k = salt.SaltID(k)
}
finalName := fmt.Sprintf("map/%s/%s", p.Name, k)
ps := &PathStruct{
Name: finalName,
Schema: p.Schema,
}
// Check for unsalted version and upgrade if so
if k != origKey {
// Generate the unsalted name
unsaltedName := fmt.Sprintf("map/%s/%s", p.Name, origKey)
// Set the path struct to use the unsalted name
ps.Name = unsaltedName
// Ensure that no matter what happens what is returned is the final
// path
defer func() {
ps.Name = finalName
}()
val, err := ps.Get(s)
if err != nil {
return nil, err
}
// If not nil, we have an unsalted entry -- upgrade it
if val != nil {
// Set the path struct to use the desired final name
ps.Name = finalName
err = ps.Put(s, val)
if err != nil {
return nil, err
}
// Set it back to the old path and delete
ps.Name = unsaltedName
err = ps.Delete(s)
if err != nil {
return nil, err
}
// We'll set this in the deferred function but doesn't hurt here
ps.Name = finalName
}
}
return ps, nil
}
// Get reads a value out of the mapping
func (p *PathMap) Get(s logical.Storage, k string) (map[string]interface{}, error) {
ps, err := p.pathStruct(s, k)
if err != nil {
return nil, err
}
return ps.Get(s)
}
// Put writes a value into the mapping
func (p *PathMap) Put(s logical.Storage, k string, v map[string]interface{}) error {
ps, err := p.pathStruct(s, k)
if err != nil {
return err
}
return ps.Put(s, v)
}
// Delete removes a value from the mapping
func (p *PathMap) Delete(s logical.Storage, k string) error {
ps, err := p.pathStruct(s, k)
if err != nil {
return err
}
return ps.Delete(s)
}
// List reads the keys under a given path
func (p *PathMap) List(s logical.Storage, prefix string) ([]string, error) {
stripPrefix := fmt.Sprintf("struct/map/%s/", p.Name)
fullPrefix := fmt.Sprintf("%s%s", stripPrefix, prefix)
out, err := s.List(fullPrefix)
if err != nil {
return nil, err
}
stripped := make([]string, len(out))
for idx, k := range out {
stripped[idx] = strings.TrimPrefix(k, stripPrefix)
}
return stripped, nil
}
// Paths are the paths to append to the Backend paths.
func (p *PathMap) Paths() []*Path {
p.once.Do(p.init)
// Build the schema by simply adding the "key"
schema := make(map[string]*FieldSchema)
for k, v := range p.Schema {
schema[k] = v
}
schema["key"] = &FieldSchema{
Type: TypeString,
Description: fmt.Sprintf("Key for the %s mapping", p.Name),
}
return []*Path{
&Path{
Pattern: fmt.Sprintf("%s/%s/?$", p.Prefix, p.Name),
Callbacks: map[logical.Operation]OperationFunc{
logical.ListOperation: p.pathList,
logical.ReadOperation: p.pathList,
},
HelpSynopsis: fmt.Sprintf("Read mappings for %s", p.Name),
},
&Path{
Pattern: fmt.Sprintf(`%s/%s/(?P<key>[-\w]+)`, p.Prefix, p.Name),
Fields: schema,
Callbacks: map[logical.Operation]OperationFunc{
logical.CreateOperation: p.pathSingleWrite,
logical.ReadOperation: p.pathSingleRead,
logical.UpdateOperation: p.pathSingleWrite,
logical.DeleteOperation: p.pathSingleDelete,
},
HelpSynopsis: fmt.Sprintf("Read/write/delete a single %s mapping", p.Name),
ExistenceCheck: p.pathSingleExistenceCheck,
},
}
}
func (p *PathMap) pathList(
req *logical.Request, d *FieldData) (*logical.Response, error) {
keys, err := p.List(req.Storage, "")
if err != nil {
return nil, err
}
return logical.ListResponse(keys), nil
}
func (p *PathMap) pathSingleRead(
req *logical.Request, d *FieldData) (*logical.Response, error) {
v, err := p.Get(req.Storage, d.Get("key").(string))
if err != nil {
return nil, err
}
return &logical.Response{
Data: v,
}, nil
}
func (p *PathMap) pathSingleWrite(
req *logical.Request, d *FieldData) (*logical.Response, error) {
err := p.Put(req.Storage, d.Get("key").(string), d.Raw)
return nil, err
}
func (p *PathMap) pathSingleDelete(
req *logical.Request, d *FieldData) (*logical.Response, error) {
err := p.Delete(req.Storage, d.Get("key").(string))
return nil, err
}
func (p *PathMap) pathSingleExistenceCheck(
req *logical.Request, d *FieldData) (bool, error) {
v, err := p.Get(req.Storage, d.Get("key").(string))
if err != nil {
return false, err
}
return v != nil, nil
}