From 54f16a57344509f96f119bb526a5e5cd89f542e0 Mon Sep 17 00:00:00 2001 From: Thy Ton Date: Wed, 4 Dec 2024 22:03:27 -0800 Subject: [PATCH 01/25] add EntCoreGetter interface as a field in PluginCatalog --- vault/plugincatalog/plugin_catalog.go | 39 ++++++++++++++++++--------- 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/vault/plugincatalog/plugin_catalog.go b/vault/plugincatalog/plugin_catalog.go index b2b61f54bb7a..cfb6a7177e63 100644 --- a/vault/plugincatalog/plugin_catalog.go +++ b/vault/plugincatalog/plugin_catalog.go @@ -45,12 +45,18 @@ var ( ErrPluginUnableToRun = errors.New("unable to run plugin") ) +// EntCoreGetter is an interface that allows the plugin catalog to only access entCore getter functions +type EntCoreGetter interface { + EntGetLicense() (string, error) +} + // 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 { builtinRegistry BuiltinRegistry catalogView logical.Storage + entCoreGetter EntCoreGetter directory string tmpdir string logger log.Logger @@ -147,6 +153,7 @@ type PluginCatalogInput struct { Logger log.Logger BuiltinRegistry BuiltinRegistry CatalogView logical.Storage + EntCoreGetter EntCoreGetter PluginDirectory string Tmpdir string EnableMlock bool @@ -158,6 +165,7 @@ func SetupPluginCatalog(ctx context.Context, in *PluginCatalogInput) (*PluginCat catalog := &PluginCatalog{ builtinRegistry: in.BuiltinRegistry, catalogView: in.CatalogView, + entCoreGetter: in.EntCoreGetter, directory: in.PluginDirectory, tmpdir: in.Tmpdir, logger: logger, @@ -961,21 +969,27 @@ func (c *PluginCatalog) Set(ctx context.Context, plugin pluginutil.SetPluginInpu func (c *PluginCatalog) setInternal(ctx context.Context, plugin pluginutil.SetPluginInput) (*pluginutil.PluginRunner, error) { command := plugin.Command + if plugin.OCIImage == "" { - // Best effort check to make sure the command isn't breaking out of the - // configured plugin directory. command = filepath.Join(c.directory, plugin.Command) - sym, err := filepath.EvalSymlinks(command) - if err != nil { - return nil, fmt.Errorf("error while validating the command path: %w", err) - } - symAbs, err := filepath.Abs(filepath.Dir(sym)) - if err != nil { - return nil, fmt.Errorf("error while validating the command path: %w", err) - } - if symAbs != c.directory { - return nil, errors.New("cannot execute files outside of configured plugin directory") + if _, err := os.Stat(command); os.IsNotExist(err) { + // Best effort to unpack the plugin artifact + } else { + // Best effort check to make sure the command isn't breaking out of the + // configured plugin directory. + sym, err := filepath.EvalSymlinks(command) + if err != nil { + return nil, fmt.Errorf("error while validating the command path: %w", err) + } + symAbs, err := filepath.Abs(filepath.Dir(sym)) + if err != nil { + return nil, fmt.Errorf("error while validating the command path: %w", err) + } + + if symAbs != c.directory { + return nil, errors.New("cannot execute files outside of configured plugin directory") + } } } @@ -992,6 +1006,7 @@ func (c *PluginCatalog) setInternal(ctx context.Context, plugin pluginutil.SetPl Sha256: plugin.Sha256, Builtin: false, } + if entryTmp.OCIImage != "" && entryTmp.Runtime != "" { var err error entryTmp.RuntimeConfig, err = c.runtimeCatalog.Get(ctx, entryTmp.Runtime, consts.PluginRuntimeTypeContainer) From f4b2b4b18c821041bdb7e72eca360a124f75667e Mon Sep 17 00:00:00 2001 From: Thy Ton Date: Thu, 5 Dec 2024 10:08:05 -0800 Subject: [PATCH 02/25] add EntPluginRunner as composing struct and Enterprise bool field to PluginRunner --- sdk/helper/pluginutil/runner.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sdk/helper/pluginutil/runner.go b/sdk/helper/pluginutil/runner.go index ebbe110c3474..bbbb7fd32dd4 100644 --- a/sdk/helper/pluginutil/runner.go +++ b/sdk/helper/pluginutil/runner.go @@ -54,9 +54,12 @@ type PluginClient interface { const MultiplexingCtxKey string = "multiplex_id" +type EntPluginRunner struct{} + // PluginRunner defines the metadata needed to run a plugin securely with // go-plugin. type PluginRunner struct { + EntPluginRunner Name string `json:"name" structs:"name"` Type consts.PluginType `json:"type" structs:"type"` Version string `json:"version" structs:"version"` @@ -67,6 +70,7 @@ type PluginRunner struct { Env []string `json:"env" structs:"env"` Sha256 []byte `json:"sha256" structs:"sha256"` Builtin bool `json:"builtin" structs:"builtin"` + Enterprise bool `json:"enterprise" structs:"enterprise"` BuiltinFactory func() (interface{}, error) `json:"-" structs:"-"` RuntimeConfig *prutil.PluginRuntimeConfig `json:"-" structs:"-"` Tmpdir string `json:"-" structs:"-"` From 4c27e2c548d2ed0e790d7901cc27dd1916e979b8 Mon Sep 17 00:00:00 2001 From: Thy Ton Date: Thu, 5 Dec 2024 10:12:31 -0800 Subject: [PATCH 03/25] add unpackPluginArtifact() and setEntPluginRunner() oss stubs to PluginCatalog --- vault/plugincatalog/plugin_catalog.go | 49 +++++++++++-------- .../plugincatalog/plugin_catalog_stubs_oss.go | 19 +++++++ 2 files changed, 48 insertions(+), 20 deletions(-) create mode 100644 vault/plugincatalog/plugin_catalog_stubs_oss.go diff --git a/vault/plugincatalog/plugin_catalog.go b/vault/plugincatalog/plugin_catalog.go index cfb6a7177e63..f1e2a6e59edf 100644 --- a/vault/plugincatalog/plugin_catalog.go +++ b/vault/plugincatalog/plugin_catalog.go @@ -969,12 +969,17 @@ func (c *PluginCatalog) Set(ctx context.Context, plugin pluginutil.SetPluginInpu func (c *PluginCatalog) setInternal(ctx context.Context, plugin pluginutil.SetPluginInput) (*pluginutil.PluginRunner, error) { command := plugin.Command + var enterprise bool if plugin.OCIImage == "" { command = filepath.Join(c.directory, plugin.Command) if _, err := os.Stat(command); os.IsNotExist(err) { // Best effort to unpack the plugin artifact + enterprise, command, err = c.unpackPluginArtifact(ctx, plugin) + if err != nil { + return nil, fmt.Errorf("failed to unpack plugin artifact: %w", err) + } } else { // Best effort check to make sure the command isn't breaking out of the // configured plugin directory. @@ -997,15 +1002,17 @@ func (c *PluginCatalog) setInternal(ctx context.Context, plugin pluginutil.SetPl // full command instead of the relative command because get() normally prepends // the plugin directory to the command, but we can't use get() here. entryTmp := &pluginutil.PluginRunner{ - Name: plugin.Name, - Command: command, - OCIImage: plugin.OCIImage, - Runtime: plugin.Runtime, - Args: plugin.Args, - Env: plugin.Env, - Sha256: plugin.Sha256, - Builtin: false, - } + Name: plugin.Name, + Command: command, + OCIImage: plugin.OCIImage, + Runtime: plugin.Runtime, + Args: plugin.Args, + Env: plugin.Env, + Sha256: plugin.Sha256, + Builtin: false, + Enterprise: enterprise, + } + c.setEntPluginRunner(ctx, entryTmp) if entryTmp.OCIImage != "" && entryTmp.Runtime != "" { var err error @@ -1056,17 +1063,19 @@ func (c *PluginCatalog) setInternal(ctx context.Context, plugin pluginutil.SetPl } entry := &pluginutil.PluginRunner{ - Name: plugin.Name, - Type: plugin.Type, - Version: plugin.Version, - Command: plugin.Command, - OCIImage: plugin.OCIImage, - Runtime: plugin.Runtime, - Args: plugin.Args, - Env: plugin.Env, - Sha256: plugin.Sha256, - Builtin: false, - } + Name: plugin.Name, + Type: plugin.Type, + Version: plugin.Version, + Command: plugin.Command, + OCIImage: plugin.OCIImage, + Runtime: plugin.Runtime, + Args: plugin.Args, + Env: plugin.Env, + Sha256: plugin.Sha256, + Builtin: false, + Enterprise: enterprise, + } + c.setEntPluginRunner(ctx, entry) buf, err := json.Marshal(entry) if err != nil { diff --git a/vault/plugincatalog/plugin_catalog_stubs_oss.go b/vault/plugincatalog/plugin_catalog_stubs_oss.go new file mode 100644 index 000000000000..cf342861324e --- /dev/null +++ b/vault/plugincatalog/plugin_catalog_stubs_oss.go @@ -0,0 +1,19 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +//go:build !enterprise + +package plugincatalog + +import ( + "context" + "fmt" + + "github.com/hashicorp/vault/sdk/helper/pluginutil" +) + +func (c *PluginCatalog) unpackPluginArtifact(context.Context, pluginutil.SetPluginInput) (bool, string, error) { + return false, "", fmt.Errorf("enterprise-only feature: plugin artifact unpacking") +} + +func (c *PluginCatalog) setEntPluginRunner(context.Context, *pluginutil.PluginRunner) {} From 435c0665634d9ad21866418ae30a5379b0ae4415 Mon Sep 17 00:00:00 2001 From: Thy Ton Date: Thu, 5 Dec 2024 15:00:21 -0800 Subject: [PATCH 04/25] revert EntCoreGetter field addition to PluginCatalog --- vault/plugincatalog/plugin_catalog.go | 8 -------- 1 file changed, 8 deletions(-) diff --git a/vault/plugincatalog/plugin_catalog.go b/vault/plugincatalog/plugin_catalog.go index f1e2a6e59edf..0df54ba80e3a 100644 --- a/vault/plugincatalog/plugin_catalog.go +++ b/vault/plugincatalog/plugin_catalog.go @@ -45,18 +45,12 @@ var ( ErrPluginUnableToRun = errors.New("unable to run plugin") ) -// EntCoreGetter is an interface that allows the plugin catalog to only access entCore getter functions -type EntCoreGetter interface { - EntGetLicense() (string, error) -} - // 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 { builtinRegistry BuiltinRegistry catalogView logical.Storage - entCoreGetter EntCoreGetter directory string tmpdir string logger log.Logger @@ -153,7 +147,6 @@ type PluginCatalogInput struct { Logger log.Logger BuiltinRegistry BuiltinRegistry CatalogView logical.Storage - EntCoreGetter EntCoreGetter PluginDirectory string Tmpdir string EnableMlock bool @@ -165,7 +158,6 @@ func SetupPluginCatalog(ctx context.Context, in *PluginCatalogInput) (*PluginCat catalog := &PluginCatalog{ builtinRegistry: in.BuiltinRegistry, catalogView: in.CatalogView, - entCoreGetter: in.EntCoreGetter, directory: in.PluginDirectory, tmpdir: in.Tmpdir, logger: logger, From 5abe606cf7bf9c704fefcf577bc72f4e827b3689 Mon Sep 17 00:00:00 2001 From: Thy Ton Date: Thu, 5 Dec 2024 15:03:10 -0800 Subject: [PATCH 05/25] revert EntPluginRunner field addition to PluginRunner --- sdk/helper/pluginutil/runner.go | 3 --- vault/plugincatalog/plugin_catalog.go | 2 -- vault/plugincatalog/plugin_catalog_stubs_oss.go | 2 -- 3 files changed, 7 deletions(-) diff --git a/sdk/helper/pluginutil/runner.go b/sdk/helper/pluginutil/runner.go index bbbb7fd32dd4..8e15fa92af96 100644 --- a/sdk/helper/pluginutil/runner.go +++ b/sdk/helper/pluginutil/runner.go @@ -54,12 +54,9 @@ type PluginClient interface { const MultiplexingCtxKey string = "multiplex_id" -type EntPluginRunner struct{} - // PluginRunner defines the metadata needed to run a plugin securely with // go-plugin. type PluginRunner struct { - EntPluginRunner Name string `json:"name" structs:"name"` Type consts.PluginType `json:"type" structs:"type"` Version string `json:"version" structs:"version"` diff --git a/vault/plugincatalog/plugin_catalog.go b/vault/plugincatalog/plugin_catalog.go index 0df54ba80e3a..df952d49190a 100644 --- a/vault/plugincatalog/plugin_catalog.go +++ b/vault/plugincatalog/plugin_catalog.go @@ -1004,7 +1004,6 @@ func (c *PluginCatalog) setInternal(ctx context.Context, plugin pluginutil.SetPl Builtin: false, Enterprise: enterprise, } - c.setEntPluginRunner(ctx, entryTmp) if entryTmp.OCIImage != "" && entryTmp.Runtime != "" { var err error @@ -1067,7 +1066,6 @@ func (c *PluginCatalog) setInternal(ctx context.Context, plugin pluginutil.SetPl Builtin: false, Enterprise: enterprise, } - c.setEntPluginRunner(ctx, entry) buf, err := json.Marshal(entry) if err != nil { diff --git a/vault/plugincatalog/plugin_catalog_stubs_oss.go b/vault/plugincatalog/plugin_catalog_stubs_oss.go index cf342861324e..d97ec018ca4c 100644 --- a/vault/plugincatalog/plugin_catalog_stubs_oss.go +++ b/vault/plugincatalog/plugin_catalog_stubs_oss.go @@ -15,5 +15,3 @@ import ( func (c *PluginCatalog) unpackPluginArtifact(context.Context, pluginutil.SetPluginInput) (bool, string, error) { return false, "", fmt.Errorf("enterprise-only feature: plugin artifact unpacking") } - -func (c *PluginCatalog) setEntPluginRunner(context.Context, *pluginutil.PluginRunner) {} From 6e4050a6f26bb1e319b26b07d18871b1aa543158 Mon Sep 17 00:00:00 2001 From: Thy Ton Date: Fri, 6 Dec 2024 07:57:50 -0800 Subject: [PATCH 06/25] remove context.Context param from unpackPluginArtifact --- vault/plugincatalog/plugin_catalog.go | 19 ++++++++++++------- .../plugincatalog/plugin_catalog_stubs_oss.go | 7 +++---- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/vault/plugincatalog/plugin_catalog.go b/vault/plugincatalog/plugin_catalog.go index df952d49190a..a833d0189c13 100644 --- a/vault/plugincatalog/plugin_catalog.go +++ b/vault/plugincatalog/plugin_catalog.go @@ -43,6 +43,7 @@ var ( ErrPinnedVersion = errors.New("cannot delete a pinned version") ErrPluginVersionMismatch = errors.New("plugin version mismatch") ErrPluginUnableToRun = errors.New("unable to run plugin") + ErrEnterpriseFeatureOnly = errors.New("enterprise-only feature") ) // PluginCatalog keeps a record of plugins known to vault. External plugins need @@ -966,19 +967,23 @@ func (c *PluginCatalog) setInternal(ctx context.Context, plugin pluginutil.SetPl if plugin.OCIImage == "" { command = filepath.Join(c.directory, plugin.Command) - if _, err := os.Stat(command); os.IsNotExist(err) { + if sym, err := filepath.EvalSymlinks(command); err != nil { // Best effort to unpack the plugin artifact - enterprise, command, err = c.unpackPluginArtifact(ctx, plugin) - if err != nil { + var unpackErr error + enterprise, plugin.Command, unpackErr = c.unpackPluginArtifact(plugin) + switch { + case unpackErr == nil: + command = filepath.Join(c.directory, plugin.Command) + case errors.Is(unpackErr, ErrEnterpriseFeatureOnly): + // Return the error that Vault CE users normally see when evaluating symlinks fails + // in the enterprise feature absence + return nil, fmt.Errorf("error while validating the command path: %w", err) + default: return nil, fmt.Errorf("failed to unpack plugin artifact: %w", err) } } else { // Best effort check to make sure the command isn't breaking out of the // configured plugin directory. - sym, err := filepath.EvalSymlinks(command) - if err != nil { - return nil, fmt.Errorf("error while validating the command path: %w", err) - } symAbs, err := filepath.Abs(filepath.Dir(sym)) if err != nil { return nil, fmt.Errorf("error while validating the command path: %w", err) diff --git a/vault/plugincatalog/plugin_catalog_stubs_oss.go b/vault/plugincatalog/plugin_catalog_stubs_oss.go index d97ec018ca4c..60af30054f04 100644 --- a/vault/plugincatalog/plugin_catalog_stubs_oss.go +++ b/vault/plugincatalog/plugin_catalog_stubs_oss.go @@ -1,17 +1,16 @@ // Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 +// SPDX-License-Identifier: MPL-2.0 //go:build !enterprise package plugincatalog import ( - "context" "fmt" "github.com/hashicorp/vault/sdk/helper/pluginutil" ) -func (c *PluginCatalog) unpackPluginArtifact(context.Context, pluginutil.SetPluginInput) (bool, string, error) { - return false, "", fmt.Errorf("enterprise-only feature: plugin artifact unpacking") +func (c *PluginCatalog) unpackPluginArtifact(plugin pluginutil.SetPluginInput) (bool, string, error) { + return false, plugin.Command, fmt.Errorf("enterprise-only feature: plugin artifact unpacking") } From 880147a7ab2a019531502a588fa78995a6847323 Mon Sep 17 00:00:00 2001 From: Thy Ton Date: Fri, 6 Dec 2024 12:00:45 -0800 Subject: [PATCH 07/25] allow plugin register with artifact to omit sha256 param --- vault/core.go | 2 ++ vault/logical_system.go | 22 ++++++++++++++++------ 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/vault/core.go b/vault/core.go index 706059667e85..0a78133dbd90 100644 --- a/vault/core.go +++ b/vault/core.go @@ -4211,6 +4211,8 @@ func (c *Core) GetHAPeerNodesCached() []PeerNode { return nodes } +// TODO VAULT-32686 plugin artifact permission + func (c *Core) CheckPluginPerms(pluginName string) (err error) { var enableFilePermissionsCheck bool if enableFilePermissionsCheckEnv := os.Getenv(consts.VaultEnableFilePermissionsCheckEnv); enableFilePermissionsCheckEnv != "" { diff --git a/vault/logical_system.go b/vault/logical_system.go index 3e328fd1dc26..d0aa346fd5d5 100644 --- a/vault/logical_system.go +++ b/vault/logical_system.go @@ -534,21 +534,31 @@ 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 } + licenseState, err := b.Core.EntGetLicenseState() + if err != nil { + return logical.ErrorResponse("failed to get license state: %w", err), nil + } + sha256 := d.Get("sha256").(string) if sha256 == "" { sha256 = d.Get("sha_256").(string) - if sha256 == "" { - return logical.ErrorResponse("missing SHA-256 value"), nil + + if licenseState == nil && sha256 == "" { + return logical.ErrorResponse("missing SHA-256 value, register plugin with artifact and implicit SHA-256 is enterprise only feature"), nil } } command := d.Get("command").(string) ociImage := d.Get("oci_image").(string) - if command == "" && ociImage == "" { - return logical.ErrorResponse("must provide at least one of command or oci_image"), nil - } - if ociImage == "" { + if command == "" { + return logical.ErrorResponse("must provide at least one of command or oci_image"), nil + } + + if licenseState != nil && pluginVersion != "" { + return logical.ErrorResponse("enterprise only: must provide version to register plugin with artifact"), nil + } + if err = b.Core.CheckPluginPerms(command); err != nil { return nil, err } From 4de66692ff6db044ca8d12144827aa943be72ff6 Mon Sep 17 00:00:00 2001 From: Thy Ton Date: Fri, 6 Dec 2024 13:09:03 -0800 Subject: [PATCH 08/25] use err EvalSymlinks(command) to determine when unpack plugin artifact --- vault/plugincatalog/plugin_catalog.go | 14 +++++++------- vault/plugincatalog/plugin_catalog_stubs_oss.go | 4 ++-- vault/plugincatalog/plugin_catalog_test.go | 1 - 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/vault/plugincatalog/plugin_catalog.go b/vault/plugincatalog/plugin_catalog.go index a833d0189c13..e3dfb387d79c 100644 --- a/vault/plugincatalog/plugin_catalog.go +++ b/vault/plugincatalog/plugin_catalog.go @@ -43,7 +43,7 @@ var ( ErrPinnedVersion = errors.New("cannot delete a pinned version") ErrPluginVersionMismatch = errors.New("plugin version mismatch") ErrPluginUnableToRun = errors.New("unable to run plugin") - ErrEnterpriseFeatureOnly = errors.New("enterprise-only feature") + ErrEnterpriseFeatureOnly = errors.New("enterprise only feature") ) // PluginCatalog keeps a record of plugins known to vault. External plugins need @@ -966,17 +966,17 @@ func (c *PluginCatalog) setInternal(ctx context.Context, plugin pluginutil.SetPl if plugin.OCIImage == "" { command = filepath.Join(c.directory, plugin.Command) - - if sym, err := filepath.EvalSymlinks(command); err != nil { - // Best effort to unpack the plugin artifact + sym, err := filepath.EvalSymlinks(command) + if err != nil { + // Enterprise only: unpack the plugin artifact var unpackErr error - enterprise, plugin.Command, unpackErr = c.unpackPluginArtifact(plugin) + enterprise, plugin.Command, plugin.Sha256, unpackErr = c.unpackPluginArtifact(plugin) switch { case unpackErr == nil: command = filepath.Join(c.directory, plugin.Command) case errors.Is(unpackErr, ErrEnterpriseFeatureOnly): - // Return the error that Vault CE users normally see when evaluating symlinks fails - // in the enterprise feature absence + // Return the error that Vault CE users normally should see + // when evaluating symlinks of the command fails return nil, fmt.Errorf("error while validating the command path: %w", err) default: return nil, fmt.Errorf("failed to unpack plugin artifact: %w", err) diff --git a/vault/plugincatalog/plugin_catalog_stubs_oss.go b/vault/plugincatalog/plugin_catalog_stubs_oss.go index 60af30054f04..1cd8c32317fd 100644 --- a/vault/plugincatalog/plugin_catalog_stubs_oss.go +++ b/vault/plugincatalog/plugin_catalog_stubs_oss.go @@ -11,6 +11,6 @@ import ( "github.com/hashicorp/vault/sdk/helper/pluginutil" ) -func (c *PluginCatalog) unpackPluginArtifact(plugin pluginutil.SetPluginInput) (bool, string, error) { - return false, plugin.Command, fmt.Errorf("enterprise-only feature: plugin artifact unpacking") +func (c *PluginCatalog) unpackPluginArtifact(plugin pluginutil.SetPluginInput) (bool, string, []byte, error) { + return false, plugin.Command, plugin.Sha256, fmt.Errorf("enterprise-only feature: plugin artifact unpacking") } diff --git a/vault/plugincatalog/plugin_catalog_test.go b/vault/plugincatalog/plugin_catalog_test.go index 94d3f321f084..a6a521631856 100644 --- a/vault/plugincatalog/plugin_catalog_test.go +++ b/vault/plugincatalog/plugin_catalog_test.go @@ -646,7 +646,6 @@ func TestPluginCatalog_ListHandlesPluginNamesWithSlashes(t *testing.T) { Command: command, Args: nil, Env: nil, - Sha256: nil, }) if err != nil { t.Fatal(err) From ba1f8fbda061ca6428eb1f285df896dc29717c9e Mon Sep 17 00:00:00 2001 From: Thy Ton Date: Mon, 9 Dec 2024 10:05:01 -0800 Subject: [PATCH 09/25] error plugin catalog update on empty plugin version and sha256 if vault is enterprise --- vault/logical_system.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/vault/logical_system.go b/vault/logical_system.go index d0aa346fd5d5..f5141e8f5aae 100644 --- a/vault/logical_system.go +++ b/vault/logical_system.go @@ -542,8 +542,7 @@ func (b *SystemBackend) handlePluginCatalogUpdate(ctx context.Context, _ *logica sha256 := d.Get("sha256").(string) if sha256 == "" { sha256 = d.Get("sha_256").(string) - - if licenseState == nil && sha256 == "" { + if sha256 == "" && licenseState == nil { return logical.ErrorResponse("missing SHA-256 value, register plugin with artifact and implicit SHA-256 is enterprise only feature"), nil } } @@ -555,7 +554,7 @@ func (b *SystemBackend) handlePluginCatalogUpdate(ctx context.Context, _ *logica return logical.ErrorResponse("must provide at least one of command or oci_image"), nil } - if licenseState != nil && pluginVersion != "" { + if licenseState != nil && pluginVersion == "" && sha256 == "" { return logical.ErrorResponse("enterprise only: must provide version to register plugin with artifact"), nil } From aefac15dee02030b257699970006185eab261215 Mon Sep 17 00:00:00 2001 From: Thy Ton Date: Mon, 9 Dec 2024 10:09:35 -0800 Subject: [PATCH 10/25] remove sha56 requirement for enterprise plugin register w/ artifact command --- command/plugin_register.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/command/plugin_register.go b/command/plugin_register.go index d124b38b8917..031a43346f1d 100644 --- a/command/plugin_register.go +++ b/command/plugin_register.go @@ -144,9 +144,6 @@ func (c *PluginRegisterCommand) Run(args []string) int { case len(args) > 2: c.UI.Error(fmt.Sprintf("Too many arguments (expected 1 or 2, got %d)", len(args))) return 1 - case c.flagSHA256 == "": - c.UI.Error("SHA256 is required for all plugins, please provide -sha256") - return 1 // These cases should come after invalid cases have been checked case len(args) == 1: From fce85bdc56d64d67b5df996f94f015597f214ebd Mon Sep 17 00:00:00 2001 From: Thy Ton Date: Mon, 9 Dec 2024 11:56:41 -0800 Subject: [PATCH 11/25] add sanitize() stub oss to plugin catalog --- vault/plugincatalog/plugin_catalog.go | 9 ++++++++- vault/plugincatalog/plugin_catalog_stubs_oss.go | 4 ++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/vault/plugincatalog/plugin_catalog.go b/vault/plugincatalog/plugin_catalog.go index e3dfb387d79c..dd5423182767 100644 --- a/vault/plugincatalog/plugin_catalog.go +++ b/vault/plugincatalog/plugin_catalog.go @@ -174,6 +174,13 @@ func SetupPluginCatalog(ctx context.Context, in *PluginCatalogInput) (*PluginCat return nil, err } + // Sanitize the plugin catalog + err = catalog.sanitize() + if err != nil { + logger.Error("error while sanitizing plugin storage", "error", err) + return nil, err + } + if legacy, _ := strconv.ParseBool(os.Getenv(pluginutil.PluginUseLegacyEnvLayering)); legacy { conflicts := false osKeys := envKeys(os.Environ()) @@ -979,7 +986,7 @@ func (c *PluginCatalog) setInternal(ctx context.Context, plugin pluginutil.SetPl // when evaluating symlinks of the command fails return nil, fmt.Errorf("error while validating the command path: %w", err) default: - return nil, fmt.Errorf("failed to unpack plugin artifact: %w", err) + return nil, fmt.Errorf("failed to unpack plugin artifact: %w", unpackErr) } } else { // Best effort check to make sure the command isn't breaking out of the diff --git a/vault/plugincatalog/plugin_catalog_stubs_oss.go b/vault/plugincatalog/plugin_catalog_stubs_oss.go index 1cd8c32317fd..901cf7770d2e 100644 --- a/vault/plugincatalog/plugin_catalog_stubs_oss.go +++ b/vault/plugincatalog/plugin_catalog_stubs_oss.go @@ -14,3 +14,7 @@ import ( func (c *PluginCatalog) unpackPluginArtifact(plugin pluginutil.SetPluginInput) (bool, string, []byte, error) { return false, plugin.Command, plugin.Sha256, fmt.Errorf("enterprise-only feature: plugin artifact unpacking") } + +func (c *PluginCatalog) sanitize() error { + return nil +} From 5148a4632905c9072794500ccaf98dedabd5c757 Mon Sep 17 00:00:00 2001 From: Thy Ton Date: Mon, 9 Dec 2024 12:26:38 -0800 Subject: [PATCH 12/25] prefix sanitize and unpackArtifact funcs with ent --- vault/plugincatalog/plugin_catalog.go | 4 ++-- vault/plugincatalog/plugin_catalog_stubs_oss.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/vault/plugincatalog/plugin_catalog.go b/vault/plugincatalog/plugin_catalog.go index dd5423182767..98a42302b62d 100644 --- a/vault/plugincatalog/plugin_catalog.go +++ b/vault/plugincatalog/plugin_catalog.go @@ -175,7 +175,7 @@ func SetupPluginCatalog(ctx context.Context, in *PluginCatalogInput) (*PluginCat } // Sanitize the plugin catalog - err = catalog.sanitize() + err = catalog.entSanitize() if err != nil { logger.Error("error while sanitizing plugin storage", "error", err) return nil, err @@ -977,7 +977,7 @@ func (c *PluginCatalog) setInternal(ctx context.Context, plugin pluginutil.SetPl if err != nil { // Enterprise only: unpack the plugin artifact var unpackErr error - enterprise, plugin.Command, plugin.Sha256, unpackErr = c.unpackPluginArtifact(plugin) + enterprise, plugin.Command, plugin.Sha256, unpackErr = c.entUnpackArtifact(plugin) switch { case unpackErr == nil: command = filepath.Join(c.directory, plugin.Command) diff --git a/vault/plugincatalog/plugin_catalog_stubs_oss.go b/vault/plugincatalog/plugin_catalog_stubs_oss.go index 901cf7770d2e..148ff99d1e00 100644 --- a/vault/plugincatalog/plugin_catalog_stubs_oss.go +++ b/vault/plugincatalog/plugin_catalog_stubs_oss.go @@ -11,10 +11,10 @@ import ( "github.com/hashicorp/vault/sdk/helper/pluginutil" ) -func (c *PluginCatalog) unpackPluginArtifact(plugin pluginutil.SetPluginInput) (bool, string, []byte, error) { +func (c *PluginCatalog) entUnpackArtifact(plugin pluginutil.SetPluginInput) (bool, string, []byte, error) { return false, plugin.Command, plugin.Sha256, fmt.Errorf("enterprise-only feature: plugin artifact unpacking") } -func (c *PluginCatalog) sanitize() error { +func (c *PluginCatalog) entSanitize() error { return nil } From 61a5c1b85e21ff65c29b01cad7c45991bb500c88 Mon Sep 17 00:00:00 2001 From: Thy Ton Date: Wed, 11 Dec 2024 17:12:09 -0800 Subject: [PATCH 13/25] add context.Context param to entSanitize() --- vault/plugincatalog/plugin_catalog.go | 2 +- vault/plugincatalog/plugin_catalog_stubs_oss.go | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/vault/plugincatalog/plugin_catalog.go b/vault/plugincatalog/plugin_catalog.go index 98a42302b62d..8d9a67406729 100644 --- a/vault/plugincatalog/plugin_catalog.go +++ b/vault/plugincatalog/plugin_catalog.go @@ -175,7 +175,7 @@ func SetupPluginCatalog(ctx context.Context, in *PluginCatalogInput) (*PluginCat } // Sanitize the plugin catalog - err = catalog.entSanitize() + err = catalog.entSanitize(ctx) if err != nil { logger.Error("error while sanitizing plugin storage", "error", err) return nil, err diff --git a/vault/plugincatalog/plugin_catalog_stubs_oss.go b/vault/plugincatalog/plugin_catalog_stubs_oss.go index 148ff99d1e00..a05c7776c0dd 100644 --- a/vault/plugincatalog/plugin_catalog_stubs_oss.go +++ b/vault/plugincatalog/plugin_catalog_stubs_oss.go @@ -6,6 +6,7 @@ package plugincatalog import ( + "context" "fmt" "github.com/hashicorp/vault/sdk/helper/pluginutil" @@ -15,6 +16,6 @@ func (c *PluginCatalog) entUnpackArtifact(plugin pluginutil.SetPluginInput) (boo return false, plugin.Command, plugin.Sha256, fmt.Errorf("enterprise-only feature: plugin artifact unpacking") } -func (c *PluginCatalog) entSanitize() error { +func (c *PluginCatalog) entSanitize(context.Context) error { return nil } From 8daeb2a48bf514742321de7f2151c63124369020 Mon Sep 17 00:00:00 2001 From: Thy Ton Date: Thu, 12 Dec 2024 12:14:14 -0800 Subject: [PATCH 14/25] add plugin name and version to unpack failure error message --- vault/plugincatalog/plugin_catalog.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vault/plugincatalog/plugin_catalog.go b/vault/plugincatalog/plugin_catalog.go index 8d9a67406729..989435715a26 100644 --- a/vault/plugincatalog/plugin_catalog.go +++ b/vault/plugincatalog/plugin_catalog.go @@ -986,7 +986,8 @@ func (c *PluginCatalog) setInternal(ctx context.Context, plugin pluginutil.SetPl // when evaluating symlinks of the command fails return nil, fmt.Errorf("error while validating the command path: %w", err) default: - return nil, fmt.Errorf("failed to unpack plugin artifact: %w", unpackErr) + return nil, fmt.Errorf("failed to unpack plugin artifact plugin %q version %q: %w", + plugin.Name, plugin.Version, unpackErr) } } else { // Best effort check to make sure the command isn't breaking out of the From 82d8b0c666956327fc1d6979e1f130fa0578e2f0 Mon Sep 17 00:00:00 2001 From: Thy Ton Date: Wed, 18 Dec 2024 11:51:35 -0800 Subject: [PATCH 15/25] call entUnpackArtifact on empty sha256 --- vault/core.go | 2 -- vault/plugincatalog/plugin_catalog.go | 19 ++++++++----------- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/vault/core.go b/vault/core.go index 32e3a8e21e13..fd9ac93126aa 100644 --- a/vault/core.go +++ b/vault/core.go @@ -4218,8 +4218,6 @@ func (c *Core) GetHAPeerNodesCached() []PeerNode { return nodes } -// TODO VAULT-32686 plugin artifact permission - func (c *Core) CheckPluginPerms(pluginName string) (err error) { var enableFilePermissionsCheck bool if enableFilePermissionsCheckEnv := os.Getenv(consts.VaultEnableFilePermissionsCheckEnv); enableFilePermissionsCheckEnv != "" { diff --git a/vault/plugincatalog/plugin_catalog.go b/vault/plugincatalog/plugin_catalog.go index 989435715a26..d7d9b3a0c7f8 100644 --- a/vault/plugincatalog/plugin_catalog.go +++ b/vault/plugincatalog/plugin_catalog.go @@ -972,26 +972,23 @@ func (c *PluginCatalog) setInternal(ctx context.Context, plugin pluginutil.SetPl var enterprise bool if plugin.OCIImage == "" { - command = filepath.Join(c.directory, plugin.Command) - sym, err := filepath.EvalSymlinks(command) - if err != nil { + if len(plugin.Sha256) == 0 { // Enterprise only: unpack the plugin artifact var unpackErr error enterprise, plugin.Command, plugin.Sha256, unpackErr = c.entUnpackArtifact(plugin) - switch { - case unpackErr == nil: - command = filepath.Join(c.directory, plugin.Command) - case errors.Is(unpackErr, ErrEnterpriseFeatureOnly): - // Return the error that Vault CE users normally should see - // when evaluating symlinks of the command fails - return nil, fmt.Errorf("error while validating the command path: %w", err) - default: + if unpackErr != nil { return nil, fmt.Errorf("failed to unpack plugin artifact plugin %q version %q: %w", plugin.Name, plugin.Version, unpackErr) } + command = filepath.Join(c.directory, plugin.Command) } else { // Best effort check to make sure the command isn't breaking out of the // configured plugin directory. + command = filepath.Join(c.directory, plugin.Command) + sym, err := filepath.EvalSymlinks(command) + if err != nil { + return nil, fmt.Errorf("error while validating the command path: %w", err) + } symAbs, err := filepath.Abs(filepath.Dir(sym)) if err != nil { return nil, fmt.Errorf("error while validating the command path: %w", err) From 0d1b748178ed296d879dda552dc4e477504ca6f8 Mon Sep 17 00:00:00 2001 From: Thy Ton Date: Wed, 18 Dec 2024 14:26:27 -0800 Subject: [PATCH 16/25] evaluate path before checking empty sha256 to unpack artifact --- vault/plugincatalog/plugin_catalog.go | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/vault/plugincatalog/plugin_catalog.go b/vault/plugincatalog/plugin_catalog.go index d7d9b3a0c7f8..3eaeaf62341e 100644 --- a/vault/plugincatalog/plugin_catalog.go +++ b/vault/plugincatalog/plugin_catalog.go @@ -972,8 +972,15 @@ func (c *PluginCatalog) setInternal(ctx context.Context, plugin pluginutil.SetPl var enterprise bool if plugin.OCIImage == "" { - if len(plugin.Sha256) == 0 { - // Enterprise only: unpack the plugin artifact + command = filepath.Join(c.directory, plugin.Command) + sym, err := filepath.EvalSymlinks(command) + if err != nil { + if len(plugin.Sha256) != 0 { + return nil, fmt.Errorf("error while validating the command path: %w", err) + } + + // When binary is missing and sha256 is unset, attempt to unpack the plugin artifact + // Enterprise only var unpackErr error enterprise, plugin.Command, plugin.Sha256, unpackErr = c.entUnpackArtifact(plugin) if unpackErr != nil { @@ -984,11 +991,6 @@ func (c *PluginCatalog) setInternal(ctx context.Context, plugin pluginutil.SetPl } else { // Best effort check to make sure the command isn't breaking out of the // configured plugin directory. - command = filepath.Join(c.directory, plugin.Command) - sym, err := filepath.EvalSymlinks(command) - if err != nil { - return nil, fmt.Errorf("error while validating the command path: %w", err) - } symAbs, err := filepath.Abs(filepath.Dir(sym)) if err != nil { return nil, fmt.Errorf("error while validating the command path: %w", err) From a9514fc7124d648dfaa30d71303045d72f6b14c4 Mon Sep 17 00:00:00 2001 From: Thy Ton Date: Thu, 19 Dec 2024 12:34:33 -0800 Subject: [PATCH 17/25] create oss stub constructor for plugin register command --- command/commands.go | 4 +--- command/plugin_register.go | 3 +++ command/plugin_register_stubs_oss.go | 14 ++++++++++++++ 3 files changed, 18 insertions(+), 3 deletions(-) create mode 100644 command/plugin_register_stubs_oss.go diff --git a/command/commands.go b/command/commands.go index daa40ead48a9..90287478ef85 100644 --- a/command/commands.go +++ b/command/commands.go @@ -551,9 +551,7 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) map[string]cli.Co }, nil }, "plugin register": func() (cli.Command, error) { - return &PluginRegisterCommand{ - BaseCommand: getBaseCommand(), - }, nil + return NewPluginRegisterCommand(getBaseCommand()), nil }, "plugin reload": func() (cli.Command, error) { return &PluginReloadCommand{ diff --git a/command/plugin_register.go b/command/plugin_register.go index 031a43346f1d..d124b38b8917 100644 --- a/command/plugin_register.go +++ b/command/plugin_register.go @@ -144,6 +144,9 @@ func (c *PluginRegisterCommand) Run(args []string) int { case len(args) > 2: c.UI.Error(fmt.Sprintf("Too many arguments (expected 1 or 2, got %d)", len(args))) return 1 + case c.flagSHA256 == "": + c.UI.Error("SHA256 is required for all plugins, please provide -sha256") + return 1 // These cases should come after invalid cases have been checked case len(args) == 1: diff --git a/command/plugin_register_stubs_oss.go b/command/plugin_register_stubs_oss.go new file mode 100644 index 000000000000..dbcfb4a44776 --- /dev/null +++ b/command/plugin_register_stubs_oss.go @@ -0,0 +1,14 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +//go:build !enterprise + +package command + +import "github.com/hashicorp/cli" + +func NewPluginRegisterCommand(baseCommand *BaseCommand) cli.Command { + return &PluginRegisterCommand{ + BaseCommand: baseCommand, + } +} From 923c43c29a22de6811052208cf8c1e93b37de343 Mon Sep 17 00:00:00 2001 From: Thy Ton Date: Thu, 19 Dec 2024 12:35:26 -0800 Subject: [PATCH 18/25] create oss stub for EntPluginRunner --- sdk/helper/pluginutil/runner.go | 3 ++- sdk/helper/pluginutil/runner_stubs_oss.go | 8 ++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 sdk/helper/pluginutil/runner_stubs_oss.go diff --git a/sdk/helper/pluginutil/runner.go b/sdk/helper/pluginutil/runner.go index 8e15fa92af96..ecae61459b5c 100644 --- a/sdk/helper/pluginutil/runner.go +++ b/sdk/helper/pluginutil/runner.go @@ -57,6 +57,8 @@ const MultiplexingCtxKey string = "multiplex_id" // PluginRunner defines the metadata needed to run a plugin securely with // go-plugin. type PluginRunner struct { + EntPluginRunner + Name string `json:"name" structs:"name"` Type consts.PluginType `json:"type" structs:"type"` Version string `json:"version" structs:"version"` @@ -67,7 +69,6 @@ type PluginRunner struct { Env []string `json:"env" structs:"env"` Sha256 []byte `json:"sha256" structs:"sha256"` Builtin bool `json:"builtin" structs:"builtin"` - Enterprise bool `json:"enterprise" structs:"enterprise"` BuiltinFactory func() (interface{}, error) `json:"-" structs:"-"` RuntimeConfig *prutil.PluginRuntimeConfig `json:"-" structs:"-"` Tmpdir string `json:"-" structs:"-"` diff --git a/sdk/helper/pluginutil/runner_stubs_oss.go b/sdk/helper/pluginutil/runner_stubs_oss.go new file mode 100644 index 000000000000..b5d390a44d24 --- /dev/null +++ b/sdk/helper/pluginutil/runner_stubs_oss.go @@ -0,0 +1,8 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +//go:build !enterprise + +package pluginutil + +type EntPluginRunner struct{} From 95a23de8e3bb9ef2209b29c6f51150be804d810e Mon Sep 17 00:00:00 2001 From: Thy Ton Date: Thu, 19 Dec 2024 12:36:50 -0800 Subject: [PATCH 19/25] convert PluginCatalog setInternal() to oss stub --- vault/plugincatalog/plugin_catalog.go | 131 ------------------ .../plugincatalog/plugin_catalog_stubs_oss.go | 121 +++++++++++++++- vault/plugincatalog/plugin_catalog_test.go | 1 + 3 files changed, 120 insertions(+), 133 deletions(-) diff --git a/vault/plugincatalog/plugin_catalog.go b/vault/plugincatalog/plugin_catalog.go index 3eaeaf62341e..5133fa707993 100644 --- a/vault/plugincatalog/plugin_catalog.go +++ b/vault/plugincatalog/plugin_catalog.go @@ -967,137 +967,6 @@ func (c *PluginCatalog) Set(ctx context.Context, plugin pluginutil.SetPluginInpu return err } -func (c *PluginCatalog) setInternal(ctx context.Context, plugin pluginutil.SetPluginInput) (*pluginutil.PluginRunner, error) { - command := plugin.Command - var enterprise bool - - if plugin.OCIImage == "" { - command = filepath.Join(c.directory, plugin.Command) - sym, err := filepath.EvalSymlinks(command) - if err != nil { - if len(plugin.Sha256) != 0 { - return nil, fmt.Errorf("error while validating the command path: %w", err) - } - - // When binary is missing and sha256 is unset, attempt to unpack the plugin artifact - // Enterprise only - var unpackErr error - enterprise, plugin.Command, plugin.Sha256, unpackErr = c.entUnpackArtifact(plugin) - if unpackErr != nil { - return nil, fmt.Errorf("failed to unpack plugin artifact plugin %q version %q: %w", - plugin.Name, plugin.Version, unpackErr) - } - command = filepath.Join(c.directory, plugin.Command) - } else { - // Best effort check to make sure the command isn't breaking out of the - // configured plugin directory. - symAbs, err := filepath.Abs(filepath.Dir(sym)) - if err != nil { - return nil, fmt.Errorf("error while validating the command path: %w", err) - } - - if symAbs != c.directory { - return nil, errors.New("cannot execute files outside of configured plugin directory") - } - } - } - - // entryTmp should only be used for the below type and version checks. It uses the - // full command instead of the relative command because get() normally prepends - // the plugin directory to the command, but we can't use get() here. - entryTmp := &pluginutil.PluginRunner{ - Name: plugin.Name, - Command: command, - OCIImage: plugin.OCIImage, - Runtime: plugin.Runtime, - Args: plugin.Args, - Env: plugin.Env, - Sha256: plugin.Sha256, - Builtin: false, - Enterprise: enterprise, - } - - if entryTmp.OCIImage != "" && entryTmp.Runtime != "" { - var err error - entryTmp.RuntimeConfig, err = c.runtimeCatalog.Get(ctx, entryTmp.Runtime, consts.PluginRuntimeTypeContainer) - if err != nil { - return nil, fmt.Errorf("failed to get configured runtime for plugin %q: %w", plugin.Name, err) - } - } - // If the plugin type is unknown, we want to attempt to determine the type - if plugin.Type == consts.PluginTypeUnknown { - var err error - plugin.Type, err = c.getPluginTypeFromUnknown(ctx, entryTmp) - if err != nil { - return nil, err - } - if plugin.Type == consts.PluginTypeUnknown { - return nil, ErrPluginBadType - } - } - - // getting the plugin version is best-effort, so errors are not fatal - runningVersion := logical.EmptyPluginVersion - var versionErr error - switch plugin.Type { - case consts.PluginTypeSecrets, consts.PluginTypeCredential: - runningVersion, versionErr = c.getBackendRunningVersion(ctx, entryTmp) - case consts.PluginTypeDatabase: - runningVersion, versionErr = c.getDatabaseRunningVersion(ctx, entryTmp) - default: - return nil, fmt.Errorf("unknown plugin type: %v", plugin.Type) - } - if versionErr != nil { - c.logger.Warn("Error determining plugin version", "error", versionErr) - if errors.Is(versionErr, ErrPluginUnableToRun) { - return nil, versionErr - } - } else if plugin.Version != "" && runningVersion.Version != "" && plugin.Version != runningVersion.Version { - c.logger.Error("Plugin self-reported version did not match requested version", - "plugin", plugin.Name, "requestedVersion", plugin.Version, "reportedVersion", runningVersion.Version) - return nil, fmt.Errorf("%w: %s reported version (%s) did not match requested version (%s)", - ErrPluginVersionMismatch, plugin.Name, runningVersion.Version, plugin.Version) - } else if plugin.Version == "" && runningVersion.Version != "" { - plugin.Version = runningVersion.Version - _, err := semver.NewVersion(plugin.Version) - if err != nil { - return nil, fmt.Errorf("plugin self-reported version %q is not a valid semantic version: %w", plugin.Version, err) - } - } - - entry := &pluginutil.PluginRunner{ - Name: plugin.Name, - Type: plugin.Type, - Version: plugin.Version, - Command: plugin.Command, - OCIImage: plugin.OCIImage, - Runtime: plugin.Runtime, - Args: plugin.Args, - Env: plugin.Env, - Sha256: plugin.Sha256, - Builtin: false, - Enterprise: enterprise, - } - - buf, err := json.Marshal(entry) - if err != nil { - return nil, fmt.Errorf("failed to encode plugin entry: %w", err) - } - - storageKey := path.Join(plugin.Type.String(), plugin.Name) - if plugin.Version != "" { - storageKey = path.Join(storageKey, plugin.Version) - } - logicalEntry := logical.StorageEntry{ - Key: storageKey, - Value: buf, - } - if err := c.catalogView.Put(ctx, &logicalEntry); err != nil { - return nil, fmt.Errorf("failed to persist plugin entry: %w", err) - } - return entry, nil -} - // Delete is used to remove an external plugin from the catalog. Builtin plugins // can not be deleted. func (c *PluginCatalog) Delete(ctx context.Context, name string, pluginType consts.PluginType, pluginVersion string) error { diff --git a/vault/plugincatalog/plugin_catalog_stubs_oss.go b/vault/plugincatalog/plugin_catalog_stubs_oss.go index a05c7776c0dd..4c8f7e647c22 100644 --- a/vault/plugincatalog/plugin_catalog_stubs_oss.go +++ b/vault/plugincatalog/plugin_catalog_stubs_oss.go @@ -7,13 +7,130 @@ package plugincatalog import ( "context" + "encoding/json" + "errors" "fmt" + "path" + "path/filepath" + semver "github.com/hashicorp/go-version" + "github.com/hashicorp/vault/sdk/helper/consts" "github.com/hashicorp/vault/sdk/helper/pluginutil" + "github.com/hashicorp/vault/sdk/logical" ) -func (c *PluginCatalog) entUnpackArtifact(plugin pluginutil.SetPluginInput) (bool, string, []byte, error) { - return false, plugin.Command, plugin.Sha256, fmt.Errorf("enterprise-only feature: plugin artifact unpacking") +// setInternal creates a new plugin entry in the catalog and persists it to storage +func (c *PluginCatalog) setInternal(ctx context.Context, plugin pluginutil.SetPluginInput) (*pluginutil.PluginRunner, error) { + command := plugin.Command + if plugin.OCIImage == "" { + // Best effort check to make sure the command isn't breaking out of the + // configured plugin directory. + command = filepath.Join(c.directory, plugin.Command) + sym, err := filepath.EvalSymlinks(command) + if err != nil { + return nil, fmt.Errorf("error while validating the command path: %w", err) + } + symAbs, err := filepath.Abs(filepath.Dir(sym)) + if err != nil { + return nil, fmt.Errorf("error while validating the command path: %w", err) + } + + if symAbs != c.directory { + return nil, errors.New("cannot execute files outside of configured plugin directory") + } + } + + // entryTmp should only be used for the below type and version checks. It uses the + // full command instead of the relative command because get() normally prepends + // the plugin directory to the command, but we can't use get() here. + entryTmp := &pluginutil.PluginRunner{ + Name: plugin.Name, + Command: command, + OCIImage: plugin.OCIImage, + Runtime: plugin.Runtime, + Args: plugin.Args, + Env: plugin.Env, + Sha256: plugin.Sha256, + Builtin: false, + } + if entryTmp.OCIImage != "" && entryTmp.Runtime != "" { + var err error + entryTmp.RuntimeConfig, err = c.runtimeCatalog.Get(ctx, entryTmp.Runtime, consts.PluginRuntimeTypeContainer) + if err != nil { + return nil, fmt.Errorf("failed to get configured runtime for plugin %q: %w", plugin.Name, err) + } + } + // If the plugin type is unknown, we want to attempt to determine the type + if plugin.Type == consts.PluginTypeUnknown { + var err error + plugin.Type, err = c.getPluginTypeFromUnknown(ctx, entryTmp) + if err != nil { + return nil, err + } + if plugin.Type == consts.PluginTypeUnknown { + return nil, ErrPluginBadType + } + } + + // getting the plugin version is best-effort, so errors are not fatal + runningVersion := logical.EmptyPluginVersion + var versionErr error + switch plugin.Type { + case consts.PluginTypeSecrets, consts.PluginTypeCredential: + runningVersion, versionErr = c.getBackendRunningVersion(ctx, entryTmp) + case consts.PluginTypeDatabase: + runningVersion, versionErr = c.getDatabaseRunningVersion(ctx, entryTmp) + default: + return nil, fmt.Errorf("unknown plugin type: %v", plugin.Type) + } + if versionErr != nil { + c.logger.Warn("Error determining plugin version", "error", versionErr) + if errors.Is(versionErr, ErrPluginUnableToRun) { + return nil, versionErr + } + } else if plugin.Version != "" && runningVersion.Version != "" && plugin.Version != runningVersion.Version { + c.logger.Error("Plugin self-reported version did not match requested version", + "plugin", plugin.Name, "requestedVersion", plugin.Version, "reportedVersion", runningVersion.Version) + return nil, fmt.Errorf("%w: %s reported version (%s) did not match requested version (%s)", + ErrPluginVersionMismatch, plugin.Name, runningVersion.Version, plugin.Version) + } else if plugin.Version == "" && runningVersion.Version != "" { + plugin.Version = runningVersion.Version + _, err := semver.NewVersion(plugin.Version) + if err != nil { + return nil, fmt.Errorf("plugin self-reported version %q is not a valid semantic version: %w", plugin.Version, err) + } + } + + entry := &pluginutil.PluginRunner{ + Name: plugin.Name, + Type: plugin.Type, + Version: plugin.Version, + Command: plugin.Command, + OCIImage: plugin.OCIImage, + Runtime: plugin.Runtime, + Args: plugin.Args, + Env: plugin.Env, + Sha256: plugin.Sha256, + Builtin: false, + } + + buf, err := json.Marshal(entry) + if err != nil { + return nil, fmt.Errorf("failed to encode plugin entry: %w", err) + } + + storageKey := path.Join(plugin.Type.String(), plugin.Name) + if plugin.Version != "" { + storageKey = path.Join(storageKey, plugin.Version) + } + logicalEntry := logical.StorageEntry{ + Key: storageKey, + Value: buf, + } + if err := c.catalogView.Put(ctx, &logicalEntry); err != nil { + return nil, fmt.Errorf("failed to persist plugin entry: %w", err) + } + return entry, nil } func (c *PluginCatalog) entSanitize(context.Context) error { diff --git a/vault/plugincatalog/plugin_catalog_test.go b/vault/plugincatalog/plugin_catalog_test.go index a6a521631856..94d3f321f084 100644 --- a/vault/plugincatalog/plugin_catalog_test.go +++ b/vault/plugincatalog/plugin_catalog_test.go @@ -646,6 +646,7 @@ func TestPluginCatalog_ListHandlesPluginNamesWithSlashes(t *testing.T) { Command: command, Args: nil, Env: nil, + Sha256: nil, }) if err != nil { t.Fatal(err) From 96b296739009524e555215314cd1d522c828b917 Mon Sep 17 00:00:00 2001 From: Thy Ton Date: Sat, 28 Dec 2024 18:06:29 -0800 Subject: [PATCH 20/25] rename entSanitize to entValidate --- vault/plugincatalog/plugin_catalog.go | 2 +- vault/plugincatalog/plugin_catalog_stubs_oss.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/vault/plugincatalog/plugin_catalog.go b/vault/plugincatalog/plugin_catalog.go index 5133fa707993..63de185699ec 100644 --- a/vault/plugincatalog/plugin_catalog.go +++ b/vault/plugincatalog/plugin_catalog.go @@ -175,7 +175,7 @@ func SetupPluginCatalog(ctx context.Context, in *PluginCatalogInput) (*PluginCat } // Sanitize the plugin catalog - err = catalog.entSanitize(ctx) + err = catalog.entValidate(ctx) if err != nil { logger.Error("error while sanitizing plugin storage", "error", err) return nil, err diff --git a/vault/plugincatalog/plugin_catalog_stubs_oss.go b/vault/plugincatalog/plugin_catalog_stubs_oss.go index 4c8f7e647c22..585f73be6f8e 100644 --- a/vault/plugincatalog/plugin_catalog_stubs_oss.go +++ b/vault/plugincatalog/plugin_catalog_stubs_oss.go @@ -133,6 +133,6 @@ func (c *PluginCatalog) setInternal(ctx context.Context, plugin pluginutil.SetPl return entry, nil } -func (c *PluginCatalog) entSanitize(context.Context) error { +func (c *PluginCatalog) entValidate(context.Context) error { return nil } From 1c8bbbc5570b2d6407772f8bad17537d4802ba62 Mon Sep 17 00:00:00 2001 From: Thy Ton Date: Sat, 28 Dec 2024 19:09:20 -0800 Subject: [PATCH 21/25] factor out functions to validate sha256 and version+sha256 --- vault/logical_system.go | 21 ++++++++------------- vault/logical_system_plugins_oss.go | 21 +++++++++++++++++++++ 2 files changed, 29 insertions(+), 13 deletions(-) create mode 100644 vault/logical_system_plugins_oss.go diff --git a/vault/logical_system.go b/vault/logical_system.go index f5141e8f5aae..dd2f34645489 100644 --- a/vault/logical_system.go +++ b/vault/logical_system.go @@ -534,28 +534,23 @@ 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 } - licenseState, err := b.Core.EntGetLicenseState() - if err != nil { - return logical.ErrorResponse("failed to get license state: %w", err), nil - } - sha256 := d.Get("sha256").(string) if sha256 == "" { sha256 = d.Get("sha_256").(string) - if sha256 == "" && licenseState == nil { - return logical.ErrorResponse("missing SHA-256 value, register plugin with artifact and implicit SHA-256 is enterprise only feature"), nil + if resp := validateSHA256(sha256); resp.IsError() { + return resp, nil } } command := d.Get("command").(string) ociImage := d.Get("oci_image").(string) - if ociImage == "" { - if command == "" { - return logical.ErrorResponse("must provide at least one of command or oci_image"), nil - } + if command == "" && ociImage == "" { + return logical.ErrorResponse("must provide at least one of command or oci_image"), nil + } - if licenseState != nil && pluginVersion == "" && sha256 == "" { - return logical.ErrorResponse("enterprise only: must provide version to register plugin with artifact"), nil + if ociImage == "" { + if resp := validateVersionSHA256(pluginVersion, sha256); resp.IsError() { + return resp, nil } if err = b.Core.CheckPluginPerms(command); err != nil { diff --git a/vault/logical_system_plugins_oss.go b/vault/logical_system_plugins_oss.go new file mode 100644 index 000000000000..b9243a364efa --- /dev/null +++ b/vault/logical_system_plugins_oss.go @@ -0,0 +1,21 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +//go:build !enterprise + +package vault + +import ( + "github.com/hashicorp/vault/sdk/logical" +) + +func validateSHA256(sha256 string) *logical.Response { + if sha256 == "" { + return logical.ErrorResponse("missing SHA-256 value") + } + return nil +} + +func validateVersionSHA256(version, sha256 string) *logical.Response { + return nil +} From 2c2e6f53c2b7307fecfe10201204897ac346639f Mon Sep 17 00:00:00 2001 From: Thy Ton Date: Mon, 30 Dec 2024 10:02:38 -0800 Subject: [PATCH 22/25] revert adding enterprise only feature error --- vault/plugincatalog/plugin_catalog.go | 1 - 1 file changed, 1 deletion(-) diff --git a/vault/plugincatalog/plugin_catalog.go b/vault/plugincatalog/plugin_catalog.go index 63de185699ec..d38cf167611f 100644 --- a/vault/plugincatalog/plugin_catalog.go +++ b/vault/plugincatalog/plugin_catalog.go @@ -43,7 +43,6 @@ var ( ErrPinnedVersion = errors.New("cannot delete a pinned version") ErrPluginVersionMismatch = errors.New("plugin version mismatch") ErrPluginUnableToRun = errors.New("unable to run plugin") - ErrEnterpriseFeatureOnly = errors.New("enterprise only feature") ) // PluginCatalog keeps a record of plugins known to vault. External plugins need From 21901ed20120a4aced6a00cf1ada5210eb2e9503 Mon Sep 17 00:00:00 2001 From: Thy Ton Date: Mon, 30 Dec 2024 10:08:29 -0800 Subject: [PATCH 23/25] update license --- vault/plugincatalog/plugin_catalog_stubs_oss.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vault/plugincatalog/plugin_catalog_stubs_oss.go b/vault/plugincatalog/plugin_catalog_stubs_oss.go index 585f73be6f8e..12673393f7e5 100644 --- a/vault/plugincatalog/plugin_catalog_stubs_oss.go +++ b/vault/plugincatalog/plugin_catalog_stubs_oss.go @@ -1,5 +1,5 @@ // Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 +// SPDX-License-Identifier: BUSL-1.1 //go:build !enterprise From fba59de3a3a64be32db190c86bc139ae68b15ef6 Mon Sep 17 00:00:00 2001 From: Thy Ton Date: Mon, 30 Dec 2024 11:18:20 -0800 Subject: [PATCH 24/25] rename file --- ..._system_plugins_oss.go => logical_system_plugins_stubs_oss.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename vault/{logical_system_plugins_oss.go => logical_system_plugins_stubs_oss.go} (100%) diff --git a/vault/logical_system_plugins_oss.go b/vault/logical_system_plugins_stubs_oss.go similarity index 100% rename from vault/logical_system_plugins_oss.go rename to vault/logical_system_plugins_stubs_oss.go From bc13b6e999d3fca77a95539c4f51dd222d0ff927 Mon Sep 17 00:00:00 2001 From: Thy Ton Date: Wed, 8 Jan 2025 09:38:37 -0800 Subject: [PATCH 25/25] remove validateVersionSHA256 --- vault/logical_system.go | 4 ---- vault/logical_system_plugins_stubs_oss.go | 4 ---- 2 files changed, 8 deletions(-) diff --git a/vault/logical_system.go b/vault/logical_system.go index dd2f34645489..688daf47fc9f 100644 --- a/vault/logical_system.go +++ b/vault/logical_system.go @@ -549,10 +549,6 @@ func (b *SystemBackend) handlePluginCatalogUpdate(ctx context.Context, _ *logica } if ociImage == "" { - if resp := validateVersionSHA256(pluginVersion, sha256); resp.IsError() { - return resp, nil - } - if err = b.Core.CheckPluginPerms(command); err != nil { return nil, err } diff --git a/vault/logical_system_plugins_stubs_oss.go b/vault/logical_system_plugins_stubs_oss.go index b9243a364efa..30ccdbc5e985 100644 --- a/vault/logical_system_plugins_stubs_oss.go +++ b/vault/logical_system_plugins_stubs_oss.go @@ -15,7 +15,3 @@ func validateSHA256(sha256 string) *logical.Response { } return nil } - -func validateVersionSHA256(version, sha256 string) *logical.Response { - return nil -}