diff --git a/agent/config/builder.go b/agent/config/builder.go index 4fb3e013bccc..d1a15124b368 100644 --- a/agent/config/builder.go +++ b/agent/config/builder.go @@ -886,17 +886,17 @@ func (b *builder) build() (rt RuntimeConfig, err error) { }, ACLEnableKeyListPolicy: boolVal(c.ACL.EnableKeyListPolicy), - ACLInitialManagementToken: stringVal(c.ACL.Tokens.InitialManagement), + ACLInitialManagementToken: b.stringValOrFile(c.ACL.Tokens.InitialManagement, c.ACL.Tokens.InitialManagementFile, "acl.tokens.initial_management"), ACLTokenReplication: boolVal(c.ACL.TokenReplication), ACLTokens: token.Config{ DataDir: dataDir, EnablePersistence: boolValWithDefault(c.ACL.EnableTokenPersistence, false), - ACLDefaultToken: stringVal(c.ACL.Tokens.Default), - ACLAgentToken: stringVal(c.ACL.Tokens.Agent), - ACLAgentRecoveryToken: stringVal(c.ACL.Tokens.AgentRecovery), - ACLReplicationToken: stringVal(c.ACL.Tokens.Replication), + ACLDefaultToken: b.stringValOrFile(c.ACL.Tokens.Default, c.ACL.Tokens.DefaultFile, "acl.tokens.default"), + ACLAgentToken: b.stringValOrFile(c.ACL.Tokens.Agent, c.ACL.Tokens.AgentFile, "acl.tokens.agent"), + ACLAgentRecoveryToken: b.stringValOrFile(c.ACL.Tokens.AgentRecovery, c.ACL.Tokens.AgentRecoveryFile, "acl.tokens.agent_recovery"), + ACLReplicationToken: b.stringValOrFile(c.ACL.Tokens.Replication, c.ACL.Tokens.ReplicationFile, "acl.tokens.replication"), ACLConfigFileRegistrationToken: stringVal(c.ACL.Tokens.ConfigFileRegistration), ACLDNSToken: stringVal(c.ACL.Tokens.DNS), }, @@ -1625,6 +1625,28 @@ func (b *builder) warn(msg string, args ...interface{}) { b.Warnings = append(b.Warnings, fmt.Sprintf(msg, args...)) } +// stringValOrFile returns the token from the given value. If tokenFile is provided, +// it reads the token from the file (trimming whitespace). If both token and tokenFile +// are provided, tokenFile takes precedence (with a warning). +func (b *builder) stringValOrFile(token *string, tokenFile *string, name string) string { + tokenVal := stringVal(token) + tokenFileVal := stringVal(tokenFile) + + if tokenFileVal != "" { + if tokenVal != "" { + b.warn("%s is set and %s_file is also set. %s_file will be used.", name, name, name) + } + data, err := os.ReadFile(tokenFileVal) + if err != nil { + b.err = multierror.Append(b.err, fmt.Errorf("%s_file: %w", name, err)) + return "" + } + return strings.TrimSpace(string(data)) + } + + return tokenVal +} + func (b *builder) checkVal(v *CheckDefinition) *structs.CheckDefinition { if v == nil { return nil diff --git a/agent/config/config.go b/agent/config/config.go index c2d913186084..a6d75e6ff02e 100644 --- a/agent/config/config.go +++ b/agent/config/config.go @@ -793,10 +793,15 @@ type ACL struct { type Tokens struct { InitialManagement *string `mapstructure:"initial_management"` + InitialManagementFile *string `mapstructure:"initial_management_file"` Replication *string `mapstructure:"replication"` + ReplicationFile *string `mapstructure:"replication_file"` AgentRecovery *string `mapstructure:"agent_recovery"` + AgentRecoveryFile *string `mapstructure:"agent_recovery_file"` Default *string `mapstructure:"default"` + DefaultFile *string `mapstructure:"default_file"` Agent *string `mapstructure:"agent"` + AgentFile *string `mapstructure:"agent_file"` ConfigFileRegistration *string `mapstructure:"config_file_service_registration"` DNS *string `mapstructure:"dns"` diff --git a/agent/config/flags.go b/agent/config/flags.go index f7e777a7e560..2bb10361224b 100644 --- a/agent/config/flags.go +++ b/agent/config/flags.go @@ -96,6 +96,7 @@ func AddFlags(fs *flag.FlagSet, f *LoadOpts) { add(&f.FlagValues.Ports.SerfWAN, "serf-wan-port", "Sets the Serf WAN port to listen on.") add(&f.FlagValues.ServerMode, "server", "Switches agent to server mode.") add(&f.FlagValues.EnableSyslog, "syslog", "Enables logging to syslog.") + add(&f.FlagValues.ACL.Tokens.DefaultFile, "token-file", "Path to a file containing the ACL token to use as the default token. This can also be specified via the CONSUL_HTTP_TOKEN_FILE environment variable.") add(&f.FlagValues.UIConfig.Enabled, "ui", "Enables the built-in static web UI server.") add(&f.FlagValues.UIConfig.ContentPath, "ui-content-path", "Sets the external UI path to a string. Defaults to: /ui/ ") add(&f.FlagValues.UIConfig.Dir, "ui-dir", "Path to directory containing the web UI resources.") diff --git a/agent/config/runtime_test.go b/agent/config/runtime_test.go index d6139af0f1d1..caa4da4045e4 100644 --- a/agent/config/runtime_test.go +++ b/agent/config/runtime_test.go @@ -905,6 +905,79 @@ func TestLoad_IntegrationWithFlags(t *testing.T) { rt.DataDir = dataDir }, }) + run(t, testCase{ + desc: "-token-file", + args: []string{ + `-token-file=` + filepath.Join(dataDir, "token-file"), + `-data-dir=` + dataDir, + }, + setup: func() { + writeFile(filepath.Join(dataDir, "token-file"), []byte("my-test-token\n")) + }, + expected: func(rt *RuntimeConfig) { + rt.ACLTokens.ACLDefaultToken = "my-test-token" + rt.DataDir = dataDir + }, + }) + run(t, testCase{ + desc: "acl.tokens.default_file in config", + args: []string{ + `-data-dir=` + dataDir, + }, + setup: func() { + writeFile(filepath.Join(dataDir, "token-file"), []byte(" token-from-file \n")) + }, + hcl: []string{` + acl { + tokens { + default_file = "` + filepath.Join(dataDir, "token-file") + `" + } + } + `}, + json: []string{`{ + "acl": { + "tokens": { + "default_file": "` + filepath.Join(dataDir, "token-file") + `" + } + } + }`}, + expected: func(rt *RuntimeConfig) { + rt.ACLTokens.ACLDefaultToken = "token-from-file" + rt.DataDir = dataDir + }, + }) + run(t, testCase{ + desc: "acl.tokens.default and default_file both set", + args: []string{ + `-data-dir=` + dataDir, + }, + setup: func() { + writeFile(filepath.Join(dataDir, "token-file"), []byte("token-from-file")) + }, + hcl: []string{` + acl { + tokens { + default = "inline-token" + default_file = "` + filepath.Join(dataDir, "token-file") + `" + } + } + `}, + json: []string{`{ + "acl": { + "tokens": { + "default": "inline-token", + "default_file": "` + filepath.Join(dataDir, "token-file") + `" + } + } + }`}, + expectedWarnings: []string{ + "acl.tokens.default is set and acl.tokens.default_file is also set. acl.tokens.default_file will be used.", + }, + expected: func(rt *RuntimeConfig) { + rt.ACLTokens.ACLDefaultToken = "token-from-file" + rt.DataDir = dataDir + }, + }) run(t, testCase{ desc: "-ui", args: []string{