VAULT-9883: Agent Reloadable Config (#18638)
* Update command/agent.go * Attempt to only reload log level and certs * Mimicked 'server' test for cert reload in 'agent' Co-authored-by: Nick Cabatoff <ncabatoff@hashicorp.com> Left out the `c.config` tweak that meant changes to lots of lines of code within the `Run` function of Agent command. :)
This commit is contained in:
parent
032ccc2373
commit
e4685c10ef
|
@ -0,0 +1,3 @@
|
||||||
|
```release-note:improvement
|
||||||
|
agent: allows some parts of config to be reloaded without requiring a restart.
|
||||||
|
```
|
317
command/agent.go
317
command/agent.go
|
@ -17,6 +17,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
ctconfig "github.com/hashicorp/consul-template/config"
|
ctconfig "github.com/hashicorp/consul-template/config"
|
||||||
|
"github.com/hashicorp/go-multierror"
|
||||||
|
|
||||||
"github.com/hashicorp/vault/command/agent/sink/inmem"
|
"github.com/hashicorp/vault/command/agent/sink/inmem"
|
||||||
|
|
||||||
|
@ -24,6 +25,7 @@ import (
|
||||||
log "github.com/hashicorp/go-hclog"
|
log "github.com/hashicorp/go-hclog"
|
||||||
"github.com/hashicorp/go-secure-stdlib/gatedwriter"
|
"github.com/hashicorp/go-secure-stdlib/gatedwriter"
|
||||||
"github.com/hashicorp/go-secure-stdlib/parseutil"
|
"github.com/hashicorp/go-secure-stdlib/parseutil"
|
||||||
|
"github.com/hashicorp/go-secure-stdlib/reloadutil"
|
||||||
"github.com/hashicorp/vault/api"
|
"github.com/hashicorp/vault/api"
|
||||||
"github.com/hashicorp/vault/command/agent/auth"
|
"github.com/hashicorp/vault/command/agent/auth"
|
||||||
"github.com/hashicorp/vault/command/agent/auth/alicloud"
|
"github.com/hashicorp/vault/command/agent/auth/alicloud"
|
||||||
|
@ -75,9 +77,14 @@ type AgentCommand struct {
|
||||||
*BaseCommand
|
*BaseCommand
|
||||||
logFlags logFlags
|
logFlags logFlags
|
||||||
|
|
||||||
|
config *agentConfig.Config
|
||||||
|
|
||||||
ShutdownCh chan struct{}
|
ShutdownCh chan struct{}
|
||||||
SighupCh chan struct{}
|
SighupCh chan struct{}
|
||||||
|
|
||||||
|
tlsReloadFuncsLock sync.RWMutex
|
||||||
|
tlsReloadFuncs []reloadutil.ReloadFunc
|
||||||
|
|
||||||
logWriter io.Writer
|
logWriter io.Writer
|
||||||
logGate *gatedwriter.Writer
|
logGate *gatedwriter.Writer
|
||||||
logger log.Logger
|
logger log.Logger
|
||||||
|
@ -87,7 +94,8 @@ type AgentCommand struct {
|
||||||
|
|
||||||
cleanupGuard sync.Once
|
cleanupGuard sync.Once
|
||||||
|
|
||||||
startedCh chan (struct{}) // for tests
|
startedCh chan struct{} // for tests
|
||||||
|
reloadedCh chan struct{} // for tests
|
||||||
|
|
||||||
flagConfigs []string
|
flagConfigs []string
|
||||||
flagExitAfterAuth bool
|
flagExitAfterAuth bool
|
||||||
|
@ -102,7 +110,7 @@ func (c *AgentCommand) Help() string {
|
||||||
helpText := `
|
helpText := `
|
||||||
Usage: vault agent [options]
|
Usage: vault agent [options]
|
||||||
|
|
||||||
This command starts a Vault agent that can perform automatic authentication
|
This command starts a Vault Agent that can perform automatic authentication
|
||||||
in certain environments.
|
in certain environments.
|
||||||
|
|
||||||
Start an agent with a configuration file:
|
Start an agent with a configuration file:
|
||||||
|
@ -193,76 +201,24 @@ func (c *AgentCommand) Run(args []string) int {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
config := agentConfig.NewConfig()
|
config, err := c.loadConfig(c.flagConfigs)
|
||||||
|
|
||||||
for _, configPath := range c.flagConfigs {
|
|
||||||
configFromPath, err := agentConfig.LoadConfig(configPath)
|
|
||||||
if err != nil {
|
|
||||||
c.UI.Error(fmt.Sprintf("Error loading configuration from %s: %s", configPath, err))
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
config = config.Merge(configFromPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
err := config.ValidateConfig()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.UI.Error(fmt.Sprintf("Error loading configuration: %s", err))
|
c.outputErrors(err)
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.AutoAuth == nil {
|
if config.AutoAuth == nil {
|
||||||
c.UI.Info("No auto_auth block found in config, not starting automatic authentication feature")
|
c.UI.Info("No auto_auth block found in config, the automatic authentication feature will not be started")
|
||||||
}
|
}
|
||||||
|
|
||||||
c.updateConfig(f, config)
|
c.updateConfig(f, config) // This only needs to happen on start-up to aggregate config from flags and env vars
|
||||||
|
c.config = config
|
||||||
|
|
||||||
// Parse all the log related config
|
l, err := c.newLogger()
|
||||||
logLevel, err := logging.ParseLogLevel(config.LogLevel)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.UI.Error(err.Error())
|
c.outputErrors(err)
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
logFormat, err := logging.ParseLogFormat(config.LogFormat)
|
|
||||||
if err != nil {
|
|
||||||
c.UI.Error(err.Error())
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
logRotateDuration, err := parseutil.ParseDurationSecond(config.LogRotateDuration)
|
|
||||||
if err != nil {
|
|
||||||
c.UI.Error(err.Error())
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
logRotateBytes, err := parseutil.ParseInt(config.LogRotateBytes)
|
|
||||||
if err != nil {
|
|
||||||
c.UI.Error(err.Error())
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
logRotateMaxFiles, err := parseutil.ParseInt(config.LogRotateMaxFiles)
|
|
||||||
if err != nil {
|
|
||||||
c.UI.Error(err.Error())
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
logCfg := &logging.LogConfig{
|
|
||||||
Name: "vault-agent",
|
|
||||||
LogLevel: logLevel,
|
|
||||||
LogFormat: logFormat,
|
|
||||||
LogFilePath: config.LogFile,
|
|
||||||
LogRotateDuration: logRotateDuration,
|
|
||||||
LogRotateBytes: int(logRotateBytes),
|
|
||||||
LogRotateMaxFiles: int(logRotateMaxFiles),
|
|
||||||
}
|
|
||||||
|
|
||||||
l, err := logging.Setup(logCfg, c.logWriter)
|
|
||||||
if err != nil {
|
|
||||||
c.UI.Error(err.Error())
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
c.logger = l
|
c.logger = l
|
||||||
|
|
||||||
infoKeys := make([]string, 0, 10)
|
infoKeys := make([]string, 0, 10)
|
||||||
|
@ -289,7 +245,7 @@ func (c *AgentCommand) Run(args []string) int {
|
||||||
if os.Getenv("VAULT_TEST_VERIFY_ONLY_DUMP_CONFIG") != "" {
|
if os.Getenv("VAULT_TEST_VERIFY_ONLY_DUMP_CONFIG") != "" {
|
||||||
c.UI.Output(fmt.Sprintf(
|
c.UI.Output(fmt.Sprintf(
|
||||||
"\nConfiguration:\n%s\n",
|
"\nConfiguration:\n%s\n",
|
||||||
pretty.Sprint(*config)))
|
pretty.Sprint(*c.config)))
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
@ -364,7 +320,7 @@ func (c *AgentCommand) Run(args []string) int {
|
||||||
}
|
}
|
||||||
s, err := file.NewFileSink(config)
|
s, err := file.NewFileSink(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.UI.Error(fmt.Errorf("Error creating file sink: %w", err).Error())
|
c.UI.Error(fmt.Errorf("error creating file sink: %w", err).Error())
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
config.Sink = s
|
config.Sink = s
|
||||||
|
@ -504,7 +460,7 @@ func (c *AgentCommand) Run(args []string) int {
|
||||||
|
|
||||||
// Output the header that the agent has started
|
// Output the header that the agent has started
|
||||||
if !c.logFlags.flagCombineLogs {
|
if !c.logFlags.flagCombineLogs {
|
||||||
c.UI.Output("==> Vault agent started! Log data will stream in below:\n")
|
c.UI.Output("==> Vault Agent started! Log data will stream in below:\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
var leaseCache *cache.LeaseCache
|
var leaseCache *cache.LeaseCache
|
||||||
|
@ -708,9 +664,13 @@ func (c *AgentCommand) Run(args []string) int {
|
||||||
if len(config.Templates) > 0 {
|
if len(config.Templates) > 0 {
|
||||||
config.Listeners = append(config.Listeners, &configutil.Listener{Type: listenerutil.BufConnType})
|
config.Listeners = append(config.Listeners, &configutil.Listener{Type: listenerutil.BufConnType})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure we've added all the reload funcs for TLS before anyone triggers a reload.
|
||||||
|
c.tlsReloadFuncsLock.Lock()
|
||||||
|
|
||||||
for i, lnConfig := range config.Listeners {
|
for i, lnConfig := range config.Listeners {
|
||||||
var ln net.Listener
|
var ln net.Listener
|
||||||
var tlsConf *tls.Config
|
var tlsCfg *tls.Config
|
||||||
|
|
||||||
if lnConfig.Type == listenerutil.BufConnType {
|
if lnConfig.Type == listenerutil.BufConnType {
|
||||||
inProcListener := bufconn.Listen(1024 * 1024)
|
inProcListener := bufconn.Listen(1024 * 1024)
|
||||||
|
@ -719,11 +679,17 @@ func (c *AgentCommand) Run(args []string) int {
|
||||||
}
|
}
|
||||||
ln = inProcListener
|
ln = inProcListener
|
||||||
} else {
|
} else {
|
||||||
ln, tlsConf, err = cache.StartListener(lnConfig)
|
lnBundle, err := cache.StartListener(lnConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.UI.Error(fmt.Sprintf("Error starting listener: %v", err))
|
c.UI.Error(fmt.Sprintf("Error starting listener: %v", err))
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tlsCfg = lnBundle.TLSConfig
|
||||||
|
ln = lnBundle.Listener
|
||||||
|
|
||||||
|
// Track the reload func, so we can reload later if needed.
|
||||||
|
c.tlsReloadFuncs = append(c.tlsReloadFuncs, lnBundle.TLSReloadFunc)
|
||||||
}
|
}
|
||||||
|
|
||||||
listeners = append(listeners, ln)
|
listeners = append(listeners, ln)
|
||||||
|
@ -768,7 +734,7 @@ func (c *AgentCommand) Run(args []string) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
scheme := "https://"
|
scheme := "https://"
|
||||||
if tlsConf == nil {
|
if tlsCfg == nil {
|
||||||
scheme = "http://"
|
scheme = "http://"
|
||||||
}
|
}
|
||||||
if ln.Addr().Network() == "unix" {
|
if ln.Addr().Network() == "unix" {
|
||||||
|
@ -781,7 +747,7 @@ func (c *AgentCommand) Run(args []string) int {
|
||||||
|
|
||||||
server := &http.Server{
|
server := &http.Server{
|
||||||
Addr: ln.Addr().String(),
|
Addr: ln.Addr().String(),
|
||||||
TLSConfig: tlsConf,
|
TLSConfig: tlsCfg,
|
||||||
Handler: mux,
|
Handler: mux,
|
||||||
ReadHeaderTimeout: 10 * time.Second,
|
ReadHeaderTimeout: 10 * time.Second,
|
||||||
ReadTimeout: 30 * time.Second,
|
ReadTimeout: 30 * time.Second,
|
||||||
|
@ -792,6 +758,8 @@ func (c *AgentCommand) Run(args []string) int {
|
||||||
go server.Serve(ln)
|
go server.Serve(ln)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.tlsReloadFuncsLock.Unlock()
|
||||||
|
|
||||||
// Ensure that listeners are closed at all the exits
|
// Ensure that listeners are closed at all the exits
|
||||||
listenerCloseFunc := func() {
|
listenerCloseFunc := func() {
|
||||||
for _, ln := range listeners {
|
for _, ln := range listeners {
|
||||||
|
@ -805,28 +773,43 @@ func (c *AgentCommand) Run(args []string) int {
|
||||||
close(c.startedCh)
|
close(c.startedCh)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Listen for signals
|
|
||||||
// TODO: implement support for SIGHUP reloading of configuration
|
|
||||||
// signal.Notify(c.signalCh)
|
|
||||||
|
|
||||||
var g run.Group
|
var g run.Group
|
||||||
|
|
||||||
|
g.Add(func() error {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-c.SighupCh:
|
||||||
|
c.UI.Output("==> Vault Agent config reload triggered")
|
||||||
|
err := c.reloadConfig(c.flagConfigs)
|
||||||
|
if err != nil {
|
||||||
|
c.outputErrors(err)
|
||||||
|
}
|
||||||
|
// Send the 'reloaded' message on the relevant channel
|
||||||
|
select {
|
||||||
|
case c.reloadedCh <- struct{}{}:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, func(error) {
|
||||||
|
cancelFunc()
|
||||||
|
})
|
||||||
|
|
||||||
// This run group watches for signal termination
|
// This run group watches for signal termination
|
||||||
g.Add(func() error {
|
g.Add(func() error {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-c.ShutdownCh:
|
case <-c.ShutdownCh:
|
||||||
c.UI.Output("==> Vault agent shutdown triggered")
|
c.UI.Output("==> Vault Agent shutdown triggered")
|
||||||
// Notify systemd that the server is shutting down
|
// Notify systemd that the server is shutting down
|
||||||
c.notifySystemd(systemd.SdNotifyStopping)
|
// Let the lease cache know this is a shutdown; no need to evict everything
|
||||||
// Let the lease cache know this is a shutdown; no need to evict
|
|
||||||
// everything
|
|
||||||
if leaseCache != nil {
|
if leaseCache != nil {
|
||||||
leaseCache.SetShuttingDown(true)
|
leaseCache.SetShuttingDown(true)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
c.notifySystemd(systemd.SdNotifyStopping)
|
|
||||||
return nil
|
return nil
|
||||||
case <-winsvc.ShutdownChannel():
|
case <-winsvc.ShutdownChannel():
|
||||||
return nil
|
return nil
|
||||||
|
@ -874,9 +857,9 @@ func (c *AgentCommand) Run(args []string) int {
|
||||||
|
|
||||||
ts := template.NewServer(&template.ServerConfig{
|
ts := template.NewServer(&template.ServerConfig{
|
||||||
Logger: c.logger.Named("template.server"),
|
Logger: c.logger.Named("template.server"),
|
||||||
LogLevel: logLevel,
|
LogLevel: c.logger.GetLevel(),
|
||||||
LogWriter: c.logWriter,
|
LogWriter: c.logWriter,
|
||||||
AgentConfig: config,
|
AgentConfig: c.config,
|
||||||
Namespace: templateNamespace,
|
Namespace: templateNamespace,
|
||||||
ExitAfterAuth: config.ExitAfterAuth,
|
ExitAfterAuth: config.ExitAfterAuth,
|
||||||
})
|
})
|
||||||
|
@ -940,7 +923,7 @@ func (c *AgentCommand) Run(args []string) int {
|
||||||
// Server configuration output
|
// Server configuration output
|
||||||
padding := 24
|
padding := 24
|
||||||
sort.Strings(infoKeys)
|
sort.Strings(infoKeys)
|
||||||
c.UI.Output("==> Vault agent configuration:\n")
|
c.UI.Output("==> Vault Agent configuration:\n")
|
||||||
for _, k := range infoKeys {
|
for _, k := range infoKeys {
|
||||||
c.UI.Output(fmt.Sprintf(
|
c.UI.Output(fmt.Sprintf(
|
||||||
"%s%s: %s",
|
"%s%s: %s",
|
||||||
|
@ -968,13 +951,14 @@ func (c *AgentCommand) Run(args []string) int {
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
var exitCode int
|
||||||
if err := g.Run(); err != nil {
|
if err := g.Run(); err != nil {
|
||||||
c.logger.Error("runtime error encountered", "error", err)
|
c.logger.Error("runtime error encountered", "error", err)
|
||||||
c.UI.Error("Error encountered during run, refer to logs for more details.")
|
c.UI.Error("Error encountered during run, refer to logs for more details.")
|
||||||
return 1
|
exitCode = 1
|
||||||
}
|
}
|
||||||
|
c.notifySystemd(systemd.SdNotifyStopping)
|
||||||
return 0
|
return exitCode
|
||||||
}
|
}
|
||||||
|
|
||||||
// updateConfig ensures that the config object accurately reflects the desired
|
// updateConfig ensures that the config object accurately reflects the desired
|
||||||
|
@ -1219,3 +1203,170 @@ func (c *AgentCommand) handleQuit(enabled bool) http.Handler {
|
||||||
close(c.ShutdownCh)
|
close(c.ShutdownCh)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// newLogger creates a logger based on parsed config field on the Agent Command struct.
|
||||||
|
func (c *AgentCommand) newLogger() (log.InterceptLogger, error) {
|
||||||
|
if c.config == nil {
|
||||||
|
return nil, fmt.Errorf("cannot create logger, no config")
|
||||||
|
}
|
||||||
|
|
||||||
|
var errors error
|
||||||
|
|
||||||
|
// Parse all the log related config
|
||||||
|
logLevel, err := logging.ParseLogLevel(c.config.LogLevel)
|
||||||
|
if err != nil {
|
||||||
|
errors = multierror.Append(errors, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
logFormat, err := logging.ParseLogFormat(c.config.LogFormat)
|
||||||
|
if err != nil {
|
||||||
|
errors = multierror.Append(errors, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
logRotateDuration, err := parseutil.ParseDurationSecond(c.config.LogRotateDuration)
|
||||||
|
if err != nil {
|
||||||
|
errors = multierror.Append(errors, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
logRotateBytes, err := parseutil.ParseInt(c.config.LogRotateBytes)
|
||||||
|
if err != nil {
|
||||||
|
errors = multierror.Append(errors, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
logRotateMaxFiles, err := parseutil.ParseInt(c.config.LogRotateMaxFiles)
|
||||||
|
if err != nil {
|
||||||
|
errors = multierror.Append(errors, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if errors != nil {
|
||||||
|
return nil, errors
|
||||||
|
}
|
||||||
|
|
||||||
|
logCfg := &logging.LogConfig{
|
||||||
|
Name: "vault-agent",
|
||||||
|
LogLevel: logLevel,
|
||||||
|
LogFormat: logFormat,
|
||||||
|
LogFilePath: c.config.LogFile,
|
||||||
|
LogRotateDuration: logRotateDuration,
|
||||||
|
LogRotateBytes: int(logRotateBytes),
|
||||||
|
LogRotateMaxFiles: int(logRotateMaxFiles),
|
||||||
|
}
|
||||||
|
|
||||||
|
l, err := logging.Setup(logCfg, c.logWriter)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return l, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadConfig attempts to generate an Agent config from the file(s) specified.
|
||||||
|
func (c *AgentCommand) loadConfig(paths []string) (*agentConfig.Config, error) {
|
||||||
|
var errors error
|
||||||
|
cfg := agentConfig.NewConfig()
|
||||||
|
|
||||||
|
for _, configPath := range paths {
|
||||||
|
configFromPath, err := agentConfig.LoadConfig(configPath)
|
||||||
|
if err != nil {
|
||||||
|
errors = multierror.Append(errors, fmt.Errorf("error loading configuration from %s: %w", configPath, err))
|
||||||
|
} else {
|
||||||
|
cfg = cfg.Merge(configFromPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if errors != nil {
|
||||||
|
return nil, errors
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := cfg.ValidateConfig(); err != nil {
|
||||||
|
return nil, fmt.Errorf("error validating configuration: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return cfg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// reloadConfig will attempt to reload the config from file(s) and adjust certain
|
||||||
|
// config values without requiring a restart of the Vault Agent.
|
||||||
|
// If config is retrieved without error it is stored in the config field of the AgentCommand.
|
||||||
|
// This operation is not atomic and could result in updated config but partially applied config settings.
|
||||||
|
// The error returned from this func may be a multierror.
|
||||||
|
// This function will most likely be called due to Vault Agent receiving a SIGHUP signal.
|
||||||
|
// Currently only reloading the following are supported:
|
||||||
|
// * log level
|
||||||
|
// * TLS certs for listeners
|
||||||
|
func (c *AgentCommand) reloadConfig(paths []string) error {
|
||||||
|
// Notify systemd that the server is reloading
|
||||||
|
c.notifySystemd(systemd.SdNotifyReloading)
|
||||||
|
defer c.notifySystemd(systemd.SdNotifyReady)
|
||||||
|
|
||||||
|
var errors error
|
||||||
|
|
||||||
|
// Reload the config
|
||||||
|
cfg, err := c.loadConfig(paths)
|
||||||
|
if err != nil {
|
||||||
|
// Returning single error as we won't continue with bad config and won't 'commit' it.
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.config = cfg
|
||||||
|
|
||||||
|
// Update the log level
|
||||||
|
err = c.reloadLogLevel()
|
||||||
|
if err != nil {
|
||||||
|
errors = multierror.Append(errors, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update certs
|
||||||
|
err = c.reloadCerts()
|
||||||
|
if err != nil {
|
||||||
|
errors = multierror.Append(errors, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors
|
||||||
|
}
|
||||||
|
|
||||||
|
// reloadLogLevel will attempt to update the log level for the logger attached
|
||||||
|
// to the AgentComment struct using the value currently set in config.
|
||||||
|
func (c *AgentCommand) reloadLogLevel() error {
|
||||||
|
logLevel, err := logging.ParseLogLevel(c.config.LogLevel)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.logger.SetLevel(logLevel)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// reloadCerts will attempt to reload certificates using a reload func which
|
||||||
|
// was provided when the listeners were configured, only funcs that were appended
|
||||||
|
// to the AgentCommand slice will be invoked.
|
||||||
|
// This function returns a multierror type so that every func can report an error
|
||||||
|
// if it encounters one.
|
||||||
|
func (c *AgentCommand) reloadCerts() error {
|
||||||
|
var errors error
|
||||||
|
|
||||||
|
c.tlsReloadFuncsLock.RLock()
|
||||||
|
defer c.tlsReloadFuncsLock.RUnlock()
|
||||||
|
|
||||||
|
for _, reloadFunc := range c.tlsReloadFuncs {
|
||||||
|
err := reloadFunc()
|
||||||
|
if err != nil {
|
||||||
|
errors = multierror.Append(errors, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors
|
||||||
|
}
|
||||||
|
|
||||||
|
// outputErrors will take an error or multierror and handle outputting each to the UI
|
||||||
|
func (c *AgentCommand) outputErrors(err error) {
|
||||||
|
if err != nil {
|
||||||
|
if me, ok := err.(*multierror.Error); ok {
|
||||||
|
for _, err := range me.Errors {
|
||||||
|
c.UI.Error(err.Error())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
c.UI.Error(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -6,12 +6,19 @@ import (
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/hashicorp/go-secure-stdlib/reloadutil"
|
||||||
"github.com/hashicorp/vault/command/server"
|
"github.com/hashicorp/vault/command/server"
|
||||||
"github.com/hashicorp/vault/internalshared/configutil"
|
"github.com/hashicorp/vault/internalshared/configutil"
|
||||||
"github.com/hashicorp/vault/internalshared/listenerutil"
|
"github.com/hashicorp/vault/internalshared/listenerutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
func StartListener(lnConfig *configutil.Listener) (net.Listener, *tls.Config, error) {
|
type ListenerBundle struct {
|
||||||
|
Listener net.Listener
|
||||||
|
TLSConfig *tls.Config
|
||||||
|
TLSReloadFunc reloadutil.ReloadFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
func StartListener(lnConfig *configutil.Listener) (*ListenerBundle, error) {
|
||||||
addr := lnConfig.Address
|
addr := lnConfig.Address
|
||||||
|
|
||||||
var ln net.Listener
|
var ln net.Listener
|
||||||
|
@ -31,7 +38,7 @@ func StartListener(lnConfig *configutil.Listener) (net.Listener, *tls.Config, er
|
||||||
|
|
||||||
ln, err = net.Listen(bindProto, addr)
|
ln, err = net.Listen(bindProto, addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
ln = &server.TCPKeepAliveListener{ln.(*net.TCPListener)}
|
ln = &server.TCPKeepAliveListener{ln.(*net.TCPListener)}
|
||||||
|
|
||||||
|
@ -48,21 +55,27 @@ func StartListener(lnConfig *configutil.Listener) (net.Listener, *tls.Config, er
|
||||||
}
|
}
|
||||||
ln, err = listenerutil.UnixSocketListener(addr, uConfig)
|
ln, err = listenerutil.UnixSocketListener(addr, uConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return nil, nil, fmt.Errorf("invalid listener type: %q", lnConfig.Type)
|
return nil, fmt.Errorf("invalid listener type: %q", lnConfig.Type)
|
||||||
}
|
}
|
||||||
|
|
||||||
props := map[string]string{"addr": ln.Addr().String()}
|
props := map[string]string{"addr": ln.Addr().String()}
|
||||||
tlsConf, _, err := listenerutil.TLSConfig(lnConfig, props, nil)
|
tlsConf, reloadFunc, err := listenerutil.TLSConfig(lnConfig, props, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if tlsConf != nil {
|
if tlsConf != nil {
|
||||||
ln = tls.NewListener(ln, tlsConf)
|
ln = tls.NewListener(ln, tlsConf)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ln, tlsConf, nil
|
cfg := &ListenerBundle{
|
||||||
|
Listener: ln,
|
||||||
|
TLSConfig: tlsConf,
|
||||||
|
TLSReloadFunc: reloadFunc,
|
||||||
|
}
|
||||||
|
|
||||||
|
return cfg, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEowIBAAKCAQEAwF7sRAyUiLcd6es6VeaTRUBOusFFGkmKJ5lU351waCJqXFju
|
||||||
|
Z6i/SQYNAAnnRgotXSTE1fIPjE2kZNH1hvqE5IpTGgAwy50xpjJrrBBI6e9lyKqj
|
||||||
|
7T8gLVNBvtC0cpQi+pGrszEI0ckDQCSZHqi/PAzcpmLUgh2KMrgagT+YlN35KHtl
|
||||||
|
/bQ/Fsn+kqykVqNw69n/CDKNKdDHn1qPwiX9q/fTMj3EG6g+3ntKrUOh8V/gHKPz
|
||||||
|
q8QGP/wIud2K+tTSorVXr/4zx7xgzlbJkCakzcQQiP6K+paPnDRlE8fK+1gRRyR7
|
||||||
|
XCzyp0irUl8G1NjYAR/tVWxiUhlk/jZutb8PpwIDAQABAoIBAEOzJELuindyujxQ
|
||||||
|
ZD9G3h1I/GwNCFyv9Mbq10u7BIwhUH0fbwdcA7WXQ4v38ERd4IkfH4aLoZ0m1ewF
|
||||||
|
V/sgvxQO+h/0YTfHImny5KGxOXfaoF92bipYROKuojydBmQsbgLwsRRm9UufCl3Q
|
||||||
|
g3KewG5JuH112oPQEYq379v8nZ4FxC3Ano1OFBTm9UhHIAX1Dn22kcHOIIw8jCsQ
|
||||||
|
zp7TZOW+nwtkS41cBwhvV4VIeL6yse2UgbOfRVRwI7B0OtswS5VgW3wysO2mTDKt
|
||||||
|
V/WCmeht1il/6ZogEHgi/mvDCKpj20wQ1EzGnPdFLdiFJFylf0oufQD/7N/uezbC
|
||||||
|
is0qJEECgYEA3AE7SeLpe3SZApj2RmE2lcD9/Saj1Y30PznxB7M7hK0sZ1yXEbtS
|
||||||
|
Qf894iDDD/Cn3ufA4xk/K52CXgAcqvH/h2geG4pWLYsT1mdWhGftprtOMCIvJvzU
|
||||||
|
8uWJzKdOGVMG7R59wNgEpPDZDpBISjexwQsFo3aw1L/H1/Sa8cdY3a0CgYEA39hB
|
||||||
|
1oLmGRyE32Q4GF/srG4FqKL1EsbISGDUEYTnaYg2XiM43gu3tC/ikfclk27Jwc2L
|
||||||
|
m7cA5FxxaEyfoOgfAizfU/uWTAbx9GoXgWsO0hWSN9+YNq61gc5WKoHyrJ/rfrti
|
||||||
|
y5d7k0OCeBxckLqGDuJqICQ0myiz0El6FU8h5SMCgYEAuhigmiNC9JbwRu40g9v/
|
||||||
|
XDVfox9oPmBRVpogdC78DYKeqN/9OZaGQiUxp3GnDni2xyqqUm8srCwT9oeJuF/z
|
||||||
|
kgpUTV96/hNCuH25BU8UC5Es1jJUSFpdlwjqwx5SRcGhfjnojZMseojwUg1h2MW7
|
||||||
|
qls0bc0cTxnaZaYW2qWRWhECgYBrT0cwyQv6GdvxJCBoPwQ9HXmFAKowWC+H0zOX
|
||||||
|
Onmd8/jsZEJM4J0uuo4Jn8vZxBDg4eL9wVuiHlcXwzP7dYv4BP8DSechh2rS21Ft
|
||||||
|
b59pQ4IXWw+jl1nYYsyYEDgAXaIN3VNder95N7ICVsZhc6n01MI/qlu1zmt1fOQT
|
||||||
|
9x2utQKBgHI9SbsfWfbGiu6oLS3+9V1t4dORhj8D8b7z3trvECrD6tPhxoZqtfrH
|
||||||
|
4apKr3OKRSXk3K+1K6pkMHJHunspucnA1ChXLhzfNF08BSRJkQDGYuaRLS6VGgab
|
||||||
|
JZTl54bGvO1GkszEBE/9QFcqNVtWGMWXnUPwNNv8t//yJT5rvQil
|
||||||
|
-----END RSA PRIVATE KEY-----
|
|
@ -0,0 +1,20 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDQzCCAiugAwIBAgIULLCz3mZKmg2xy3rWCud0f1zcmBwwDQYJKoZIhvcNAQEL
|
||||||
|
BQAwFjEUMBIGA1UEAxMLZXhhbXBsZS5jb20wHhcNMTYwMzEwMDIzNjQ0WhcNMzYw
|
||||||
|
MzA1MDEzNzE0WjAaMRgwFgYDVQQDEw9iYXIuZXhhbXBsZS5jb20wggEiMA0GCSqG
|
||||||
|
SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDAXuxEDJSItx3p6zpV5pNFQE66wUUaSYon
|
||||||
|
mVTfnXBoImpcWO5nqL9JBg0ACedGCi1dJMTV8g+MTaRk0fWG+oTkilMaADDLnTGm
|
||||||
|
MmusEEjp72XIqqPtPyAtU0G+0LRylCL6kauzMQjRyQNAJJkeqL88DNymYtSCHYoy
|
||||||
|
uBqBP5iU3fkoe2X9tD8Wyf6SrKRWo3Dr2f8IMo0p0MefWo/CJf2r99MyPcQbqD7e
|
||||||
|
e0qtQ6HxX+Aco/OrxAY//Ai53Yr61NKitVev/jPHvGDOVsmQJqTNxBCI/or6lo+c
|
||||||
|
NGUTx8r7WBFHJHtcLPKnSKtSXwbU2NgBH+1VbGJSGWT+Nm61vw+nAgMBAAGjgYQw
|
||||||
|
gYEwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBSVoF8F
|
||||||
|
7qbzSryIFrldurAG78LvSjAfBgNVHSMEGDAWgBRzDNvqF/Tq21OgWs13B5YydZjl
|
||||||
|
vzAgBgNVHREEGTAXgg9iYXIuZXhhbXBsZS5jb22HBH8AAAEwDQYJKoZIhvcNAQEL
|
||||||
|
BQADggEBAGmz2N282iT2IaEZvOmzIE4znHGkvoxZmrr/2byq5PskBg9ysyCHfUvw
|
||||||
|
SFA8U7jWjezKTnGRUu5blB+yZdjrMtB4AePWyEqtkJwVsZ2SPeP+9V2gNYK4iktP
|
||||||
|
UF3aIgBbAbw8rNuGIIB0T4D+6Zyo9Y3MCygs6/N4bRPZgLhewWn1ilklfnl3eqaC
|
||||||
|
a+JY1NBuTgCMa28NuC+Hy3mCveqhI8tFNiOthlLdgAEbuQaOuNutAG73utZ2aq6Q
|
||||||
|
W4pajFm3lEf5zt7Lo6ZCFtY/Q8jjURJ9e4O7VjXcqIhBM5bSMI6+fgQyOH0SLboj
|
||||||
|
RNanJ2bcyF1iPVyPBGzV3dF0ngYzxEY=
|
||||||
|
-----END CERTIFICATE-----
|
|
@ -0,0 +1,20 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDNTCCAh2gAwIBAgIUBeVo+Ce2BrdRT1cogKvJLtdOky8wDQYJKoZIhvcNAQEL
|
||||||
|
BQAwFjEUMBIGA1UEAxMLZXhhbXBsZS5jb20wHhcNMTYwMzEwMDIzNTM4WhcNMzYw
|
||||||
|
MzA1MDIzNjA4WjAWMRQwEgYDVQQDEwtleGFtcGxlLmNvbTCCASIwDQYJKoZIhvcN
|
||||||
|
AQEBBQADggEPADCCAQoCggEBAPTQGWPRIOECGeJB6tR/ftvvtioC9f84fY2QdJ5k
|
||||||
|
JBupXjPAGYKgS4MGzyT5bz9yY400tCtmh6h7p9tZwHl/TElTugtLQ/8ilMbJTiOM
|
||||||
|
SiyaMDPHiMJJYKTjm9bu6bKeU1qPZ0Cryes4rygbqs7w2XPgA2RxNmDh7JdX7/h+
|
||||||
|
VB5onBmv8g4WFSayowGyDcJWWCbu5yv6ZdH1bqQjgRzQ5xp17WXNmvlzdp2vate/
|
||||||
|
9UqPdA8sdJzW/91Gvmros0o/FnG7c2pULhk22wFqO8t2HRjKb3nuxALEJvqoPvad
|
||||||
|
KjpDTaq1L1ZzxcB7wvWyhy/lNLZL7jiNWy0mN1YB0UpSWdECAwEAAaN7MHkwDgYD
|
||||||
|
VR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFHMM2+oX9Orb
|
||||||
|
U6BazXcHljJ1mOW/MB8GA1UdIwQYMBaAFHMM2+oX9OrbU6BazXcHljJ1mOW/MBYG
|
||||||
|
A1UdEQQPMA2CC2V4YW1wbGUuY29tMA0GCSqGSIb3DQEBCwUAA4IBAQAp17XsOaT9
|
||||||
|
hculRqrFptn3+zkH3HrIckHm+28R5xYT8ASFXFcLFugGizJAXVL5lvsRVRIwCoOX
|
||||||
|
Nhi8XSNEFP640VbHcEl81I84bbRIIDS+Yheu6JDZGemTaDYLv1J3D5SHwgoM+nyf
|
||||||
|
oTRgotUCIXcwJHmTpWEUkZFKuqBxsoTGzk0jO8wOP6xoJkzxVVG5PvNxs924rxY8
|
||||||
|
Y8iaLdDfMeT7Pi0XIliBa/aSp/iqSW8XKyJl5R5vXg9+DOgZUrVzIxObaF5RBl/a
|
||||||
|
mJOeklJBdNVzQm5+iMpO42lu0TA9eWtpP+YiUEXU17XDvFeQWOocFbQ1Peo0W895
|
||||||
|
XRz2GCwCNyvW
|
||||||
|
-----END CERTIFICATE-----
|
|
@ -0,0 +1,27 @@
|
||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEpgIBAAKCAQEAzNyVieSti9XBb5/celB5u8YKRJv3mQS9A4/X0mqY1ePznt1i
|
||||||
|
ilG7OmG0yM2VAk0ceIAQac3Bsn74jxn2cDlrrVniPXcNgYtMtW0kRqNEo4doo4EX
|
||||||
|
xZguS9vNBu29useHhif1TGX/pA3dgvaVycUCjzTEVk6qI8UEehMK6gEGZb7nOr0A
|
||||||
|
A9nipSqoeHpDLe3a4KVqj1vtlJKUvD2i1MuBuQ130cB1K9rufLCShGu7mEgzEosc
|
||||||
|
gr+K3Bf03IejbeVRyIfLtgj1zuvV1katec75UqRA/bsvt5G9JfJqiZ9mwFN0vp3g
|
||||||
|
Cr7pdQBSBQ2q4yf9s8CuY5c5w9fl3F8f5QFQoQIDAQABAoIBAQCbCb1qNFRa5ZSV
|
||||||
|
I8i6ELlwMDqJHfhOJ9XcIjpVljLAfNlcu3Ld92jYkCU/asaAjVckotbJG9yhd5Io
|
||||||
|
yp9E40/oS4P6vGTOS1vsWgMAKoPBtrKsOwCAm+E9q8UIn1fdSS/5ibgM74x+3bds
|
||||||
|
a62Em8KKGocUQkhk9a+jq1GxMsFisbHRxEHvClLmDMgGnW3FyGmWwT6yZLPSC0ey
|
||||||
|
szmmjt3ouP8cLAOmSjzcQBMmEZpQMCgR6Qckg6nrLQAGzZyTdCd875wbGA57DpWX
|
||||||
|
Lssn95+A5EFvr/6b7DkXeIFCrYBFFa+UQN3PWGEQ6Zjmiw4VgV2vO8yX2kCLlUhU
|
||||||
|
02bL393ZAoGBAPXPD/0yWINbKUPcRlx/WfWQxfz0bu50ytwIXzVK+pRoAMuNqehK
|
||||||
|
BJ6kNzTTBq40u+IZ4f5jbLDulymR+4zSkirLE7CyWFJOLNI/8K4Pf5DJUgNdrZjJ
|
||||||
|
LCtP9XRdxiPatQF0NGfdgHlSJh+/CiRJP4AgB17AnB/4z9/M0ZlJGVrzAoGBANVa
|
||||||
|
69P3Rp/WPBQv0wx6f0tWppJolWekAHKcDIdQ5HdOZE5CPAYSlTrTUW3uJuqMwU2L
|
||||||
|
M0Er2gIPKWIR5X+9r7Fvu9hQW6l2v3xLlcrGPiapp3STJvuMxzhRAmXmu3bZfVn1
|
||||||
|
Vn7Vf1jPULHtTFSlNFEvYG5UJmygK9BeyyVO5KMbAoGBAMCyAibLQPg4jrDUDZSV
|
||||||
|
gUAwrgUO2ae1hxHWvkxY6vdMUNNByuB+pgB3W4/dnm8Sh/dHsxJpftt1Lqs39ar/
|
||||||
|
p/ZEHLt4FCTxg9GOrm7FV4t5RwG8fko36phJpnIC0UFqQltRbYO+8OgqrhhU+u5X
|
||||||
|
PaCDe0OcWsf1lYAsYGN6GpZhAoGBAMJ5Ksa9+YEODRs1cIFKUyd/5ztC2xRqOAI/
|
||||||
|
3WemQ2nAacuvsfizDZVeMzYpww0+maAuBt0btI719PmwaGmkpDXvK+EDdlmkpOwO
|
||||||
|
FY6MXvBs6fdnfjwCWUErDi2GQFAX9Jt/9oSL5JU1+08DhvUM1QA/V/2Y9KFE6kr3
|
||||||
|
bOIn5F4LAoGBAKQzH/AThDGhT3hwr4ktmReF3qKxBgxzjVa8veXtkY5VWwyN09iT
|
||||||
|
jnTTt6N1CchZoK5WCETjdzNYP7cuBTcV4d3bPNRiJmxXaNVvx3Tlrk98OiffT8Qa
|
||||||
|
5DO/Wfb43rNHYXBjU6l0n2zWcQ4PUSSbu0P0bM2JTQPRCqSthXvSHw2P
|
||||||
|
-----END RSA PRIVATE KEY-----
|
|
@ -0,0 +1,20 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDQzCCAiugAwIBAgIUFVW6i/M+yJUsDrXWgRKO/Dnb+L4wDQYJKoZIhvcNAQEL
|
||||||
|
BQAwFjEUMBIGA1UEAxMLZXhhbXBsZS5jb20wHhcNMTYwMzEwMDIzNjA1WhcNMzYw
|
||||||
|
MzA1MDEzNjM1WjAaMRgwFgYDVQQDEw9mb28uZXhhbXBsZS5jb20wggEiMA0GCSqG
|
||||||
|
SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDM3JWJ5K2L1cFvn9x6UHm7xgpEm/eZBL0D
|
||||||
|
j9fSapjV4/Oe3WKKUbs6YbTIzZUCTRx4gBBpzcGyfviPGfZwOWutWeI9dw2Bi0y1
|
||||||
|
bSRGo0Sjh2ijgRfFmC5L280G7b26x4eGJ/VMZf+kDd2C9pXJxQKPNMRWTqojxQR6
|
||||||
|
EwrqAQZlvuc6vQAD2eKlKqh4ekMt7drgpWqPW+2UkpS8PaLUy4G5DXfRwHUr2u58
|
||||||
|
sJKEa7uYSDMSixyCv4rcF/Tch6Nt5VHIh8u2CPXO69XWRq15zvlSpED9uy+3kb0l
|
||||||
|
8mqJn2bAU3S+neAKvul1AFIFDarjJ/2zwK5jlznD1+XcXx/lAVChAgMBAAGjgYQw
|
||||||
|
gYEwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBRNJoOJ
|
||||||
|
dnazDiuqLhV6truQ4cRe9jAfBgNVHSMEGDAWgBRzDNvqF/Tq21OgWs13B5YydZjl
|
||||||
|
vzAgBgNVHREEGTAXgg9mb28uZXhhbXBsZS5jb22HBH8AAAEwDQYJKoZIhvcNAQEL
|
||||||
|
BQADggEBAHzv67mtbxMWcuMsxCFBN1PJNAyUDZVCB+1gWhk59EySbVg81hWJDCBy
|
||||||
|
fl3TKjz3i7wBGAv+C2iTxmwsSJbda22v8JQbuscXIfLFbNALsPzF+J0vxAgJs5Gc
|
||||||
|
sDbfJ7EQOIIOVKQhHLYnQoLnigSSPc1kd0JjYyHEBjgIaSuXgRRTBAeqLiBMx0yh
|
||||||
|
RKL1lQ+WoBU/9SXUZZkwokqWt5G7khi5qZkNxVXZCm8VGPg0iywf6gGyhI1SU5S2
|
||||||
|
oR219S6kA4JY/stw1qne85/EmHmoImHGt08xex3GoU72jKAjsIpqRWopcD/+uene
|
||||||
|
Tc9nn3fTQW/Z9fsoJ5iF5OdJnDEswqE=
|
||||||
|
-----END CERTIFICATE-----
|
|
@ -1,6 +1,8 @@
|
||||||
package command
|
package command
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
@ -14,7 +16,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
hclog "github.com/hashicorp/go-hclog"
|
"github.com/hashicorp/go-hclog"
|
||||||
vaultjwt "github.com/hashicorp/vault-plugin-auth-jwt"
|
vaultjwt "github.com/hashicorp/vault-plugin-auth-jwt"
|
||||||
logicalKv "github.com/hashicorp/vault-plugin-secrets-kv"
|
logicalKv "github.com/hashicorp/vault-plugin-secrets-kv"
|
||||||
"github.com/hashicorp/vault/api"
|
"github.com/hashicorp/vault/api"
|
||||||
|
@ -34,7 +36,8 @@ import (
|
||||||
|
|
||||||
const (
|
const (
|
||||||
BasicHclConfig = `
|
BasicHclConfig = `
|
||||||
log_file = "/foo/bar/juan.log"
|
log_file = "TMPDIR/juan.log"
|
||||||
|
log_level="warn"
|
||||||
vault {
|
vault {
|
||||||
address = "http://127.0.0.1:8200"
|
address = "http://127.0.0.1:8200"
|
||||||
retry {
|
retry {
|
||||||
|
@ -44,7 +47,25 @@ vault {
|
||||||
|
|
||||||
listener "tcp" {
|
listener "tcp" {
|
||||||
address = "127.0.0.1:8100"
|
address = "127.0.0.1:8100"
|
||||||
tls_disable = true
|
tls_disable = false
|
||||||
|
tls_cert_file = "TMPDIR/reload_cert.pem"
|
||||||
|
tls_key_file = "TMPDIR/reload_key.pem"
|
||||||
|
}`
|
||||||
|
BasicHclConfig2 = `
|
||||||
|
log_file = "TMPDIR/juan.log"
|
||||||
|
log_level="debug"
|
||||||
|
vault {
|
||||||
|
address = "http://127.0.0.1:8200"
|
||||||
|
retry {
|
||||||
|
num_retries = 5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
listener "tcp" {
|
||||||
|
address = "127.0.0.1:8100"
|
||||||
|
tls_disable = false
|
||||||
|
tls_cert_file = "TMPDIR/reload_cert.pem"
|
||||||
|
tls_key_file = "TMPDIR/reload_key.pem"
|
||||||
}`
|
}`
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -57,7 +78,10 @@ func testAgentCommand(tb testing.TB, logger hclog.Logger) (*cli.MockUi, *AgentCo
|
||||||
UI: ui,
|
UI: ui,
|
||||||
},
|
},
|
||||||
ShutdownCh: MakeShutdownCh(),
|
ShutdownCh: MakeShutdownCh(),
|
||||||
|
SighupCh: MakeSighupCh(),
|
||||||
logger: logger,
|
logger: logger,
|
||||||
|
startedCh: make(chan struct{}, 5),
|
||||||
|
reloadedCh: make(chan struct{}, 5),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1512,7 +1536,6 @@ vault {
|
||||||
%s
|
%s
|
||||||
%s
|
%s
|
||||||
`, serverClient.Address(), retryConf, cacheConfig, listenConfig)
|
`, serverClient.Address(), retryConf, cacheConfig, listenConfig)
|
||||||
|
|
||||||
configPath := makeTempFile(t, "config.hcl", config)
|
configPath := makeTempFile(t, "config.hcl", config)
|
||||||
defer os.Remove(configPath)
|
defer os.Remove(configPath)
|
||||||
|
|
||||||
|
@ -2075,7 +2098,7 @@ func TestAgent_LogFile_CliOverridesConfig(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sanity check that the config value is the current value
|
// Sanity check that the config value is the current value
|
||||||
assert.Equal(t, "/foo/bar/juan.log", cfg.LogFile)
|
assert.Equal(t, "TMPDIR/juan.log", cfg.LogFile)
|
||||||
|
|
||||||
// Initialize the command and parse any flags
|
// Initialize the command and parse any flags
|
||||||
cmd := &AgentCommand{BaseCommand: &BaseCommand{}}
|
cmd := &AgentCommand{BaseCommand: &BaseCommand{}}
|
||||||
|
@ -2089,7 +2112,7 @@ func TestAgent_LogFile_CliOverridesConfig(t *testing.T) {
|
||||||
// Update the config based on the inputs.
|
// Update the config based on the inputs.
|
||||||
cmd.updateConfig(f, cfg)
|
cmd.updateConfig(f, cfg)
|
||||||
|
|
||||||
assert.NotEqual(t, "/foo/bar/juan.log", cfg.LogFile)
|
assert.NotEqual(t, "TMPDIR/juan.log", cfg.LogFile)
|
||||||
assert.NotEqual(t, "/squiggle/logs.txt", cfg.LogFile)
|
assert.NotEqual(t, "/squiggle/logs.txt", cfg.LogFile)
|
||||||
assert.Equal(t, "/foo/bar/test.log", cfg.LogFile)
|
assert.Equal(t, "/foo/bar/test.log", cfg.LogFile)
|
||||||
}
|
}
|
||||||
|
@ -2103,7 +2126,7 @@ func TestAgent_LogFile_Config(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sanity check that the config value is the current value
|
// Sanity check that the config value is the current value
|
||||||
assert.Equal(t, "/foo/bar/juan.log", cfg.LogFile, "sanity check on log config failed")
|
assert.Equal(t, "TMPDIR/juan.log", cfg.LogFile, "sanity check on log config failed")
|
||||||
|
|
||||||
// Parse the cli flags (but we pass in an empty slice)
|
// Parse the cli flags (but we pass in an empty slice)
|
||||||
cmd := &AgentCommand{BaseCommand: &BaseCommand{}}
|
cmd := &AgentCommand{BaseCommand: &BaseCommand{}}
|
||||||
|
@ -2115,7 +2138,180 @@ func TestAgent_LogFile_Config(t *testing.T) {
|
||||||
|
|
||||||
cmd.updateConfig(f, cfg)
|
cmd.updateConfig(f, cfg)
|
||||||
|
|
||||||
assert.Equal(t, "/foo/bar/juan.log", cfg.LogFile, "actual config check")
|
assert.Equal(t, "TMPDIR/juan.log", cfg.LogFile, "actual config check")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAgent_Config_NewLogger_Default(t *testing.T) {
|
||||||
|
cmd := &AgentCommand{BaseCommand: &BaseCommand{}}
|
||||||
|
cmd.config = agentConfig.NewConfig()
|
||||||
|
logger, err := cmd.newLogger()
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, logger)
|
||||||
|
assert.Equal(t, hclog.Info.String(), logger.GetLevel().String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAgent_Config_ReloadLogLevel(t *testing.T) {
|
||||||
|
cmd := &AgentCommand{BaseCommand: &BaseCommand{}}
|
||||||
|
var err error
|
||||||
|
tempDir := t.TempDir()
|
||||||
|
|
||||||
|
// Load an initial config
|
||||||
|
hcl := strings.ReplaceAll(BasicHclConfig, "TMPDIR", tempDir)
|
||||||
|
configFile := populateTempFile(t, "agent-config.hcl", hcl)
|
||||||
|
cmd.config, err = agentConfig.LoadConfigFile(configFile.Name())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Cannot load config to test update/merge", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tweak the loaded config to make sure we can put log files into a temp dir
|
||||||
|
// and systemd log attempts work fine, this would usually happen during Run.
|
||||||
|
cmd.logWriter = os.Stdout
|
||||||
|
cmd.logger, err = cmd.newLogger()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("logger required for systemd log messages", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sanity check
|
||||||
|
assert.Equal(t, "warn", cmd.config.LogLevel)
|
||||||
|
|
||||||
|
// Load a new config
|
||||||
|
hcl = strings.ReplaceAll(BasicHclConfig2, "TMPDIR", tempDir)
|
||||||
|
configFile = populateTempFile(t, "agent-config.hcl", hcl)
|
||||||
|
err = cmd.reloadConfig([]string{configFile.Name()})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "debug", cmd.config.LogLevel)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAgent_Config_ReloadTls(t *testing.T) {
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("unable to get current working directory")
|
||||||
|
}
|
||||||
|
workingDir := filepath.Join(wd, "/agent/test-fixtures/reload")
|
||||||
|
fooCert := "reload_foo.pem"
|
||||||
|
fooKey := "reload_foo.key"
|
||||||
|
|
||||||
|
barCert := "reload_bar.pem"
|
||||||
|
barKey := "reload_bar.key"
|
||||||
|
|
||||||
|
reloadCert := "reload_cert.pem"
|
||||||
|
reloadKey := "reload_key.pem"
|
||||||
|
caPem := "reload_ca.pem"
|
||||||
|
|
||||||
|
tempDir := t.TempDir()
|
||||||
|
|
||||||
|
// Set up initial 'foo' certs
|
||||||
|
inBytes, err := os.ReadFile(filepath.Join(workingDir, fooCert))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("unable to read cert required for test", fooCert, err)
|
||||||
|
}
|
||||||
|
err = os.WriteFile(filepath.Join(tempDir, reloadCert), inBytes, 0o777)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("unable to write temp cert required for test", reloadCert, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
inBytes, err = os.ReadFile(filepath.Join(workingDir, fooKey))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("unable to read cert key required for test", fooKey, err)
|
||||||
|
}
|
||||||
|
err = os.WriteFile(filepath.Join(tempDir, reloadKey), inBytes, 0o777)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("unable to write temp cert key required for test", reloadKey, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
inBytes, err = os.ReadFile(filepath.Join(workingDir, caPem))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("unable to read CA pem required for test", caPem, err)
|
||||||
|
}
|
||||||
|
certPool := x509.NewCertPool()
|
||||||
|
ok := certPool.AppendCertsFromPEM(inBytes)
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("not ok when appending CA cert")
|
||||||
|
}
|
||||||
|
|
||||||
|
replacedHcl := strings.ReplaceAll(BasicHclConfig, "TMPDIR", tempDir)
|
||||||
|
configFile := populateTempFile(t, "agent-config.hcl", replacedHcl)
|
||||||
|
|
||||||
|
// Set up Agent/cmd
|
||||||
|
logger := logging.NewVaultLogger(hclog.Trace)
|
||||||
|
ui, cmd := testAgentCommand(t, logger)
|
||||||
|
|
||||||
|
wg.Add(1)
|
||||||
|
args := []string{"-config", configFile.Name()}
|
||||||
|
go func() {
|
||||||
|
if code := cmd.Run(args); code != 0 {
|
||||||
|
output := ui.ErrorWriter.String() + ui.OutputWriter.String()
|
||||||
|
t.Errorf("got a non-zero exit status: %s", output)
|
||||||
|
}
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
|
||||||
|
testCertificateName := func(cn string) error {
|
||||||
|
conn, err := tls.Dial("tcp", "127.0.0.1:8100", &tls.Config{
|
||||||
|
RootCAs: certPool,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
if err = conn.Handshake(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
servName := conn.ConnectionState().PeerCertificates[0].Subject.CommonName
|
||||||
|
if servName != cn {
|
||||||
|
return fmt.Errorf("expected %s, got %s", cn, servName)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start
|
||||||
|
select {
|
||||||
|
case <-cmd.startedCh:
|
||||||
|
case <-time.After(5 * time.Second):
|
||||||
|
t.Fatalf("timeout")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := testCertificateName("foo.example.com"); err != nil {
|
||||||
|
t.Fatalf("certificate name didn't check out: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swap out certs
|
||||||
|
inBytes, err = os.ReadFile(filepath.Join(workingDir, barCert))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("unable to read cert required for test", barCert, err)
|
||||||
|
}
|
||||||
|
err = os.WriteFile(filepath.Join(tempDir, reloadCert), inBytes, 0o777)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("unable to write temp cert required for test", reloadCert, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
inBytes, err = os.ReadFile(filepath.Join(workingDir, barKey))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("unable to read cert key required for test", barKey, err)
|
||||||
|
}
|
||||||
|
err = os.WriteFile(filepath.Join(tempDir, reloadKey), inBytes, 0o777)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("unable to write temp cert key required for test", reloadKey, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reload
|
||||||
|
cmd.SighupCh <- struct{}{}
|
||||||
|
select {
|
||||||
|
case <-cmd.reloadedCh:
|
||||||
|
case <-time.After(5 * time.Second):
|
||||||
|
t.Fatalf("timeout")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := testCertificateName("bar.example.com"); err != nil {
|
||||||
|
t.Fatalf("certificate name didn't check out: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shut down
|
||||||
|
cmd.ShutdownCh <- struct{}{}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get a randomly assigned port and then free it again before returning it.
|
// Get a randomly assigned port and then free it again before returning it.
|
||||||
|
|
|
@ -259,6 +259,7 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
|
||||||
UI: serverCmdUi,
|
UI: serverCmdUi,
|
||||||
},
|
},
|
||||||
ShutdownCh: MakeShutdownCh(),
|
ShutdownCh: MakeShutdownCh(),
|
||||||
|
SighupCh: MakeSighupCh(),
|
||||||
}, nil
|
}, nil
|
||||||
},
|
},
|
||||||
"audit": func() (cli.Command, error) {
|
"audit": func() (cli.Command, error) {
|
||||||
|
|
|
@ -186,6 +186,9 @@ These are the currently-available general configuration options:
|
||||||
|
|
||||||
- `listener` <code>([listener][listener]: <optional\>)</code> - Specifies the addresses and ports on which the Agent will respond to requests.
|
- `listener` <code>([listener][listener]: <optional\>)</code> - Specifies the addresses and ports on which the Agent will respond to requests.
|
||||||
|
|
||||||
|
~> **Note:** On `SIGHUP` (`kill -SIGHUP $(pidof vault)`), Vault Agent will attempt to reload listener TLS configuration.
|
||||||
|
This method can be used to refresh certificates used by Vault Agent without having to restart its process.
|
||||||
|
|
||||||
- `pid_file` `(string: "")` - Path to the file in which the agent's Process ID
|
- `pid_file` `(string: "")` - Path to the file in which the agent's Process ID
|
||||||
(PID) should be stored
|
(PID) should be stored
|
||||||
|
|
||||||
|
@ -213,6 +216,9 @@ These are the currently-available general configuration options:
|
||||||
|
|
||||||
- `log_level` - Equivalent to the [`-log-level` command-line flag](#_log_level).
|
- `log_level` - Equivalent to the [`-log-level` command-line flag](#_log_level).
|
||||||
|
|
||||||
|
~> **Note:** On `SIGHUP` (`kill -SIGHUP $(pidof vault)`), Vault Agent will update the log level to the value
|
||||||
|
specified by configuration file (including overriding values set using CLI or environment variable parameters).
|
||||||
|
|
||||||
- `log_format` - Equivalent to the [`-log-format` command-line flag](#_log_format).
|
- `log_format` - Equivalent to the [`-log-format` command-line flag](#_log_format).
|
||||||
|
|
||||||
- `log_file` - Equivalent to the [`-log-file` command-line flag](#_log_file).
|
- `log_file` - Equivalent to the [`-log-file` command-line flag](#_log_file).
|
||||||
|
|
Loading…
Reference in New Issue