diff --git a/README.md b/README.md index 5f6f73fd2..49dab2b16 100644 --- a/README.md +++ b/README.md @@ -223,6 +223,7 @@ The current processes environment is used when executing commands with the follo - `CONSUL_HTTP_ADDR` - `CONSUL_HTTP_TOKEN` +- `CONSUL_HTTP_TOKEN_FILE` - `CONSUL_HTTP_AUTH` - `CONSUL_HTTP_SSL` - `CONSUL_HTTP_SSL_VERIFY` diff --git a/cli.go b/cli.go index ada9df3df..e3f4c137d 100644 --- a/cli.go +++ b/cli.go @@ -297,6 +297,11 @@ func (cli *CLI) ParseFlags(args []string) ( return nil }), "consul-token", "") + flags.Var((funcVar)(func(s string) error { + c.Consul.TokenFile = config.String(s) + return nil + }), "consul-token-file", "") + flags.Var((funcDurationVar)(func(d time.Duration) error { c.Consul.Transport.DialKeepAlive = config.TimeDuration(d) return nil @@ -700,6 +705,9 @@ Options: -consul-token= Sets the Consul API token + -consul-token-file= + Sets the path to a file containing the Consul API token + -consul-transport-dial-keep-alive= Sets the amount of time to use for keep-alives diff --git a/cli_test.go b/cli_test.go index 1f8e5c760..cc3503b90 100644 --- a/cli_test.go +++ b/cli_test.go @@ -218,6 +218,16 @@ func TestCLI_ParseFlags(t *testing.T) { }, false, }, + { + "consul-token-file", + []string{"-consul-token-file", "/a/very/secret/path"}, + &config.Config{ + Consul: &config.ConsulConfig{ + TokenFile: config.String("/a/very/secret/path"), + }, + }, + false, + }, { "consul-transport-dial-keep-alive", []string{"-consul-transport-dial-keep-alive", "30s"}, diff --git a/config/consul.go b/config/consul.go index 9f153c1e7..8ace72654 100644 --- a/config/consul.go +++ b/config/consul.go @@ -25,6 +25,9 @@ type ConsulConfig struct { // Token is the token to communicate with Consul securely. Token *string + // TokenFile is the path to a token to communicate with Consul securely. + TokenFile *string `mapstructure:"token_file"` + // Transport configures the low-level network connection details. Transport *TransportConfig `mapstructure:"transport"` } @@ -65,6 +68,7 @@ func (c *ConsulConfig) Copy() *ConsulConfig { } o.Token = c.Token + o.TokenFile = c.TokenFile if c.Transport != nil { o.Transport = c.Transport.Copy() @@ -115,6 +119,10 @@ func (c *ConsulConfig) Merge(o *ConsulConfig) *ConsulConfig { r.Token = o.Token } + if o.TokenFile != nil { + r.TokenFile = o.TokenFile + } + if o.Transport != nil { r.Transport = r.Transport.Merge(o.Transport) } @@ -156,6 +164,13 @@ func (c *ConsulConfig) Finalize() { }, "") } + if c.TokenFile == nil { + c.TokenFile = stringFromEnv([]string{ + "CONSUL_TOKEN_FILE", + "CONSUL_HTTP_TOKEN_FILE", + }, "") + } + if c.Transport == nil { c.Transport = DefaultTransportConfig() } @@ -175,6 +190,7 @@ func (c *ConsulConfig) GoString() string { "Retry:%#v, "+ "SSL:%#v, "+ "Token:%t, "+ + "TokenFile:%s, "+ "Transport:%#v"+ "}", StringGoString(c.Address), @@ -183,6 +199,7 @@ func (c *ConsulConfig) GoString() string { c.Retry, c.SSL, StringPresent(c.Token), + StringGoString(c.TokenFile), c.Transport, ) } diff --git a/config/consul_test.go b/config/consul_test.go index 88f4ed0c0..4fbd8fe94 100644 --- a/config/consul_test.go +++ b/config/consul_test.go @@ -30,6 +30,7 @@ func TestConsulConfig_Copy(t *testing.T) { Retry: &RetryConfig{Enabled: Bool(true)}, SSL: &SSLConfig{Enabled: Bool(true)}, Token: String("abcd1234"), + TokenFile: String("/a/very/secret/path"), Transport: &TransportConfig{ DialKeepAlive: TimeDuration(20 * time.Second), }, @@ -223,6 +224,30 @@ func TestConsulConfig_Merge(t *testing.T) { &ConsulConfig{Token: String("same")}, &ConsulConfig{Token: String("same")}, }, + { + "token_file_overrides", + &ConsulConfig{TokenFile: String("same")}, + &ConsulConfig{TokenFile: String("different")}, + &ConsulConfig{TokenFile: String("different")}, + }, + { + "token_file_empty_one", + &ConsulConfig{TokenFile: String("same")}, + &ConsulConfig{}, + &ConsulConfig{TokenFile: String("same")}, + }, + { + "token_file_empty_two", + &ConsulConfig{}, + &ConsulConfig{TokenFile: String("same")}, + &ConsulConfig{TokenFile: String("same")}, + }, + { + "token_file_same", + &ConsulConfig{TokenFile: String("same")}, + &ConsulConfig{TokenFile: String("same")}, + &ConsulConfig{TokenFile: String("same")}, + }, { "transport_overrides", &ConsulConfig{Transport: &TransportConfig{DialKeepAlive: TimeDuration(10 * time.Second)}}, @@ -292,7 +317,8 @@ func TestConsulConfig_Finalize(t *testing.T) { ServerName: String(""), Verify: Bool(true), }, - Token: String(""), + Token: String(""), + TokenFile: String(""), Transport: &TransportConfig{ DialKeepAlive: TimeDuration(DefaultDialKeepAlive), DialTimeout: TimeDuration(DefaultDialTimeout), diff --git a/dependency/client_set.go b/dependency/client_set.go index 695ef47f2..918e0abbc 100644 --- a/dependency/client_set.go +++ b/dependency/client_set.go @@ -51,6 +51,7 @@ type CreateConsulClientInput struct { Address string Namespace string Token string + TokenFile string AuthEnabled bool AuthUsername string AuthPassword string @@ -116,6 +117,10 @@ func (c *ClientSet) CreateConsulClient(i *CreateConsulClientInput) error { consulConfig.Token = i.Token } + if i.TokenFile != "" { + consulConfig.TokenFile = i.TokenFile + } + if i.AuthEnabled { consulConfig.HttpAuth = &consulapi.HttpBasicAuth{ Username: i.AuthUsername, diff --git a/docs/configuration.md b/docs/configuration.md index 77a37efde..86fc86374 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -258,11 +258,18 @@ consul { # This is the ACL token to use when connecting to Consul. If you did not # enable ACLs on your Consul cluster, you do not need to set this option. # - # This option is also available via the environment variable CONSUL_TOKEN. + # This option is also available via the environment variable CONSUL_TOKEN or + # CONSUL_HTTP_TOKEN # It is highly recommended that you do not put your token in plain-text in a # configuration file. token = "" + # Alternatively, you can specify a path to a file containing the token with + # this option. + # This option is also available via the environment variable CONSUL_TOKEN_FILE or + # CONSUL_HTTP_TOKEN_FILE + token_file = "" + # This controls the retry behavior when an error is returned from Consul. # Consul Template is highly fault tolerant, meaning it does not exit in the # face of failure. Instead, it uses exponential back-off and retry functions diff --git a/manager/runner.go b/manager/runner.go index eb2db8abf..284d032d4 100644 --- a/manager/runner.go +++ b/manager/runner.go @@ -1035,6 +1035,14 @@ func (r *Runner) childEnv() []string { m["CONSUL_HTTP_AUTH"] = r.config.Consul.Auth.String() } + if config.StringPresent(r.config.Consul.Token) { + m["CONSUL_HTTP_TOKEN"] = config.StringVal(r.config.Consul.Token) + } + + if config.StringPresent(r.config.Consul.TokenFile) { + m["CONSUL_HTTP_TOKEN_FILE"] = config.StringVal(r.config.Consul.TokenFile) + } + m["CONSUL_HTTP_SSL"] = strconv.FormatBool(config.BoolVal(r.config.Consul.SSL.Enabled)) m["CONSUL_HTTP_SSL_VERIFY"] = strconv.FormatBool(config.BoolVal(r.config.Consul.SSL.Verify)) @@ -1251,6 +1259,7 @@ func newClientSet(c *config.Config) (*dep.ClientSet, error) { Address: config.StringVal(c.Consul.Address), Namespace: config.StringVal(c.Consul.Namespace), Token: config.StringVal(c.Consul.Token), + TokenFile: config.StringVal(c.Consul.TokenFile), AuthEnabled: config.BoolVal(c.Consul.Auth.Enabled), AuthUsername: config.StringVal(c.Consul.Auth.Username), AuthPassword: config.StringVal(c.Consul.Auth.Password),