Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

plugin register with artifact stubs VAULT-32686 #29113

Merged
merged 32 commits into from
Jan 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
54f16a5
add EntCoreGetter interface as a field in PluginCatalog
thyton Dec 5, 2024
f4b2b4b
add EntPluginRunner as composing struct and Enterprise bool field to …
thyton Dec 5, 2024
4c27e2c
add unpackPluginArtifact() and setEntPluginRunner() oss stubs to Plug…
thyton Dec 5, 2024
435c066
revert EntCoreGetter field addition to PluginCatalog
thyton Dec 5, 2024
5abe606
revert EntPluginRunner field addition to PluginRunner
thyton Dec 5, 2024
6e4050a
remove context.Context param from unpackPluginArtifact
thyton Dec 6, 2024
880147a
allow plugin register with artifact to omit sha256 param
thyton Dec 6, 2024
4de6669
use err EvalSymlinks(command) to determine when unpack plugin artifact
thyton Dec 6, 2024
ba1f8fb
error plugin catalog update on empty plugin version and sha256 if vau…
thyton Dec 9, 2024
aefac15
remove sha56 requirement for enterprise plugin register w/ artifact c…
thyton Dec 9, 2024
fce85bd
add sanitize() stub oss to plugin catalog
thyton Dec 9, 2024
5148a46
prefix sanitize and unpackArtifact funcs with ent
thyton Dec 9, 2024
61a5c1b
add context.Context param to entSanitize()
thyton Dec 12, 2024
468a7f3
Merge branch 'main' into VAULT-32686-plugin-register-with-artifact-stubs
thyton Dec 12, 2024
8daeb2a
add plugin name and version to unpack failure error message
thyton Dec 12, 2024
0a4b21b
Merge branch 'VAULT-32686-plugin-register-with-artifact-stubs' of git…
thyton Dec 12, 2024
82d8b0c
call entUnpackArtifact on empty sha256
thyton Dec 18, 2024
2acd09d
Merge branch 'main' into VAULT-32686-plugin-register-with-artifact-stubs
thyton Dec 18, 2024
0d1b748
evaluate path before checking empty sha256 to unpack artifact
thyton Dec 18, 2024
f47ce9f
Merge branch 'main' into VAULT-32686-plugin-register-with-artifact-stubs
thyton Dec 18, 2024
a9514fc
create oss stub constructor for plugin register command
thyton Dec 19, 2024
923c43c
create oss stub for EntPluginRunner
thyton Dec 19, 2024
95a23de
convert PluginCatalog setInternal() to oss stub
thyton Dec 19, 2024
96b2967
rename entSanitize to entValidate
thyton Dec 29, 2024
1c8bbbc
factor out functions to validate sha256 and version+sha256
thyton Dec 29, 2024
bb1ab04
Merge branch 'main' into VAULT-32686-plugin-register-with-artifact-stubs
thyton Dec 30, 2024
2c2e6f5
revert adding enterprise only feature error
thyton Dec 30, 2024
57742fe
Merge branch 'VAULT-32686-plugin-register-with-artifact-stubs' of git…
thyton Dec 30, 2024
21901ed
update license
thyton Dec 30, 2024
fba59de
rename file
thyton Dec 30, 2024
bc13b6e
remove validateVersionSHA256
thyton Jan 8, 2025
1eaa231
Merge branch 'main' into VAULT-32686-plugin-register-with-artifact-stubs
thyton Jan 8, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions command/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down
14 changes: 14 additions & 0 deletions command/plugin_register_stubs_oss.go
Original file line number Diff line number Diff line change
@@ -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,
}
}
2 changes: 2 additions & 0 deletions sdk/helper/pluginutil/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down
8 changes: 8 additions & 0 deletions sdk/helper/pluginutil/runner_stubs_oss.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

//go:build !enterprise

package pluginutil

type EntPluginRunner struct{}
4 changes: 2 additions & 2 deletions vault/logical_system.go
Original file line number Diff line number Diff line change
Expand Up @@ -537,8 +537,8 @@ func (b *SystemBackend) handlePluginCatalogUpdate(ctx context.Context, _ *logica
sha256 := d.Get("sha256").(string)
if sha256 == "" {
sha256 = d.Get("sha_256").(string)
if sha256 == "" {
return logical.ErrorResponse("missing SHA-256 value"), nil
if resp := validateSHA256(sha256); resp.IsError() {
return resp, nil
}
}

Expand Down
17 changes: 17 additions & 0 deletions vault/logical_system_plugins_stubs_oss.go
Original file line number Diff line number Diff line change
@@ -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
}
120 changes: 7 additions & 113 deletions vault/plugincatalog/plugin_catalog.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,13 @@ func SetupPluginCatalog(ctx context.Context, in *PluginCatalogInput) (*PluginCat
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 {
conflicts := false
osKeys := envKeys(os.Environ())
Expand Down Expand Up @@ -959,119 +966,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
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
// can not be deleted.
func (c *PluginCatalog) Delete(ctx context.Context, name string, pluginType consts.PluginType, pluginVersion string) error {
Expand Down
138 changes: 138 additions & 0 deletions vault/plugincatalog/plugin_catalog_stubs_oss.go
Original file line number Diff line number Diff line change
@@ -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