2abf916ddb
* store unauthenticated path wildcards in map * working unauthenticated paths with basic unit tests * refactor wildcard logic * add parseUnauthenticatedPaths unit tests * use parseUnauthenticatedPaths when reloading backend * add more wildcard test cases * update special paths doc; add changelog * remove buggy prefix check; add test cases * prevent false positives for prefix matches If we ever encounter a mismatched segment, break and set a flag to prevent false positives for prefix matches. If it is a match we need to do a prefix check. But we should not return unless HasPrefix also evaluates to true. Otherwise we should let the for loop continue to check other possibilities and only return false once all wildcard paths have been evaluated. * refactor switch and add more test cases * remove comment leftover from debug session * add more wildcard path validation and test cases * update changelong; feature -> improvement * simplify wildcard segment matching logic * refactor wildcard matching into func * fix glob matching, add more wildcard validation, refactor * refactor common wildcard errors to func * move doc comment to logical.Paths * optimize wildcard paths storage with pre-split slices * fix comment typo * fix test case after changing wildcard paths storage type * move prefix check to parseUnauthenticatedPaths * tweak regex, remove unneeded array copy, refactor * add test case around wildcard and glob matching
205 lines
5.1 KiB
Go
205 lines
5.1 KiB
Go
package vault
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/hashicorp/vault/helper/namespace"
|
|
|
|
multierror "github.com/hashicorp/go-multierror"
|
|
"github.com/hashicorp/go-secure-stdlib/strutil"
|
|
"github.com/hashicorp/vault/sdk/logical"
|
|
)
|
|
|
|
// reloadPluginMounts reloads provided mounts, regardless of
|
|
// plugin name, as long as the backend type is plugin.
|
|
func (c *Core) reloadMatchingPluginMounts(ctx context.Context, mounts []string) error {
|
|
c.mountsLock.RLock()
|
|
defer c.mountsLock.RUnlock()
|
|
c.authLock.RLock()
|
|
defer c.authLock.RUnlock()
|
|
|
|
ns, err := namespace.FromContext(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var errors error
|
|
for _, mount := range mounts {
|
|
entry := c.router.MatchingMountEntry(ctx, mount)
|
|
if entry == nil {
|
|
errors = multierror.Append(errors, fmt.Errorf("cannot fetch mount entry on %q", mount))
|
|
continue
|
|
}
|
|
|
|
var isAuth bool
|
|
fullPath := c.router.MatchingMount(ctx, mount)
|
|
if strings.HasPrefix(fullPath, credentialRoutePrefix) {
|
|
isAuth = true
|
|
}
|
|
|
|
// We dont reload mounts that are not in the same namespace
|
|
if ns.ID != entry.Namespace().ID {
|
|
continue
|
|
}
|
|
|
|
err := c.reloadBackendCommon(ctx, entry, isAuth)
|
|
if err != nil {
|
|
errors = multierror.Append(errors, fmt.Errorf("cannot reload plugin on %q: %w", mount, err))
|
|
continue
|
|
}
|
|
c.logger.Info("successfully reloaded plugin", "plugin", entry.Accessor, "path", entry.Path)
|
|
}
|
|
return errors
|
|
}
|
|
|
|
// reloadPlugin reloads all mounted backends that are of
|
|
// plugin pluginName (name of the plugin as registered in
|
|
// the plugin catalog).
|
|
func (c *Core) reloadMatchingPlugin(ctx context.Context, pluginName string) error {
|
|
c.mountsLock.RLock()
|
|
defer c.mountsLock.RUnlock()
|
|
c.authLock.RLock()
|
|
defer c.authLock.RUnlock()
|
|
|
|
ns, err := namespace.FromContext(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Filter mount entries that only matches the plugin name
|
|
for _, entry := range c.mounts.Entries {
|
|
// We dont reload mounts that are not in the same namespace
|
|
if ns.ID != entry.Namespace().ID {
|
|
continue
|
|
}
|
|
if entry.Type == pluginName || (entry.Type == "plugin" && entry.Config.PluginName == pluginName) {
|
|
err := c.reloadBackendCommon(ctx, entry, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
c.logger.Info("successfully reloaded plugin", "plugin", pluginName, "path", entry.Path)
|
|
}
|
|
}
|
|
|
|
// Filter auth mount entries that ony matches the plugin name
|
|
for _, entry := range c.auth.Entries {
|
|
// We dont reload mounts that are not in the same namespace
|
|
if ns.ID != entry.Namespace().ID {
|
|
continue
|
|
}
|
|
|
|
if entry.Type == pluginName || (entry.Type == "plugin" && entry.Config.PluginName == pluginName) {
|
|
err := c.reloadBackendCommon(ctx, entry, true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
c.logger.Info("successfully reloaded plugin", "plugin", entry.Accessor, "path", entry.Path)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// reloadBackendCommon is a generic method to reload a backend provided a
|
|
// MountEntry.
|
|
func (c *Core) reloadBackendCommon(ctx context.Context, entry *MountEntry, isAuth bool) error {
|
|
// Make sure our cache is up-to-date. Since some singleton mounts can be
|
|
// tuned, we do this before the below check.
|
|
entry.SyncCache()
|
|
|
|
// We don't want to reload the singleton mounts. They often have specific
|
|
// inmemory elements and we don't want to touch them here.
|
|
if strutil.StrListContains(singletonMounts, entry.Type) {
|
|
c.logger.Debug("skipping reload of singleton mount", "type", entry.Type)
|
|
return nil
|
|
}
|
|
|
|
path := entry.Path
|
|
|
|
if isAuth {
|
|
path = credentialRoutePrefix + path
|
|
}
|
|
|
|
// Fast-path out if the backend doesn't exist
|
|
raw, ok := c.router.root.Get(entry.Namespace().Path + path)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
re := raw.(*routeEntry)
|
|
|
|
// Grab the lock, this allows requests to drain before we cleanup the
|
|
// client.
|
|
re.l.Lock()
|
|
defer re.l.Unlock()
|
|
|
|
// Only call Cleanup if backend is initialized
|
|
if re.backend != nil {
|
|
// Call backend's Cleanup routine
|
|
re.backend.Cleanup(ctx)
|
|
}
|
|
|
|
view := re.storageView
|
|
viewPath := entry.UUID + "/"
|
|
switch entry.Table {
|
|
case mountTableType:
|
|
viewPath = backendBarrierPrefix + viewPath
|
|
case credentialTableType:
|
|
viewPath = credentialBarrierPrefix + viewPath
|
|
}
|
|
|
|
removePathCheckers(c, entry, viewPath)
|
|
|
|
sysView := c.mountEntrySysView(entry)
|
|
|
|
nilMount, err := preprocessMount(c, entry, view.(*BarrierView))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var backend logical.Backend
|
|
if !isAuth {
|
|
// Dispense a new backend
|
|
backend, err = c.newLogicalBackend(ctx, entry, sysView, view)
|
|
} else {
|
|
backend, err = c.newCredentialBackend(ctx, entry, sysView, view)
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if backend == nil {
|
|
return fmt.Errorf("nil backend of type %q returned from creation function", entry.Type)
|
|
}
|
|
|
|
addPathCheckers(c, entry, backend, viewPath)
|
|
|
|
if nilMount {
|
|
backend.Cleanup(ctx)
|
|
backend = nil
|
|
}
|
|
|
|
// Set the backend back
|
|
re.backend = backend
|
|
|
|
if backend != nil {
|
|
// Set paths as well
|
|
paths := backend.SpecialPaths()
|
|
if paths != nil {
|
|
re.rootPaths.Store(pathsToRadix(paths.Root))
|
|
loginPathsEntry, err := parseUnauthenticatedPaths(paths.Unauthenticated)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
re.loginPaths.Store(loginPathsEntry)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *Core) setupPluginReload() error {
|
|
return handleSetupPluginReload(c)
|
|
}
|