2017-04-04 00:52:29 +00:00
|
|
|
package vault
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"path/filepath"
|
2017-04-12 16:40:54 +00:00
|
|
|
"sort"
|
2017-04-04 00:52:29 +00:00
|
|
|
"strings"
|
|
|
|
"sync"
|
|
|
|
|
2017-04-05 23:20:31 +00:00
|
|
|
"github.com/hashicorp/vault/helper/builtinplugins"
|
2017-05-12 17:52:33 +00:00
|
|
|
"github.com/hashicorp/vault/helper/consts"
|
2017-04-04 00:52:29 +00:00
|
|
|
"github.com/hashicorp/vault/helper/jsonutil"
|
|
|
|
"github.com/hashicorp/vault/helper/pluginutil"
|
|
|
|
"github.com/hashicorp/vault/logical"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
2017-04-24 17:30:33 +00:00
|
|
|
pluginCatalogPath = "core/plugin-catalog/"
|
|
|
|
ErrDirectoryNotConfigured = errors.New("could not set plugin, plugin directory is not configured")
|
2017-09-01 05:02:03 +00:00
|
|
|
ErrPluginNotFound = errors.New("plugin not found in the catalog")
|
2017-04-04 00:52:29 +00:00
|
|
|
)
|
|
|
|
|
2017-04-12 17:01:36 +00:00
|
|
|
// 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.
|
2017-04-04 00:52:29 +00:00
|
|
|
type PluginCatalog struct {
|
2017-04-21 01:46:41 +00:00
|
|
|
catalogView *BarrierView
|
|
|
|
directory string
|
2017-04-04 00:52:29 +00:00
|
|
|
|
2017-04-05 00:12:02 +00:00
|
|
|
lock sync.RWMutex
|
2017-04-04 00:52:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Core) setupPluginCatalog() error {
|
2017-04-04 21:43:39 +00:00
|
|
|
c.pluginCatalog = &PluginCatalog{
|
2017-04-24 17:30:33 +00:00
|
|
|
catalogView: NewBarrierView(c.barrier, pluginCatalogPath),
|
2017-04-21 01:46:41 +00:00
|
|
|
directory: c.pluginDirectory,
|
2017-04-04 21:43:39 +00:00
|
|
|
}
|
2017-04-04 00:52:29 +00:00
|
|
|
|
2017-09-01 05:02:03 +00:00
|
|
|
if c.logger.IsInfo() {
|
|
|
|
c.logger.Info("core: successfully setup plugin catalog", "plugin-directory", c.pluginDirectory)
|
|
|
|
}
|
|
|
|
|
2017-04-04 00:52:29 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2017-04-12 17:01:36 +00:00
|
|
|
// 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.
|
2017-04-04 00:52:29 +00:00
|
|
|
func (c *PluginCatalog) Get(name string) (*pluginutil.PluginRunner, error) {
|
2017-04-12 16:40:54 +00:00
|
|
|
c.lock.RLock()
|
|
|
|
defer c.lock.RUnlock()
|
|
|
|
|
2017-04-24 17:30:33 +00:00
|
|
|
// 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)
|
2017-04-04 21:43:39 +00:00
|
|
|
}
|
2017-04-24 17:30:33 +00:00
|
|
|
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)
|
|
|
|
}
|
2017-04-04 21:43:39 +00:00
|
|
|
|
2017-05-04 19:36:06 +00:00
|
|
|
// prepend the plugin directory to the command
|
|
|
|
entry.Command = filepath.Join(c.directory, entry.Command)
|
|
|
|
|
2017-04-24 17:30:33 +00:00
|
|
|
return entry, nil
|
|
|
|
}
|
2017-04-04 00:52:29 +00:00
|
|
|
}
|
2017-04-04 21:43:39 +00:00
|
|
|
// Look for builtin plugins
|
2017-04-24 17:30:33 +00:00
|
|
|
if factory, ok := builtinplugins.Get(name); ok {
|
2017-04-21 01:46:41 +00:00
|
|
|
return &pluginutil.PluginRunner{
|
|
|
|
Name: name,
|
|
|
|
Builtin: true,
|
|
|
|
BuiltinFactory: factory,
|
|
|
|
}, nil
|
2017-04-04 00:52:29 +00:00
|
|
|
}
|
|
|
|
|
2017-04-25 01:31:27 +00:00
|
|
|
return nil, nil
|
2017-04-04 00:52:29 +00:00
|
|
|
}
|
|
|
|
|
2017-04-12 17:01:36 +00:00
|
|
|
// 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.
|
2017-04-04 00:52:29 +00:00
|
|
|
func (c *PluginCatalog) Set(name, command string, sha256 []byte) error {
|
2017-04-24 17:30:33 +00:00
|
|
|
if c.directory == "" {
|
|
|
|
return ErrDirectoryNotConfigured
|
|
|
|
}
|
|
|
|
|
2017-05-12 17:52:33 +00:00
|
|
|
switch {
|
|
|
|
case strings.Contains(name, ".."):
|
|
|
|
fallthrough
|
|
|
|
case strings.Contains(command, ".."):
|
|
|
|
return consts.ErrPathContainsParentReferences
|
|
|
|
}
|
|
|
|
|
2017-04-12 16:40:54 +00:00
|
|
|
c.lock.Lock()
|
|
|
|
defer c.lock.Unlock()
|
|
|
|
|
2017-04-04 00:52:29 +00:00
|
|
|
parts := strings.Split(command, " ")
|
|
|
|
|
|
|
|
// Best effort check to make sure the command isn't breaking out of the
|
|
|
|
// configured plugin directory.
|
2017-05-04 19:36:06 +00:00
|
|
|
commandFull := filepath.Join(c.directory, parts[0])
|
|
|
|
sym, err := filepath.EvalSymlinks(commandFull)
|
2017-04-04 00:52:29 +00:00
|
|
|
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,
|
2017-05-04 19:36:06 +00:00
|
|
|
Command: parts[0],
|
|
|
|
Args: parts[1:],
|
2017-04-04 00:52:29 +00:00
|
|
|
Sha256: sha256,
|
2017-04-11 00:12:52 +00:00
|
|
|
Builtin: false,
|
2017-04-04 00:52:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
2017-04-12 16:40:54 +00:00
|
|
|
|
2017-04-12 17:01:36 +00:00
|
|
|
// Delete is used to remove an external plugin from the catalog. Builtin plugins
|
|
|
|
// can not be deleted.
|
2017-04-12 16:40:54 +00:00
|
|
|
func (c *PluginCatalog) Delete(name string) error {
|
|
|
|
c.lock.Lock()
|
|
|
|
defer c.lock.Unlock()
|
|
|
|
|
|
|
|
return c.catalogView.Delete(name)
|
|
|
|
}
|
|
|
|
|
2017-04-12 17:01:36 +00:00
|
|
|
// 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.
|
2017-04-12 16:40:54 +00:00
|
|
|
func (c *PluginCatalog) List() ([]string, error) {
|
|
|
|
c.lock.RLock()
|
|
|
|
defer c.lock.RUnlock()
|
|
|
|
|
2017-04-12 17:01:36 +00:00
|
|
|
// Collect keys for external plugins in the barrier.
|
2017-04-12 16:40:54 +00:00
|
|
|
keys, err := logical.CollectKeys(c.catalogView)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2017-04-12 17:01:36 +00:00
|
|
|
// Get the keys for builtin plugins
|
2017-04-24 17:30:33 +00:00
|
|
|
builtinKeys := builtinplugins.Keys()
|
2017-04-12 16:40:54 +00:00
|
|
|
|
2017-04-12 17:01:36 +00:00
|
|
|
// Use a map to unique the two lists
|
2017-04-12 16:40:54 +00:00
|
|
|
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
|
|
|
|
}
|