open-vault/vault/plugin_reload.go
John-Michael Faircloth 2abf916ddb
Add support to parameterize unauthenticated paths (#12668)
* 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
2021-10-13 11:51:20 -05:00

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)
}