Skip to content

Commit 88e76a1

Browse files
committed
Add version pinning to plugin catalog
No HTTP API implementation yet for managing pins, so no user-facing effects yet
1 parent 80f85a0 commit 88e76a1

File tree

17 files changed

+685
-184
lines changed

17 files changed

+685
-184
lines changed

builtin/logical/database/backend.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,17 @@ func (b *databaseBackend) GetConnectionWithConfig(ctx context.Context, name stri
298298
return nil, err
299299
}
300300

301-
dbw, err := newDatabaseWrapper(ctx, config.PluginName, config.PluginVersion, b.System(), b.logger)
301+
// Override the configured version if there is a pinned version.
302+
pinnedVersion, err := b.getPinnedVersion(ctx, config.PluginName)
303+
if err != nil {
304+
return nil, err
305+
}
306+
pluginVersion := config.PluginVersion
307+
if pinnedVersion != "" {
308+
pluginVersion = pinnedVersion
309+
}
310+
311+
dbw, err := newDatabaseWrapper(ctx, config.PluginName, pluginVersion, b.System(), b.logger)
302312
if err != nil {
303313
return nil, fmt.Errorf("unable to create database instance: %w", err)
304314
}

builtin/logical/database/path_config_connection.go

Lines changed: 90 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -436,58 +436,9 @@ func (b *databaseBackend) connectionWriteHandler() framework.OperationFunc {
436436
return logical.ErrorResponse(respErrEmptyPluginName), nil
437437
}
438438

