-
Notifications
You must be signed in to change notification settings - Fork 4.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Auditbeat recursive file watches for Windows (#6893)
* Add support for case-insensitive text search in system-tests Allow system tests to search for text in the beat logfile using case-insensitive search. This is necessary to match paths in case-insensitive file systems, where the path logged may have different capitalisation than the one used in the system test. * Use custom version of fsnotify with recursion support * Added system-test for the file_integrity module * Auditbeat: Fix deadlock in fsnotify under Windows Under Windows, directories to be watched need to be installed after the event consumer loop is started. Otherwise there's a chance of a potential deadlock, as fsnotify under Windows uses a single goroutine to deliver events and install watches. If the channel it uses to deliver events is full, it will block and won't be able to install further watches. * Auditbeat: Use OS-support for recursive watches This patch enables OS-supported recursive watching in fsnotify if it is available. Currently only supported under Windows via our custom fsnotify fork. Fixes #6864
- Loading branch information
1 parent
2a0ad4d
commit 8088e53
Showing
16 changed files
with
452 additions
and
48 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,189 @@ | ||
import sys | ||
import os | ||
import shutil | ||
import time | ||
import unittest | ||
from auditbeat import * | ||
from beat.beat import INTEGRATION_TESTS | ||
|
||
|
||
# Escapes a path to match what's printed in the logs | ||
def escape_path(path): | ||
return path.replace('\\', '\\\\') | ||
|
||
|
||
def has_file(objs, path, sha1hash): | ||
found = False | ||
for obj in objs: | ||
if 'file.path' in obj and 'hash.sha1' in obj \ | ||
and obj['file.path'].lower() == path.lower() and obj['hash.sha1'] == sha1hash: | ||
found = True | ||
break | ||
assert found, "File '{0}' with sha1sum '{1}' not found".format(path, sha1hash) | ||
|
||
|
||
def has_dir(objs, path): | ||
found = False | ||
for obj in objs: | ||
if 'file.path' in obj and obj['file.path'].lower() == path.lower() and obj['file.type'] == "dir": | ||
found = True | ||
break | ||
assert found, "Dir '{0}' not found".format(path) | ||
|
||
|
||
def file_events(objs, path, expected): | ||
evts = set() | ||
for obj in objs: | ||
if 'file.path' in obj and 'event.action' in obj and obj['file.path'].lower() == path.lower(): | ||
if type(obj['event.action']) == list: | ||
evts = evts.union(set(obj['event.action'])) | ||
else: | ||
evts.add(obj['event.action']) | ||
for wanted in set(expected): | ||
assert wanted in evts, "Event {0} for path '{1}' not found (got {2})".format( | ||
wanted, path, evts) | ||
|
||
|
||
def wrap_except(expr): | ||
try: | ||
return expr() | ||
except IOError: | ||
return False | ||
|
||
|
||
class Test(BaseTest): | ||
|
||
def wait_output(self, min_events): | ||
self.wait_until(lambda: wrap_except(lambda: len(self.read_output()) >= min_events)) | ||
# wait for the number of lines in the file to stay constant for a second | ||
prev_lines = -1 | ||
while True: | ||
num_lines = self.output_lines() | ||
if prev_lines < num_lines: | ||
prev_lines = num_lines | ||
time.sleep(1) | ||
else: | ||
break | ||
|
||
def test_non_recursive(self): | ||
""" | ||
file_integrity monitors watched directories (non recursive). | ||
""" | ||
|
||
dirs = [self.temp_dir("auditbeat_test"), | ||
self.temp_dir("auditbeat_test")] | ||
|
||
with PathCleanup(dirs): | ||
self.render_config_template( | ||
modules=[{ | ||
"name": "file_integrity", | ||
"extras": { | ||
"paths": dirs, | ||
"scan_at_start": False | ||
} | ||
}], | ||
) | ||
proc = self.start_beat() | ||
|
||
# wait until the directories to watch are printed in the logs | ||
# this happens when the file_integrity module starts. | ||
# Case must be ignored under windows as capitalisation of paths | ||
# may differ | ||
self.wait_log_contains(escape_path(dirs[0]), max_timeout=30, ignore_case=True) | ||
|
||
file1 = os.path.join(dirs[0], 'file.txt') | ||
self.create_file(file1, "hello world!") | ||
|
||
file2 = os.path.join(dirs[1], 'file2.txt') | ||
self.create_file(file2, "Foo bar") | ||
|
||
# wait until file1 is reported before deleting. Otherwise the hash | ||
# might not be calculated | ||
self.wait_log_contains("\"path\": \"{0}\"".format(escape_path(file1)), ignore_case=True) | ||
|
||
os.unlink(file1) | ||
|
||
subdir = os.path.join(dirs[0], "subdir") | ||
os.mkdir(subdir) | ||
file3 = os.path.join(subdir, "other_file.txt") | ||
self.create_file(file3, "not reported.") | ||
|
||
self.wait_log_contains("\"deleted\"") | ||
self.wait_log_contains("\"path\": \"{0}\"".format(escape_path(subdir)), ignore_case=True) | ||
self.wait_output(3) | ||
|
||
proc.check_kill_and_wait() | ||
self.assert_no_logged_warnings() | ||
|
||
# Ensure all Beater stages are used. | ||
assert self.log_contains("Setup Beat: auditbeat") | ||
assert self.log_contains("auditbeat start running") | ||
assert self.log_contains("auditbeat stopped") | ||
|
||
objs = self.read_output() | ||
|
||
has_file(objs, file1, "430ce34d020724ed75a196dfc2ad67c77772d169") | ||
has_file(objs, file2, "d23be250530a24be33069572db67995f21244c51") | ||
has_dir(objs, subdir) | ||
|
||
file_events(objs, file1, ['created', 'deleted']) | ||
file_events(objs, file2, ['created']) | ||
|
||
# assert file inside subdir is not reported | ||
assert self.log_contains(file3) is False | ||
|
||
def test_recursive(self): | ||
""" | ||
file_integrity monitors watched directories (recursive). | ||
""" | ||
|
||
dirs = [self.temp_dir("auditbeat_test")] | ||
|
||
with PathCleanup(dirs): | ||
self.render_config_template( | ||
modules=[{ | ||
"name": "file_integrity", | ||
"extras": { | ||
"paths": dirs, | ||
"scan_at_start": False, | ||
"recursive": True | ||
} | ||
}], | ||
) | ||
proc = self.start_beat() | ||
|
||
# wait until the directories to watch are printed in the logs | ||
# this happens when the file_integrity module starts | ||
self.wait_log_contains(escape_path(dirs[0]), max_timeout=30, ignore_case=True) | ||
self.wait_log_contains("\"recursive\": true") | ||
|
||
subdir = os.path.join(dirs[0], "subdir") | ||
os.mkdir(subdir) | ||
file1 = os.path.join(subdir, "file.txt") | ||
self.create_file(file1, "hello world!") | ||
|
||
subdir2 = os.path.join(subdir, "other") | ||
os.mkdir(subdir2) | ||
file2 = os.path.join(subdir2, "more.txt") | ||
self.create_file(file2, "") | ||
|
||
self.wait_log_contains("\"path\": \"{0}\"".format(escape_path(file2)), ignore_case=True) | ||
self.wait_output(4) | ||
|
||
proc.check_kill_and_wait() | ||
self.assert_no_logged_warnings() | ||
|
||
# Ensure all Beater stages are used. | ||
assert self.log_contains("Setup Beat: auditbeat") | ||
assert self.log_contains("auditbeat start running") | ||
assert self.log_contains("auditbeat stopped") | ||
|
||
objs = self.read_output() | ||
|
||
has_file(objs, file1, "430ce34d020724ed75a196dfc2ad67c77772d169") | ||
has_file(objs, file2, "da39a3ee5e6b4b0d3255bfef95601890afd80709") | ||
has_dir(objs, subdir) | ||
has_dir(objs, subdir2) | ||
|
||
file_events(objs, file1, ['created']) | ||
file_events(objs, file2, ['created']) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.