From e88494684cec8360ed989255f1e2092abd10e8ae Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Thu, 18 Jan 2018 00:19:28 +0000 Subject: [PATCH] Make the plugin catalog endpoint roundtrip so we can use terraform to manage them. (#3778) --- vault/logical_system.go | 41 ++++++++++++++++++++++++++++++++---- vault/logical_system_test.go | 41 ++++++++++++++++++++++-------------- vault/plugin_catalog.go | 10 ++++----- vault/plugin_catalog_test.go | 10 ++++----- vault/testing.go | 10 +++++---- 5 files changed, 77 insertions(+), 35 deletions(-) diff --git a/vault/logical_system.go b/vault/logical_system.go index 1b531f689cbf..3980877b50e5 100644 --- a/vault/logical_system.go +++ b/vault/logical_system.go @@ -10,12 +10,12 @@ import ( "errors" "fmt" "hash" + "path/filepath" "strconv" "strings" "sync" "time" - "github.com/fatih/structs" uuid "github.com/hashicorp/go-uuid" "github.com/hashicorp/vault/helper/consts" "github.com/hashicorp/vault/helper/parseutil" @@ -860,6 +860,10 @@ func NewSystemBackend(core *Core) *SystemBackend { Type: framework.TypeString, Description: strings.TrimSpace(sysHelp["plugin-catalog_command"][0]), }, + "args": &framework.FieldSchema{ + Type: framework.TypeStringSlice, + Description: strings.TrimSpace(sysHelp["plugin-catalog_args"][0]), + }, }, Callbacks: map[logical.Operation]framework.OperationFunc{ @@ -1098,12 +1102,24 @@ func (b *SystemBackend) handlePluginCatalogUpdate(ctx context.Context, req *logi return logical.ErrorResponse("missing command value"), nil } + // For backwards compatibility, also accept args as part of command. Don't + // accepts args in both command and args. + args := d.Get("args").([]string) + parts := strings.Split(command, " ") + if len(parts) <= 0 { + return logical.ErrorResponse("missing command value"), nil + } else if len(parts) > 1 && len(args) > 0 { + return logical.ErrorResponse("must not speficy args in command and args field"), nil + } else if len(parts) > 1 { + args = parts[1:] + } + sha256Bytes, err := hex.DecodeString(sha256) if err != nil { return logical.ErrorResponse("Could not decode SHA-256 value from Hex"), err } - err = b.Core.pluginCatalog.Set(pluginName, command, sha256Bytes) + err = b.Core.pluginCatalog.Set(pluginName, parts[0], args, sha256Bytes) if err != nil { return nil, err } @@ -1124,8 +1140,21 @@ func (b *SystemBackend) handlePluginCatalogRead(ctx context.Context, req *logica return nil, nil } - // Create a map of data to be returned and remove sensitive information from it - data := structs.New(plugin).Map() + command := "" + if !plugin.Builtin { + command, err = filepath.Rel(b.Core.pluginCatalog.directory, plugin.Command) + if err != nil { + return nil, err + } + } + + data := map[string]interface{}{ + "name": plugin.Name, + "args": plugin.Args, + "command": command, + "sha256": hex.EncodeToString(plugin.Sha256), + "builtin": plugin.Builtin, + } return &logical.Response{ Data: data, @@ -3332,6 +3361,10 @@ executable defined in this command must exist in vault's plugin directory.`, "", }, + "plugin-catalog_args": { + `The args passed to plugin command.`, + "", + }, "leases": { `View or list lease metadata.`, ` diff --git a/vault/logical_system_test.go b/vault/logical_system_test.go index 5db256cacb9f..03469317d525 100644 --- a/vault/logical_system_test.go +++ b/vault/logical_system_test.go @@ -17,7 +17,6 @@ import ( "github.com/fatih/structs" "github.com/hashicorp/vault/audit" "github.com/hashicorp/vault/helper/builtinplugins" - "github.com/hashicorp/vault/helper/pluginutil" "github.com/hashicorp/vault/helper/salt" "github.com/hashicorp/vault/logical" "github.com/mitchellh/mapstructure" @@ -1787,14 +1786,15 @@ func TestSystemBackend_PluginCatalog_CRUD(t *testing.T) { if err != nil { t.Fatalf("err: %v", err) } - actualRespData := resp.Data - expectedBuiltin := &pluginutil.PluginRunner{ - Name: "mysql-database-plugin", - Builtin: true, + actualRespData := resp.Data + expectedRespData := map[string]interface{}{ + "name": "mysql-database-plugin", + "command": "", + "args": []string(nil), + "sha256": "", + "builtin": true, } - expectedRespData := structs.New(expectedBuiltin).Map() - if !reflect.DeepEqual(actualRespData, expectedRespData) { t.Fatalf("expected did not match actual, got %#v\n expected %#v\n", actualRespData, expectedRespData) } @@ -1806,31 +1806,40 @@ func TestSystemBackend_PluginCatalog_CRUD(t *testing.T) { } defer file.Close() + // Check we can only specify args in one of command or args. command := fmt.Sprintf("%s --test", filepath.Base(file.Name())) req = logical.TestRequest(t, logical.UpdateOperation, "plugins/catalog/test-plugin") + req.Data["args"] = []string{"--foo"} req.Data["sha_256"] = hex.EncodeToString([]byte{'1'}) req.Data["command"] = command resp, err = b.HandleRequest(context.Background(), req) if err != nil { t.Fatalf("err: %v", err) } + if resp.Error().Error() != "must not speficy args in command and args field" { + t.Fatalf("err: %v", resp.Error()) + } + + delete(req.Data, "args") + resp, err = b.HandleRequest(context.Background(), req) + if err != nil || resp.Error() != nil { + t.Fatalf("err: %v %v", err, resp.Error()) + } req = logical.TestRequest(t, logical.ReadOperation, "plugins/catalog/test-plugin") resp, err = b.HandleRequest(context.Background(), req) if err != nil { t.Fatalf("err: %v", err) } - actual := resp.Data - expectedRunner := &pluginutil.PluginRunner{ - Name: "test-plugin", - Command: filepath.Join(sym, filepath.Base(file.Name())), - Args: []string{"--test"}, - Sha256: []byte{'1'}, - Builtin: false, + actual := resp.Data + expected := map[string]interface{}{ + "name": "test-plugin", + "command": filepath.Base(file.Name()), + "args": []string{"--test"}, + "sha256": "31", + "builtin": false, } - expected := structs.New(expectedRunner).Map() - if !reflect.DeepEqual(actual, expected) { t.Fatalf("expected did not match actual, got %#v\n expected %#v\n", actual, expected) } diff --git a/vault/plugin_catalog.go b/vault/plugin_catalog.go index 3e2466ff6420..a08762679543 100644 --- a/vault/plugin_catalog.go +++ b/vault/plugin_catalog.go @@ -85,7 +85,7 @@ func (c *PluginCatalog) Get(name string) (*pluginutil.PluginRunner, error) { // 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 { +func (c *PluginCatalog) Set(name, command string, args []string, sha256 []byte) error { if c.directory == "" { return ErrDirectoryNotConfigured } @@ -100,11 +100,9 @@ func (c *PluginCatalog) Set(name, command string, sha256 []byte) error { 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]) + commandFull := filepath.Join(c.directory, command) sym, err := filepath.EvalSymlinks(commandFull) if err != nil { return fmt.Errorf("error while validating the command path: %v", err) @@ -120,8 +118,8 @@ func (c *PluginCatalog) Set(name, command string, sha256 []byte) error { entry := &pluginutil.PluginRunner{ Name: name, - Command: parts[0], - Args: parts[1:], + Command: command, + Args: args, Sha256: sha256, Builtin: false, } diff --git a/vault/plugin_catalog_test.go b/vault/plugin_catalog_test.go index 6cfacda7e576..a0fae67e969a 100644 --- a/vault/plugin_catalog_test.go +++ b/vault/plugin_catalog_test.go @@ -50,8 +50,8 @@ func TestPluginCatalog_CRUD(t *testing.T) { } defer file.Close() - command := fmt.Sprintf("%s --test", filepath.Base(file.Name())) - err = core.pluginCatalog.Set("mysql-database-plugin", command, []byte{'1'}) + command := fmt.Sprintf("%s", filepath.Base(file.Name())) + err = core.pluginCatalog.Set("mysql-database-plugin", command, []string{"--test"}, []byte{'1'}) if err != nil { t.Fatal(err) } @@ -139,14 +139,14 @@ func TestPluginCatalog_List(t *testing.T) { } defer file.Close() - command := fmt.Sprintf("%s --test", filepath.Base(file.Name())) - err = core.pluginCatalog.Set("mysql-database-plugin", command, []byte{'1'}) + command := filepath.Base(file.Name()) + err = core.pluginCatalog.Set("mysql-database-plugin", command, []string{"--test"}, []byte{'1'}) if err != nil { t.Fatal(err) } // Set another plugin - err = core.pluginCatalog.Set("aaaaaaa", command, []byte{'1'}) + err = core.pluginCatalog.Set("aaaaaaa", command, []string{"--test"}, []byte{'1'}) if err != nil { t.Fatal(err) } diff --git a/vault/testing.go b/vault/testing.go index 6e033c3894c3..a841db1d6acb 100644 --- a/vault/testing.go +++ b/vault/testing.go @@ -409,8 +409,9 @@ func TestAddTestPlugin(t testing.T, c *Core, name, testFunc string) { c.pluginDirectory = directoryPath c.pluginCatalog.directory = directoryPath - command := fmt.Sprintf("%s --test.run=%s", filepath.Base(os.Args[0]), testFunc) - err = c.pluginCatalog.Set(name, command, sum) + command := fmt.Sprintf("%s", filepath.Base(os.Args[0])) + args := []string{fmt.Sprintf("--test.run=%s", testFunc)} + err = c.pluginCatalog.Set(name, command, args, sum) if err != nil { t.Fatal(err) } @@ -472,8 +473,9 @@ func TestAddTestPluginTempDir(t testing.T, c *Core, name, testFunc, tempDir stri c.pluginDirectory = fullPath c.pluginCatalog.directory = fullPath - command := fmt.Sprintf("%s --test.run=%s", filepath.Base(os.Args[0]), testFunc) - err = c.pluginCatalog.Set(name, command, sum) + command := fmt.Sprintf("%s", filepath.Base(os.Args[0])) + args := []string{fmt.Sprintf("--test.run=%s", testFunc)} + err = c.pluginCatalog.Set(name, command, args, sum) if err != nil { t.Fatal(err) }