This adds a new mechanism for worker authentication that uses the third-party arbitration of the original KMS flow by leveraging recently-added capabilities in nodeenrollment to support a registration wrapper. This allows for workers to be registered to the system with just a name and a matching wrapper, while at the same time allowing these workers to be used for private Vault access and multi-hop as they contain per-worker encryption keys and support rotation. Nodes using this mode will generate and rotate credentials in-memory (using another recently added nodeenrollment capability) so will have a new set of keys on every startup. This has the side effect of ensuring that there is never a conflict or re-use of these credentials. Note that the normal test worker flow was using the now-deprecated KMS method; there is still a test that uses this (explicitly) but all other tests using test workers that did not specify a specific PKI flow now actually use this mechanism. This means there is fairly decent test coverage in a general sense, plus the specific tests updated/added for this feature. This also adds a new KMS type that can be used to have distinct KMSes for upstream authentication vs. accepting from downstream.
103 lines
3.1 KiB
Go
103 lines
3.1 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package wrapper
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
|
|
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
|
|
configutil "github.com/hashicorp/go-secure-stdlib/configutil/v2"
|
|
"github.com/hashicorp/go-secure-stdlib/pluginutil/v2"
|
|
"github.com/hashicorp/go-secure-stdlib/strutil"
|
|
"github.com/hashicorp/hcl"
|
|
)
|
|
|
|
// pluginsConfig is used to pre-parse any plugins stanza
|
|
// in the configuration file, so that we can use the correct
|
|
// configuration when creating the KMS plugin for reading the
|
|
// rest of the config.
|
|
type pluginsConfig struct {
|
|
Plugins struct {
|
|
ExecutionDir string `hcl:"execution_dir"`
|
|
} `hcl:"plugins"`
|
|
}
|
|
|
|
func GetWrapperFromPath(ctx context.Context, path, purpose string, opt ...configutil.Option) (wrapping.Wrapper, func() error, error) {
|
|
kmses, err := configutil.LoadConfigKMSes(path)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("Error parsing config file: %w", err)
|
|
}
|
|
hclBytes, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("Error reading config file: %w", err)
|
|
}
|
|
pluginsConfig, err := parsePluginsConfig(string(hclBytes))
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("Error parsing plugins stanza in config file: %w", err)
|
|
}
|
|
if pluginsConfig.Plugins.ExecutionDir != "" {
|
|
// Note, this is safe to use because configutil.WithPluginOptions invocations
|
|
// are additive with each other.
|
|
opt = append(opt, configutil.WithPluginOptions(pluginutil.WithPluginExecutionDirectory(pluginsConfig.Plugins.ExecutionDir)))
|
|
}
|
|
|
|
return getWrapper(ctx, kmses, purpose, opt...)
|
|
}
|
|
|
|
func GetWrapperFromHcl(ctx context.Context, inHcl, purpose string, opt ...configutil.Option) (wrapping.Wrapper, func() error, error) {
|
|
kmses, err := configutil.ParseKMSes(inHcl, configutil.WithMaxKmsBlocks(-1))
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("Error parsing KMS HCL: %w", err)
|
|
}
|
|
pluginsConfig, err := parsePluginsConfig(inHcl)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("Error parsing plugins stanza in config file: %w", err)
|
|
}
|
|
if pluginsConfig.Plugins.ExecutionDir != "" {
|
|
// Note, this is safe to use because configutil.WithPluginOptions invocations
|
|
// are additive with each other.
|
|
opt = append(opt, configutil.WithPluginOptions(pluginutil.WithPluginExecutionDirectory(pluginsConfig.Plugins.ExecutionDir)))
|
|
}
|
|
|
|
return getWrapper(ctx, kmses, purpose, opt...)
|
|
}
|
|
|
|
func getWrapper(ctx context.Context, kmses []*configutil.KMS, purpose string, opt ...configutil.Option) (wrapping.Wrapper, func() error, error) {
|
|
var kms *configutil.KMS
|
|
for _, v := range kmses {
|
|
if strutil.StrListContains(v.Purpose, purpose) {
|
|
if kms != nil {
|
|
return nil, nil, fmt.Errorf("Only one %q block marked for %q purpose is allowed", "kms", purpose)
|
|
}
|
|
kms = v
|
|
}
|
|
}
|
|
if kms == nil {
|
|
return nil, nil, nil
|
|
}
|
|
|
|
wrapper, cleanup, err := configutil.ConfigureWrapper(
|
|
ctx,
|
|
kms,
|
|
nil,
|
|
nil,
|
|
opt...,
|
|
)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("Error configuring kms: %w", err)
|
|
}
|
|
|
|
return wrapper, cleanup, nil
|
|
}
|
|
|
|
func parsePluginsConfig(inHcl string) (*pluginsConfig, error) {
|
|
var conf pluginsConfig
|
|
if err := hcl.Decode(&conf, inHcl); err != nil {
|
|
return nil, err
|
|
}
|
|
return &conf, nil
|
|
}
|