2025-10-21 17:20:20 -04:00
// Copyright IBM Corp. 2016, 2025
2023-08-10 21:14:03 -04:00
// SPDX-License-Identifier: BUSL-1.1
2023-03-15 12:00:52 -04:00
2018-07-24 22:02:27 -04:00
package command
import (
"context"
2021-10-15 20:22:19 -04:00
"crypto/tls"
2023-05-25 09:23:56 -04:00
"errors"
2019-02-28 17:29:28 -05:00
"flag"
2018-07-24 22:02:27 -04:00
"fmt"
"io"
2019-02-14 20:10:36 -05:00
"net"
"net/http"
2018-07-24 22:02:27 -04:00
"os"
2024-06-17 13:10:00 -04:00
"path/filepath"
2018-07-24 22:02:27 -04:00
"sort"
"strings"
"sync"
2024-04-09 15:35:39 -04:00
"sync/atomic"
2019-02-21 17:53:34 -05:00
"time"
2018-07-24 22:02:27 -04:00
2022-08-29 08:18:47 -04:00
systemd "github.com/coreos/go-systemd/daemon"
2023-12-04 14:05:02 -05:00
"github.com/hashicorp/cli"
2023-05-17 09:38:34 -04:00
ctconfig "github.com/hashicorp/consul-template/config"
2023-12-01 06:30:34 -05:00
"github.com/hashicorp/go-hclog"
2023-05-17 09:38:34 -04:00
"github.com/hashicorp/go-multierror"
2021-07-15 20:17:31 -04:00
"github.com/hashicorp/go-secure-stdlib/gatedwriter"
2022-11-29 09:07:04 -05:00
"github.com/hashicorp/go-secure-stdlib/parseutil"
2023-01-10 12:45:34 -05:00
"github.com/hashicorp/go-secure-stdlib/reloadutil"
2019-02-19 16:53:29 -05:00
"github.com/hashicorp/vault/api"
2019-10-11 18:56:07 -04:00
agentConfig "github.com/hashicorp/vault/command/agent/config"
2023-05-25 09:23:56 -04:00
"github.com/hashicorp/vault/command/agent/exec"
2026-03-25 09:43:27 -04:00
"github.com/hashicorp/vault/command/agent/pkiexternalca"
Vault Agent Template (#7652)
* Vault Agent Template: parse templates (#7540)
* add template config parsing, but it's wrong b/c it's not using mapstructure
* parsing consul templates in agent config
* add additional test to configuration parsing, to cover basics
* another test fixture, rework simple test into table
* refactor into table test
* rename test
* remove flattenKeys and add other test fixture
* Update command/agent/config/config.go
Co-Authored-By: Jim Kalafut <jkalafut@hashicorp.com>
* return the decode error instead of swallowing it
* Update command/agent/config/config_test.go
Co-Authored-By: Jim Kalafut <jkalafut@hashicorp.com>
* go mod tidy
* change error checking style
* Add agent template doc
* TemplateServer: render secrets with Consul Template (#7621)
* add template config parsing, but it's wrong b/c it's not using mapstructure
* parsing consul templates in agent config
* add additional test to configuration parsing, to cover basics
* another test fixture, rework simple test into table
* refactor into table test
* rename test
* remove flattenKeys and add other test fixture
* add template package
* WIP: add runner
* fix panic, actually copy templates, etc
* rework how the config.Vault is created and enable reading from the environment
* this was supposed to be a part of the prior commit
* move/add methods to testhelpers for converting some values to pointers
* use new methods in testhelpers
* add an unblock channel to block agent until a template has been rendered
* add note
* unblock if there are no templates
* cleanups
* go mod tidy
* remove dead code
* simple test to starT
* add simple, empty templates test
* Update package doc, error logs, and add missing close() on channel
* update code comment to be clear what I'm referring to
* have template.NewServer return a (<- chan) type, even though it's a normal chan, as a better practice to enforce reading only
* Update command/agent.go
Co-Authored-By: Jim Kalafut <jkalafut@hashicorp.com>
* update with test
* Add README and doc.go to the command/agent directory (#7503)
* Add README and doc.go to the command/agent directory
* Add link to website
* address feedback for agent.go
* updated with feedback from Calvin
* Rework template.Server to export the unblock channel, and remove it from the NewServer function
* apply feedback from Nick
* fix/restructure rendering test
* Add pointerutil package for converting types to their pointers
* Remove pointer helper methods; use sdk/helper/pointerutil instead
* update newRunnerConfig to use pointerutil and empty strings
* only wait for unblock if template server is initialized
* drain the token channel in this test
* conditionally send on channel
2019-10-18 17:21:46 -04:00
"github.com/hashicorp/vault/command/agent/template"
2023-05-19 13:17:48 -04:00
"github.com/hashicorp/vault/command/agentproxyshared"
2023-05-17 09:38:34 -04:00
"github.com/hashicorp/vault/command/agentproxyshared/auth"
2023-12-01 06:30:34 -05:00
"github.com/hashicorp/vault/command/agentproxyshared/cache"
2023-05-17 09:38:34 -04:00
"github.com/hashicorp/vault/command/agentproxyshared/sink"
"github.com/hashicorp/vault/command/agentproxyshared/sink/file"
"github.com/hashicorp/vault/command/agentproxyshared/sink/inmem"
"github.com/hashicorp/vault/command/agentproxyshared/winsvc"
2022-11-11 05:59:16 -05:00
"github.com/hashicorp/vault/helper/logging"
2022-02-17 20:10:26 -05:00
"github.com/hashicorp/vault/helper/metricsutil"
2022-12-07 13:29:51 -05:00
"github.com/hashicorp/vault/helper/useragent"
2021-10-15 20:22:19 -04:00
"github.com/hashicorp/vault/internalshared/configutil"
"github.com/hashicorp/vault/internalshared/listenerutil"
2019-04-13 03:44:06 -04:00
"github.com/hashicorp/vault/sdk/helper/consts"
2019-10-11 18:56:07 -04:00
"github.com/hashicorp/vault/sdk/logical"
2022-12-07 13:29:51 -05:00
"github.com/hashicorp/vault/version"
2023-11-03 10:24:43 -04:00
"github.com/kr/pretty"
"github.com/oklog/run"
"github.com/posener/complete"
"golang.org/x/text/cases"
"golang.org/x/text/language"
"google.golang.org/grpc/test/bufconn"
2018-07-24 22:02:27 -04:00
)
2021-04-08 12:43:39 -04:00
var (
_ cli . Command = ( * AgentCommand ) ( nil )
_ cli . CommandAutocomplete = ( * AgentCommand ) ( nil )
)
2018-07-24 22:02:27 -04:00
2022-11-11 05:59:16 -05:00
const (
// flagNameAgentExitAfterAuth is used as an Agent specific flag to indicate
// that agent should exit after a single successful auth
flagNameAgentExitAfterAuth = "exit-after-auth"
2023-12-01 06:30:34 -05:00
nameAgent = "agent"
2022-11-11 05:59:16 -05:00
)
2018-07-24 22:02:27 -04:00
type AgentCommand struct {
* BaseCommand
2022-11-29 09:07:04 -05:00
logFlags logFlags
2018-07-24 22:02:27 -04:00
2023-01-10 12:45:34 -05:00
config * agentConfig . Config
2018-07-24 22:02:27 -04:00
ShutdownCh chan struct { }
SighupCh chan struct { }
2024-06-17 13:10:00 -04:00
SigUSR2Ch chan struct { }
2018-07-24 22:02:27 -04:00
2023-01-10 12:45:34 -05:00
tlsReloadFuncsLock sync . RWMutex
tlsReloadFuncs [ ] reloadutil . ReloadFunc
2018-07-24 22:02:27 -04:00
logWriter io . Writer
logGate * gatedwriter . Writer
2023-11-29 07:46:18 -05:00
logger hclog . Logger
2018-07-24 22:02:27 -04:00
2022-02-17 20:10:26 -05:00
// Telemetry object
metricsHelper * metricsutil . MetricsHelper
2024-03-27 13:06:45 -04:00
cleanupGuard sync . Once
2018-07-24 22:02:27 -04:00
2023-01-10 12:45:34 -05:00
startedCh chan struct { } // for tests
reloadedCh chan struct { } // for tests
2018-07-24 22:02:27 -04:00
2022-11-29 09:07:04 -05:00
flagConfigs [ ] string
flagExitAfterAuth bool
2018-07-24 22:02:27 -04:00
flagTestVerifyOnly bool
}
func ( c * AgentCommand ) Synopsis ( ) string {
return "Start a Vault agent"
}
func ( c * AgentCommand ) Help ( ) string {
helpText := `
Usage : vault agent [ options ]
2023-01-10 12:45:34 -05:00
This command starts a Vault Agent that can perform automatic authentication
2018-07-24 22:02:27 -04:00
in certain environments .
Start an agent with a configuration file :
$ vault agent - config = / etc / vault / config . hcl
For a full list of examples , please see the documentation .
` + c . Flags ( ) . Help ( )
return strings . TrimSpace ( helpText )
}
func ( c * AgentCommand ) Flags ( ) * FlagSets {
set := c . flagSet ( FlagSetHTTP )
f := set . NewFlagSet ( "Command Options" )
2022-11-29 09:07:04 -05:00
// Augment with the log flags
f . addLogFlags ( & c . logFlags )
2018-07-24 22:02:27 -04:00
f . StringSliceVar ( & StringSliceVar {
Name : "config" ,
Target : & c . flagConfigs ,
Completion : complete . PredictOr (
complete . PredictFiles ( "*.hcl" ) ,
complete . PredictFiles ( "*.json" ) ,
) ,
Usage : "Path to a configuration file. This configuration file should " +
"contain only agent directives." ,
} )
2019-11-21 17:46:15 -05:00
f . BoolVar ( & BoolVar {
2022-11-11 05:59:16 -05:00
Name : flagNameAgentExitAfterAuth ,
2019-11-21 17:46:15 -05:00
Target : & c . flagExitAfterAuth ,
Default : false ,
Usage : "If set to true, the agent will exit with code 0 after a single " +
"successful auth, where success means that a token was retrieved and " +
"all sinks successfully wrote it" ,
} )
2018-07-24 22:02:27 -04:00
// Internal-only flags to follow.
//
// Why hello there little source code reader! Welcome to the Vault source
// code. The remaining options are intentionally undocumented and come with
2019-03-19 09:32:45 -04:00
// no warranty or backwards-compatibility promise. Do not use these flags
2018-07-24 22:02:27 -04:00
// in production. Do not build automation using these flags. Unless you are
// developing against Vault, you should not need any of these flags.
f . BoolVar ( & BoolVar {
Name : "test-verify-only" ,
Target : & c . flagTestVerifyOnly ,
Default : false ,
Hidden : true ,
} )
// End internal-only flags.
return set
}
func ( c * AgentCommand ) AutocompleteArgs ( ) complete . Predictor {
return complete . PredictNothing
}
func ( c * AgentCommand ) AutocompleteFlags ( ) complete . Flags {
return c . Flags ( ) . Completions ( )
}
func ( c * AgentCommand ) Run ( args [ ] string ) int {
f := c . Flags ( )
if err := f . Parse ( args ) ; err != nil {
c . UI . Error ( err . Error ( ) )
return 1
}
// Create a logger. We wrap it in a gated writer so that it doesn't
// start logging too early.
2020-01-23 13:57:18 -05:00
c . logGate = gatedwriter . NewWriter ( os . Stderr )
2018-07-24 22:02:27 -04:00
c . logWriter = c . logGate
2022-11-29 09:07:04 -05:00
if c . logFlags . flagCombineLogs {
2018-07-24 22:02:27 -04:00
c . logWriter = os . Stdout
}
// Validation
2023-01-03 12:50:19 -05:00
if len ( c . flagConfigs ) < 1 {
c . UI . Error ( "Must specify exactly at least one config path using -config" )
2018-07-24 22:02:27 -04:00
return 1
}
2023-01-10 12:45:34 -05:00
config , err := c . loadConfig ( c . flagConfigs )
2023-01-03 12:50:19 -05:00
if err != nil {
2023-01-10 12:45:34 -05:00
c . outputErrors ( err )
2018-07-24 22:02:27 -04:00
return 1
}
2023-01-03 12:50:19 -05:00
2019-03-15 14:58:53 -04:00
if config . AutoAuth == nil {
2023-01-10 12:45:34 -05:00
c . UI . Info ( "No auto_auth block found in config, the automatic authentication feature will not be started" )
2022-11-11 05:59:16 -05:00
}
2023-01-11 15:04:57 -05:00
c . applyConfigOverrides ( f , config ) // This only needs to happen on start-up to aggregate config from flags and env vars
2023-01-10 12:45:34 -05:00
c . config = config
2022-11-29 09:07:04 -05:00
2023-01-10 12:45:34 -05:00
l , err := c . newLogger ( )
2022-11-29 09:07:04 -05:00
if err != nil {
2023-01-10 12:45:34 -05:00
c . outputErrors ( err )
2022-11-29 09:07:04 -05:00
return 1
}
2023-11-29 07:46:18 -05:00
// Update the logger and then base the log writer on that logger.
// Log writer is supplied to consul-template runners for templates and execs.
// We want to ensure that consul-template will honor the settings, for example
// if the -log-format is JSON we want JSON, not a mix of JSON and non-JSON messages.
2022-11-11 05:59:16 -05:00
c . logger = l
2023-11-29 07:46:18 -05:00
c . logWriter = l . StandardWriter ( & hclog . StandardLoggerOptions {
InferLevels : true ,
InferLevelsWithTimestamp : true ,
} )
2019-02-28 17:29:28 -05:00
2023-12-11 11:08:48 -05:00
// release log gate if the disable-gated-logs flag is set
if c . logFlags . flagDisableGatedLogs {
c . logGate . Flush ( )
}
2018-07-24 22:02:27 -04:00
infoKeys := make ( [ ] string , 0 , 10 )
info := make ( map [ string ] string )
2022-11-29 09:07:04 -05:00
info [ "log level" ] = config . LogLevel
2018-07-24 22:02:27 -04:00
infoKeys = append ( infoKeys , "log level" )
infoKeys = append ( infoKeys , "version" )
verInfo := version . GetVersion ( )
info [ "version" ] = verInfo . FullVersionNumber ( false )
if verInfo . Revision != "" {
info [ "version sha" ] = strings . Trim ( verInfo . Revision , "'" )
infoKeys = append ( infoKeys , "version sha" )
}
infoKeys = append ( infoKeys , "cgo" )
info [ "cgo" ] = "disabled"
if version . CgoEnabled {
info [ "cgo" ] = "enabled"
}
// Tests might not want to start a vault server and just want to verify
// the configuration.
if c . flagTestVerifyOnly {
if os . Getenv ( "VAULT_TEST_VERIFY_ONLY_DUMP_CONFIG" ) != "" {
c . UI . Output ( fmt . Sprintf (
"\nConfiguration:\n%s\n" ,
2023-01-10 12:45:34 -05:00
pretty . Sprint ( * c . config ) ) )
2018-07-24 22:02:27 -04:00
}
return 0
}
2023-01-13 09:49:36 -05:00
// Ignore any setting of Agent's address. This client is used by the Agent
2019-02-28 17:29:28 -05:00
// to reach out to Vault. This should never loop back to agent.
2023-05-17 09:38:34 -04:00
c . flagAgentProxyAddress = ""
2018-07-24 22:02:27 -04:00
client , err := c . Client ( )
if err != nil {
c . UI . Error ( fmt . Sprintf (
"Error fetching client: %v" ,
err ) )
return 1
}
2023-01-13 09:49:36 -05:00
serverHealth , err := client . Sys ( ) . Health ( )
if err == nil {
// We don't exit on error here, as this is not worth stopping Agent over
serverVersion := serverHealth . Version
agentVersion := version . GetVersion ( ) . VersionNumber ( )
if serverVersion != agentVersion {
c . UI . Info ( "==> Note: Vault Agent version does not match Vault server version. " +
fmt . Sprintf ( "Vault Agent version: %s, Vault server version: %s" , agentVersion , serverVersion ) )
}
}
2023-05-19 13:17:48 -04:00
if config . IsDefaultListerDefined ( ) {
// Notably, we cannot know for sure if they are using the API proxy functionality unless
// we log on each API proxy call, which would be too noisy.
// A customer could have a listener defined but only be using e.g. the cache-clear API,
// even though the API proxy is something they have available.
c . UI . Warn ( "==> Note: Vault Agent will be deprecating API proxy functionality in a future " +
"release, and this functionality has moved to a new subcommand, vault proxy. If you rely on this " +
"functionality, plan to move to Vault Proxy instead." )
}
2023-05-25 09:23:56 -04:00
// ctx and cancelFunc are passed to the AuthHandler, SinkServer, ExecServer and
Vault Agent Template (#7652)
* Vault Agent Template: parse templates (#7540)
* add template config parsing, but it's wrong b/c it's not using mapstructure
* parsing consul templates in agent config
* add additional test to configuration parsing, to cover basics
* another test fixture, rework simple test into table
* refactor into table test
* rename test
* remove flattenKeys and add other test fixture
* Update command/agent/config/config.go
Co-Authored-By: Jim Kalafut <jkalafut@hashicorp.com>
* return the decode error instead of swallowing it
* Update command/agent/config/config_test.go
Co-Authored-By: Jim Kalafut <jkalafut@hashicorp.com>
* go mod tidy
* change error checking style
* Add agent template doc
* TemplateServer: render secrets with Consul Template (#7621)
* add template config parsing, but it's wrong b/c it's not using mapstructure
* parsing consul templates in agent config
* add additional test to configuration parsing, to cover basics
* another test fixture, rework simple test into table
* refactor into table test
* rename test
* remove flattenKeys and add other test fixture
* add template package
* WIP: add runner
* fix panic, actually copy templates, etc
* rework how the config.Vault is created and enable reading from the environment
* this was supposed to be a part of the prior commit
* move/add methods to testhelpers for converting some values to pointers
* use new methods in testhelpers
* add an unblock channel to block agent until a template has been rendered
* add note
* unblock if there are no templates
* cleanups
* go mod tidy
* remove dead code
* simple test to starT
* add simple, empty templates test
* Update package doc, error logs, and add missing close() on channel
* update code comment to be clear what I'm referring to
* have template.NewServer return a (<- chan) type, even though it's a normal chan, as a better practice to enforce reading only
* Update command/agent.go
Co-Authored-By: Jim Kalafut <jkalafut@hashicorp.com>
* update with test
* Add README and doc.go to the command/agent directory (#7503)
* Add README and doc.go to the command/agent directory
* Add link to website
* address feedback for agent.go
* updated with feedback from Calvin
* Rework template.Server to export the unblock channel, and remove it from the NewServer function
* apply feedback from Nick
* fix/restructure rendering test
* Add pointerutil package for converting types to their pointers
* Remove pointer helper methods; use sdk/helper/pointerutil instead
* update newRunnerConfig to use pointerutil and empty strings
* only wait for unblock if template server is initialized
* drain the token channel in this test
* conditionally send on channel
2019-10-18 17:21:46 -04:00
// TemplateServer that periodically listen for ctx.Done() to fire and shut
// down accordingly.
2018-07-24 22:02:27 -04:00
ctx , cancelFunc := context . WithCancel ( context . Background ( ) )
2021-09-30 07:33:14 -04:00
defer cancelFunc ( )
2018-07-24 22:02:27 -04:00
2022-02-17 20:10:26 -05:00
// telemetry configuration
inmemMetrics , _ , prometheusEnabled , err := configutil . SetupTelemetry ( & configutil . SetupTelemetryOpts {
Config : config . Telemetry ,
Ui : c . UI ,
ServiceName : "vault" ,
DisplayName : "Vault" ,
2023-04-03 14:14:47 -04:00
UserAgent : useragent . AgentString ( ) ,
2022-02-17 20:10:26 -05:00
ClusterName : config . ClusterName ,
} )
if err != nil {
c . UI . Error ( fmt . Sprintf ( "Error initializing telemetry: %s" , err ) )
return 1
}
c . metricsHelper = metricsutil . NewMetricsHelper ( inmemMetrics , prometheusEnabled )
2024-01-04 13:01:38 -05:00
var templateNamespace string
// This indicates whether the namespace for the client has been set by environment variable.
// If it has, we don't touch it
namespaceSetByEnvironmentVariable := client . Namespace ( ) != ""
if ! namespaceSetByEnvironmentVariable && config . Vault != nil && config . Vault . Namespace != "" {
client . SetNamespace ( config . Vault . Namespace )
}
2019-03-15 14:58:53 -04:00
var method auth . AuthMethod
2018-07-24 22:02:27 -04:00
var sinks [ ] * sink . SinkConfig
2019-03-15 14:58:53 -04:00
if config . AutoAuth != nil {
2024-01-04 13:01:38 -05:00
// Note: This will only set namespace header to the value in config.AutoAuth.Method.Namespace
// only if it hasn't been set by config.Vault.Namespace above. In that case, the config value
// present at config.AutoAuth.Method.Namespace will still be used for auto-auth.
if ! namespaceSetByEnvironmentVariable && config . AutoAuth . Method . Namespace != "" {
2022-01-19 12:43:12 -05:00
client . SetNamespace ( config . AutoAuth . Method . Namespace )
}
2024-01-04 13:01:38 -05:00
templateNamespace = client . Namespace ( )
2024-04-15 17:05:38 -04:00
if ! namespaceSetByEnvironmentVariable && config . Vault != nil && config . Vault . Namespace != "" {
templateNamespace = config . Vault . Namespace
}
2022-06-16 18:06:22 -04:00
sinkClient , err := client . CloneWithHeaders ( )
if err != nil {
c . UI . Error ( fmt . Sprintf ( "Error cloning client for file sink: %v" , err ) )
return 1
}
if config . DisableIdleConnsAutoAuth {
sinkClient . SetMaxIdleConnections ( - 1 )
}
2022-07-28 15:59:49 -04:00
if config . DisableKeepAlivesAutoAuth {
sinkClient . SetDisableKeepAlives ( true )
}
2019-03-15 14:58:53 -04:00
for _ , sc := range config . AutoAuth . Sinks {
switch sc . Type {
case "file" :
config := & sink . SinkConfig {
2020-08-25 17:26:06 -04:00
Logger : c . logger . Named ( "sink.file" ) ,
Config : sc . Config ,
2022-06-16 18:06:22 -04:00
Client : sinkClient ,
2020-08-25 17:26:06 -04:00
WrapTTL : sc . WrapTTL ,
DHType : sc . DHType ,
2020-08-17 12:36:16 -04:00
DeriveKey : sc . DeriveKey ,
2020-08-25 17:26:06 -04:00
DHPath : sc . DHPath ,
AAD : sc . AAD ,
2019-03-15 14:58:53 -04:00
}
s , err := file . NewFileSink ( config )
if err != nil {
2023-01-10 12:45:34 -05:00
c . UI . Error ( fmt . Errorf ( "error creating file sink: %w" , err ) . Error ( ) )
2019-03-15 14:58:53 -04:00
return 1
}
config . Sink = s
sinks = append ( sinks , config )
default :
c . UI . Error ( fmt . Sprintf ( "Unknown sink type %q" , sc . Type ) )
2018-07-24 22:02:27 -04:00
return 1
}
2019-03-15 14:58:53 -04:00
}
authConfig := & auth . AuthConfig {
Logger : c . logger . Named ( fmt . Sprintf ( "auth.%s" , config . AutoAuth . Method . Type ) ) ,
2022-01-19 12:43:12 -05:00
MountPath : config . AutoAuth . Method . MountPath ,
2019-03-15 14:58:53 -04:00
Config : config . AutoAuth . Method . Config ,
}
2023-05-19 13:17:48 -04:00
method , err = agentproxyshared . GetAutoAuthMethodFromConfig ( config . AutoAuth . Method . Type , authConfig , config . Vault . Address )
2019-03-15 14:58:53 -04:00
if err != nil {
2023-05-19 13:17:48 -04:00
c . UI . Error ( fmt . Sprintf ( "Error creating %s auth method: %v" , config . AutoAuth . Method . Type , err ) )
2018-07-24 22:02:27 -04:00
return 1
}
}
2021-03-18 14:14:09 -04:00
// We do this after auto-auth has been configured, because we don't want to
// confuse the issue of retries for auth failures which have their own
// config and are handled a bit differently.
if os . Getenv ( api . EnvVaultMaxRetries ) == "" {
2023-01-03 12:50:19 -05:00
client . SetMaxRetries ( ctconfig . DefaultRetryAttempts )
if config . Vault != nil {
if config . Vault . Retry != nil {
client . SetMaxRetries ( config . Vault . Retry . NumRetries )
}
}
2021-03-18 14:14:09 -04:00
}
2021-02-24 06:58:10 -05:00
enforceConsistency := cache . EnforceConsistencyNever
whenInconsistent := cache . WhenInconsistentFail
2022-12-05 10:51:03 -05:00
if config . APIProxy != nil {
switch config . APIProxy . EnforceConsistency {
case "always" :
enforceConsistency = cache . EnforceConsistencyAlways
case "never" , "" :
default :
c . UI . Error ( fmt . Sprintf ( "Unknown api_proxy setting for enforce_consistency: %q" , config . APIProxy . EnforceConsistency ) )
return 1
}
switch config . APIProxy . WhenInconsistent {
case "retry" :
whenInconsistent = cache . WhenInconsistentRetry
case "forward" :
whenInconsistent = cache . WhenInconsistentForward
case "fail" , "" :
default :
c . UI . Error ( fmt . Sprintf ( "Unknown api_proxy setting for when_inconsistent: %q" , config . APIProxy . WhenInconsistent ) )
return 1
}
}
// Keep Cache configuration for legacy reasons, but error if defined alongside API Proxy
2021-02-24 06:58:10 -05:00
if config . Cache != nil {
switch config . Cache . EnforceConsistency {
case "always" :
2022-12-05 10:51:03 -05:00
if enforceConsistency != cache . EnforceConsistencyNever {
c . UI . Error ( "enforce_consistency configured in both api_proxy and cache blocks. Please remove this configuration from the cache block." )
return 1
} else {
enforceConsistency = cache . EnforceConsistencyAlways
}
2021-02-24 06:58:10 -05:00
case "never" , "" :
default :
c . UI . Error ( fmt . Sprintf ( "Unknown cache setting for enforce_consistency: %q" , config . Cache . EnforceConsistency ) )
return 1
}
switch config . Cache . WhenInconsistent {
case "retry" :
2022-12-05 10:51:03 -05:00
if whenInconsistent != cache . WhenInconsistentFail {
c . UI . Error ( "when_inconsistent configured in both api_proxy and cache blocks. Please remove this configuration from the cache block." )
return 1
} else {
whenInconsistent = cache . WhenInconsistentRetry
}
2021-02-24 06:58:10 -05:00
case "forward" :
2022-12-05 10:51:03 -05:00
if whenInconsistent != cache . WhenInconsistentFail {
c . UI . Error ( "when_inconsistent configured in both api_proxy and cache blocks. Please remove this configuration from the cache block." )
return 1
} else {
whenInconsistent = cache . WhenInconsistentForward
}
2021-02-24 06:58:10 -05:00
case "fail" , "" :
default :
c . UI . Error ( fmt . Sprintf ( "Unknown cache setting for when_inconsistent: %q" , config . Cache . WhenInconsistent ) )
return 1
}
}
2020-08-25 17:26:06 -04:00
// Warn if cache _and_ cert auto-auth is enabled but certificates were not
// provided in the auto_auth.method["cert"].config stanza.
if config . Cache != nil && ( config . AutoAuth != nil && config . AutoAuth . Method != nil && config . AutoAuth . Method . Type == "cert" ) {
_ , okCertFile := config . AutoAuth . Method . Config [ "client_cert" ]
_ , okCertKey := config . AutoAuth . Method . Config [ "client_key" ]
// If neither of these exists in the cert stanza, agent will use the
// certs from the vault stanza.
if ! okCertFile && ! okCertKey {
c . UI . Warn ( wrapAtLength ( "WARNING! Cache is enabled and using the same certificates " +
"from the 'cert' auto-auth method specified in the 'vault' stanza. Consider " +
"specifying certificate information in the 'cert' auto-auth's config stanza." ) )
}
}
// Output the header that the agent has started
2022-11-29 09:07:04 -05:00
if ! c . logFlags . flagCombineLogs {
2023-01-10 12:45:34 -05:00
c . UI . Output ( "==> Vault Agent started! Log data will stream in below:\n" )
2018-07-24 22:02:27 -04:00
}
2021-03-03 17:01:33 -05:00
var leaseCache * cache . LeaseCache
var previousToken string
2019-02-14 20:10:36 -05:00
2022-12-05 10:51:03 -05:00
proxyClient , err := client . CloneWithHeaders ( )
if err != nil {
c . UI . Error ( fmt . Sprintf ( "Error cloning client for proxying: %v" , err ) )
return 1
}
2022-06-16 18:06:22 -04:00
2022-12-05 10:51:03 -05:00
if config . DisableIdleConnsAPIProxy {
proxyClient . SetMaxIdleConnections ( - 1 )
}
2022-06-16 18:06:22 -04:00
2022-12-05 10:51:03 -05:00
if config . DisableKeepAlivesAPIProxy {
proxyClient . SetDisableKeepAlives ( true )
}
2022-07-28 15:59:49 -04:00
2022-12-05 10:51:03 -05:00
apiProxyLogger := c . logger . Named ( "apiproxy" )
// The API proxy to be used, if listeners are configured
apiProxy , err := cache . NewAPIProxy ( & cache . APIProxyConfig {
2023-05-17 09:38:34 -04:00
Client : proxyClient ,
Logger : apiProxyLogger ,
EnforceConsistency : enforceConsistency ,
WhenInconsistentAction : whenInconsistent ,
UserAgentStringFunction : useragent . AgentProxyStringWithProxiedUserAgent ,
UserAgentString : useragent . AgentProxyString ( ) ,
2022-12-05 10:51:03 -05:00
} )
if err != nil {
c . UI . Error ( fmt . Sprintf ( "Error creating API proxy: %v" , err ) )
return 1
}
// Parse agent cache configurations
if config . Cache != nil {
cacheLogger := c . logger . Named ( "cache" )
2019-02-14 20:10:36 -05:00
// Create the lease cache proxier and set its underlying proxier to
// the API proxier.
2021-03-03 17:01:33 -05:00
leaseCache , err = cache . NewLeaseCache ( & cache . LeaseCacheConfig {
2023-11-15 09:11:10 -05:00
Client : proxyClient ,
BaseContext : ctx ,
Proxier : apiProxy ,
Logger : cacheLogger . Named ( "leasecache" ) ,
CacheDynamicSecrets : true ,
UserAgentToUse : useragent . ProxyAPIProxyString ( ) ,
2019-02-14 20:10:36 -05:00
} )
if err != nil {
c . UI . Error ( fmt . Sprintf ( "Error creating lease cache: %v" , err ) )
return 1
}
2021-03-03 17:01:33 -05:00
// Configure persistent storage and add to LeaseCache
if config . Cache . Persist != nil {
2023-05-19 13:17:48 -04:00
deferFunc , oldToken , err := agentproxyshared . AddPersistentStorageToLeaseCache ( ctx , leaseCache , config . Cache . Persist , cacheLogger )
2021-03-03 17:01:33 -05:00
if err != nil {
2023-05-19 13:17:48 -04:00
c . UI . Error ( fmt . Sprintf ( "Error creating persistent cache: %v" , err ) )
2021-03-03 17:01:33 -05:00
return 1
}
2023-05-19 13:17:48 -04:00
previousToken = oldToken
if deferFunc != nil {
defer deferFunc ( )
2021-03-03 17:01:33 -05:00
}
}
2022-12-05 10:51:03 -05:00
}
2021-03-03 17:01:33 -05:00
2024-04-09 15:35:39 -04:00
// Create the AuthHandler, SinkServer, TemplateServer, and ExecServer now so that we can pass AuthHandler struct
// values into the Proxy http.Handler. We will wait to actually start these servers
// once we have configured the handlers for each listener below
authInProgress := & atomic . Bool { }
invalidTokenErrCh := make ( chan error )
var ah * auth . AuthHandler
var ss * sink . SinkServer
var ts * template . Server
var es * exec . Server
2026-03-25 09:43:27 -04:00
var ps * pkiexternalca . Server
2024-04-09 15:35:39 -04:00
if method != nil {
enableTemplateTokenCh := len ( config . Templates ) > 0
enableEnvTemplateTokenCh := len ( config . EnvTemplates ) > 0
2026-03-25 09:43:27 -04:00
enablePKIExternalCATokenCh := len ( config . PKIExternalCAs ) > 0
2024-04-09 15:35:39 -04:00
// Auth Handler is going to set its own retry values, so we want to
// work on a copy of the client to not affect other subsystems.
ahClient , err := c . client . CloneWithHeaders ( )
if err != nil {
c . UI . Error ( fmt . Sprintf ( "Error cloning client for auth handler: %v" , err ) )
return 1
}
// Override the set namespace with the auto-auth specific namespace
if ! namespaceSetByEnvironmentVariable && config . AutoAuth . Method . Namespace != "" {
ahClient . SetNamespace ( config . AutoAuth . Method . Namespace )
}
if config . DisableIdleConnsAutoAuth {
ahClient . SetMaxIdleConnections ( - 1 )
}
if config . DisableKeepAlivesAutoAuth {
ahClient . SetDisableKeepAlives ( true )
}
ah = auth . NewAuthHandler ( & auth . AuthHandlerConfig {
Logger : c . logger . Named ( "auth.handler" ) ,
Client : ahClient ,
WrapTTL : config . AutoAuth . Method . WrapTTL ,
MinBackoff : config . AutoAuth . Method . MinBackoff ,
MaxBackoff : config . AutoAuth . Method . MaxBackoff ,
EnableReauthOnNewCredentials : config . AutoAuth . EnableReauthOnNewCredentials ,
EnableTemplateTokenCh : enableTemplateTokenCh ,
EnableExecTokenCh : enableEnvTemplateTokenCh ,
2026-03-25 09:43:27 -04:00
EnablePKIExternalCATokenCh : enablePKIExternalCATokenCh ,
2024-04-09 15:35:39 -04:00
Token : previousToken ,
ExitOnError : config . AutoAuth . Method . ExitOnError ,
UserAgent : useragent . AgentAutoAuthString ( ) ,
MetricsSignifier : "agent" ,
} )
ss = sink . NewSinkServer ( & sink . SinkServerConfig {
Logger : c . logger . Named ( "sink.server" ) ,
Client : ahClient ,
ExitAfterAuth : config . ExitAfterAuth ,
} )
2026-03-25 09:43:27 -04:00
// Create PKI External CA server if any pki_external_ca blocks are configured
var pkiProvider template . PKIExternalCAProvider
if len ( config . PKIExternalCAs ) > 0 {
ps , err = pkiexternalca . NewServer ( & pkiexternalca . ServerConfig {
Logger : c . logger . Named ( "pkiexternalca.server" ) ,
AgentConfig : c . config ,
LogLevel : c . logger . GetLevel ( ) ,
LogWriter : c . logWriter ,
} )
if err != nil {
c . logger . Error ( "could not create pki external ca server" , "error" , err )
return 1
}
pkiProvider = ps
}
2024-04-09 15:35:39 -04:00
ts = template . NewServer ( & template . ServerConfig {
2026-03-25 09:43:27 -04:00
Logger : c . logger . Named ( "template.server" ) ,
LogLevel : c . logger . GetLevel ( ) ,
LogWriter : c . logWriter ,
AgentConfig : c . config ,
PKIExternalCAServer : pkiProvider ,
Namespace : templateNamespace ,
ExitAfterAuth : config . ExitAfterAuth ,
2024-04-09 15:35:39 -04:00
} )
es , err = exec . NewServer ( & exec . ServerConfig {
AgentConfig : c . config ,
Namespace : templateNamespace ,
Logger : c . logger . Named ( "exec.server" ) ,
LogLevel : c . logger . GetLevel ( ) ,
LogWriter : c . logWriter ,
} )
if err != nil {
c . logger . Error ( "could not create exec server" , "error" , err )
return 1
}
}
2022-12-05 10:51:03 -05:00
var listeners [ ] net . Listener
// If there are templates, add an in-process listener
2023-06-01 15:01:55 -04:00
if len ( config . Templates ) > 0 || len ( config . EnvTemplates ) > 0 {
2022-12-05 10:51:03 -05:00
config . Listeners = append ( config . Listeners , & configutil . Listener { Type : listenerutil . BufConnType } )
}
2023-01-10 12:45:34 -05:00
// Ensure we've added all the reload funcs for TLS before anyone triggers a reload.
c . tlsReloadFuncsLock . Lock ( )
2022-12-05 10:51:03 -05:00
for i , lnConfig := range config . Listeners {
var ln net . Listener
2023-01-10 12:45:34 -05:00
var tlsCfg * tls . Config
2022-12-05 10:51:03 -05:00
if lnConfig . Type == listenerutil . BufConnType {
inProcListener := bufconn . Listen ( 1024 * 1024 )
if config . Cache != nil {
config . Cache . InProcDialer = listenerutil . NewBufConnWrapper ( inProcListener )
}
ln = inProcListener
} else {
2023-01-10 12:45:34 -05:00
lnBundle , err := cache . StartListener ( lnConfig )
2019-02-27 16:14:58 -05:00
if err != nil {
2022-12-05 10:51:03 -05:00
c . UI . Error ( fmt . Sprintf ( "Error starting listener: %v" , err ) )
2024-03-04 14:48:46 -05:00
c . tlsReloadFuncsLock . Unlock ( )
2019-02-27 16:14:58 -05:00
return 1
}
2023-01-10 12:45:34 -05:00
tlsCfg = lnBundle . TLSConfig
ln = lnBundle . Listener
// Track the reload func, so we can reload later if needed.
c . tlsReloadFuncs = append ( c . tlsReloadFuncs , lnBundle . TLSReloadFunc )
2019-02-27 16:14:58 -05:00
}
2022-12-05 10:51:03 -05:00
listeners = append ( listeners , ln )
2019-02-14 20:10:36 -05:00
2024-04-09 15:35:39 -04:00
apiProxyLogger . Debug ( "auto-auth token is allowed to be used; configuring inmem sink" )
inmemSink , err := inmem . New ( & sink . SinkConfig {
Logger : apiProxyLogger ,
} , leaseCache )
if err != nil {
c . UI . Error ( fmt . Sprintf ( "Error creating inmem sink for cache: %v" , err ) )
c . tlsReloadFuncsLock . Unlock ( )
return 1
}
sinks = append ( sinks , & sink . SinkConfig {
Logger : apiProxyLogger ,
Sink : inmemSink ,
} )
2024-04-11 10:17:04 -04:00
useAutoAuthToken := false
2024-04-12 13:51:53 -04:00
forceAutoAuthToken := false
2022-12-05 10:51:03 -05:00
if config . APIProxy != nil {
2024-04-12 13:51:53 -04:00
useAutoAuthToken = config . APIProxy . UseAutoAuthToken
forceAutoAuthToken = config . APIProxy . ForceAutoAuthToken
2022-12-05 10:51:03 -05:00
}
2019-02-14 20:10:36 -05:00
2023-02-01 11:40:20 -05:00
var muxHandler http . Handler
if leaseCache != nil {
2024-04-12 13:51:53 -04:00
muxHandler = cache . ProxyHandler ( ctx , apiProxyLogger , leaseCache , inmemSink , forceAutoAuthToken , useAutoAuthToken , authInProgress , invalidTokenErrCh )
2023-02-01 11:40:20 -05:00
} else {
2024-04-12 13:51:53 -04:00
muxHandler = cache . ProxyHandler ( ctx , apiProxyLogger , apiProxy , inmemSink , forceAutoAuthToken , useAutoAuthToken , authInProgress , invalidTokenErrCh )
2023-02-01 11:40:20 -05:00
}
2019-02-14 20:10:36 -05:00
2022-12-05 10:51:03 -05:00
// Parse 'require_request_header' listener config option, and wrap
// the request handler if necessary
if lnConfig . RequireRequestHeader && ( "metrics_only" != lnConfig . Role ) {
muxHandler = verifyRequestHeader ( muxHandler )
}
2019-10-11 18:56:07 -04:00
2022-12-05 10:51:03 -05:00
// Create a muxer and add paths relevant for the lease cache layer
mux := http . NewServeMux ( )
quitEnabled := lnConfig . AgentAPI != nil && lnConfig . AgentAPI . EnableQuit
2022-02-25 05:29:05 -05:00
2022-12-05 10:51:03 -05:00
mux . Handle ( consts . AgentPathMetrics , c . handleMetrics ( ) )
if "metrics_only" != lnConfig . Role {
mux . Handle ( consts . AgentPathCacheClear , leaseCache . HandleCacheClear ( ctx ) )
mux . Handle ( consts . AgentPathQuit , c . handleQuit ( quitEnabled ) )
mux . Handle ( "/" , muxHandler )
}
2019-02-14 20:10:36 -05:00
2022-12-05 10:51:03 -05:00
scheme := "https://"
2023-01-10 12:45:34 -05:00
if tlsCfg == nil {
2022-12-05 10:51:03 -05:00
scheme = "http://"
}
if ln . Addr ( ) . Network ( ) == "unix" {
scheme = "unix://"
}
2019-03-14 14:53:14 -04:00
2022-12-05 10:51:03 -05:00
infoKey := fmt . Sprintf ( "api address %d" , i + 1 )
info [ infoKey ] = scheme + ln . Addr ( ) . String ( )
infoKeys = append ( infoKeys , infoKey )
server := & http . Server {
Addr : ln . Addr ( ) . String ( ) ,
2023-01-10 12:45:34 -05:00
TLSConfig : tlsCfg ,
2022-12-05 10:51:03 -05:00
Handler : mux ,
ReadHeaderTimeout : 10 * time . Second ,
ReadTimeout : 30 * time . Second ,
IdleTimeout : 5 * time . Minute ,
ErrorLog : apiProxyLogger . StandardLogger ( nil ) ,
2019-02-14 20:10:36 -05:00
}
2022-12-05 10:51:03 -05:00
go server . Serve ( ln )
}
2023-01-10 12:45:34 -05:00
c . tlsReloadFuncsLock . Unlock ( )
2022-12-05 10:51:03 -05:00
// Ensure that listeners are closed at all the exits
listenerCloseFunc := func ( ) {
for _ , ln := range listeners {
ln . Close ( )
2019-02-14 20:10:36 -05:00
}
}
2022-12-05 10:51:03 -05:00
defer c . cleanupGuard . Do ( listenerCloseFunc )
2019-02-14 20:10:36 -05:00
2021-02-24 06:58:10 -05:00
// Inform any tests that the server is ready
2021-03-18 14:14:09 -04:00
if c . startedCh != nil {
close ( c . startedCh )
2021-02-24 06:58:10 -05:00
}
2020-09-29 21:03:09 -04:00
var g run . Group
2023-01-10 12:45:34 -05:00
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 :
}
2024-06-17 13:10:00 -04:00
case <- c . SigUSR2Ch :
pprofPath := filepath . Join ( os . TempDir ( ) , "vault-agent-pprof" )
cpuProfileDuration := time . Second * 1
err := WritePprofToFile ( pprofPath , cpuProfileDuration )
if err != nil {
c . logger . Error ( err . Error ( ) )
continue
}
c . logger . Info ( fmt . Sprintf ( "Wrote pprof files to: %s" , pprofPath ) )
2023-01-10 12:45:34 -05:00
case <- ctx . Done ( ) :
return nil
}
}
} , func ( error ) {
cancelFunc ( )
} )
2020-09-29 21:03:09 -04:00
// This run group watches for signal termination
g . Add ( func ( ) error {
for {
select {
case <- c . ShutdownCh :
2023-01-10 12:45:34 -05:00
c . UI . Output ( "==> Vault Agent shutdown triggered" )
2022-08-29 08:18:47 -04:00
// Notify systemd that the server is shutting down
2023-01-10 12:45:34 -05:00
// Let the lease cache know this is a shutdown; no need to evict everything
2021-03-03 17:01:33 -05:00
if leaseCache != nil {
leaseCache . SetShuttingDown ( true )
}
2020-09-29 21:03:09 -04:00
return nil
case <- ctx . Done ( ) :
return nil
2020-11-23 17:24:32 -05:00
case <- winsvc . ShutdownChannel ( ) :
return nil
2020-09-29 21:03:09 -04:00
}
}
} , func ( error ) { } )
2019-02-20 02:11:05 -05:00
// Start auto-auth and sink servers
2019-03-15 14:58:53 -04:00
if method != nil {
2023-05-25 09:23:56 -04:00
2020-09-29 21:03:09 -04:00
g . Add ( func ( ) error {
return ah . Run ( ctx , method )
} , func ( error ) {
2021-03-03 17:01:33 -05:00
// Let the lease cache know this is a shutdown; no need to evict
// everything
if leaseCache != nil {
leaseCache . SetShuttingDown ( true )
}
2020-09-29 21:03:09 -04:00
cancelFunc ( )
} )
g . Add ( func ( ) error {
2024-03-27 13:06:45 -04:00
err := ss . Run ( ctx , ah . OutputCh , sinks , ah . AuthInProgress )
2020-09-29 21:03:09 -04:00
c . logger . Info ( "sinks finished, exiting" )
// Start goroutine to drain from ah.OutputCh from this point onward
// to prevent ah.Run from being blocked.
go func ( ) {
for {
select {
case <- ctx . Done ( ) :
return
case <- ah . OutputCh :
}
}
} ( )
// Wait until templates are rendered
if len ( config . Templates ) > 0 {
<- ts . DoneCh
}
return err
} , func ( error ) {
2021-03-03 17:01:33 -05:00
// Let the lease cache know this is a shutdown; no need to evict
// everything
if leaseCache != nil {
leaseCache . SetShuttingDown ( true )
}
2020-09-29 21:03:09 -04:00
cancelFunc ( )
} )
2026-04-06 09:58:22 -04:00
// When a PKI external CA server is running, both it and the template server
// need every token. The auth handler sends tokens to ah.PKIExternalCATokenCh
// when PKI is configured. This fan-out goroutine reads each token once from
// ah.PKIExternalCATokenCh and forwards it to separate channels for each consumer.
2026-03-25 09:43:27 -04:00
templateTokenCh := ah . TemplateTokenCh
var pkiTokenCh chan string
if ps != nil {
templateTokenCh = make ( chan string , 1 )
pkiTokenCh = make ( chan string , 1 )
go func ( ) {
for {
select {
case <- ctx . Done ( ) :
return
2026-04-06 09:58:22 -04:00
case token , ok := <- ah . PKIExternalCATokenCh :
2026-03-25 09:43:27 -04:00
if ! ok {
return
}
for _ , ch := range [ ] chan string { templateTokenCh , pkiTokenCh } {
select {
case ch <- token :
case <- ctx . Done ( ) :
return
}
}
}
}
} ( )
}
2020-09-29 21:03:09 -04:00
g . Add ( func ( ) error {
2026-03-25 09:43:27 -04:00
return ts . Run ( ctx , templateTokenCh , config . Templates , ah . AuthInProgress , ah . InvalidToken )
2020-09-29 21:03:09 -04:00
} , func ( error ) {
2021-03-03 17:01:33 -05:00
// Let the lease cache know this is a shutdown; no need to evict
// everything
if leaseCache != nil {
leaseCache . SetShuttingDown ( true )
}
2020-09-29 21:03:09 -04:00
cancelFunc ( )
ts . Stop ( )
} )
2023-05-25 09:23:56 -04:00
g . Add ( func ( ) error {
return es . Run ( ctx , ah . ExecTokenCh )
} , func ( err error ) {
// Let the lease cache know this is a shutdown; no need to evict
// everything
if leaseCache != nil {
leaseCache . SetShuttingDown ( true )
}
cancelFunc ( )
2023-09-12 13:54:37 -04:00
es . Close ( )
2023-05-25 09:23:56 -04:00
} )
2026-03-25 09:43:27 -04:00
// Start PKI External CA server if configured
if ps != nil {
g . Add ( func ( ) error {
return ps . Run ( ctx , pkiTokenCh , c . client )
} , func ( err error ) {
// Let the lease cache know this is a shutdown; no need to evict
// everything
if leaseCache != nil {
leaseCache . SetShuttingDown ( true )
}
cancelFunc ( )
ps . Stop ( )
} )
}
2019-03-15 14:58:53 -04:00
}
2019-02-20 02:11:05 -05:00
2019-02-14 20:10:36 -05:00
// Server configuration output
padding := 24
sort . Strings ( infoKeys )
2023-05-17 09:38:34 -04:00
caser := cases . Title ( language . English )
2023-01-10 12:45:34 -05:00
c . UI . Output ( "==> Vault Agent configuration:\n" )
2019-02-14 20:10:36 -05:00
for _ , k := range infoKeys {
c . UI . Output ( fmt . Sprintf (
"%s%s: %s" ,
strings . Repeat ( " " , padding - len ( k ) ) ,
2023-05-17 09:38:34 -04:00
caser . String ( k ) ,
2019-02-14 20:10:36 -05:00
info [ k ] ) )
}
c . UI . Output ( "" )
2018-07-24 22:02:27 -04:00
// Release the log gate.
c . logGate . Flush ( )
// Write out the PID to the file now that server has successfully started
if err := c . storePidFile ( config . PidFile ) ; err != nil {
c . UI . Error ( fmt . Sprintf ( "Error storing PID: %s" , err ) )
return 1
}
2022-08-29 08:18:47 -04:00
// Notify systemd that the server is ready (if applicable)
c . notifySystemd ( systemd . SdNotifyReady )
2018-07-24 22:02:27 -04:00
defer func ( ) {
if err := c . removePidFile ( config . PidFile ) ; err != nil {
c . UI . Error ( fmt . Sprintf ( "Error deleting the PID file: %s" , err ) )
}
} ( )
2023-01-10 12:45:34 -05:00
var exitCode int
2020-09-29 21:03:09 -04:00
if err := g . Run ( ) ; err != nil {
2023-05-25 09:23:56 -04:00
var processExitError * exec . ProcessExitError
if errors . As ( err , & processExitError ) {
exitCode = processExitError . ExitCode
} else {
exitCode = 1
}
2023-05-31 12:22:16 -04:00
if exitCode != 0 {
c . logger . Error ( "runtime error encountered" , "error" , err , "exitCode" , exitCode )
c . UI . Error ( "Error encountered during run, refer to logs for more details." )
}
2018-07-24 22:02:27 -04:00
}
2023-05-31 12:22:16 -04:00
2023-01-10 12:45:34 -05:00
c . notifySystemd ( systemd . SdNotifyStopping )
2023-05-31 12:22:16 -04:00
2023-01-10 12:45:34 -05:00
return exitCode
2018-07-24 22:02:27 -04:00
}
2023-01-11 15:04:57 -05:00
// applyConfigOverrides ensures that the config object accurately reflects the desired
2022-11-11 05:59:16 -05:00
// settings as configured by the user. It applies the relevant config setting based
// on the precedence (env var overrides file config, cli overrides env var).
2022-11-29 09:07:04 -05:00
// It mutates the config object supplied.
2023-01-11 15:04:57 -05:00
func ( c * AgentCommand ) applyConfigOverrides ( f * FlagSets , config * agentConfig . Config ) {
2023-01-03 12:50:19 -05:00
if config . Vault == nil {
config . Vault = & agentConfig . Vault { }
}
2023-01-11 15:04:57 -05:00
f . applyLogConfigOverrides ( config . SharedConfig )
2022-11-29 09:07:04 -05:00
2022-11-11 05:59:16 -05:00
f . Visit ( func ( fl * flag . Flag ) {
if fl . Name == flagNameAgentExitAfterAuth {
config . ExitAfterAuth = c . flagExitAfterAuth
}
} )
c . setStringFlag ( f , config . Vault . Address , & StringVar {
2025-02-27 17:57:46 -05:00
Name : flagNameAddress ,
Target : & c . flagAddress ,
Default : "https://127.0.0.1:8200" ,
EnvVar : api . EnvVaultAddress ,
Normalizers : [ ] func ( string ) string { configutil . NormalizeAddr } ,
2022-11-11 05:59:16 -05:00
} )
config . Vault . Address = c . flagAddress
c . setStringFlag ( f , config . Vault . CACert , & StringVar {
Name : flagNameCACert ,
Target : & c . flagCACert ,
Default : "" ,
EnvVar : api . EnvVaultCACert ,
} )
config . Vault . CACert = c . flagCACert
c . setStringFlag ( f , config . Vault . CAPath , & StringVar {
Name : flagNameCAPath ,
Target : & c . flagCAPath ,
Default : "" ,
EnvVar : api . EnvVaultCAPath ,
} )
config . Vault . CAPath = c . flagCAPath
c . setStringFlag ( f , config . Vault . ClientCert , & StringVar {
Name : flagNameClientCert ,
Target : & c . flagClientCert ,
Default : "" ,
EnvVar : api . EnvVaultClientCert ,
} )
config . Vault . ClientCert = c . flagClientCert
c . setStringFlag ( f , config . Vault . ClientKey , & StringVar {
Name : flagNameClientKey ,
Target : & c . flagClientKey ,
Default : "" ,
EnvVar : api . EnvVaultClientKey ,
} )
config . Vault . ClientKey = c . flagClientKey
c . setBoolFlag ( f , config . Vault . TLSSkipVerify , & BoolVar {
Name : flagNameTLSSkipVerify ,
Target : & c . flagTLSSkipVerify ,
Default : false ,
EnvVar : api . EnvVaultSkipVerify ,
} )
config . Vault . TLSSkipVerify = c . flagTLSSkipVerify
c . setStringFlag ( f , config . Vault . TLSServerName , & StringVar {
Name : flagTLSServerName ,
Target : & c . flagTLSServerName ,
Default : "" ,
EnvVar : api . EnvVaultTLSServerName ,
} )
config . Vault . TLSServerName = c . flagTLSServerName
}
2019-10-11 18:56:07 -04:00
// verifyRequestHeader wraps an http.Handler inside a Handler that checks for
// the request header that is used for SSRF protection.
func verifyRequestHeader ( handler http . Handler ) http . Handler {
return http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
if val , ok := r . Header [ consts . RequestHeaderName ] ; ! ok || len ( val ) != 1 || val [ 0 ] != "true" {
logical . RespondError ( w ,
http . StatusPreconditionFailed ,
2022-08-03 14:32:45 -04:00
fmt . Errorf ( "missing %q header" , consts . RequestHeaderName ) )
2019-10-11 18:56:07 -04:00
return
}
handler . ServeHTTP ( w , r )
} )
}
2022-08-29 08:18:47 -04:00
func ( c * AgentCommand ) notifySystemd ( status string ) {
sent , err := systemd . SdNotify ( false , status )
if err != nil {
c . logger . Error ( "error notifying systemd" , "error" , err )
} else {
if sent {
c . logger . Debug ( "sent systemd notification" , "notification" , status )
} else {
c . logger . Debug ( "would have sent systemd notification (systemd not present)" , "notification" , status )
}
}
}
2019-02-28 17:29:28 -05:00
func ( c * AgentCommand ) setStringFlag ( f * FlagSets , configVal string , fVar * StringVar ) {
var isFlagSet bool
f . Visit ( func ( f * flag . Flag ) {
if f . Name == fVar . Name {
isFlagSet = true
}
} )
flagEnvValue , flagEnvSet := os . LookupEnv ( fVar . EnvVar )
switch {
case isFlagSet :
// Don't do anything as the flag is already set from the command line
case flagEnvSet :
// Use value from env var
2025-02-27 17:57:46 -05:00
fVar . SetTarget ( flagEnvValue )
2019-02-28 17:29:28 -05:00
case configVal != "" :
// Use value from config
2025-02-27 17:57:46 -05:00
fVar . SetTarget ( configVal )
2019-02-28 17:29:28 -05:00
default :
// Use the default value
2025-02-27 17:57:46 -05:00
fVar . SetTarget ( fVar . Default )
2019-02-28 17:29:28 -05:00
}
}
func ( c * AgentCommand ) setBoolFlag ( f * FlagSets , configVal bool , fVar * BoolVar ) {
var isFlagSet bool
f . Visit ( func ( f * flag . Flag ) {
if f . Name == fVar . Name {
isFlagSet = true
}
} )
flagEnvValue , flagEnvSet := os . LookupEnv ( fVar . EnvVar )
switch {
case isFlagSet :
// Don't do anything as the flag is already set from the command line
case flagEnvSet :
// Use value from env var
2024-01-11 08:56:50 -05:00
val , err := parseutil . ParseBool ( flagEnvValue )
if err != nil {
c . logger . Error ( "error parsing bool from environment variable, using default instead" , "environment variable" , fVar . EnvVar , "provided value" , flagEnvValue , "default" , fVar . Default , "err" , err )
val = fVar . Default
}
* fVar . Target = val
2021-09-30 07:33:14 -04:00
case configVal :
2019-02-28 17:29:28 -05:00
// Use value from config
* fVar . Target = configVal
default :
// Use the default value
* fVar . Target = fVar . Default
}
}
2018-07-24 22:02:27 -04:00
// storePidFile is used to write out our PID to a file if necessary
func ( c * AgentCommand ) storePidFile ( pidPath string ) error {
// Quit fast if no pidfile
if pidPath == "" {
return nil
}
// Open the PID file
2022-04-01 12:57:38 -04:00
pidFile , err := os . OpenFile ( pidPath , os . O_CREATE | os . O_WRONLY | os . O_TRUNC , 0 o600 )
2018-07-24 22:02:27 -04:00
if err != nil {
2021-06-02 09:22:31 -04:00
return fmt . Errorf ( "could not open pid file: %w" , err )
2018-07-24 22:02:27 -04:00
}
defer pidFile . Close ( )
// Write out the PID
pid := os . Getpid ( )
_ , err = pidFile . WriteString ( fmt . Sprintf ( "%d" , pid ) )
if err != nil {
2021-06-02 09:22:31 -04:00
return fmt . Errorf ( "could not write to pid file: %w" , err )
2018-07-24 22:02:27 -04:00
}
return nil
}
// removePidFile is used to cleanup the PID file if necessary
func ( c * AgentCommand ) removePidFile ( pidPath string ) error {
if pidPath == "" {
return nil
}
return os . Remove ( pidPath )
}
2021-03-03 17:01:33 -05:00
2022-02-17 20:10:26 -05:00
func ( c * AgentCommand ) handleMetrics ( ) http . Handler {
return http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
if r . Method != http . MethodGet {
logical . RespondError ( w , http . StatusMethodNotAllowed , nil )
return
}
if err := r . ParseForm ( ) ; err != nil {
logical . RespondError ( w , http . StatusBadRequest , err )
return
}
format := r . Form . Get ( "format" )
if format == "" {
format = metricsutil . FormatFromRequest ( & logical . Request {
Headers : r . Header ,
} )
}
resp := c . metricsHelper . ResponseForFormat ( format )
status := resp . Data [ logical . HTTPStatusCode ] . ( int )
w . Header ( ) . Set ( "Content-Type" , resp . Data [ logical . HTTPContentType ] . ( string ) )
switch v := resp . Data [ logical . HTTPRawBody ] . ( type ) {
case string :
2023-05-17 09:38:34 -04:00
w . WriteHeader ( status )
2022-02-17 20:10:26 -05:00
w . Write ( [ ] byte ( v ) )
case [ ] byte :
w . WriteHeader ( status )
w . Write ( v )
default :
logical . RespondError ( w , http . StatusInternalServerError , fmt . Errorf ( "wrong response returned" ) )
}
} )
}
2022-02-25 05:29:05 -05:00
func ( c * AgentCommand ) handleQuit ( enabled bool ) http . Handler {
return http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
if ! enabled {
w . WriteHeader ( http . StatusNotFound )
return
}
switch r . Method {
case http . MethodPost :
default :
w . WriteHeader ( http . StatusMethodNotAllowed )
return
}
c . logger . Debug ( "received quit request" )
close ( c . ShutdownCh )
} )
}
2023-01-10 12:45:34 -05:00
// newLogger creates a logger based on parsed config field on the Agent Command struct.
2023-11-29 07:46:18 -05:00
func ( c * AgentCommand ) newLogger ( ) ( hclog . InterceptLogger , error ) {
2023-01-10 12:45:34 -05:00
if c . config == nil {
return nil , fmt . Errorf ( "cannot create logger, no config" )
}
2023-11-29 07:46:18 -05:00
var errs * multierror . Error
2023-01-10 12:45:34 -05:00
// Parse all the log related config
logLevel , err := logging . ParseLogLevel ( c . config . LogLevel )
if err != nil {
2023-11-29 07:46:18 -05:00
errs = multierror . Append ( errs , err )
2023-01-10 12:45:34 -05:00
}
logFormat , err := logging . ParseLogFormat ( c . config . LogFormat )
if err != nil {
2023-11-29 07:46:18 -05:00
errs = multierror . Append ( errs , err )
2023-01-10 12:45:34 -05:00
}
logRotateDuration , err := parseutil . ParseDurationSecond ( c . config . LogRotateDuration )
if err != nil {
2023-11-29 07:46:18 -05:00
errs = multierror . Append ( errs , err )
2023-01-10 12:45:34 -05:00
}
2023-11-29 07:46:18 -05:00
if errs != nil {
return nil , errs
2023-01-10 12:45:34 -05:00
}
2023-12-01 06:30:34 -05:00
logCfg , err := logging . NewLogConfig ( nameAgent )
if err != nil {
return nil , err
2023-01-10 12:45:34 -05:00
}
2023-12-01 06:30:34 -05:00
logCfg . Name = nameAgent
logCfg . LogLevel = logLevel
logCfg . LogFormat = logFormat
logCfg . LogFilePath = c . config . LogFile
logCfg . LogRotateDuration = logRotateDuration
logCfg . LogRotateBytes = c . config . LogRotateBytes
logCfg . LogRotateMaxFiles = c . config . LogRotateMaxFiles
2023-01-10 12:45:34 -05:00
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 ) {
2023-11-29 07:46:18 -05:00
var errs * multierror . Error
2023-01-10 12:45:34 -05:00
cfg := agentConfig . NewConfig ( )
for _ , configPath := range paths {
VAULT-32657 deprecate duplicate attributes in HCL configs and policies (#30386)
* upgrade hcl dependency on api pkg
This upgrades the hcl dependency for the API pkg,
and adapts its usage so users of our API pkg are
not affected. There's no good way of communicating
a warning via a library call so we don't.
The tokenHelper which is used by all Vault CLI
commands in order to create the Vault client, as
well as directly used by the login and server
commands, is implemented on the api pkg, so this
upgrade also affects all of those commands. Seems
like this was only moved to the api pkg because
the Terraform provider uses it, and I thought
creating a full copy of all those files back under
command would be too much spaghetti.
Also leaving some TODOs to make next deprecation
steps easier.
* upgrade hcl dependency in vault and sdk pkgs
* upgrade hcl dependency in vault and sdk pkgs
* add CLI warnings to commands that take a config
- vault agent (unit test on CMD warning)
- vault proxy (unit test on CMD warning)
- vault server (no test for the warning)
- vault operator diagnose (no tests at all, uses the
same function as vault server
* ignore duplicates on ParseKMSes function
* Extend policy parsing functions and warn on policy store
* Add warning on policy fmt with duplicate attributes
* Add warnings when creating/updating policy with duplicate HCL attrs
* Add log warning when switchedGetPolicy finds duplicate attrs
Following operations can trigger this warning when they run into a policy
with duplicate attributes:
* replication filtered path namespaces invalidation
* policy read API
* building an ACL (for many different purposes like most authZ operations)
* looking up DR token policies
* creating a token with named policies
* when caching the policies for all namespaces during unseal
* Print log warnings when token inline policy has duplicate attrs
No unit tests on these as new test infra would have to be built on all.
Operations affected, which will now print a log warning when the retrieved
token has an inline policy with duplicate attributes:
* capabilities endpoints in sys mount
* handing events under a subscription with a token with duplicate
attrs in inline policies
* token used to create another token has duplicate attrs in inline
policies (sudo check)
* all uses of fetchACLTokenEntryAndEntity when the request uses a
token with inline policies with duplicate attrs. Almost all reqs
are subject to this
* when tokens are created with inline policies (unclear exactly how that
can happen)
* add changelog and deprecation notice
* add missing copywrite notice
* fix copy-paste mistake
good thing it was covered by unit tests
* Fix manual parsing of telemetry field in SharedConfig
This commit in the hcl library was not in the
v1.0.1-vault-5 version we're using but is
included in v1.0.1-vault-7:
https://github.com/hashicorp/hcl/commit/e80118accb521e47bc5b93104bf46c67d89d2242
This thing of reusing when parsing means that
our approach of manually re-parsing fields
on top of fields that have already been parsed
by the hcl annotation causes strings (maybe
more?) to concatenate.
Fix that by removing annotation. There's
actually more occurrences of this thing of
automatically parsing something that is also
manually parsing. In some places we could
just remove the boilerplate manual parsing, in
others we better remove the auto parsing, but
I don't wanna pull at that thread right now. I
just checked that all places at least fully
overwrite the automatically parsed field
instead of reusing it as the target of the
decode call. The only exception is the AOP
field on ent but that doesn't have maps or
slices, so I think it's fine.
An alternative approach would be to ensure
that the auto-parsed value is discarded,
like the current parseCache function does
note how it's template not templates
* Fix linter complaints
* Update command/base_predict.go
Co-authored-by: Mike Palmiotto <mike.palmiotto@hashicorp.com>
* address review
* remove copywrite headers
* re-add copywrite headers
* make fmt
* Update website/content/partials/deprecation/duplicate-hcl-attributes.mdx
Co-authored-by: Sarah Chavis <62406755+schavis@users.noreply.github.com>
* Update website/content/partials/deprecation/duplicate-hcl-attributes.mdx
Co-authored-by: Sarah Chavis <62406755+schavis@users.noreply.github.com>
* Update website/content/partials/deprecation/duplicate-hcl-attributes.mdx
Co-authored-by: Sarah Chavis <62406755+schavis@users.noreply.github.com>
* undo changes to deprecation.mdx
* remove deprecation doc
* fix conflict with changes from main
---------
Co-authored-by: Mike Palmiotto <mike.palmiotto@hashicorp.com>
Co-authored-by: Sarah Chavis <62406755+schavis@users.noreply.github.com>
2025-05-23 15:02:07 -04:00
// TODO (HCL_DUP_KEYS_DEPRECATION): go back to agentConfig.LoadConfig and remove duplicate when deprecation is done
configFromPath , duplicate , err := agentConfig . LoadConfigCheckDuplicates ( configPath )
2023-01-10 12:45:34 -05:00
if err != nil {
2023-11-29 07:46:18 -05:00
errs = multierror . Append ( errs , fmt . Errorf ( "error loading configuration from %s: %w" , configPath , err ) )
2023-01-10 12:45:34 -05:00
} else {
cfg = cfg . Merge ( configFromPath )
}
VAULT-32657 deprecate duplicate attributes in HCL configs and policies (#30386)
* upgrade hcl dependency on api pkg
This upgrades the hcl dependency for the API pkg,
and adapts its usage so users of our API pkg are
not affected. There's no good way of communicating
a warning via a library call so we don't.
The tokenHelper which is used by all Vault CLI
commands in order to create the Vault client, as
well as directly used by the login and server
commands, is implemented on the api pkg, so this
upgrade also affects all of those commands. Seems
like this was only moved to the api pkg because
the Terraform provider uses it, and I thought
creating a full copy of all those files back under
command would be too much spaghetti.
Also leaving some TODOs to make next deprecation
steps easier.
* upgrade hcl dependency in vault and sdk pkgs
* upgrade hcl dependency in vault and sdk pkgs
* add CLI warnings to commands that take a config
- vault agent (unit test on CMD warning)
- vault proxy (unit test on CMD warning)
- vault server (no test for the warning)
- vault operator diagnose (no tests at all, uses the
same function as vault server
* ignore duplicates on ParseKMSes function
* Extend policy parsing functions and warn on policy store
* Add warning on policy fmt with duplicate attributes
* Add warnings when creating/updating policy with duplicate HCL attrs
* Add log warning when switchedGetPolicy finds duplicate attrs
Following operations can trigger this warning when they run into a policy
with duplicate attributes:
* replication filtered path namespaces invalidation
* policy read API
* building an ACL (for many different purposes like most authZ operations)
* looking up DR token policies
* creating a token with named policies
* when caching the policies for all namespaces during unseal
* Print log warnings when token inline policy has duplicate attrs
No unit tests on these as new test infra would have to be built on all.
Operations affected, which will now print a log warning when the retrieved
token has an inline policy with duplicate attributes:
* capabilities endpoints in sys mount
* handing events under a subscription with a token with duplicate
attrs in inline policies
* token used to create another token has duplicate attrs in inline
policies (sudo check)
* all uses of fetchACLTokenEntryAndEntity when the request uses a
token with inline policies with duplicate attrs. Almost all reqs
are subject to this
* when tokens are created with inline policies (unclear exactly how that
can happen)
* add changelog and deprecation notice
* add missing copywrite notice
* fix copy-paste mistake
good thing it was covered by unit tests
* Fix manual parsing of telemetry field in SharedConfig
This commit in the hcl library was not in the
v1.0.1-vault-5 version we're using but is
included in v1.0.1-vault-7:
https://github.com/hashicorp/hcl/commit/e80118accb521e47bc5b93104bf46c67d89d2242
This thing of reusing when parsing means that
our approach of manually re-parsing fields
on top of fields that have already been parsed
by the hcl annotation causes strings (maybe
more?) to concatenate.
Fix that by removing annotation. There's
actually more occurrences of this thing of
automatically parsing something that is also
manually parsing. In some places we could
just remove the boilerplate manual parsing, in
others we better remove the auto parsing, but
I don't wanna pull at that thread right now. I
just checked that all places at least fully
overwrite the automatically parsed field
instead of reusing it as the target of the
decode call. The only exception is the AOP
field on ent but that doesn't have maps or
slices, so I think it's fine.
An alternative approach would be to ensure
that the auto-parsed value is discarded,
like the current parseCache function does
note how it's template not templates
* Fix linter complaints
* Update command/base_predict.go
Co-authored-by: Mike Palmiotto <mike.palmiotto@hashicorp.com>
* address review
* remove copywrite headers
* re-add copywrite headers
* make fmt
* Update website/content/partials/deprecation/duplicate-hcl-attributes.mdx
Co-authored-by: Sarah Chavis <62406755+schavis@users.noreply.github.com>
* Update website/content/partials/deprecation/duplicate-hcl-attributes.mdx
Co-authored-by: Sarah Chavis <62406755+schavis@users.noreply.github.com>
* Update website/content/partials/deprecation/duplicate-hcl-attributes.mdx
Co-authored-by: Sarah Chavis <62406755+schavis@users.noreply.github.com>
* undo changes to deprecation.mdx
* remove deprecation doc
* fix conflict with changes from main
---------
Co-authored-by: Mike Palmiotto <mike.palmiotto@hashicorp.com>
Co-authored-by: Sarah Chavis <62406755+schavis@users.noreply.github.com>
2025-05-23 15:02:07 -04:00
if duplicate {
c . UI . Warn ( fmt . Sprintf (
"WARNING: Duplicate keys found in the Vault agent configuration file %q, duplicate keys in HCL files are deprecated and will be forbidden in a future release." , configPath ) )
}
2023-01-10 12:45:34 -05:00
}
2023-11-29 07:46:18 -05:00
if errs != nil {
return nil , errs
2023-01-10 12:45:34 -05:00
}
2026-03-25 09:43:27 -04:00
if err := cfg . ValidateConfig ( c . logger ) ; err != nil {
2023-01-10 12:45:34 -05:00
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 {
2023-03-09 10:09:16 -05:00
// Non-TLS listeners will have a nil reload func.
if reloadFunc != nil {
err := reloadFunc ( )
if err != nil {
errors = multierror . Append ( errors , err )
}
2023-01-10 12:45:34 -05:00
}
}
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 ( ) )
}
}
}