Skip to content

Commit

Permalink
backport of commit 64dfff0 (#24285)
Browse files Browse the repository at this point in the history
Co-authored-by: Peter Wilson <peter.wilson@hashicorp.com>
  • Loading branch information
1 parent 44fdf3b commit 46bcae6
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 15 deletions.
3 changes: 3 additions & 0 deletions changelog/24252.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:bug
agent/logging: Agent should now honor correct -log-format and -log-file settings in logs generated by the consul-template library.
```
35 changes: 22 additions & 13 deletions command/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import (

systemd "github.com/coreos/go-systemd/daemon"
ctconfig "github.com/hashicorp/consul-template/config"
log "github.com/hashicorp/go-hclog"
hclog "github.com/hashicorp/go-hclog"
"github.com/hashicorp/go-multierror"
"github.com/hashicorp/go-secure-stdlib/gatedwriter"
"github.com/hashicorp/go-secure-stdlib/parseutil"
Expand Down Expand Up @@ -79,7 +79,7 @@ type AgentCommand struct {

logWriter io.Writer
logGate *gatedwriter.Writer
logger log.Logger
logger hclog.Logger

// Telemetry object
metricsHelper *metricsutil.MetricsHelper
Expand Down Expand Up @@ -211,7 +211,16 @@ func (c *AgentCommand) Run(args []string) int {
c.outputErrors(err)
return 1
}

// 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.
c.logger = l
c.logWriter = l.StandardWriter(&hclog.StandardLoggerOptions{
InferLevels: true,
InferLevelsWithTimestamp: true,
})

infoKeys := make([]string, 0, 10)
info := make(map[string]string)
Expand Down Expand Up @@ -1087,31 +1096,31 @@ func (c *AgentCommand) handleQuit(enabled bool) http.Handler {
}

// newLogger creates a logger based on parsed config field on the Agent Command struct.
func (c *AgentCommand) newLogger() (log.InterceptLogger, error) {
func (c *AgentCommand) newLogger() (hclog.InterceptLogger, error) {
if c.config == nil {
return nil, fmt.Errorf("cannot create logger, no config")
}

var errors error
var errs *multierror.Error

// Parse all the log related config
logLevel, err := logging.ParseLogLevel(c.config.LogLevel)
if err != nil {
errors = multierror.Append(errors, err)
errs = multierror.Append(errs, err)
}

logFormat, err := logging.ParseLogFormat(c.config.LogFormat)
if err != nil {
errors = multierror.Append(errors, err)
errs = multierror.Append(errs, err)
}

logRotateDuration, err := parseutil.ParseDurationSecond(c.config.LogRotateDuration)
if err != nil {
errors = multierror.Append(errors, err)
errs = multierror.Append(errs, err)
}

if errors != nil {
return nil, errors
if errs != nil {
return nil, errs
}

logCfg := &logging.LogConfig{
Expand All @@ -1134,20 +1143,20 @@ func (c *AgentCommand) newLogger() (log.InterceptLogger, error) {

// loadConfig attempts to generate an Agent config from the file(s) specified.
func (c *AgentCommand) loadConfig(paths []string) (*agentConfig.Config, error) {
var errors error
var errs *multierror.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))
errs = multierror.Append(errs, fmt.Errorf("error loading configuration from %s: %w", configPath, err))
} else {
cfg = cfg.Merge(configFromPath)
}
}

if errors != nil {
return nil, errors
if errs != nil {
return nil, errs
}

if err := cfg.ValidateConfig(); err != nil {
Expand Down
1 change: 0 additions & 1 deletion command/agent/internal/ctmanager/runner_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
ctconfig "github.com/hashicorp/consul-template/config"
ctlogging "github.com/hashicorp/consul-template/logging"
"github.com/hashicorp/go-hclog"

"github.com/hashicorp/vault/command/agent/config"
"github.com/hashicorp/vault/sdk/helper/pointerutil"
)
Expand Down
112 changes: 111 additions & 1 deletion command/agent_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package command

import (
"bufio"
"crypto/tls"
"crypto/x509"
"encoding/json"
Expand Down Expand Up @@ -829,7 +830,7 @@ auto_auth {
verify := func(suffix string) {
t.Helper()
// We need to poll for a bit to give Agent time to render the
// templates. Without this this, the test will attempt to read
// templates. Without this, the test will attempt to read
// the temp dir before Agent has had time to render and will
// likely fail the test
tick := time.Tick(1 * time.Second)
Expand Down Expand Up @@ -3099,6 +3100,115 @@ vault {
}
}

// TestAgent_Logging_ConsulTemplate attempts to ensure two things about Vault Agent logs:
// 1. When -log-format command line arg is set to JSON, it is honored as the output format
// for messages generated from within the consul-template library.
// 2. When -log-file command line arg is supplied, a file receives all log messages
// generated by the consul-template library (they don't just go to stdout/stderr).
// Should prevent a regression of: https://github.com/hashicorp/vault/issues/21109
func TestAgent_Logging_ConsulTemplate(t *testing.T) {
const (
runnerLogMessage = "(runner) creating new runner (dry: false, once: false)"
)

// Configure a Vault server so Agent can successfully communicate and render its templates
coreConfig := &vault.CoreConfig{
CredentialBackends: map[string]logical.Factory{"approle": credAppRole.Factory},
LogicalBackends: map[string]logical.Factory{"kv": logicalKv.Factory},
}
opts := &vault.TestClusterOptions{
HandlerFunc: vaulthttp.Handler,
}
cluster := vault.NewTestCluster(t, coreConfig, opts)
defer cluster.Cleanup()
apiClient := cluster.Cores[0].Client
t.Setenv(api.EnvVaultAddress, apiClient.Address())
tempDir := t.TempDir()
roleIDPath, secretIDPath := setupAppRoleAndKVMounts(t, apiClient)

// Create relevant configs for Vault Agent (config, template config)
templateSrc := filepath.Join(tempDir, "render_1.tmpl")
err := os.WriteFile(templateSrc, []byte(templateContents(1)), 0o600)
require.NoError(t, err)
templateConfig := fmt.Sprintf(templateConfigString, templateSrc, tempDir, "render_1.json")

config := `
vault {
address = "%s"
tls_skip_verify = true
}
auto_auth {
method "approle" {
mount_path = "auth/approle"
config = {
role_id_file_path = "%s"
secret_id_file_path = "%s"
remove_secret_id_file_after_reading = false
}
}
}
%s
`
config = fmt.Sprintf(config, apiClient.Address(), roleIDPath, secretIDPath, templateConfig)
configFileName := filepath.Join(tempDir, "config.hcl")
err = os.WriteFile(configFileName, []byte(config), 0o600)
require.NoError(t, err)
_, cmd := testAgentCommand(t, nil)
logFilePath := filepath.Join(tempDir, "agent")

// Start Vault Agent
go func() {
code := cmd.Run([]string{"-config", configFileName, "-log-format", "json", "-log-file", logFilePath, "-log-level", "trace"})
require.Equalf(t, 0, code, "Vault Agent returned a non-zero exit code")
}()

select {
case <-cmd.startedCh:
case <-time.After(5 * time.Second):
t.Fatal("timeout starting agent")
}

// Give Vault Agent some time to render our template.
time.Sleep(3 * time.Second)

// This flag will be used to capture whether we saw a consul-template log
// message in the log file (the presence of the log file is also part of the test)
found := false

// Vault Agent file logs will match agent-{timestamp}.log based on the
// cmd line argument we supplied, e.g. agent-1701258869573205000.log
m, err := filepath.Glob(logFilePath + "*")
require.NoError(t, err)
require.Truef(t, len(m) > 0, "no files were found")

for _, p := range m {
f, err := os.Open(p)
require.NoError(t, err)

fs := bufio.NewScanner(f)
fs.Split(bufio.ScanLines)

for fs.Scan() {
s := fs.Text()
entry := make(map[string]string)
err := json.Unmarshal([]byte(s), &entry)
require.NoError(t, err)
v, ok := entry["@message"]
if !ok {
continue
}
if v == runnerLogMessage {
found = true
break
}
}
}

require.Truef(t, found, "unable to find consul-template partial message in logs: %s", runnerLogMessage)
}

// Get a randomly assigned port and then free it again before returning it.
// There is still a race when trying to use it, but should work better
// than a static port.
Expand Down

0 comments on commit 46bcae6

Please sign in to comment.