-
Notifications
You must be signed in to change notification settings - Fork 781
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Remove assumptions about temp file locations #509
Conversation
Hi @doublerebel The reason we write to the parent directory is because Consul Template may be writing secret data. Writing secrets into a world-readable directory like
|
@sethvargo I'd be glad to change it if necessary, though from what I can tell consul-template isn't guaranteed to have any writable directories.
|
Hi @doublerebel Sorry for the delayed reply here. The implication here is actually the same reason the build is failing. In order for the file write to be atomic, all file permissions must be properly set before the file is moved into place. As you can see from the build failure, your solution creates the file in TempDir, but then it is chmoded on 843 to match the value of the existing file. Let's say the existing template is 644, but it's in a directory that is 700. The result is that the file is momentarily stored in /tmp with permissions of 644 before being moved into the more secure directory. So the obvious logical followup is "why don't we chmod the file after we move it". This is problematic because then the write operation is no longer atomic and there exists a race condition where an application could attempt to read the file before the permissions have properly changed, or (even worse), the permissions fail to update and now you're left in a bad state. This is the reason we write into the same directory as the original file, because it avoids all the issues. Does that make sense? |
Hi @sethvargo Thanks, you are right to point out that the rendered file is vulnerable between when perms are set and the file is moved from the tempDir. To solve this issue, I have wrapped the temporary file in a temporary subdir. The subdir is only readable by consul-template, and the file perms can be safely set before copying. I looked for a precedent for this technique and found it recommended by Apple's security document under "Create Temporary Files Correctly". As for the failed tests, I discovered the cause was tempFile perms being set to 0000. The mock template configs are not passed through the parseConfig funcs and therefore never have default permissions set. Now all tests are passing, but please let me know if I have missed anything. |
@@ -822,11 +822,16 @@ func atomicWrite(path string, contents []byte, perms os.FileMode, backup bool) e | |||
} | |||
} | |||
|
|||
f, err := ioutil.TempFile(parent, "") | |||
d, err := ioutil.TempDir(os.TempDir(), "") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's more idiomatic to use ""
here instead of os.TempDir
, which will default to tmpdir anyway
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, I almost did that -- but I grepped the codebase and found that os.TempDir is used with the instances of ioutil.TempFile. Easy change either way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fair enough 😄
Hi @doublerebel Thanks for clearing that up. I'd like to get @jefferai and @slackpad to weigh in and make sure we aren't opening a vuln here. |
@@ -854,7 +859,7 @@ func atomicWrite(path string, contents []byte, perms os.FileMode, backup bool) e | |||
} | |||
} | |||
|
|||
if err := os.Rename(f.Name(), path); err != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems like it may introduce some non-atomicity when swapping the file since this does an open, and then an io.Copy
from Go, whereas a rename is usually an atomic thing. If the service was watching for changes to this file it seems like it may see some intermediate state. I'm not sure how we'd fix this in a general way unless consul-template gets a scratch dir to work in under the parent, but that's ugly.
The only places default perms are set is in config.ParseConfig(...) and config.ParseConfigTemplate. These code paths are never reached in the runner tests, and if perms are not defined, they are interpreted to be 0000. This causes os.Open(...) to fail after perms are set.
@slackpad thanks for working through this with me, I see where you're coming from and I've learned a lot today. While not all OS targets' syscalls default to an atomic rename, golang now basically guarantees it and with go 1.6 provides atomic rename on Windows.
This has the side effect of keeping temporary files clean and understandable to the user. I noticed during testing that failures in consul-template lead to orphaned temporary files in the template target directories. Since the filenames were completely random I had to examine the contents to discover at which file the failure occurred. By adding the filename prefix in addition to the working directory, errors and problems are more descriptive and easier to debug. This solution allows me to create a .ctmpl working directory that is writable by consul-template in any directory to where I need templates written. |
@doublerebel your analysis makes sense. I think with this scheme you'd still have to have the parent directory writable in order to create the temporary directory in there, right? |
@slackpad Yes, this defaults to the existing behavior of requiring a writable parent directory. But to solve my case, the .ctmpl directory can be created manually with the correct permissions and consul-template will just adopt that .ctmpl without needing a writable parent. |
Hi @doublerebel I think this is looking good. Just had a couple thoughts:
Any thoughts on that first one? |
Ping @doublerebel |
Thanks @sethvargo . @slackpad I can support 1) only using .ctmpl if it exists. I'll work on 2) and 3) and update this PR. |
Ping @doublerebel |
Hi @doublerebel Thank you for opening this PR, but we haven't heard back from you in some time. I'm going to close this out. If you'd still like to see this, please open a new PR. Thanks and sorry! 😄 |
Many configuration files live in a parent directory that is only writeable by root or another user. Despite this, current code assumes the parent directory is writable by the consul-template process.
In addition, the assumption is made that the temporary file is created on the same filesystem.
We can still retain an atomic copy+delete using the existing
copyFile(...)
and deferredos.Remove(...)
.This patch allows for more granular security and more variation in the host filesystem. In my case, this patch allows consul-template to run as a dedicated user, with explicit permission to writeable locations.