-
Notifications
You must be signed in to change notification settings - Fork 23
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
Support deleting findings #93
Conversation
Generate changelog in
|
2f00919
to
14fa74c
Compare
14fa74c
to
fd83441
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.
Thanks for working on this! I like/am encouraged by the fact that this is able to leverage the existing Crawl
framework.
Given the current design of the rest of the system, I think this PR/implementation is very reasonable and well-structured -- left some general feedback, but nothing too major.
cmd/delete.go
Outdated
for _, value := range directoriesWithOwners { | ||
split := strings.Split(value, ":") | ||
if len(split) != 2 { | ||
return fmt.Errorf(`invalid directory-with-owner, must contain 2 colon-separated segments but got %q`, value) |
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.
Previous 2 returns were doing errors.New
, but this is doing fmt.Errorf
-- no strong opinion on which should be used, but they should be used consistently within the same function.
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.
My understanding was that errors.New
should be used when the message is static and fmt.Errorf
when the message changes depending on the state of the application/codepath.
What's the reasoning behind sticking to one of errors.New
or fmt.Errorf
within a function? I don't follow.
internal/deleter/delete.go
Outdated
// | ||
// If the filepath and detailed finding both match for a given crawl.Path then a file is eligible for deletion. | ||
// In this case, Process will always return false to state that this file should no longer exist and that inspecting | ||
// this file for more findings should not be undertaken. |
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.
nit: extra whitespace between "should" and "not"
internal/deleter/templatedowner.go
Outdated
|
||
// DirectoryMatch returns true when the directory containing path matches the TemplatedOwner DirectoryExpression field. | ||
func (r TemplatedOwner) DirectoryMatch(path string) bool { | ||
dir, _ := filepath.Split(path) |
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.
Pretty sure that filepath.Dir
is more appropriate here (functionality should be mostly the same, but believe it's semantically more correct -- you can play around with exact output at https://go.dev/play/p/L5SwrEo8uxc)
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.
Awesome, thanks, forgot about that function.
pkg/crawl/identify.go
Outdated
@@ -108,7 +154,9 @@ type Log4jIdentifier struct { | |||
|
|||
// HandleFindingFunc is called with the given findings and versions when Log4jIdentifier identifies | |||
// a log4j vulnerability whilst crawling the filesystem. | |||
type HandleFindingFunc func(ctx context.Context, path Path, result Finding, version Versions) | |||
// The bool returned by HandleFindingFunc, when false, will instruct the identification of the file to cease. |
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.
Would expand upon this/update phrasing, as I'm unsure of what "will instruct the identification of the file to cease" actually means
internal/deleter/ownerscope.go
Outdated
directoryMatches int | ||
owner string | ||
) | ||
for _, match := range ms.Matchers { |
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.
nit, but would name variable matcher
rather than match
for clarity
internal/deleter/delete.go
Outdated
if len(path) == 0 { | ||
return true | ||
} | ||
match, err := d.FilepathMatch(path[0]) |
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.
Would extract path[0]
to a named variable based on number of times it's used here (and to make it more clear what it semantically represents)
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 will currently crash if d.FilepathMatch
is not specified (since default value is nil
). Generally, it is best form for exported structs to be functional/have sensible defaults in their empty form.
Main options would be:
- Define behavior when these values aren't specified (match all/match none)
- Check if these values aren't specified and throw an appropriate error on function invocation
- Unexport the struct, make it an interface and then have creation function return properly initialized one
cmd/delete.go
Outdated
return errors.New("at least one --directory-with-owner value must be provided or --skip-owner-check must be set") | ||
} | ||
|
||
var ms []deleter.Matcher |
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.
For this and the 3-4 blocks after, decomposing into a function that returns the arguments based on the relevant input flags may make the function more readable (just be decreasing size). Not a huge deal, but a suggestion.
internal/deleter/delete.go
Outdated
FilepathMatch func(filepath string) (bool, error) | ||
FindingMatch func(finding crawl.Finding) bool | ||
DryRun bool | ||
Delete func(filepath string) error |
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.
Is there a reason that this is configurable right now? Look like only current usage sets it to os.Remove
, and right now if this isn't specified as part of the struct then using it will cause a crash (will attempt to invoke a nil function). Unless there's a concrete planned usage where this is set to something different, probably fine for the implementation to start with just hard-coding os.Remove
in the delete code.
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 was purely for testing purposes to stub it out and test the handling of the returned error whilst avoiding interacting with the filesystem.
I'll move it to be hardcoded and add forced deleting of non-existant files to test the error handling case.
That does seem less clean to me. Am I thinking about this the wrong 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.
Ah got it, sorry, missed that this was used in tests. In that case, I think it's fine 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.
Thanks for iterating! Provided a bit more feedback, but on the whole I think this looks good.
pkg/crawl/finding.go
Outdated
func (f Finding) String() string { | ||
var out []string | ||
// for all non-zero bits, append string for finding if it exists | ||
for i := 0; f > 0; i, f = i+1, f>>1 { |
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 logic is a bit hard to parse/read to me -- personally, I would find it easier to follow if it was a for loop of the form:
for i := JndiLookupClassName /* or NothingDetected? */; i != (valueAfterClassFileMd5); i>>1 {
// logic
}
The above construction would make it clearer to me that we're iterating through each valid "Finding" type and appending based on that. However, it would require defining a new terminal Finding
type (so that there is an end point for the iteration).
I don't feel strongly enough about this to assert that the change must be made, but wanted to provide the feedback.
Closes #71
Adds a subcommand,
delete
, that crawls the filesystem for vulnerable log4j versions and can delete them based on some configuration supplied by flags.The command uses the same flags as
crawl
to configure rate limiting of scanning, max archive depth, etc.Features unique to
delete
are:--filepath-owner
- A filepath regex pattern and templated owner can be provided. When encountering a finding, if the containing filepath matches the pattern provided byfilepath-owner
, then the owner template will be expanded against the filepath match to see if the owner of the file matches.This is specifically designed for cases like
^/home/(\w+)/.+:$1
where the owner of a file may depend on the path to it.--skip-owner-check
- Skips any file owner checks. This is required on Windows systems where file ownership seems to be non-trivial.--finding-match
- Specify findings that are required to be found for a vulnerability for the containing file to be deleted.