2023-03-15 16:00:52 +00:00
|
|
|
// Copyright (c) HashiCorp, Inc.
|
|
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
|
2015-04-16 00:08:12 +00:00
|
|
|
package transit
|
|
|
|
|
|
|
|
import (
|
2018-01-19 06:44:44 +00:00
|
|
|
"context"
|
2021-04-22 15:20:59 +00:00
|
|
|
"fmt"
|
2021-09-13 21:44:56 +00:00
|
|
|
"io"
|
2022-01-20 15:10:15 +00:00
|
|
|
"strconv"
|
2017-02-16 21:29:30 +00:00
|
|
|
"strings"
|
2021-09-13 21:44:56 +00:00
|
|
|
"sync"
|
2022-01-20 15:10:15 +00:00
|
|
|
"time"
|
2017-02-16 21:29:30 +00:00
|
|
|
|
2022-01-20 15:10:15 +00:00
|
|
|
"github.com/hashicorp/go-multierror"
|
2019-04-13 07:44:06 +00:00
|
|
|
"github.com/hashicorp/vault/sdk/framework"
|
2022-01-20 15:10:15 +00:00
|
|
|
"github.com/hashicorp/vault/sdk/helper/consts"
|
2019-04-12 21:54:35 +00:00
|
|
|
"github.com/hashicorp/vault/sdk/helper/keysutil"
|
|
|
|
"github.com/hashicorp/vault/sdk/logical"
|
2015-04-16 00:08:12 +00:00
|
|
|
)
|
|
|
|
|
2021-09-13 21:44:56 +00:00
|
|
|
// Minimum cache size for transit backend
|
|
|
|
const minCacheSize = 10
|
|
|
|
|
2018-01-19 06:44:44 +00:00
|
|
|
func Factory(ctx context.Context, conf *logical.BackendConfig) (logical.Backend, error) {
|
2019-06-04 22:40:56 +00:00
|
|
|
b, err := Backend(ctx, conf)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2018-01-19 06:44:44 +00:00
|
|
|
if err := b.Setup(ctx, conf); err != nil {
|
2016-01-27 21:24:11 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
Backend plugin system (#2874)
* Add backend plugin changes
* Fix totp backend plugin tests
* Fix logical/plugin InvalidateKey test
* Fix plugin catalog CRUD test, fix NoopBackend
* Clean up commented code block
* Fix system backend mount test
* Set plugin_name to omitempty, fix handleMountTable config parsing
* Clean up comments, keep shim connections alive until cleanup
* Include pluginClient, disallow LookupPlugin call from within a plugin
* Add wrapper around backendPluginClient for proper cleanup
* Add logger shim tests
* Add logger, storage, and system shim tests
* Use pointer receivers for system view shim
* Use plugin name if no path is provided on mount
* Enable plugins for auth backends
* Add backend type attribute, move builtin/plugin/package
* Fix merge conflict
* Fix missing plugin name in mount config
* Add integration tests on enabling auth backend plugins
* Remove dependency cycle on mock-plugin
* Add passthrough backend plugin, use logical.BackendType to determine lease generation
* Remove vault package dependency on passthrough package
* Add basic impl test for passthrough plugin
* Incorporate feedback; set b.backend after shims creation on backendPluginServer
* Fix totp plugin test
* Add plugin backends docs
* Fix tests
* Fix builtin/plugin tests
* Remove flatten from PluginRunner fields
* Move mock plugin to logical/plugin, remove totp and passthrough plugins
* Move pluginMap into newPluginClient
* Do not create storage RPC connection on HandleRequest and HandleExistenceCheck
* Change shim logger's Fatal to no-op
* Change BackendType to uint32, match UX backend types
* Change framework.Backend Setup signature
* Add Setup func to logical.Backend interface
* Move OptionallyEnableMlock call into plugin.Serve, update docs and comments
* Remove commented var in plugin package
* RegisterLicense on logical.Backend interface (#3017)
* Add RegisterLicense to logical.Backend interface
* Update RegisterLicense to use callback func on framework.Backend
* Refactor framework.Backend.RegisterLicense
* plugin: Prevent plugin.SystemViewClient.ResponseWrapData from getting JWTs
* plugin: Revert BackendType to remove TypePassthrough and related references
* Fix typo in plugin backends docs
2017-07-20 17:28:40 +00:00
|
|
|
return b, nil
|
2015-04-16 00:08:12 +00:00
|
|
|
}
|
|
|
|
|
2019-06-04 22:40:56 +00:00
|
|
|
func Backend(ctx context.Context, conf *logical.BackendConfig) (*backend, error) {
|
2015-04-16 00:08:12 +00:00
|
|
|
var b backend
|
|
|
|
b.Backend = &framework.Backend{
|
2017-10-23 21:39:21 +00:00
|
|
|
PathsSpecial: &logical.Paths{
|
|
|
|
SealWrapStorage: []string{
|
|
|
|
"archive/",
|
|
|
|
"policy/",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2015-04-16 00:08:12 +00:00
|
|
|
Paths: []*framework.Path{
|
2015-09-17 22:49:50 +00:00
|
|
|
// Rotate/Config needs to come before Keys
|
2015-09-14 20:28:46 +00:00
|
|
|
// as the handler is greedy
|
2016-01-27 21:24:11 +00:00
|
|
|
b.pathRotate(),
|
|
|
|
b.pathRewrap(),
|
2022-05-11 16:28:32 +00:00
|
|
|
b.pathWrappingKey(),
|
2022-05-16 16:50:38 +00:00
|
|
|
b.pathImport(),
|
|
|
|
b.pathImportVersion(),
|
2016-01-27 21:24:11 +00:00
|
|
|
b.pathKeys(),
|
2016-10-18 14:13:01 +00:00
|
|
|
b.pathListKeys(),
|
2017-01-23 16:04:43 +00:00
|
|
|
b.pathExportKeys(),
|
2022-12-08 20:45:18 +00:00
|
|
|
b.pathKeysConfig(),
|
2016-01-27 21:24:11 +00:00
|
|
|
b.pathEncrypt(),
|
|
|
|
b.pathDecrypt(),
|
|
|
|
b.pathDatakey(),
|
2016-09-21 14:29:42 +00:00
|
|
|
b.pathRandom(),
|
|
|
|
b.pathHash(),
|
|
|
|
b.pathHMAC(),
|
|
|
|
b.pathSign(),
|
|
|
|
b.pathVerify(),
|
2017-12-14 17:51:50 +00:00
|
|
|
b.pathBackup(),
|
|
|
|
b.pathRestore(),
|
2018-10-17 16:05:05 +00:00
|
|
|
b.pathTrim(),
|
2019-06-04 22:40:56 +00:00
|
|
|
b.pathCacheConfig(),
|
2022-12-08 20:45:18 +00:00
|
|
|
b.pathConfigKeys(),
|
2015-04-16 00:08:12 +00:00
|
|
|
},
|
|
|
|
|
2022-01-20 15:10:15 +00:00
|
|
|
Secrets: []*framework.Secret{},
|
|
|
|
Invalidate: b.invalidate,
|
|
|
|
BackendType: logical.TypeLogical,
|
|
|
|
PeriodicFunc: b.periodicFunc,
|
2015-04-16 00:08:12 +00:00
|
|
|
}
|
|
|
|
|
2023-01-27 19:39:58 +00:00
|
|
|
b.backendUUID = conf.BackendUUID
|
|
|
|
|
2019-06-04 22:40:56 +00:00
|
|
|
// determine cacheSize to use. Defaults to 0 which means unlimited
|
|
|
|
cacheSize := 0
|
|
|
|
useCache := !conf.System.CachingDisabled()
|
|
|
|
if useCache {
|
|
|
|
var err error
|
|
|
|
cacheSize, err = GetCacheSizeFromStorage(ctx, conf.StorageView)
|
|
|
|
if err != nil {
|
2021-04-22 15:20:59 +00:00
|
|
|
return nil, fmt.Errorf("Error retrieving cache size from storage: %w", err)
|
2019-06-04 22:40:56 +00:00
|
|
|
}
|
2021-09-13 21:44:56 +00:00
|
|
|
|
|
|
|
if cacheSize != 0 && cacheSize < minCacheSize {
|
|
|
|
b.Logger().Warn("size %d is less than minimum %d. Cache size is set to %d", cacheSize, minCacheSize, minCacheSize)
|
|
|
|
cacheSize = minCacheSize
|
|
|
|
}
|
2019-06-04 22:40:56 +00:00
|
|
|
}
|
2016-01-27 21:24:11 +00:00
|
|
|
|
2019-06-04 22:40:56 +00:00
|
|
|
var err error
|
|
|
|
b.lm, err = keysutil.NewLockManager(useCache, cacheSize)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &b, nil
|
2015-04-16 00:08:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type backend struct {
|
|
|
|
*framework.Backend
|
2016-10-26 23:52:31 +00:00
|
|
|
lm *keysutil.LockManager
|
2021-09-13 21:44:56 +00:00
|
|
|
// Lock to make changes to any of the backend's cache configuration.
|
2022-01-20 15:10:15 +00:00
|
|
|
configMutex sync.RWMutex
|
|
|
|
cacheSizeChanged bool
|
|
|
|
checkAutoRotateAfter time.Time
|
|
|
|
autoRotateOnce sync.Once
|
2023-01-27 19:39:58 +00:00
|
|
|
backendUUID string
|
2015-04-16 00:08:12 +00:00
|
|
|
}
|
2017-02-16 21:29:30 +00:00
|
|
|
|
2019-06-04 22:40:56 +00:00
|
|
|
func GetCacheSizeFromStorage(ctx context.Context, s logical.Storage) (int, error) {
|
|
|
|
size := 0
|
|
|
|
entry, err := s.Get(ctx, "config/cache")
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
if entry != nil {
|
|
|
|
var storedCache configCache
|
|
|
|
if err := entry.DecodeJSON(&storedCache); err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
size = storedCache.Size
|
|
|
|
}
|
|
|
|
return size, nil
|
|
|
|
}
|
|
|
|
|
2021-09-13 21:44:56 +00:00
|
|
|
// Update cache size and get policy
|
|
|
|
func (b *backend) GetPolicy(ctx context.Context, polReq keysutil.PolicyRequest, rand io.Reader) (retP *keysutil.Policy, retUpserted bool, retErr error) {
|
|
|
|
// Acquire read lock to read cacheSizeChanged
|
|
|
|
b.configMutex.RLock()
|
|
|
|
if b.lm.GetUseCache() && b.cacheSizeChanged {
|
|
|
|
var err error
|
|
|
|
currentCacheSize := b.lm.GetCacheSize()
|
|
|
|
storedCacheSize, err := GetCacheSizeFromStorage(ctx, polReq.Storage)
|
|
|
|
if err != nil {
|
2021-09-28 16:59:30 +00:00
|
|
|
b.configMutex.RUnlock()
|
2021-09-13 21:44:56 +00:00
|
|
|
return nil, false, err
|
|
|
|
}
|
|
|
|
if currentCacheSize != storedCacheSize {
|
|
|
|
err = b.lm.InitCache(storedCacheSize)
|
|
|
|
if err != nil {
|
2021-09-28 16:59:30 +00:00
|
|
|
b.configMutex.RUnlock()
|
2021-09-13 21:44:56 +00:00
|
|
|
return nil, false, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Release the read lock and acquire the write lock
|
|
|
|
b.configMutex.RUnlock()
|
|
|
|
b.configMutex.Lock()
|
|
|
|
defer b.configMutex.Unlock()
|
|
|
|
b.cacheSizeChanged = false
|
2021-09-28 16:59:30 +00:00
|
|
|
} else {
|
|
|
|
b.configMutex.RUnlock()
|
2021-09-13 21:44:56 +00:00
|
|
|
}
|
|
|
|
p, _, err := b.lm.GetPolicy(ctx, polReq, rand)
|
|
|
|
if err != nil {
|
|
|
|
return p, false, err
|
|
|
|
}
|
|
|
|
return p, true, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *backend) invalidate(ctx context.Context, key string) {
|
2018-04-03 00:46:59 +00:00
|
|
|
if b.Logger().IsDebug() {
|
|
|
|
b.Logger().Debug("invalidating key", "key", key)
|
2017-02-16 21:29:30 +00:00
|
|
|
}
|
|
|
|
switch {
|
|
|
|
case strings.HasPrefix(key, "policy/"):
|
|
|
|
name := strings.TrimPrefix(key, "policy/")
|
|
|
|
b.lm.InvalidatePolicy(name)
|
2021-09-13 21:44:56 +00:00
|
|
|
case strings.HasPrefix(key, "cache-config/"):
|
|
|
|
// Acquire the lock to set the flag to indicate that cache size needs to be refreshed from storage
|
|
|
|
b.configMutex.Lock()
|
|
|
|
defer b.configMutex.Unlock()
|
|
|
|
b.cacheSizeChanged = true
|
2017-02-16 21:29:30 +00:00
|
|
|
}
|
|
|
|
}
|
2022-01-20 15:10:15 +00:00
|
|
|
|
|
|
|
// periodicFunc is a central collection of functions that run on an interval.
|
|
|
|
// Anything that should be called regularly can be placed within this method.
|
|
|
|
func (b *backend) periodicFunc(ctx context.Context, req *logical.Request) error {
|
|
|
|
// These operations ensure the auto-rotate only happens once simultaneously. It's an unlikely edge
|
|
|
|
// given the time scale, but a safeguard nonetheless.
|
|
|
|
var err error
|
|
|
|
didAutoRotate := false
|
|
|
|
autoRotateOnceFn := func() {
|
|
|
|
err = b.autoRotateKeys(ctx, req)
|
|
|
|
didAutoRotate = true
|
|
|
|
}
|
|
|
|
b.autoRotateOnce.Do(autoRotateOnceFn)
|
|
|
|
if didAutoRotate {
|
|
|
|
b.autoRotateOnce = sync.Once{}
|
|
|
|
}
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// autoRotateKeys retrieves all transit keys and rotates those which have an
|
2022-02-17 20:17:59 +00:00
|
|
|
// auto rotate period defined which has passed. This operation only happens
|
2022-01-20 15:10:15 +00:00
|
|
|
// on primary nodes and performance secondary nodes which have a local mount.
|
|
|
|
func (b *backend) autoRotateKeys(ctx context.Context, req *logical.Request) error {
|
|
|
|
// Only check for autorotation once an hour to avoid unnecessarily iterating
|
|
|
|
// over all keys too frequently.
|
|
|
|
if time.Now().Before(b.checkAutoRotateAfter) {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
b.checkAutoRotateAfter = time.Now().Add(1 * time.Hour)
|
|
|
|
|
|
|
|
// Early exit if not a primary or performance secondary with a local mount.
|
|
|
|
if b.System().ReplicationState().HasState(consts.ReplicationDRSecondary|consts.ReplicationPerformanceStandby) ||
|
|
|
|
(!b.System().LocalMount() && b.System().ReplicationState().HasState(consts.ReplicationPerformanceSecondary)) {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Retrieve all keys and loop over them to check if they need to be rotated.
|
|
|
|
keys, err := req.Storage.List(ctx, "policy/")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Collect errors in a multierror to ensure a single failure doesn't prevent
|
|
|
|
// all keys from being rotated.
|
|
|
|
var errs *multierror.Error
|
|
|
|
|
|
|
|
for _, key := range keys {
|
|
|
|
p, _, err := b.GetPolicy(ctx, keysutil.PolicyRequest{
|
|
|
|
Storage: req.Storage,
|
|
|
|
Name: key,
|
|
|
|
}, b.GetRandomReader())
|
|
|
|
if err != nil {
|
|
|
|
errs = multierror.Append(errs, err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the policy is nil, move onto the next one.
|
|
|
|
if p == nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2022-01-27 12:57:11 +00:00
|
|
|
err = b.rotateIfRequired(ctx, req, key, p)
|
|
|
|
if err != nil {
|
|
|
|
errs = multierror.Append(errs, err)
|
2022-01-20 15:10:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return errs.ErrorOrNil()
|
|
|
|
}
|
2022-01-27 12:57:11 +00:00
|
|
|
|
|
|
|
// rotateIfRequired rotates a key if it is due for autorotation.
|
|
|
|
func (b *backend) rotateIfRequired(ctx context.Context, req *logical.Request, key string, p *keysutil.Policy) error {
|
|
|
|
if !b.System().CachingDisabled() {
|
|
|
|
p.Lock(true)
|
|
|
|
}
|
|
|
|
defer p.Unlock()
|
|
|
|
|
2022-05-16 16:50:38 +00:00
|
|
|
// If the key is imported, it can only be rotated from within Vault if allowed.
|
|
|
|
if p.Imported && !p.AllowImportedKeyRotation {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-02-17 20:17:59 +00:00
|
|
|
// If the policy's automatic rotation period is 0, it should not
|
2022-01-27 12:57:11 +00:00
|
|
|
// automatically rotate.
|
2022-02-17 20:17:59 +00:00
|
|
|
if p.AutoRotatePeriod == 0 {
|
2022-01-27 12:57:11 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Retrieve the latest version of the policy and determine if it is time to rotate.
|
|
|
|
latestKey := p.Keys[strconv.Itoa(p.LatestVersion)]
|
2022-02-17 20:17:59 +00:00
|
|
|
if time.Now().After(latestKey.CreationTime.Add(p.AutoRotatePeriod)) {
|
2022-01-27 12:57:11 +00:00
|
|
|
if b.Logger().IsDebug() {
|
|
|
|
b.Logger().Debug("automatically rotating key", "key", key)
|
|
|
|
}
|
|
|
|
return p.Rotate(ctx, req.Storage, b.GetRandomReader())
|
|
|
|
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|