mirror of
https://github.com/hashicorp/vault.git
synced 2025-08-07 07:07:05 +02: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:
parent
80fe86a352
commit
4f14f7bfec
@ -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
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user