-
Notifications
You must be signed in to change notification settings - Fork 12
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
Write secret files atomically #440
Conversation
36ddae1
to
3c2dd02
Compare
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.
Aside from the comment, I was wondering about testing and so did some reading on why we might need atomic writes. I'm still not sure how we can simulate a write that gets messed up mid-way. The only thing that comes to mind is using a buffer to write then having the buffer error midway... still thinking about this one.
9ac63ec
to
e2c2ff1
Compare
960122c
to
4f57b0a
Compare
f189415
to
a68a5e4
Compare
pkg/atomicwriter/atomic_writer.go
Outdated
defer func() { | ||
go w.Cleanup() | ||
}() |
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.
- Why does this need retry logic ?
- Deferring a call to a go routine feels like trouble
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.
- See this thread
- Why? Is there a preferred way to do this? My assumption is that a regular defer() function will block execution, which is a problem if we're doing timed retries.
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'm not sure about the appropriateness of retry here. I think this is an instance of an application assuming the reliability responsibilities of something like the OS/file system. I think we ought to be able to safely assume reliability here. I understand that deletion might fail, but I think a single attempt + errors should be enough. An alternative might be to try and delete and if it doesn't work Truncate the file to 0, which is highly likely to work if done after successful writes.
- It's sort of a code smell, at least in my eyes. Defer creating Go routines that are not tracked by anything... An alternative, if we really decide to keep an eye on those tmp file and make sure they are cleaned up might be to a have some sort of temp file manager that keeps track of a collection (maybe a queue) of temp files and in the background in a single goroutine shared between all the files can carry out the regular task of cleaning up the temp files.
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.
@andytinkham What do you think?
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.
It feels like truncate to 0 solves the problem of possibly leaking secrets which is my main concern. If that's easier, I'm ok with it. For #2, I don't think I have any specific thoughts - seems OK.
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.
OK. So instead of the whole retry logic, we just
- Attempt to delete the file
- If that fails, attempt to truncate it (and maybe log it)
- If that fails, log the error and exit
Is that right?
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.
@szh I've added some comments re. one way to mock the os.Remove
function for testing to your query on the about-golang
Teams channel (along with the os.Chmod
and os.Rename
functions).
8a95610
to
f493ff3
Compare
Thanks! @diverdane! I've added some unit tests the ideas you posted. That brought code coverage up into the 90s. |
f493ff3
to
5cd38c3
Compare
a4864f9
to
8b68411
Compare
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.
Looks terrific!
One minor comment/question, otherwise good-to-go!!!
8b68411
to
20ab1b3
Compare
Requesting security review from @andytinkham |
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.
LGTM!!!
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.
LGTM!
56105e8
to
311c61f
Compare
Code Climate has analyzed commit ed7fb5f and detected 0 issues on this pull request. The test coverage on the diff in this pull request is 92.1% (50% is the threshold). This pull request will bring the total coverage in the repository to 93.2% (0.1% change). View more on Code Climate. |
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.
Left a couple comments. Otherwise PR looking good
func mockErrorLog() *bytes.Buffer { | ||
buf := &bytes.Buffer{} | ||
log.ErrorLogger.SetOutput(buf) | ||
return buf | ||
} | ||
|
||
// Unmocks the logger output, sending it back to os.Stderr | ||
func unmockErrorLog() { | ||
log.ErrorLogger.SetOutput(os.Stderr) | ||
} |
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.
In this case it doesn't seem that we have to worry about running tests in parallel but if we did I suspect the mocking and un-mocking might breakdown. To make testing the logs simpler I would have dependency injected the error logger function, much like you did the os funcs, since the atomic writer only uses log.Error
.
@@ -143,7 +144,7 @@ func TestSecretProvider(t *testing.T) { | |||
description: "sidecar container, happy path", | |||
mode: "sidecar", | |||
interval: time.Duration(10) * time.Millisecond, | |||
testTime: time.Duration(65) * time.Millisecond, | |||
testTime: time.Duration(65+atomicWriteDelayMsecs) * time.Millisecond, |
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.
What's all this about ? (Just curious)
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.
In the case where we're doing atomic writes, it adds a very small delay which causes the retry testing to get thrown off (though only on Jenkins, not when running locally). This fixes that.
Desired Outcome
This functionality is described here:
https://github.com/cyberark/secrets-provider-for-k8s/blob/secrets-rotation-design/design/secret_rotation_design.md#atomic-file-writes-for-push-to-file-mode
Implemented Changes
Files are written to a temp file first. If that succeeds, the temp file is moved to the destination in an atomic operation.
Connected Issue/Story
CyberArk internal issue link: ONYX-16710
Definition of Done
Changelog
CHANGELOG update
Test coverage
changes, or
Documentation
README
s) were updated in this PRBehavior
Security