plugins: Mount missing plugin entries and skip loading (#18189)
* Skip plugin startup for missing plugins * Skip secrets startup for missing plugins * Add changelog for bugfix * Make plugin handling on unseal version-aware * Update plugin lazy-load logic/comments for readability * Add register/mount/deregister/seal/unseal go test * Consolidate lazy mount logic to prevent inconsistencies Co-authored-by: Tom Proctor <tomhjp@users.noreply.github.com>
This commit is contained in:
parent
eba490ccef
commit
ea41e62e83
|
@ -0,0 +1,3 @@
|
||||||
|
```release-note:bug
|
||||||
|
plugins: Skip loading but still mount data associated with missing plugins on unseal.
|
||||||
|
```
|
|
@ -800,11 +800,8 @@ func (c *Core) setupCredentials(ctx context.Context) error {
|
||||||
backend, entry.RunningSha256, err = c.newCredentialBackend(ctx, entry, sysView, view)
|
backend, entry.RunningSha256, err = c.newCredentialBackend(ctx, entry, sysView, view)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.logger.Error("failed to create credential entry", "path", entry.Path, "error", err)
|
c.logger.Error("failed to create credential entry", "path", entry.Path, "error", err)
|
||||||
plug, plugerr := c.pluginCatalog.Get(ctx, entry.Type, consts.PluginTypeCredential, "")
|
|
||||||
if plugerr == nil && plug != nil && !plug.Builtin {
|
if c.isMountable(ctx, entry, consts.PluginTypeCredential) {
|
||||||
// If we encounter an error instantiating the backend due to an error,
|
|
||||||
// skip backend initialization but register the entry to the mount table
|
|
||||||
// to preserve storage and path.
|
|
||||||
c.logger.Warn("skipping plugin-based credential entry", "path", entry.Path)
|
c.logger.Warn("skipping plugin-based credential entry", "path", entry.Path)
|
||||||
goto ROUTER_MOUNT
|
goto ROUTER_MOUNT
|
||||||
}
|
}
|
||||||
|
|
|
@ -3042,6 +3042,32 @@ func (c *Core) readFeatureFlags(ctx context.Context) (*FeatureFlags, error) {
|
||||||
return &flags, nil
|
return &flags, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isMountable tells us whether or not we can continue mounting a plugin-based
|
||||||
|
// mount entry after failing to instantiate a backend. We do this to preserve
|
||||||
|
// the storage and path when a plugin is missing or has otherwise been
|
||||||
|
// misconfigured. This allows users to recover from errors when starting Vault
|
||||||
|
// with misconfigured plugins. It should not be possible for existing builtins
|
||||||
|
// to be misconfigured, so that is a fatal error.
|
||||||
|
func (c *Core) isMountable(ctx context.Context, entry *MountEntry, pluginType consts.PluginType) bool {
|
||||||
|
// Prevent a panic early on
|
||||||
|
if entry == nil || c.pluginCatalog == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle aliases
|
||||||
|
t := entry.Type
|
||||||
|
if alias, ok := mountAliases[t]; ok {
|
||||||
|
t = alias
|
||||||
|
}
|
||||||
|
|
||||||
|
plug, err := c.pluginCatalog.Get(ctx, t, pluginType, entry.Version)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return plug == nil || !plug.Builtin
|
||||||
|
}
|
||||||
|
|
||||||
// MatchingMount returns the path of the mount that will be responsible for
|
// MatchingMount returns the path of the mount that will be responsible for
|
||||||
// handling the given request path.
|
// handling the given request path.
|
||||||
func (c *Core) MatchingMount(ctx context.Context, reqPath string) string {
|
func (c *Core) MatchingMount(ctx context.Context, reqPath string) string {
|
||||||
|
|
|
@ -277,6 +277,71 @@ func TestCore_EnableExternalPlugin_MultipleVersions(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCore_EnableExternalPlugin_Deregister_SealUnseal(t *testing.T) {
|
||||||
|
pluginDir, cleanup := MakeTestPluginDir(t)
|
||||||
|
t.Cleanup(func() { cleanup(t) })
|
||||||
|
|
||||||
|
// create an external plugin to shadow the builtin "pending-removal-test-plugin"
|
||||||
|
pluginName := "therug"
|
||||||
|
plugin := compilePlugin(t, consts.PluginTypeCredential, "", pluginDir)
|
||||||
|
err := os.Link(path.Join(pluginDir, plugin.fileName), path.Join(pluginDir, pluginName))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
conf := &CoreConfig{
|
||||||
|
BuiltinRegistry: NewMockBuiltinRegistry(),
|
||||||
|
PluginDirectory: pluginDir,
|
||||||
|
}
|
||||||
|
|
||||||
|
c := TestCoreWithSealAndUI(t, conf)
|
||||||
|
c, keys, root := testCoreUnsealed(t, c)
|
||||||
|
|
||||||
|
// Register a plugin
|
||||||
|
registerPlugin(t, c.systemBackend, pluginName, consts.PluginTypeCredential.String(), "", plugin.sha256, plugin.fileName)
|
||||||
|
mountPlugin(t, c.systemBackend, pluginName, consts.PluginTypeCredential, "", "")
|
||||||
|
plugct := len(c.pluginCatalog.externalPlugins)
|
||||||
|
if plugct != 1 {
|
||||||
|
t.Fatalf("expected a single external plugin entry after registering, got: %d", plugct)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now pull the rug out from underneath us
|
||||||
|
deregisterPlugin(t, c.systemBackend, pluginName, consts.PluginTypeCredential.String(), "", "", "")
|
||||||
|
|
||||||
|
if err := c.Seal(root); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
for i, key := range keys {
|
||||||
|
unseal, err := TestCoreUnseal(c, key)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
if i+1 == len(keys) && !unseal {
|
||||||
|
t.Fatalf("err: should be unsealed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
plugct = len(c.pluginCatalog.externalPlugins)
|
||||||
|
if plugct != 0 {
|
||||||
|
t.Fatalf("expected no plugin entries after unseal, got: %d", plugct)
|
||||||
|
}
|
||||||
|
|
||||||
|
found := false
|
||||||
|
mounts, err := c.ListAuths()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for _, mount := range mounts {
|
||||||
|
if mount.Type == pluginName {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
t.Fatalf("expected to find %s mount, but got none", pluginName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestCore_EnableExternalPlugin_ShadowBuiltin(t *testing.T) {
|
func TestCore_EnableExternalPlugin_ShadowBuiltin(t *testing.T) {
|
||||||
pluginDir, cleanup := MakeTestPluginDir(t)
|
pluginDir, cleanup := MakeTestPluginDir(t)
|
||||||
t.Cleanup(func() { cleanup(t) })
|
t.Cleanup(func() { cleanup(t) })
|
||||||
|
|
|
@ -1467,10 +1467,8 @@ func (c *Core) setupMounts(ctx context.Context) error {
|
||||||
backend, entry.RunningSha256, err = c.newLogicalBackend(ctx, entry, sysView, view)
|
backend, entry.RunningSha256, err = c.newLogicalBackend(ctx, entry, sysView, view)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.logger.Error("failed to create mount entry", "path", entry.Path, "error", err)
|
c.logger.Error("failed to create mount entry", "path", entry.Path, "error", err)
|
||||||
if !c.builtinRegistry.Contains(entry.Type, consts.PluginTypeSecrets) {
|
|
||||||
// If we encounter an error instantiating the backend due to an error,
|
if c.isMountable(ctx, entry, consts.PluginTypeSecrets) {
|
||||||
// skip backend initialization but register the entry to the mount table
|
|
||||||
// to preserve storage and path.
|
|
||||||
c.logger.Warn("skipping plugin-based mount entry", "path", entry.Path)
|
c.logger.Warn("skipping plugin-based mount entry", "path", entry.Path)
|
||||||
goto ROUTER_MOUNT
|
goto ROUTER_MOUNT
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue