Skip to content
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 filesystem monitoring config embedded in main Santa config #1054

Merged
merged 2 commits into from
Mar 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion Source/common/SNTConfigurator.h
Original file line number Diff line number Diff line change
Expand Up @@ -245,10 +245,20 @@
///
@property(readonly, nonatomic) float spoolDirectoryEventMaxFlushTimeSec;

///
/// If set, contains the filesystem access policy configuration.
///
/// @note: The property fileAccessPolicyPlist will be ignored if
/// fileAccessPolicy is set.
/// @note: This property is KVO compliant.
///
@property(readonly, nonatomic) NSDictionary *fileAccessPolicy;

///
/// If set, contains the path to the filesystem access policy config plist.
///
/// @note: This property is KVO compliant, but is only read once at santad startup.
/// @note: This property will be ignored if fileAccessPolicy is set.
/// @note: This property is KVO compliant.
///
@property(readonly, nonatomic) NSString *fileAccessPolicyPlist;

Expand Down
17 changes: 16 additions & 1 deletion Source/common/SNTConfigurator.m
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ @implementation SNTConfigurator
static NSString *const kSpoolDirectorySizeThresholdMB = @"SpoolDirectorySizeThresholdMB";
static NSString *const kSpoolDirectoryEventMaxFlushTimeSec = @"SpoolDirectoryEventMaxFlushTimeSec";

static NSString *const kFileAccessPolicy = @"FileAccessPolicy";
static NSString *const kFileAccessPolicyPlist = @"FileAccessPolicyPlist";
static NSString *const kFileAccessPolicyUpdateIntervalSec = @"FileAccessPolicyUpdateIntervalSec";

