diff --git a/CHANGELOG.md b/CHANGELOG.md index 74e075aa1ab5..2257b32a2695 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,8 +22,8 @@ IMPROVEMENTS: * core: Added Password Policies for user-configurable password generation [[GH-8637](https://github.com/hashicorp/vault/pull/8637)] * core: New telemetry metrics covering token counts, token creation, KV secret counts, lease creation. [[GH-9239](https://github.com/hashicorp/vault/pull/9239)] [[GH-9250](https://github.com/hashicorp/vault/pull/9250)] [[GH-9244](https://github.com/hashicorp/vault/pull/9244)] [[GH-9052](https://github.com/hashicorp/vault/pull/9052)] * cli: Support reading TLS parameters from file for the `vault operator raft join` command. [[GH-9060](https://github.com/hashicorp/vault/pull/9060)] -* plugin: Add SDK method, `Sys.ReloadPlugin`, and CLI command, `vault plugin reload`, - for reloading plugins. [[GH-8777](https://github.com/hashicorp/vault/pull/8777)] +* plugin: Add SDK method, `Sys.ReloadPlugin`, and CLI command, `vault plugin reload`, for reloading plugins. [[GH-8777](https://github.com/hashicorp/vault/pull/8777)] +* plugin (enterprise): Add a scope field to plugin reload, which when global, reloads the plugin anywhere in a cluster. [[GH-9347](https://github.com/hashicorp/vault/pull/9347)] * sdk/framework: Support accepting TypeFloat parameters over the API [[GH-8923](https://github.com/hashicorp/vault/pull/8923)] * secrets/aws: Add iam_groups parameter to role create/update [[GH-8811](https://github.com/hashicorp/vault/pull/8811)] * secrets/database: Add static role rotation for MongoDB Atlas database plugin [[GH-11](https://github.com/hashicorp/vault-plugin-database-mongodbatlas/pull/11)] diff --git a/api/sys_plugins.go b/api/sys_plugins.go index 01127e01bef8..d90bcd0ab337 100644 --- a/api/sys_plugins.go +++ b/api/sys_plugins.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "net/http" + "time" "github.com/hashicorp/vault/sdk/helper/consts" "github.com/mitchellh/mapstructure" @@ -232,15 +233,19 @@ type ReloadPluginInput struct { // Mounts is the array of string mount paths of the plugin backends to reload Mounts []string `json:"mounts"` + + // Scope is the scope of the plugin reload + Scope string `json:"scope"` } -// ReloadPlugin reloads mounted plugin backends -func (c *Sys) ReloadPlugin(i *ReloadPluginInput) error { +// ReloadPlugin reloads mounted plugin backends, possibly returning +// reloadId for a cluster scoped reload +func (c *Sys) ReloadPlugin(i *ReloadPluginInput) (string, error) { path := "/v1/sys/plugins/reload/backend" req := c.c.NewRequest(http.MethodPut, path) if err := req.SetJSONBody(i); err != nil { - return err + return "", err } ctx, cancelFunc := context.WithCancel(context.Background()) @@ -248,10 +253,77 @@ func (c *Sys) ReloadPlugin(i *ReloadPluginInput) error { resp, err := c.c.RawRequestWithContext(ctx, req) if err != nil { - return err + return "", err } defer resp.Body.Close() - return err + + if i.Scope == "global" { + // Get the reload id + secret, parseErr := ParseSecret(resp.Body) + if parseErr != nil { + return "", parseErr + } + if _, ok := secret.Data["reload_id"]; ok { + return secret.Data["reload_id"].(string), nil + } + } + return "", err +} + +// ReloadStatus is the status of an individual node's plugin reload +type ReloadStatus struct { + Timestamp time.Time `json:"timestamp" mapstructure:"timestamp"` + Error string `json:"error" mapstructure:"error"` +} + +// ReloadStatusResponse is the combined response of all known completed plugin reloads +type ReloadStatusResponse struct { + ReloadID string `mapstructure:"reload_id"` + Results map[string]*ReloadStatus `mapstructure:"results"` +} + +// ReloadPluginStatusInput is used as input to the ReloadStatusPlugin function. +type ReloadPluginStatusInput struct { + // ReloadID is the ID of the reload operation + ReloadID string `json:"reload_id"` +} + +// ReloadPluginStatus retrieves the status of a reload operation +func (c *Sys) ReloadPluginStatus(reloadStatusInput *ReloadPluginStatusInput) (*ReloadStatusResponse, error) { + path := "/v1/sys/plugins/reload/backend/status" + req := c.c.NewRequest(http.MethodGet, path) + req.Params.Add("reload_id", reloadStatusInput.ReloadID) + + ctx, cancelFunc := context.WithCancel(context.Background()) + defer cancelFunc() + + resp, err := c.c.RawRequestWithContext(ctx, req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + if resp != nil { + secret, parseErr := ParseSecret(resp.Body) + if parseErr != nil { + return nil, err + } + + var r ReloadStatusResponse + d, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ + DecodeHook: mapstructure.StringToTimeHookFunc(time.RFC3339), + Result: &r, + }) + if err != nil { + return nil, err + } + err = d.Decode(secret.Data) + if err != nil { + return nil, err + } + return &r, nil + } + return nil, nil + } // catalogPathByType is a helper to construct the proper API path by plugin type diff --git a/command/commands.go b/command/commands.go index a679303fa056..581d615ef0d7 100644 --- a/command/commands.go +++ b/command/commands.go @@ -447,6 +447,11 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) { BaseCommand: getBaseCommand(), }, nil }, + "plugin reload-status": func() (cli.Command, error) { + return &PluginReloadStatusCommand{ + BaseCommand: getBaseCommand(), + }, nil + }, "policy": func() (cli.Command, error) { return &PolicyCommand{ BaseCommand: getBaseCommand(), diff --git a/command/plugin_reload.go b/command/plugin_reload.go index d02553872004..06f7a5032800 100644 --- a/command/plugin_reload.go +++ b/command/plugin_reload.go @@ -16,6 +16,7 @@ type PluginReloadCommand struct { *BaseCommand plugin string mounts []string + scope string } func (c *PluginReloadCommand) Synopsis() string { @@ -58,6 +59,13 @@ func (c *PluginReloadCommand) Flags() *FlagSets { Usage: "Array or comma-separated string mount paths of the plugin backends to reload.", }) + f.StringVar(&StringVar{ + Name: "scope", + Target: &c.scope, + Completion: complete.PredictAnything, + Usage: "The scope of the reload, omitted for local, 'global', for replicated reloads", + }) + return set } @@ -84,6 +92,8 @@ func (c *PluginReloadCommand) Run(args []string) int { case c.plugin != "" && len(c.mounts) > 0: c.UI.Error(fmt.Sprintf("Too many arguments (expected 1, got %d)", len(args))) return 1 + case c.scope != "" && c.scope != "global": + c.UI.Error(fmt.Sprintf("Invalid reload scope: %s", c.scope)) } client, err := c.Client() @@ -92,18 +102,28 @@ func (c *PluginReloadCommand) Run(args []string) int { return 2 } - if err := client.Sys().ReloadPlugin(&api.ReloadPluginInput{ + rid, err := client.Sys().ReloadPlugin(&api.ReloadPluginInput{ Plugin: c.plugin, Mounts: c.mounts, - }); err != nil { + Scope: c.scope, + }) + if err != nil { c.UI.Error(fmt.Sprintf("Error reloading plugin/mounts: %s", err)) return 2 } if len(c.mounts) > 0 { - c.UI.Output(fmt.Sprintf("Success! Reloaded mounts: %s", c.mounts)) + if rid != "" { + c.UI.Output(fmt.Sprintf("Success! Reloading mounts: %s, reload_id: %s", c.mounts, rid)) + } else { + c.UI.Output(fmt.Sprintf("Success! Reloaded mounts: %s", c.mounts)) + } } else { - c.UI.Output(fmt.Sprintf("Success! Reloaded plugin: %s", c.plugin)) + if rid != "" { + c.UI.Output(fmt.Sprintf("Success! Reloading plugin: %s, reload_id: %s", c.plugin, rid)) + } else { + c.UI.Output(fmt.Sprintf("Success! Reloaded plugin: %s", c.plugin)) + } } return 0 diff --git a/command/plugin_reload_status.go b/command/plugin_reload_status.go new file mode 100644 index 000000000000..4579b5e89797 --- /dev/null +++ b/command/plugin_reload_status.go @@ -0,0 +1,91 @@ +package command + +import ( + "fmt" + "github.com/hashicorp/vault/api" + "github.com/mitchellh/cli" + "github.com/posener/complete" + "strings" +) + +var _ cli.Command = (*PluginReloadCommand)(nil) +var _ cli.CommandAutocomplete = (*PluginReloadCommand)(nil) + +type PluginReloadStatusCommand struct { + *BaseCommand +} + +func (c *PluginReloadStatusCommand) Synopsis() string { + return "Get the status of an active or recently completed global plugin reload" +} + +func (c *PluginReloadStatusCommand) Help() string { + helpText := ` +Usage: vault plugin reload-status RELOAD_ID + + Retrieves the status of a recent cluster plugin reload. The reload id must be provided. + + $ vault plugin reload-status d60a3e83-a598-4f3a-879d-0ddd95f11d4e + +` + c.Flags().Help() + + return strings.TrimSpace(helpText) +} + +func (c *PluginReloadStatusCommand) Flags() *FlagSets { + return c.flagSet(FlagSetHTTP) +} + +func (c *PluginReloadStatusCommand) AutocompleteArgs() complete.Predictor { + return complete.PredictNothing +} + +func (c *PluginReloadStatusCommand) AutocompleteFlags() complete.Flags { + return c.Flags().Completions() +} + +func (c *PluginReloadStatusCommand) Run(args []string) int { + f := c.Flags() + + if err := f.Parse(args); err != nil { + c.UI.Error(err.Error()) + return 1 + } + + args = f.Args() + switch { + case len(args) < 1: + c.UI.Error(fmt.Sprintf("Not enough arguments (expected 1, got %d)", len(args))) + return 1 + case len(args) > 1: + c.UI.Error(fmt.Sprintf("Too many arguments (expected 1, got %d)", len(args))) + return 1 + } + + reloadId := strings.TrimSpace(args[0]) + + client, err := c.Client() + if err != nil { + c.UI.Error(err.Error()) + return 2 + } + + r, err := client.Sys().ReloadPluginStatus(&api.ReloadPluginStatusInput{ + ReloadID: reloadId, + }) + + if err != nil { + c.UI.Error(fmt.Sprintf("Error retrieving plugin reload status: %s", err)) + return 2 + } + out := []string{"Time | Participant | Success | Message "} + for i, s := range r.Results { + out = append(out, fmt.Sprintf("%s | %s | %t | %s ", + s.Timestamp.Format("15:04:05"), + i, + s.Error == "", + s.Error)) + } + c.UI.Output(tableOutput(out, nil)) + return 0 +} diff --git a/command/plugin_reload_test.go b/command/plugin_reload_test.go index 2622fb00633b..1f7191a5c1ae 100644 --- a/command/plugin_reload_test.go +++ b/command/plugin_reload_test.go @@ -20,6 +20,17 @@ func testPluginReloadCommand(tb testing.TB) (*cli.MockUi, *PluginReloadCommand) } } +func testPluginReloadStatusCommand(tb testing.TB) (*cli.MockUi, *PluginReloadStatusCommand) { + tb.Helper() + + ui := cli.NewMockUi() + return ui, &PluginReloadStatusCommand{ + BaseCommand: &BaseCommand{ + UI: ui, + }, + } +} + func TestPluginReloadCommand_Run(t *testing.T) { t.Parallel() @@ -108,3 +119,46 @@ func TestPluginReloadCommand_Run(t *testing.T) { }) } + +func TestPluginReloadStatusCommand_Run(t *testing.T) { + t.Parallel() + + cases := []struct { + name string + args []string + out string + code int + }{ + { + "not_enough_args", + nil, + "Not enough arguments", + 1, + }, + } + + for _, tc := range cases { + tc := tc + + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + client, closer := testVaultServer(t) + defer closer() + + ui, cmd := testPluginReloadCommand(t) + cmd.client = client + + args := append([]string{}, tc.args...) + code := cmd.Run(args) + if code != tc.code { + t.Errorf("expected %d to be %d", code, tc.code) + } + + combined := ui.OutputWriter.String() + ui.ErrorWriter.String() + if !strings.Contains(combined, tc.out) { + t.Errorf("expected %q to contain %q", combined, tc.out) + } + }) + } +} diff --git a/go.mod b/go.mod index 3c672727ab7f..47fe336ff052 100644 --- a/go.mod +++ b/go.mod @@ -83,8 +83,7 @@ require ( github.com/hashicorp/vault-plugin-secrets-gcpkms v0.5.6 github.com/hashicorp/vault-plugin-secrets-kv v0.5.6 github.com/hashicorp/vault-plugin-secrets-mongodbatlas v0.1.2 - github.com/hashicorp/vault-plugin-secrets-openldap v0.1.4 - github.com/hashicorp/vault/api v1.0.5-0.20200519221902-385fac77e20f + github.com/hashicorp/vault/api v1.0.5-0.20200630205458-1a16f3c699c6 github.com/hashicorp/vault/sdk v0.1.14-0.20200527182800-ad90e0b39d2f github.com/influxdata/influxdb v0.0.0-20190411212539-d24b7ba8c4c4 github.com/jcmturner/gokrb5/v8 v8.0.0 diff --git a/go.sum b/go.sum index cbad7d26a336..539b975d64d7 100644 --- a/go.sum +++ b/go.sum @@ -524,6 +524,7 @@ github.com/hashicorp/vault-plugin-auth-alicloud v0.5.5 h1:JYf3VYpKs7mOdtcwZWi73S github.com/hashicorp/vault-plugin-auth-alicloud v0.5.5/go.mod h1:sQ+VNwPQlemgXHXikYH6onfH9gPwDZ1GUVRLz0ZvHx8= github.com/hashicorp/vault-plugin-auth-azure v0.5.5 h1:kN79ai+aMVU9hUmwscHjmweW2fGa8V/t+ScIchPZGrk= github.com/hashicorp/vault-plugin-auth-azure v0.5.5/go.mod h1:RCVBsf8AJndh4c6iGZtvVZFui9SG0Bj9fnF0SodNIkw= +github.com/hashicorp/vault-plugin-auth-azure v0.5.6-0.20200422235613-1b5c70f9ef68 h1:o4ekpvOmfRxCYE7+g4dV6FQc9H+Sl1jv4JoGDrLDKt0= github.com/hashicorp/vault-plugin-auth-azure v0.5.6-0.20200422235613-1b5c70f9ef68/go.mod h1:RCVBsf8AJndh4c6iGZtvVZFui9SG0Bj9fnF0SodNIkw= github.com/hashicorp/vault-plugin-auth-azure v0.5.6 h1:yg1zVb0zf5dkCz68sVwO9Y258NkF4kDKFCZHvwidKpo= github.com/hashicorp/vault-plugin-auth-azure v0.5.6/go.mod h1:xUpcusehlkR1cNbA+wNta9W4slS64pYe7CXx5UYM2OQ= @@ -534,6 +535,7 @@ github.com/hashicorp/vault-plugin-auth-cf v0.5.4/go.mod h1:idkFYHc6ske2BE7fe00Sp github.com/hashicorp/vault-plugin-auth-gcp v0.5.1/go.mod h1:eLj92eX8MPI4vY1jaazVLF2sVbSAJ3LRHLRhF/pUmlI= github.com/hashicorp/vault-plugin-auth-gcp v0.6.1 h1:WXTuja3WC2BdZekYCnzuZGoVvZTAGH8kSDUHzOK2PQY= github.com/hashicorp/vault-plugin-auth-gcp v0.6.1/go.mod h1:8eBRzg+JIhAaDBfDndDAQKIhDrQ3WW8OPklxAYftNFs= +github.com/hashicorp/vault-plugin-auth-gcp v0.6.2-0.20200428223335-82bd3a3ad5b3 h1:GTQYSsqv/2jjdTm0DawBSljMmZTc/js6zLer+9A5e2U= github.com/hashicorp/vault-plugin-auth-gcp v0.6.2-0.20200428223335-82bd3a3ad5b3/go.mod h1:U0fkAlxWTEyQ74lx8wlGdD493lP1DD/qpMjXgOEbwj0= github.com/hashicorp/vault-plugin-auth-gcp v0.6.2 h1:3g3xNzHI9A6Zb14Zz0mCDp8OXCzkePwcVYhXaAMAqLc= github.com/hashicorp/vault-plugin-auth-gcp v0.6.2/go.mod h1:sHDguHmyGScoalGLEjuxvDCrMPVlw2c3f+ieeiHcv6w= @@ -554,6 +556,7 @@ github.com/hashicorp/vault-plugin-database-elasticsearch v0.5.4 h1:YE4qndazWmYGp github.com/hashicorp/vault-plugin-database-elasticsearch v0.5.4/go.mod h1:QjGrrxcRXv/4XkEZAlM0VMZEa3uxKAICFqDj27FP/48= github.com/hashicorp/vault-plugin-database-mongodbatlas v0.1.0-beta1.0.20200521152755-9cf156a44f9c h1:9pXwe7sEVhZ5C3U6egIrKaZBb5lD0FvLIjISEvpbQQA= github.com/hashicorp/vault-plugin-database-mongodbatlas v0.1.0-beta1.0.20200521152755-9cf156a44f9c/go.mod h1:HTXNzFr/SAVtJOs7jz0XxZ69jlKtaceEwp37l86UAQ0= +github.com/hashicorp/vault-plugin-database-mongodbatlas v0.1.2-0.20200520204052-f840e9d4895c h1:P9rZXBJx+UHu/T8lK8NEtS2PGeSnyZ31zeOtkvGo4yo= github.com/hashicorp/vault-plugin-database-mongodbatlas v0.1.2-0.20200520204052-f840e9d4895c/go.mod h1:MP3kfr0N+7miOTZFwKv952b9VkXM4S2Q6YtQCiNKWq8= github.com/hashicorp/vault-plugin-database-mongodbatlas v0.1.2 h1:tSToR3JRARqQkV9B10rk4VlZe2Sr9fOdhEP2NdxPo0I= github.com/hashicorp/vault-plugin-database-mongodbatlas v0.1.2/go.mod h1:HTXNzFr/SAVtJOs7jz0XxZ69jlKtaceEwp37l86UAQ0= diff --git a/vault/core.go b/vault/core.go index a376d10caf7d..1bd5a81fa873 100644 --- a/vault/core.go +++ b/vault/core.go @@ -1943,6 +1943,13 @@ func (s standardUnsealStrategy) unseal(ctx context.Context, logger log.Logger, c c.auditBroker = NewAuditBroker(c.logger) } + if !c.ReplicationState().HasState(consts.ReplicationPerformanceSecondary | consts.ReplicationDRSecondary) { + //Cannot do this above, as we need other resources like mounts to be setup + if err := c.setupPluginReload(); err != nil { + return err + } + } + if c.getClusterListener() != nil && (c.ha != nil || shouldStartClusterListener(c)) { if err := c.setupRaftActiveNode(ctx); err != nil { return err diff --git a/vault/logical_system.go b/vault/logical_system.go index f3bfef87973b..e9f537eeed3f 100644 --- a/vault/logical_system.go +++ b/vault/logical_system.go @@ -40,6 +40,7 @@ import ( ) const maxBytes = 128 * 1024 +const globalScope = "global" func systemBackendMemDBSchema() *memdb.DBSchema { systemSchema := &memdb.DBSchema{ @@ -436,6 +437,11 @@ func (b *SystemBackend) handlePluginCatalogDelete(ctx context.Context, req *logi func (b *SystemBackend) handlePluginReloadUpdate(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { pluginName := d.Get("plugin").(string) pluginMounts := d.Get("mounts").([]string) + scope := d.Get("scope").(string) + + if scope != "" && scope != globalScope { + return logical.ErrorResponse("reload scope must be omitted or 'global'"), nil + } if pluginName != "" && len(pluginMounts) > 0 { return logical.ErrorResponse("plugin and mounts cannot be set at the same time"), nil @@ -456,7 +462,20 @@ func (b *SystemBackend) handlePluginReloadUpdate(ctx context.Context, req *logic } } - return nil, nil + r := logical.Response{ + Data: map[string]interface{}{ + "reload_id": req.ID, + }, + } + + if scope == globalScope { + err := handleGlobalPluginReload(ctx, b.Core, req.ID, pluginName, pluginMounts) + if err != nil { + return nil, err + } + return logical.RespondWithStatusCode(&r, req, http.StatusAccepted) + } + return &r, nil } // handleAuditedHeaderUpdate creates or overwrites a header entry diff --git a/vault/logical_system_helpers.go b/vault/logical_system_helpers.go index 14cee1f0dfe6..d2e27eba7e6e 100644 --- a/vault/logical_system_helpers.go +++ b/vault/logical_system_helpers.go @@ -84,6 +84,12 @@ var ( }, } } + handleGlobalPluginReload = func(context.Context, *Core, string, string, []string) error { + return nil + } + handleSetupPluginReload = func(*Core) error { + return nil + } checkRaw = func(b *SystemBackend, path string) error { return nil } ) diff --git a/vault/logical_system_integ_test.go b/vault/logical_system_integ_test.go index fef2a44ebc6c..3ca1846bea72 100644 --- a/vault/logical_system_integ_test.go +++ b/vault/logical_system_integ_test.go @@ -467,9 +467,12 @@ func testSystemBackend_PluginReload(t *testing.T, reqData map[string]interface{} if err != nil { t.Fatalf("err: %v", err) } - if resp != nil { + if resp == nil { t.Fatalf("bad: %v", resp) } + if resp.Data["reload_id"] == nil { + t.Fatal("no reload_id in response") + } for i := 0; i < 2; i++ { // Ensure internal backed value is reset diff --git a/vault/logical_system_paths.go b/vault/logical_system_paths.go index eb36e4f37f15..10787ebad61e 100644 --- a/vault/logical_system_paths.go +++ b/vault/logical_system_paths.go @@ -708,13 +708,17 @@ func (b *SystemBackend) pluginsReloadPath() *framework.Path { Type: framework.TypeCommaStringSlice, Description: strings.TrimSpace(sysHelp["plugin-backend-reload-mounts"][0]), }, + "scope": &framework.FieldSchema{ + Type: framework.TypeString, + Description: strings.TrimSpace(sysHelp["plugin-backend-reload-scope"][0]), + }, }, Operations: map[logical.Operation]framework.OperationHandler{ logical.UpdateOperation: &framework.PathOperation{ Callback: b.handlePluginReloadUpdate, Summary: "Reload mounted plugin backends.", - Description: "Either the plugin name (`plugin`) or the desired plugin backend mounts (`mounts`) must be provided, but not both. In the case that the plugin name is provided, all mounted paths that use that plugin backend will be reloaded.", + Description: "Either the plugin name (`plugin`) or the desired plugin backend mounts (`mounts`) must be provided, but not both. In the case that the plugin name is provided, all mounted paths that use that plugin backend will be reloaded. If (`scope`) is provided and is (`global`), the plugin(s) are reloaded globally.", }, }, diff --git a/vault/plugin_reload.go b/vault/plugin_reload.go index 3b38c1a4415d..0d94623e7c01 100644 --- a/vault/plugin_reload.go +++ b/vault/plugin_reload.go @@ -195,3 +195,7 @@ func (c *Core) reloadBackendCommon(ctx context.Context, entry *MountEntry, isAut return nil } + +func (c *Core) setupPluginReload() error { + return handleSetupPluginReload(c) +} diff --git a/vendor/github.com/hashicorp/vault-plugin-database-mongodbatlas/go.sum b/vendor/github.com/hashicorp/vault-plugin-database-mongodbatlas/go.sum index b517dfca9b0a..5c5a45f55bbc 100644 --- a/vendor/github.com/hashicorp/vault-plugin-database-mongodbatlas/go.sum +++ b/vendor/github.com/hashicorp/vault-plugin-database-mongodbatlas/go.sum @@ -75,12 +75,12 @@ github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/vault/api v1.0.5-0.20200215224050-f6547fa8e820 h1:biZidYDDEWnuOI9mXnJre8lwHKhb5ym85aSXk3oz/dc= -github.com/hashicorp/vault/api v1.0.5-0.20200215224050-f6547fa8e820/go.mod h1:3f12BMfgDGjTsTtIUj+ZKZwSobQpZtYGFIEehOv5z1o= +github.com/hashicorp/vault/api v1.0.5-0.20200317185738-82f498082f02 h1:OGEV0U0+lb8SP5aZA1m456Sr3MYxFel2awVr55QRri0= +github.com/hashicorp/vault/api v1.0.5-0.20200317185738-82f498082f02/go.mod h1:3f12BMfgDGjTsTtIUj+ZKZwSobQpZtYGFIEehOv5z1o= github.com/hashicorp/vault/sdk v0.1.14-0.20200215195600-2ca765f0a500 h1:tiMX2ewq4ble+e2zENzBvaH2dMoFHe80NbnrF5Ir9Kk= github.com/hashicorp/vault/sdk v0.1.14-0.20200215195600-2ca765f0a500/go.mod h1:WX57W2PwkrOPQ6rVQk+dy5/htHIaB4aBM70EwKThu10= -github.com/hashicorp/vault/sdk v0.1.14-0.20200215224050-f6547fa8e820 h1:TmDZ1sS6gU0hFeFlFuyJVUwRPEzifZIHCBeS2WF2uSc= -github.com/hashicorp/vault/sdk v0.1.14-0.20200215224050-f6547fa8e820/go.mod h1:WX57W2PwkrOPQ6rVQk+dy5/htHIaB4aBM70EwKThu10= +github.com/hashicorp/vault/sdk v0.1.14-0.20200317185738-82f498082f02 h1:vVrOAVfunVvkTkE9iF3Fe1+PGPLwGIp3nP4qgHGrHFs= +github.com/hashicorp/vault/sdk v0.1.14-0.20200317185738-82f498082f02/go.mod h1:WX57W2PwkrOPQ6rVQk+dy5/htHIaB4aBM70EwKThu10= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d h1:kJCB4vdITiW1eC1vq2e6IsrXKrZit1bv/TDYFGMp4BQ= github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= diff --git a/vendor/github.com/hashicorp/vault/api/sys_plugins.go b/vendor/github.com/hashicorp/vault/api/sys_plugins.go index 01127e01bef8..d90bcd0ab337 100644 --- a/vendor/github.com/hashicorp/vault/api/sys_plugins.go +++ b/vendor/github.com/hashicorp/vault/api/sys_plugins.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "net/http" + "time" "github.com/hashicorp/vault/sdk/helper/consts" "github.com/mitchellh/mapstructure" @@ -232,15 +233,19 @@ type ReloadPluginInput struct { // Mounts is the array of string mount paths of the plugin backends to reload Mounts []string `json:"mounts"` + + // Scope is the scope of the plugin reload + Scope string `json:"scope"` } -// ReloadPlugin reloads mounted plugin backends -func (c *Sys) ReloadPlugin(i *ReloadPluginInput) error { +// ReloadPlugin reloads mounted plugin backends, possibly returning +// reloadId for a cluster scoped reload +func (c *Sys) ReloadPlugin(i *ReloadPluginInput) (string, error) { path := "/v1/sys/plugins/reload/backend" req := c.c.NewRequest(http.MethodPut, path) if err := req.SetJSONBody(i); err != nil { - return err + return "", err } ctx, cancelFunc := context.WithCancel(context.Background()) @@ -248,10 +253,77 @@ func (c *Sys) ReloadPlugin(i *ReloadPluginInput) error { resp, err := c.c.RawRequestWithContext(ctx, req) if err != nil { - return err + return "", err } defer resp.Body.Close() - return err + + if i.Scope == "global" { + // Get the reload id + secret, parseErr := ParseSecret(resp.Body) + if parseErr != nil { + return "", parseErr + } + if _, ok := secret.Data["reload_id"]; ok { + return secret.Data["reload_id"].(string), nil + } + } + return "", err +} + +// ReloadStatus is the status of an individual node's plugin reload +type ReloadStatus struct { + Timestamp time.Time `json:"timestamp" mapstructure:"timestamp"` + Error string `json:"error" mapstructure:"error"` +} + +// ReloadStatusResponse is the combined response of all known completed plugin reloads +type ReloadStatusResponse struct { + ReloadID string `mapstructure:"reload_id"` + Results map[string]*ReloadStatus `mapstructure:"results"` +} + +// ReloadPluginStatusInput is used as input to the ReloadStatusPlugin function. +type ReloadPluginStatusInput struct { + // ReloadID is the ID of the reload operation + ReloadID string `json:"reload_id"` +} + +// ReloadPluginStatus retrieves the status of a reload operation +func (c *Sys) ReloadPluginStatus(reloadStatusInput *ReloadPluginStatusInput) (*ReloadStatusResponse, error) { + path := "/v1/sys/plugins/reload/backend/status" + req := c.c.NewRequest(http.MethodGet, path) + req.Params.Add("reload_id", reloadStatusInput.ReloadID) + + ctx, cancelFunc := context.WithCancel(context.Background()) + defer cancelFunc() + + resp, err := c.c.RawRequestWithContext(ctx, req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + if resp != nil { + secret, parseErr := ParseSecret(resp.Body) + if parseErr != nil { + return nil, err + } + + var r ReloadStatusResponse + d, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ + DecodeHook: mapstructure.StringToTimeHookFunc(time.RFC3339), + Result: &r, + }) + if err != nil { + return nil, err + } + err = d.Decode(secret.Data) + if err != nil { + return nil, err + } + return &r, nil + } + return nil, nil + } // catalogPathByType is a helper to construct the proper API path by plugin type diff --git a/vendor/modules.txt b/vendor/modules.txt index 4f1d7438ad1f..ebfdc40da0dd 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -492,7 +492,7 @@ github.com/hashicorp/vault-plugin-secrets-gcpkms github.com/hashicorp/vault-plugin-secrets-kv # github.com/hashicorp/vault-plugin-secrets-mongodbatlas v0.1.2 github.com/hashicorp/vault-plugin-secrets-mongodbatlas -# github.com/hashicorp/vault-plugin-secrets-openldap v0.1.4 +# github.com/hashicorp/vault-plugin-secrets-openldap v0.1.3-0.20200518214608-746aba5fead6 github.com/hashicorp/vault-plugin-secrets-openldap github.com/hashicorp/vault-plugin-secrets-openldap/client # github.com/hashicorp/vault/api v1.0.5-0.20200630205458-1a16f3c699c6 => ./api