439-
if pluginVersionRaw, ok := data.GetOk("plugin_version"); ok {
440-
config.PluginVersion = pluginVersionRaw.(string)
441-
}
442-
443-
var builtinShadowed bool
444-
if unversionedPlugin, err := b.System().LookupPlugin(ctx, config.PluginName, consts.PluginTypeDatabase); err == nil && !unversionedPlugin.Builtin {
445-
builtinShadowed = true
446-
}
447-
switch {
448-
case config.PluginVersion != "":
449-
semanticVersion, err := version.NewVersion(config.PluginVersion)
450-
if err != nil {
451-
return logical.ErrorResponse("version %q is not a valid semantic version: %s", config.PluginVersion, err), nil
452-
}
453-
454-
// Canonicalize the version.
455-
config.PluginVersion = "v" + semanticVersion.String()
456-
457-
if config.PluginVersion == versions.GetBuiltinVersion(consts.PluginTypeDatabase, config.PluginName) {
458-
if builtinShadowed {
459-
return logical.ErrorResponse("database plugin %q, version %s not found, as it is"+
460-
" overridden by an unversioned plugin of the same name. Omit `plugin_version` to use the unversioned plugin", config.PluginName, config.PluginVersion), nil
461-
}
462-
463-
config.PluginVersion = ""
464-
}
465-
case builtinShadowed:
466-
// We'll select the unversioned plugin that's been registered.
467-
case req.Operation == logical.CreateOperation:
468-
// No version provided and no unversioned plugin of that name available.
469-
// Pin to the current latest version if any versioned plugins are registered.
470-
plugins, err := b.System().ListVersionedPlugins(ctx, consts.PluginTypeDatabase)
471-
if err != nil {
472-
return nil, err
473-
}
474-
475-
var versionedCandidates []pluginutil.VersionedPlugin
476-
for _, plugin := range plugins {
477-
if !plugin.Builtin && plugin.Name == config.PluginName && plugin.Version != "" {
478-
versionedCandidates = append(versionedCandidates, plugin)
479-
}
480-
}
481-
482-
if len(versionedCandidates) != 0 {
483-
// Sort in reverse order.
484-
sort.SliceStable(versionedCandidates, func(i, j int) bool {
485-
return versionedCandidates[i].SemanticVersion.GreaterThan(versionedCandidates[j].SemanticVersion)
486-
})
487-
488-
config.PluginVersion = "v" + versionedCandidates[0].SemanticVersion.String()
489-
b.logger.Debug(fmt.Sprintf("pinning %q database plugin version %q from candidates %v", config.PluginName, config.PluginVersion, versionedCandidates))
490-
}
439+
pluginVersion, respErr, err := b.selectPluginVersion(ctx, config, data, req.Operation)
440+
if respErr != nil || err != nil {
441+
return respErr, err
491442
}
492443

493444
if allowedRolesRaw, ok := data.GetOk("allowed_roles"); ok {
@@ -536,7 +487,7 @@ func (b *databaseBackend) connectionWriteHandler() framework.OperationFunc {
536487
}
537488

538489
// Create a database plugin and initialize it.
539-
dbw, err := newDatabaseWrapper(ctx, config.PluginName, config.PluginVersion, b.System(), b.logger)
490+
dbw, err := newDatabaseWrapper(ctx, config.PluginName, pluginVersion, b.System(), b.logger)
540491
if err != nil {
541492
return logical.ErrorResponse("error creating database object: %s", err), nil
542493
}
@@ -613,6 +564,92 @@ func storeConfig(ctx context.Context, storage logical.Storage, name string, conf
613564
return nil
614565
}
615566

567+
func (b *databaseBackend) getPinnedVersion(ctx context.Context, pluginName string) (string, error) {
568+
extendedSys, ok := b.System().(logical.ExtendedSystemView)
569+
if !ok {
570+
return "", fmt.Errorf("database backend does not support running as an external plugin")
571+
}
572+
573+
pin, err := extendedSys.GetPinnedPluginVersion(ctx, consts.PluginTypeDatabase, pluginName)
574+
if err != nil {
575+
return "", err
576+
}
577+
if pin == nil {
578+
return "", nil
579+
}
580+
581+
return pin.Version, nil
582+
}
583+
584+
func (b *databaseBackend) selectPluginVersion(ctx context.Context, config *DatabaseConfig, data *framework.FieldData, op logical.Operation) (string, *logical.Response, error) {
585+
pinnedVersion, err := b.getPinnedVersion(ctx, config.PluginName)
586+
if err != nil {
587+
return "", nil, err
588+
}
589+
pluginVersionRaw, ok := data.GetOk("plugin_version")
590+
591+
switch {
592+
case ok && pinnedVersion != "":
593+
return "", logical.ErrorResponse("cannot specify plugin_version for plugin %q as it is pinned (v%s)", config.PluginName, pinnedVersion), nil
594+
case pinnedVersion != "":
595+
return pinnedVersion, nil, nil
596+
case ok:
597+
config.PluginVersion = pluginVersionRaw.(string)
598+
}
599+
600+
var builtinShadowed bool
601+
if unversionedPlugin, err := b.System().LookupPlugin(ctx, config.PluginName, consts.PluginTypeDatabase); err == nil && !unversionedPlugin.Builtin {
602+
builtinShadowed = true
603+
}
604+
switch {
605+
case config.PluginVersion != "":
606+
semanticVersion, err := version.NewVersion(config.PluginVersion)
607+
if err != nil {
608+
return "", logical.ErrorResponse("version %q is not a valid semantic version: %s", config.PluginVersion, err), nil
609+
}
610+
611+
// Canonicalize the version.
612+
config.PluginVersion = "v" + semanticVersion.String()
613+
614+
if config.PluginVersion == versions.GetBuiltinVersion(consts.PluginTypeDatabase, config.PluginName) {
615+
if builtinShadowed {
616+
return "", logical.ErrorResponse("database plugin %q, version %s not found, as it is"+
617+
" overridden by an unversioned plugin of the same name. Omit `plugin_version` to use the unversioned plugin", config.PluginName, config.PluginVersion), nil
618+
}
619+
620+
config.PluginVersion = ""
621+
}
622+
case builtinShadowed:
623+
// We'll select the unversioned plugin that's been registered.
624+
case op == logical.CreateOperation:
625+
// No version provided and no unversioned plugin of that name available.
626+
// Pin to the current latest version if any versioned plugins are registered.
627+
plugins, err := b.System().ListVersionedPlugins(ctx, consts.PluginTypeDatabase)
628+
if err != nil {
629+
return "", nil, err
630+
}
631+
632+
var versionedCandidates []pluginutil.VersionedPlugin
633+
for _, plugin := range plugins {
634+
if !plugin.Builtin && plugin.Name == config.PluginName && plugin.Version != "" {
635+
versionedCandidates = append(versionedCandidates, plugin)
636+
}
637+
}
638+
639+
if len(versionedCandidates) != 0 {
640+
// Sort in reverse order.
641+
sort.SliceStable(versionedCandidates, func(i, j int) bool {
642+
return versionedCandidates[i].SemanticVersion.GreaterThan(versionedCandidates[j].SemanticVersion)
643+
})
644+
645+
config.PluginVersion = "v" + versionedCandidates[0].SemanticVersion.String()
646+
b.logger.Debug(fmt.Sprintf("pinning %q database plugin version %q from candidates %v", config.PluginName, config.PluginVersion, versionedCandidates))
647+
}
648+
}
649+
650+
return config.PluginVersion, nil, nil
651+
}
652+
616653
const pathConfigConnectionHelpSyn = `
617654
Configure connection details to a database plugin.
618655
`

sdk/helper/pluginutil/runner.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,12 @@ type VersionedPlugin struct {
144144
SemanticVersion *version.Version `json:"-"`
145145
}
146146

147+
type PinnedVersion struct {
148+
Name string `json:"name"`
149+
Type consts.PluginType `json:"type"`
150+
Version string `json:"version"`
151+
}
152+
147153
// CtxCancelIfCanceled takes a context cancel func and a context. If the context is
148154
// shutdown the cancelfunc is called. This is useful for merging two cancel
149155
// functions.

sdk/logical/system_view.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,9 @@ type ExtendedSystemView interface {
117117
RequestWellKnownRedirect(ctx context.Context, src, dest string) error
118118
// Deregister a specific redirect. Returns true if that redirect source was found
119119
DeregisterWellKnownRedirect(ctx context.Context, src string) bool
120+
121+
// GetPinnedPluginVersion returns the pinned version for the given plugin, if any.
122+
GetPinnedPluginVersion(ctx context.Context, pluginType consts.PluginType, pluginName string) (*pluginutil.PinnedVersion, error)
120123
}
121124

122125
type PasswordGenerator func() (password string, err error)

vault/auth.go

Lines changed: 36 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ func (c *Core) enableCredentialInternal(ctx context.Context, entry *MountEntry,
175175
var backend logical.Backend
176176
// Create the new backend
177177
sysView := c.mountEntrySysView(entry)
178-
backend, entry.RunningSha256, err = c.newCredentialBackend(ctx, entry, sysView, view)
178+
backend, err = c.newCredentialBackend(ctx, entry, sysView, view)
179179
if err != nil {
180180
return err
181181
}
@@ -188,14 +188,6 @@ func (c *Core) enableCredentialInternal(ctx context.Context, entry *MountEntry,
188188
if backendType != logical.TypeCredential {
189189
return fmt.Errorf("cannot mount %q of type %q as an auth backend", entry.Type, backendType)
190190
}
191-
// update the entry running version with the configured version, which was verified during registration.
192-
entry.RunningVersion = entry.Version
193-
if entry.RunningVersion == "" {
194-
// don't set the running version to a builtin if it is running as an external plugin
195-
if entry.RunningSha256 == "" {
196-
entry.RunningVersion = versions.GetBuiltinVersion(consts.PluginTypeCredential, entry.Type)
197-
}
198-
}
199191
addPathCheckers(c, entry, backend, viewPath)
200192

201193
// If the mount is filtered or we are on a DR secondary we don't want to
@@ -249,7 +241,7 @@ func (c *Core) enableCredentialInternal(ctx context.Context, entry *MountEntry,
249241
}
250242

251243
if c.logger.IsInfo() {
252-
c.logger.Info("enabled credential backend", "path", entry.Path, "type", entry.Type, "version", entry.Version)
244+
c.logger.Info("enabled credential backend", "path", entry.Path, "type", entry.Type, "version", entry.RunningVersion)
253245
}
254246
return nil
255247
}
@@ -805,29 +797,24 @@ func (c *Core) setupCredentials(ctx context.Context) error {
805797
// Initialize the backend
806798
sysView := c.mountEntrySysView(entry)
807799

808-
backend, entry.RunningSha256, err = c.newCredentialBackend(ctx, entry, sysView, view)
800+
backend, err = c.newCredentialBackend(ctx, entry, sysView, view)
809801
if err != nil {
810802
c.logger.Error("failed to create credential entry", "path", entry.Path, "error", err)
811803

812-
if c.isMountable(ctx, entry, consts.PluginTypeCredential) {
804+
mountable, checkErr := c.isMountable(ctx, entry, consts.PluginTypeSecrets)
805+
if checkErr != nil {
806+
return errors.Join(errLoadMountsFailed, checkErr, err)
807+
}
808+
if mountable {
813809
c.logger.Warn("skipping plugin-based auth entry", "path", entry.Path)
814810
goto ROUTER_MOUNT
815811
}
816-
return errLoadAuthFailed
812+
return errors.Join(errLoadAuthFailed, err)
817813
}
818814
if backend == nil {
819815
return fmt.Errorf("nil backend returned from %q factory", entry.Type)
820816
}
821817

822-
// update the entry running version with the configured version, which was verified during registration.
823-
entry.RunningVersion = entry.Version
824-
if entry.RunningVersion == "" {
825-
// don't set the running version to a builtin if it is running as an external plugin
826-
if entry.RunningSha256 == "" {
827-
entry.RunningVersion = versions.GetBuiltinVersion(consts.PluginTypeCredential, entry.Type)
828-
}
829-
}
830-
831818
// Do not start up deprecated builtin plugins. If this is a major
832819
// upgrade, stop unsealing and shutdown. If we've already mounted this
833820
// plugin, skip backend initialization and mount the data for posterity.
@@ -953,33 +940,37 @@ func (c *Core) teardownCredentials(ctx context.Context) error {
953940

954941
// newCredentialBackend is used to create and configure a new credential backend by name.
955942
// It also returns the SHA256 of the plugin, if available.
956-
func (c *Core) newCredentialBackend(ctx context.Context, entry *MountEntry, sysView logical.SystemView, view logical.Storage) (logical.Backend, string, error) {
943+
func (c *Core) newCredentialBackend(ctx context.Context, entry *MountEntry, sysView logical.SystemView, view logical.Storage) (logical.Backend, error) {
957944
t := entry.Type
958945
if alias, ok := credentialAliases[t]; ok {
959946
t = alias
960947
}
961948

949+
pluginVersion, err := c.resolveMountEntryVersion(ctx, consts.PluginTypeCredential, entry)
950+
if err != nil {
951+
return nil, err
952+
}
962953
var runningSha string
963-
f, ok := c.credentialBackends[t]
954+
factory, ok := c.credentialBackends[t]
964955
if !ok {
965-
plug, err := c.pluginCatalog.Get(ctx, t, consts.PluginTypeCredential, entry.Version)
956+
plug, err := c.pluginCatalog.Get(ctx, t, consts.PluginTypeCredential, pluginVersion)
966957
if err != nil {
967-
return nil, "", err
958+
return nil, err
968959
}
969960
if plug == nil {
970961
errContext := t
971-
if entry.Version != "" {
972-
errContext += fmt.Sprintf(", version=%s", entry.Version)
962+
if pluginVersion != "" {
963+
errContext += fmt.Sprintf(", version=%s", pluginVersion)
973964
}
974-
return nil, "", fmt.Errorf("%w: %s", plugincatalog.ErrPluginNotFound, errContext)
965+
return nil, fmt.Errorf("%w: %s", plugincatalog.ErrPluginNotFound, errContext)
975966
}
976967
if len(plug.Sha256) > 0 {
977968
runningSha = hex.EncodeToString(plug.Sha256)
978969
}
979970

980-
f = plugin.Factory
971+
factory = plugin.Factory
981972
if !plug.Builtin {
982-
f = wrapFactoryCheckPerms(c, plugin.Factory)
973+
factory = wrapFactoryCheckPerms(c, plugin.Factory)
983974
}
984975
}
985976
// Set up conf to pass in plugin_name
@@ -996,7 +987,7 @@ func (c *Core) newCredentialBackend(ctx context.Context, entry *MountEntry, sysV
996987
}
997988

998989
conf["plugin_type"] = consts.PluginTypeCredential.String()
999-
conf["plugin_version"] = entry.Version
990+
conf["plugin_version"] = pluginVersion
1000991

1001992
authLogger := c.baseLogger.Named(fmt.Sprintf("auth.%s.%s", t, entry.Accessor))
1002993
c.AddLogger(authLogger)
@@ -1005,11 +996,11 @@ func (c *Core) newCredentialBackend(ctx context.Context, entry *MountEntry, sysV
1005996
MountAccessor: entry.Accessor,
1006997
MountPath: entry.Path,
1007998
Plugin: entry.Type,
1008-
PluginVersion: entry.RunningVersion,
1009-
Version: entry.Version,
999+
PluginVersion: pluginVersion,
1000+
Version: entry.Options["version"],
10101001
})
10111002
if err != nil {
1012-
return nil, "", err
1003+
return nil, err
10131004
}
10141005

10151006
config := &logical.BackendConfig{
@@ -1021,12 +1012,19 @@ func (c *Core) newCredentialBackend(ctx context.Context, entry *MountEntry, sysV
10211012
EventsSender: pluginEventSender,
10221013
}
10231014

1024-
b, err := f(ctx, config)
1015+
backend, err := factory(ctx, config)
10251016
if err != nil {
1026-
return nil, "", err
1017+
return nil, err
1018+
}
1019+
if backend != nil {
1020+
entry.RunningVersion = pluginVersion
1021+
entry.RunningSha256 = runningSha
1022+
if entry.RunningVersion == "" && entry.RunningSha256 == "" {
1023+
entry.RunningVersion = versions.GetBuiltinVersion(consts.PluginTypeCredential, entry.Type)
1024+
}
10271025
}
10281026

1029-
return b, runningSha, nil
1027+
return backend, nil
10301028
}
10311029

10321030
func wrapFactoryCheckPerms(core *Core, f logical.Factory) logical.Factory {

0 commit comments

Comments
 (0)