open-vault/vault/plugin_catalog.go

185 lines
4.7 KiB
Go

package vault
import (
"encoding/json"
"errors"
"fmt"
"path/filepath"
"sort"
"strings"
"sync"
"github.com/hashicorp/vault/helper/builtinplugins"
"github.com/hashicorp/vault/helper/consts"
"github.com/hashicorp/vault/helper/jsonutil"
"github.com/hashicorp/vault/helper/pluginutil"
"github.com/hashicorp/vault/logical"
)
var (
pluginCatalogPath = "core/plugin-catalog/"
ErrDirectoryNotConfigured = errors.New("could not set plugin, plugin directory is not configured")
)
// PluginCatalog keeps a record of plugins known to vault. External plugins need
// to be registered to the catalog before they can be used in backends. Builtin
// plugins are automatically detected and included in the catalog.
type PluginCatalog struct {
catalogView *BarrierView
directory string
lock sync.RWMutex
}
func (c *Core) setupPluginCatalog() error {
c.pluginCatalog = &PluginCatalog{
catalogView: NewBarrierView(c.barrier, pluginCatalogPath),
directory: c.pluginDirectory,
}
return nil
}
// Get retrieves a plugin with the specified name from the catalog. It first
// looks for external plugins with this name and then looks for builtin plugins.
// It returns a PluginRunner or an error if no plugin was found.
func (c *PluginCatalog) Get(name string) (*pluginutil.PluginRunner, error) {
c.lock.RLock()
defer c.lock.RUnlock()
// If the directory isn't set only look for builtin plugins.
if c.directory != "" {
// Look for external plugins in the barrier
out, err := c.catalogView.Get(name)
if err != nil {
return nil, fmt.Errorf("failed to retrieve plugin \"%s\": %v", name, err)
}
if out != nil {
entry := new(pluginutil.PluginRunner)
if err := jsonutil.DecodeJSON(out.Value, entry); err != nil {
return nil, fmt.Errorf("failed to decode plugin entry: %v", err)
}
// prepend the plugin directory to the command
entry.Command = filepath.Join(c.directory, entry.Command)
return entry, nil
}
}
// Look for builtin plugins
if factory, ok := builtinplugins.Get(name); ok {
return &pluginutil.PluginRunner{
Name: name,
Builtin: true,
BuiltinFactory: factory,
}, nil
}
return nil, nil
}
// Set registers a new external plugin with the catalog, or updates an existing
// external plugin. It takes the name, command and SHA256 of the plugin.
func (c *PluginCatalog) Set(name, command string, sha256 []byte) error {
if c.directory == "" {
return ErrDirectoryNotConfigured
}
switch {
case strings.Contains(name, ".."):
fallthrough
case strings.Contains(command, ".."):
return consts.ErrPathContainsParentReferences
}
c.lock.Lock()
defer c.lock.Unlock()
parts := strings.Split(command, " ")
// Best effort check to make sure the command isn't breaking out of the
// configured plugin directory.
commandFull := filepath.Join(c.directory, parts[0])
sym, err := filepath.EvalSymlinks(commandFull)
if err != nil {
return fmt.Errorf("error while validating the command path: %v", err)
}
symAbs, err := filepath.Abs(filepath.Dir(sym))
if err != nil {
return fmt.Errorf("error while validating the command path: %v", err)
}
if symAbs != c.directory {
return errors.New("can not execute files outside of configured plugin directory")
}
entry := &pluginutil.PluginRunner{
Name: name,
Command: parts[0],
Args: parts[1:],
Sha256: sha256,
Builtin: false,
}
buf, err := json.Marshal(entry)
if err != nil {
return fmt.Errorf("failed to encode plugin entry: %v", err)
}
logicalEntry := logical.StorageEntry{
Key: name,
Value: buf,
}
if err := c.catalogView.Put(&logicalEntry); err != nil {
return fmt.Errorf("failed to persist plugin entry: %v", err)
}
return nil
}
// Delete is used to remove an external plugin from the catalog. Builtin plugins
// can not be deleted.
func (c *PluginCatalog) Delete(name string) error {
c.lock.Lock()
defer c.lock.Unlock()
return c.catalogView.Delete(name)
}
// List returns a list of all the known plugin names. If an external and builtin
// plugin share the same name, only one instance of the name will be returned.
func (c *PluginCatalog) List() ([]string, error) {
c.lock.RLock()
defer c.lock.RUnlock()
// Collect keys for external plugins in the barrier.
keys, err := logical.CollectKeys(c.catalogView)
if err != nil {
return nil, err
}
// Get the keys for builtin plugins
builtinKeys := builtinplugins.Keys()
// Use a map to unique the two lists
mapKeys := make(map[string]bool)
for _, plugin := range keys {
mapKeys[plugin] = true
}
for _, plugin := range builtinKeys {
mapKeys[plugin] = true
}
retList := make([]string, len(mapKeys))
i := 0
for k := range mapKeys {
retList[i] = k
i++
}
// sort for consistent ordering of builtin pluings
sort.Strings(retList)
return retList, nil
}