diff --git a/command/plugin_register.go b/command/plugin_register.go index 577da80472..f9f503289e 100644 --- a/command/plugin_register.go +++ b/command/plugin_register.go @@ -33,6 +33,7 @@ type PluginRegisterCommand struct { flagOCIImage string flagRuntime string flagEnv []string + flagDownload bool } func (c *PluginRegisterCommand) Synopsis() string { @@ -59,6 +60,12 @@ Usage: vault plugin register [options] TYPE NAME -args=--with-glibc,--with-cgo \ auth my-custom-plugin + Register a plugin with -download (enterprise only): + + $ vault plugin register \ + -version=v0.17.0+ent \ + -download=true \ + secret vault-plugin-secrets-keymgmt ` + c.Flags().Help() return strings.TrimSpace(helpText) @@ -127,6 +134,14 @@ func (c *PluginRegisterCommand) Flags() *FlagSets { "flag can be specified multiple times to specify multiple environment variables.", }) + f.BoolVar(&BoolVar{ + Name: "download", + Target: &c.flagDownload, + Completion: complete.PredictAnything, + Usage: "Enterprise only. If set, Vault will automatically download plugins from" + + "releases.hashicorp.com", + }) + return set } @@ -198,6 +213,7 @@ func (c *PluginRegisterCommand) Run(args []string) int { OCIImage: c.flagOCIImage, Runtime: c.flagRuntime, Env: c.flagEnv, + Download: c.flagDownload, }) if err != nil { c.UI.Error(fmt.Sprintf("Error registering plugin %s: %s", pluginName, err)) diff --git a/vault/extended_system_view.go b/vault/extended_system_view.go index b8a0902500..8364d8d959 100644 --- a/vault/extended_system_view.go +++ b/vault/extended_system_view.go @@ -142,7 +142,3 @@ func (e extendedSystemViewImpl) DeregisterWellKnownRedirect(ctx context.Context, func (e extendedSystemViewImpl) GetPinnedPluginVersion(ctx context.Context, pluginType consts.PluginType, pluginName string) (*pluginutil.PinnedVersion, error) { return e.core.pluginCatalog.GetPinnedVersion(ctx, pluginType, pluginName) } - -func (e extendedSystemViewImpl) DownloadExtractVerifyPlugin(_ context.Context, _ *pluginutil.PluginRunner) error { - return fmt.Errorf("cannot call DownloadExtractVerifyPlugin from a plugin backend") -} diff --git a/vault/logical_system.go b/vault/logical_system.go index a36fbd8111..c77fdda534 100644 --- a/vault/logical_system.go +++ b/vault/logical_system.go @@ -538,6 +538,10 @@ func (b *SystemBackend) handlePluginCatalogUpdate(ctx context.Context, _ *logica return logical.ErrorResponse("version %q is not allowed because 'builtin' is a reserved metadata identifier", pluginVersion), nil } + if download := d.Get("download").(bool); download { + return logical.ErrorResponse("download is an enterprise only feature"), nil + } + sha256 := d.Get("sha256").(string) if sha256 == "" { sha256 = d.Get("sha_256").(string) @@ -6923,6 +6927,11 @@ Must already be present on the machine.`, `The Vault plugin runtime to use when running the plugin.`, "", }, + "plugin-catalog_download": { + `Automatically downloads official HashiCorp plugins +from releases.hashicorp.com (beta)`, + "", + }, "plugin-catalog-pins": { "Configures pinned plugin versions from the plugin catalog", ` diff --git a/vault/logical_system_oss_test.go b/vault/logical_system_oss_test.go new file mode 100644 index 0000000000..285d439b85 --- /dev/null +++ b/vault/logical_system_oss_test.go @@ -0,0 +1,69 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +//go:build !enterprise + +package vault + +import ( + "fmt" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/hashicorp/vault/helper/namespace" + "github.com/hashicorp/vault/sdk/helper/consts" + "github.com/hashicorp/vault/sdk/logical" +) + +// TestSystemBackend_PluginCatalog_Update_Download_Should_Fail tests the update failure +// cases when download is true +func TestSystemBackend_PluginCatalog_Update_Download_Should_Fail(t *testing.T) { + const expectedErrStr = "download is an enterprise only feature" + sym, err := filepath.EvalSymlinks(os.TempDir()) + if err != nil { + t.Fatalf("error: %v", err) + } + c, _, _ := TestCoreUnsealedWithConfig(t, &CoreConfig{ + PluginDirectory: sym, + }) + b := c.systemBackend + + tests := []struct { + pluginType consts.PluginType + pluginVersion string + pluginName string + }{ + { + pluginName: "vault-plugin-database-redis", + pluginVersion: "v0.6.0", + pluginType: consts.PluginTypeDatabase, + }, + { + pluginName: "vault-plugin-secrets-kv", + pluginVersion: "v0.24.0", + pluginType: consts.PluginTypeSecrets, + }, + { + pluginName: "vault-plugin-auth-jwt", + pluginVersion: "v0.24.1", + pluginType: consts.PluginTypeCredential, + }, + } + + for _, tt := range tests { + t.Run(fmt.Sprintf("%s %s", tt.pluginName, tt.pluginVersion), func(t *testing.T) { + req := logical.TestRequest(t, logical.UpdateOperation, + "plugins/catalog/"+tt.pluginType.String()+"/"+tt.pluginName) + req.Data["version"] = tt.pluginVersion + req.Data["download"] = true + resp, err := b.HandleRequest(namespace.RootContext(nil), req) + if err != nil || resp.Error() == nil { + t.Fatalf("expected error when download is true, got resp: %v, err: %v", resp, err) + } else if !strings.Contains(resp.Error().Error(), expectedErrStr) { + t.Fatalf("expected error %q, got resp: %v", expectedErrStr, resp) + } + }) + } +} diff --git a/vault/logical_system_paths.go b/vault/logical_system_paths.go index daac98b4d5..f5747a58e8 100644 --- a/vault/logical_system_paths.go +++ b/vault/logical_system_paths.go @@ -1891,6 +1891,10 @@ func (b *SystemBackend) pluginsCatalogCRUDPath() *framework.Path { Type: framework.TypeString, Description: strings.TrimSpace(sysHelp["plugin-catalog_version"][0]), }, + "download": { + Type: framework.TypeBool, + Description: strings.TrimSpace(sysHelp["plugin-catalog_download"][0]), + }, }, Operations: map[logical.Operation]framework.OperationHandler{