hashicorp / consul-template

Template rendering, notifier, and supervisor for @HashiCorp Consul and Vault data.
https://www.hashicorp.com/
Mozilla Public License 2.0
4.76k stars 781 forks source link

consul-template does not preserve user and group owners of existing destination file #1061

Closed jrandall closed 5 years ago

jrandall commented 6 years ago

Consul Template version

consul-template v0.19.4 (68b1da2)

Configuration

template {
  destination = "/tmp/munge.key"
  contents = "test"
}

Command

# rm /tmp/munge.key && touch /tmp/munge.key && chown munge:munge /tmp/munge.key && consul-template -config=ct-munge.hcl -once && ls -ld /tmp/munge.key
-rw-r--r-- 1 root root 4 Jan 22 19:54 /tmp/munge.key

Debug output

# rm /tmp/munge.key && touch /tmp/munge.key && chown munge:munge /tmp/munge.key && consul-template -config=ct-munge.hcl -once -log-level=trace && ls -ld /tmp/munge.key
2018/01/22 19:56:29.384172 [INFO] consul-template v0.19.4 (68b1da2)
2018/01/22 19:56:29.384185 [INFO] (runner) creating new runner (dry: false, once: true)
2018/01/22 19:56:29.384432 [DEBUG] (runner) final config: {"Consul":{"Address":"","Auth":{"Enabled":false,"Username":"","Password":""},"Retry":{"Attempts":12,"Backoff":250000000,"MaxBackoff":60000000000,"Enabled":true},"SSL":{"CaCert":"","CaPath":"","Cert":"","Enabled":false,"Key":"","ServerName":"","Verify":true},"Token":"","Transport":{"DialKeepAlive":30000000000,"DialTimeout":30000000000,"DisableKeepAlives":false,"IdleConnTimeout":90000000000,"MaxIdleConns":100,"MaxIdleConnsPerHost":17,"TLSHandshakeTimeout":10000000000}},"Dedup":{"Enabled":false,"MaxStale":2000000000,"Prefix":"consul-template/dedup/","TTL":15000000000},"Exec":{"Command":"","Enabled":false,"Env":{"Blacklist":[],"Custom":[],"Pristine":false,"Whitelist":[]},"KillSignal":2,"KillTimeout":30000000000,"ReloadSignal":null,"Splay":0,"Timeout":0},"KillSignal":2,"LogLevel":"trace","MaxStale":2000000000,"PidFile":"","ReloadSignal":1,"Syslog":{"Enabled":false,"Facility":"LOCAL0"},"Templates":[{"Backup":false,"Command":"","CommandTimeout":30000000000,"Contents":"test","CreateDestDirs":true,"Destination":"/tmp/munge.key","ErrMissingKey":false,"Exec":{"Command":"","Enabled":false,"Env":{"Blacklist":[],"Custom":[],"Pristine":false,"Whitelist":[]},"KillSignal":2,"KillTimeout":30000000000,"ReloadSignal":null,"Splay":0,"Timeout":30000000000},"Perms":0,"Source":"","Wait":{"Enabled":false,"Min":0,"Max":0},"LeftDelim":"","RightDelim":""}],"Vault":{"Address":"","Enabled":false,"Grace":15000000000,"RenewToken":true,"Retry":{"Attempts":12,"Backoff":250000000,"MaxBackoff":60000000000,"Enabled":true},"SSL":{"CaCert":"","CaPath":"","Cert":"","Enabled":true,"Key":"","ServerName":"","Verify":true},"Transport":{"DialKeepAlive":30000000000,"DialTimeout":30000000000,"DisableKeepAlives":false,"IdleConnTimeout":90000000000,"MaxIdleConns":100,"MaxIdleConnsPerHost":17,"TLSHandshakeTimeout":10000000000},"UnwrapToken":false},"Wait":{"Enabled":false,"Min":0,"Max":0}}
2018/01/22 19:56:29.384469 [INFO] (runner) creating watcher
2018/01/22 19:56:29.384572 [INFO] (runner) starting
2018/01/22 19:56:29.384591 [DEBUG] (runner) running initial templates
2018/01/22 19:56:29.384595 [INFO] (runner) initiating run
2018/01/22 19:56:29.384615 [DEBUG] (runner) checking template 098f6bcd4621d373cade4e832627b4f6
2018/01/22 19:56:29.384834 [DEBUG] (runner) rendering "(dynamic)" => "/tmp/munge.key"
2018/01/22 19:56:29.386384 [INFO] (runner) rendered "(dynamic)" => "/tmp/munge.key"
2018/01/22 19:56:29.386399 [DEBUG] (runner) diffing and updating dependencies
2018/01/22 19:56:29.386403 [DEBUG] (runner) watching 0 dependencies
2018/01/22 19:56:29.386405 [DEBUG] (runner) all templates rendered
2018/01/22 19:56:29.386406 [INFO] (runner) once mode and all templates rendered
2018/01/22 19:56:29.386408 [INFO] (runner) stopping
2018/01/22 19:56:29.386409 [DEBUG] (runner) stopping watcher
2018/01/22 19:56:29.386410 [DEBUG] (watcher) stopping all views
-rw-r--r-- 1 root root 4 Jan 22 19:56 /tmp/munge.key

Expected behavior

The destination file should still be owned by user munge and group munge after consul-template runs.

Actual behavior

The ownership of the destination file is changed to root:root (in this case, since consul-template is run as root, but the destination file will be owned by whatever user and default group consul-template is run as).

Steps to reproduce

  1. Create a destination file and set user and group ownership as desired
  2. Run consul-template as another user (e.g. as root or as another user with CAP_DAC_OVERRIDE)
  3. Consul-template changes the owner of the destination file such that it is owned by the user consul-template is run as

References

tay-bird commented 6 years ago

Chiming in here, I'm also seeing this issue. I read Seth Vargo's comment in #461 suggesting that consul-client respects file ownership. However, I am fairly certain it does not, as the above commenter has described.

jrandall commented 6 years ago

I've just finished going through the code and it does not do anything to maintain ownership of existing files, though there is code to maintain the mode of an existing file.

It would be an easy fix to add code to renderer.go to Chown the newly created file to the same uid and gid of the existing file, but using core go libraries finding the uid/gid is platform dependent. Should be pretty easy to do for all posix systems, but probably wouldn't do the right thing on windows. However, I think it would be an improvement to add this functionality even if it only works on some systems.

That said, if all we want to do is set ownership of a templated file to a particular user and group, a workaround to consul-template not being able to do that is just to include a call to chown in the command run when the template destination changes.

For example, the toy example given above could become:

template {
  destination = "/tmp/munge.key"
  contents = "test"
  command = "chown munge:munge /tmp/munge.key"
}

What I've done for my real templates (which are running as non-root and also had an existing restart command) is something more like:

template {
  destination = "/tmp/munge.key"
  contents = "test"
  command = "sudo bash -c 'chown munge:munge /tmp/munge.key && systemctl restart munge'"
}
tay-bird commented 6 years ago

@jrandall That would work, but the use case here is where consul-template is an unprivileged user.