Expand Down Expand Up @@ -211,6 +212,7 @@ - (instancetype)init {
kSpoolDirectoryFileSizeThresholdKB : number,
kSpoolDirectorySizeThresholdMB : number,
kSpoolDirectoryEventMaxFlushTimeSec : number,
kFileAccessPolicy : dictionary,
kFileAccessPolicyPlist : string,
kFileAccessPolicyUpdateIntervalSec : number,
kEnableMachineIDDecoration : number,
Expand Down Expand Up @@ -419,6 +421,10 @@ + (NSSet *)keyPathsForValuesAffectingSpoolDirectoryEventMaxFlushTimeSec {
return [self configStateSet];
}

+ (NSSet *)keyPathsForValuesAffectingFileAccessPolicy {
return [self configStateSet];
}

+ (NSSet *)keyPathsForValuesAffectingFileAccessPolicyPlist {
return [self configStateSet];
}
Expand Down Expand Up @@ -809,8 +815,17 @@ - (float)spoolDirectoryEventMaxFlushTimeSec {
: 15.0;
}

- (NSDictionary *)fileAccessPolicy {
return self.configState[kFileAccessPolicy];
}

- (NSString *)fileAccessPolicyPlist {
return self.configState[kFileAccessPolicyPlist];
// This property is ignored when kFileAccessPolicy is set
if (self.configState[kFileAccessPolicy]) {
return nil;
} else {
return self.configState[kFileAccessPolicyPlist];
}
}

- (uint32_t)fileAccessPolicyUpdateIntervalSec {
Expand Down
4 changes: 2 additions & 2 deletions Source/santactl/Commands/SNTCommandStatus.m
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ - (void)runWithArguments:(NSArray *)arguments {
@"enabled" : @(watchItemsEnabled),
@"rule_count" : @(watchItemsRuleCount),
@"policy_version" : watchItemsPolicyVersion,
@"config_path" : watchItemsConfigPath,
@"config_path" : watchItemsConfigPath ?: @"null",
@"last_policy_update" : watchItemsLastUpdateStr ?: @"null",
};
} else {
Expand Down Expand Up @@ -272,7 +272,7 @@ - (void)runWithArguments:(NSArray *)arguments {
if (watchItemsEnabled) {
printf(" %-25s | %s\n", "Policy Version", watchItemsPolicyVersion.UTF8String);
printf(" %-25s | %llu\n", "Rule Count", watchItemsRuleCount);
printf(" %-25s | %s\n", "Config Path", watchItemsConfigPath.UTF8String);
printf(" %-25s | %s\n", "Config Path", (watchItemsConfigPath ?: @"(embedded)").UTF8String);
printf(" %-25s | %s\n", "Last Policy Update", watchItemsLastUpdateStr.UTF8String);
}

Expand Down
14 changes: 13 additions & 1 deletion Source/santad/DataLayer/WatchItems.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,23 +70,34 @@ class WatchItems : public std::enable_shared_from_this<WatchItems> {
// Factory
static std::shared_ptr<WatchItems> Create(NSString *config_path,
uint64_t reapply_config_frequency_secs);
// Factory
static std::shared_ptr<WatchItems> Create(NSDictionary *config,
uint64_t reapply_config_frequency_secs);

WatchItems(NSString *config_path_, dispatch_queue_t q, dispatch_source_t timer_source,
WatchItems(NSString *config_path, dispatch_queue_t q, dispatch_source_t timer_source,
void (^periodic_task_complete_f)(void) = nullptr);
WatchItems(NSDictionary *config, dispatch_queue_t q, dispatch_source_t timer_source,
void (^periodic_task_complete_f)(void) = nullptr);

~WatchItems();

void BeginPeriodicTask();

void RegisterClient(id<SNTEndpointSecurityDynamicEventHandler> client);

void SetConfigPath(NSString *config_path);
void SetConfig(NSDictionary *config);

VersionAndPolicies FindPolciesForPaths(const std::vector<std::string_view> &paths);

std::optional<WatchItemsState> State();

friend class santa::santad::data_layer::WatchItemsPeer;

private:
static std::shared_ptr<WatchItems> CreateInternal(NSString *config_path, NSDictionary *config,
uint64_t reapply_config_frequency_secs);

NSDictionary *ReadConfig();
NSDictionary *ReadConfigLocked() ABSL_SHARED_LOCKS_REQUIRED(lock_);
void ReloadConfig(NSDictionary *new_config);
Expand All @@ -98,6 +109,7 @@ class WatchItems : public std::enable_shared_from_this<WatchItems> {
std::set<std::pair<std::string, WatchItemPathType>> &paths);

NSString *config_path_;
NSDictionary *embedded_config_;
dispatch_queue_t q_;
dispatch_source_t timer_source_;
void (^periodic_task_complete_f_)(void);
Expand Down
43 changes: 41 additions & 2 deletions Source/santad/DataLayer/WatchItems.mm
Original file line number Diff line number Diff line change
Expand Up @@ -520,24 +520,53 @@ bool ParseConfig(NSDictionary *config, std::vector<std::shared_ptr<WatchItemPoli

std::shared_ptr<WatchItems> WatchItems::Create(NSString *config_path,
uint64_t reapply_config_frequency_secs) {
return CreateInternal(config_path, nil, reapply_config_frequency_secs);
}

std::shared_ptr<WatchItems> WatchItems::Create(NSDictionary *config,
uint64_t reapply_config_frequency_secs) {
return CreateInternal(nil, config, reapply_config_frequency_secs);
}

std::shared_ptr<WatchItems> WatchItems::CreateInternal(NSString *config_path, NSDictionary *config,
uint64_t reapply_config_frequency_secs) {
if (reapply_config_frequency_secs < kMinReapplyConfigFrequencySecs) {
LOGW(@"Invalid watch item update interval provided: %llu. Min allowed: %llu",
reapply_config_frequency_secs, kMinReapplyConfigFrequencySecs);
return nullptr;
}

if (config_path && config) {
LOGW(@"Invalid arguments creating WatchItems - both config and config_path cannot be set.");
return nullptr;
}

dispatch_queue_t q = dispatch_queue_create("com.google.santa.daemon.watch_items.q",
DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL);
dispatch_source_t timer_source = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, q);
dispatch_source_set_timer(timer_source, dispatch_time(DISPATCH_TIME_NOW, 0),
NSEC_PER_SEC * reapply_config_frequency_secs, 0);

return std::make_shared<WatchItems>(config_path, q, timer_source);
if (config_path) {
return std::make_shared<WatchItems>(config_path, q, timer_source);
} else {
return std::make_shared<WatchItems>(config, q, timer_source);
}
}

WatchItems::WatchItems(NSString *config_path, dispatch_queue_t q, dispatch_source_t timer_source,
void (^periodic_task_complete_f)(void))
: config_path_(config_path),
embedded_config_(nil),
q_(q),
timer_source_(timer_source),
periodic_task_complete_f_(periodic_task_complete_f),
watch_items_(std::make_unique<WatchItemsTree>()) {}

WatchItems::WatchItems(NSDictionary *config, dispatch_queue_t q, dispatch_source_t timer_source,
void (^periodic_task_complete_f)(void))
: config_path_(nil),
embedded_config_(config),
q_(q),
timer_source_(timer_source),
periodic_task_complete_f_(periodic_task_complete_f),
Expand Down Expand Up @@ -688,7 +717,7 @@ bool ParseConfig(NSDictionary *config, std::vector<std::shared_ptr<WatchItemPoli
return;
}

shared_watcher->ReloadConfig(shared_watcher->ReadConfig());
shared_watcher->ReloadConfig(embedded_config_ ?: shared_watcher->ReadConfig());

if (shared_watcher->periodic_task_complete_f_) {
shared_watcher->periodic_task_complete_f_();
Expand Down Expand Up @@ -718,11 +747,21 @@ bool ParseConfig(NSDictionary *config, std::vector<std::shared_ptr<WatchItemPoli
{
absl::MutexLock lock(&lock_);
config_path_ = config_path;
embedded_config_ = nil;
config = ReadConfigLocked();
}
ReloadConfig(config);
}

void WatchItems::SetConfig(NSDictionary *config) {
{
absl::MutexLock lock(&lock_);
config_path_ = nil;
embedded_config_ = config;
}
ReloadConfig(embedded_config_);
}

std::optional<WatchItemsState> WatchItems::State() {
absl::ReaderMutexLock lock(&lock_);

Expand Down
33 changes: 29 additions & 4 deletions Source/santad/DataLayer/WatchItemsTest.mm
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,14 @@ extern bool ParseConfigSingleWatchItem(NSString *name, NSDictionary *watch_item,
NSDictionary *watch_item, NSError **err);
class WatchItemsPeer : public WatchItems {
public:
using WatchItems::ReloadConfig;
using WatchItems::WatchItems;

using WatchItems::ReloadConfig;
using WatchItems::SetConfig;
using WatchItems::SetConfigPath;

using WatchItems::config_path_;
using WatchItems::embedded_config_;
};

} // namespace santa::santad::data_layer
Expand Down Expand Up @@ -191,7 +197,7 @@ - (void)testReloadScenarios {
// Changes in config dictionary will update policy info even if the
// filesystem didn't change.
{
WatchItemsPeer watchItems(nil, NULL, NULL);
WatchItemsPeer watchItems((NSString *)nil, NULL, NULL);
[self pushd:@"a"];
watchItems.ReloadConfig(configAllFilesOriginal);

Expand All @@ -212,7 +218,7 @@ - (void)testReloadScenarios {

// Changes to fileystem structure are reflected when a config is reloaded
{
WatchItemsPeer watchItems(nil, NULL, NULL);
WatchItemsPeer watchItems((NSString *)nil, NULL, NULL);
[self pushd:@"a"];
watchItems.ReloadConfig(configAllFilesOriginal);
[self popd];
Expand Down Expand Up @@ -313,7 +319,7 @@ - (void)testPolicyLookup {
}
});

WatchItemsPeer watchItems(nil, NULL, NULL);
WatchItemsPeer watchItems((NSString *)nil, NULL, NULL);
WatchItems::VersionAndPolicies policies;

// Resultant vector is same size as input vector
Expand Down Expand Up @@ -842,4 +848,23 @@ - (void)testState {
XCTAssertGreaterThanOrEqual(state.last_config_load_epoch, startTime);
}

- (void)testSetConfigAndSetConfigPath {
// Test internal state when switching back and forth between path-based and
// dictionary-based config options.
WatchItemsPeer watchItems(@{}, NULL, NULL);

XCTAssertNil(watchItems.config_path_);
XCTAssertNotNil(watchItems.embedded_config_);

watchItems.SetConfigPath(@"/path/to/a/nonexistent/file/so/nothing/is/opened");

XCTAssertNotNil(watchItems.config_path_);
XCTAssertNil(watchItems.embedded_config_);

watchItems.SetConfig(@{});

XCTAssertNil(watchItems.config_path_);
XCTAssertNotNil(watchItems.embedded_config_);
}

@end
15 changes: 15 additions & 0 deletions Source/santad/Santad.mm
Original file line number Diff line number Diff line change
Expand Up @@ -297,12 +297,27 @@ void SantadMain(std::shared_ptr<EndpointSecurityAPI> esapi, std::shared_ptr<Logg
selector:@selector(fileAccessPolicyPlist)
type:[NSString class]
callback:^(NSString *oldValue, NSString *newValue) {
if ([configurator fileAccessPolicy]) {
// Ignore any changes to this key if fileAccessPolicy is set
return;
}

if (oldValue != newValue || (newValue && ![oldValue isEqualToString:newValue])) {
LOGI(@"Filesystem monitoring policy config path changed: %@ -> %@", oldValue,
newValue);
watch_items->SetConfigPath(newValue);
}
}],
[[SNTKVOManager alloc] initWithObject:configurator
selector:@selector(fileAccessPolicy)
type:[NSDictionary class]
callback:^(NSDictionary *oldValue, NSDictionary *newValue) {
if (oldValue != newValue ||
(newValue && ![oldValue isEqualToDictionary:newValue])) {
LOGI(@"Filesystem monitoring policy embedded config changed");
watch_items->SetConfig(newValue);
}
}],
[[SNTKVOManager alloc] initWithObject:configurator
selector:@selector(staticRules)
type:[NSArray class]
Expand Down
8 changes: 6 additions & 2 deletions Source/santad/SantadDeps.mm
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,12 @@
exit(EXIT_FAILURE);
}

std::shared_ptr<::WatchItems> watch_items = WatchItems::Create(
[configurator fileAccessPolicyPlist], [configurator fileAccessPolicyUpdateIntervalSec]);
std::shared_ptr<::WatchItems> watch_items =
[configurator fileAccessPolicy]
? WatchItems::Create([configurator fileAccessPolicy],
[configurator fileAccessPolicyUpdateIntervalSec])
: WatchItems::Create([configurator fileAccessPolicyPlist],
[configurator fileAccessPolicyUpdateIntervalSec]);
if (!watch_items) {
LOGE(@"Failed to create watch items");
exit(EXIT_FAILURE);
Expand Down