mirror of
				https://github.com/optim-enterprises-bv/vault.git
				synced 2025-10-31 18:48:08 +00:00 
			
		
		
		
	plugin register with artifact stubs VAULT-32686 (#29113)
* add plugin catalog's entValidate() and setInternal() oss stubs * create plugin register command constructor oss stub * create EntPluginRunner oss stub * add validateSHA256() oss stub to validate plugin catalog update input
This commit is contained in:
		| @@ -551,9 +551,7 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) map[string]cli.Co | |||||||
| 			}, nil | 			}, nil | ||||||
| 		}, | 		}, | ||||||
| 		"plugin register": func() (cli.Command, error) { | 		"plugin register": func() (cli.Command, error) { | ||||||
| 			return &PluginRegisterCommand{ | 			return NewPluginRegisterCommand(getBaseCommand()), nil | ||||||
| 				BaseCommand: getBaseCommand(), |  | ||||||
| 			}, nil |  | ||||||
| 		}, | 		}, | ||||||
| 		"plugin reload": func() (cli.Command, error) { | 		"plugin reload": func() (cli.Command, error) { | ||||||
| 			return &PluginReloadCommand{ | 			return &PluginReloadCommand{ | ||||||
|   | |||||||
							
								
								
									
										14
									
								
								command/plugin_register_stubs_oss.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								command/plugin_register_stubs_oss.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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, | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -57,6 +57,8 @@ const MultiplexingCtxKey string = "multiplex_id" | |||||||
| // PluginRunner defines the metadata needed to run a plugin securely with | // PluginRunner defines the metadata needed to run a plugin securely with | ||||||
| // go-plugin. | // go-plugin. | ||||||
| type PluginRunner struct { | type PluginRunner struct { | ||||||
|  | 	EntPluginRunner | ||||||
|  |  | ||||||
| 	Name           string                      `json:"name" structs:"name"` | 	Name           string                      `json:"name" structs:"name"` | ||||||
| 	Type           consts.PluginType           `json:"type" structs:"type"` | 	Type           consts.PluginType           `json:"type" structs:"type"` | ||||||
| 	Version        string                      `json:"version" structs:"version"` | 	Version        string                      `json:"version" structs:"version"` | ||||||
|   | |||||||
							
								
								
									
										8
									
								
								sdk/helper/pluginutil/runner_stubs_oss.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								sdk/helper/pluginutil/runner_stubs_oss.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | |||||||
|  | // Copyright (c) HashiCorp, Inc. | ||||||
|  | // SPDX-License-Identifier: MPL-2.0 | ||||||
|  |  | ||||||
|  | //go:build !enterprise | ||||||
|  |  | ||||||
|  | package pluginutil | ||||||
|  |  | ||||||
|  | type EntPluginRunner struct{} | ||||||
| @@ -538,8 +538,8 @@ func (b *SystemBackend) handlePluginCatalogUpdate(ctx context.Context, _ *logica | |||||||
| 	sha256 := d.Get("sha256").(string) | 	sha256 := d.Get("sha256").(string) | ||||||
| 	if sha256 == "" { | 	if sha256 == "" { | ||||||
| 		sha256 = d.Get("sha_256").(string) | 		sha256 = d.Get("sha_256").(string) | ||||||
| 		if sha256 == "" { | 		if resp := validateSHA256(sha256); resp.IsError() { | ||||||
| 			return logical.ErrorResponse("missing SHA-256 value"), nil | 			return resp, nil | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										17
									
								
								vault/logical_system_plugins_stubs_oss.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								vault/logical_system_plugins_stubs_oss.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | |||||||
|  | // 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 | ||||||
|  | } | ||||||
| @@ -173,6 +173,13 @@ func SetupPluginCatalog(ctx context.Context, in *PluginCatalogInput) (*PluginCat | |||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	// Sanitize the plugin catalog | ||||||
|  | 	err = catalog.entValidate(ctx) | ||||||
|  | 	if err != nil { | ||||||
|  | 		logger.Error("error while sanitizing plugin storage", "error", err) | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	if legacy, _ := strconv.ParseBool(os.Getenv(pluginutil.PluginUseLegacyEnvLayering)); legacy { | 	if legacy, _ := strconv.ParseBool(os.Getenv(pluginutil.PluginUseLegacyEnvLayering)); legacy { | ||||||
| 		conflicts := false | 		conflicts := false | ||||||
| 		osKeys := envKeys(os.Environ()) | 		osKeys := envKeys(os.Environ()) | ||||||
| @@ -959,119 +966,6 @@ func (c *PluginCatalog) Set(ctx context.Context, plugin pluginutil.SetPluginInpu | |||||||
| 	return err | 	return err | ||||||
| } | } | ||||||
|  |  | ||||||
| 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 |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Delete is used to remove an external plugin from the catalog. Builtin plugins | // Delete is used to remove an external plugin from the catalog. Builtin plugins | ||||||
| // can not be deleted. | // can not be deleted. | ||||||
| func (c *PluginCatalog) Delete(ctx context.Context, name string, pluginType consts.PluginType, pluginVersion string) error { | func (c *PluginCatalog) Delete(ctx context.Context, name string, pluginType consts.PluginType, pluginVersion string) error { | ||||||
|   | |||||||
							
								
								
									
										138
									
								
								vault/plugincatalog/plugin_catalog_stubs_oss.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										138
									
								
								vault/plugincatalog/plugin_catalog_stubs_oss.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,138 @@ | |||||||
|  | // Copyright (c) HashiCorp, Inc. | ||||||
|  | // SPDX-License-Identifier: BUSL-1.1 | ||||||
|  |  | ||||||
|  | //go:build !enterprise | ||||||
|  |  | ||||||
|  | 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" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // 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) entValidate(context.Context) error { | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user
	 Thy Ton
					Thy Ton