From c9591ced857d77e2446cdea1aef30c870472f7d3 Mon Sep 17 00:00:00 2001 From: Matt W <436037+mlw@users.noreply.github.com> Date: Sat, 23 Nov 2024 21:54:19 -0500 Subject: [PATCH] Configurable telemetry event filtering --- Source/common/BUILD | 17 +++ Source/common/SNTConfigurator.h | 7 ++ Source/common/SNTConfigurator.m | 24 +++- Source/common/TelemetryEventMap.h | 88 +++++++++++++ Source/common/TelemetryEventMap.mm | 103 +++++++++++++++ Source/common/TelemetryEventMapTest.mm | 118 ++++++++++++++++++ Source/santad/BUILD | 10 ++ .../EndpointSecurity/EnrichedTypes.h | 15 ++- .../SNTEndpointSecurityRecorder.mm | 17 ++- .../SNTEndpointSecurityRecorderTest.mm | 49 +++++--- Source/santad/Logs/EndpointSecurity/Logger.h | 28 +++-- Source/santad/Logs/EndpointSecurity/Logger.mm | 83 ++++++------ .../Logs/EndpointSecurity/LoggerTest.mm | 48 ++++--- .../santad/Logs/EndpointSecurity/MockLogger.h | 6 +- Source/santad/Santad.mm | 55 +++++++- Source/santad/SantadDeps.h | 13 +- Source/santad/SantadDeps.mm | 10 +- 17 files changed, 582 insertions(+), 109 deletions(-) create mode 100644 Source/common/TelemetryEventMap.h create mode 100644 Source/common/TelemetryEventMap.mm create mode 100644 Source/common/TelemetryEventMapTest.mm diff --git a/Source/common/BUILD b/Source/common/BUILD index a506d99c..c196c34b 100644 --- a/Source/common/BUILD +++ b/Source/common/BUILD @@ -161,6 +161,22 @@ objc_library( ], ) +objc_library( + name = "TelemetryEventMap", + srcs = ["TelemetryEventMap.mm"], + hdrs = ["TelemetryEventMap.h"], + deps = [ + ":String", + "@com_google_absl//absl/container:flat_hash_map", + ], +) + +santa_unit_test( + name = "TelemetryEventMapTest", + srcs = ["TelemetryEventMapTest.mm"], + deps = [":TelemetryEventMap"], +) + objc_library( name = "SNTBlockMessage", srcs = ["SNTBlockMessage.m"], @@ -529,6 +545,7 @@ test_suite( ":SantaCacheTest", ":ScopedCFTypeRefTest", ":ScopedIOObjectRefTest", + ":TelemetryEventMapTest", ], visibility = ["//:santa_package_group"], ) diff --git a/Source/common/SNTConfigurator.h b/Source/common/SNTConfigurator.h index a822984e..e0a78dd7 100644 --- a/Source/common/SNTConfigurator.h +++ b/Source/common/SNTConfigurator.h @@ -210,6 +210,13 @@ /// @property(nonnull, readonly, nonatomic) NSString *eventLogPath; +/// +/// Array of strings of telemetry events that should be logged. +/// +/// @note: This property is KVO compliant. +/// +@property(nullable, readonly, nonatomic) NSArray *telemetry; + /// /// If eventLogType is set to protobuf, spoolDirectory will provide the base path used for /// saving logs using a maildir-like format. diff --git a/Source/common/SNTConfigurator.m b/Source/common/SNTConfigurator.m index efc42d69..5e7d9cfa 100644 --- a/Source/common/SNTConfigurator.m +++ b/Source/common/SNTConfigurator.m @@ -132,6 +132,7 @@ @implementation SNTConfigurator static NSString *const kEnableForkAndExitLogging = @"EnableForkAndExitLogging"; static NSString *const kIgnoreOtherEndpointSecurityClients = @"IgnoreOtherEndpointSecurityClients"; static NSString *const kEnableDebugLogging = @"EnableDebugLogging"; +static NSString *const kTelemetryKey = @"Telemetry"; static NSString *const kClientContentEncoding = @"SyncClientContentEncoding"; @@ -284,6 +285,7 @@ - (instancetype)initWithSyncStateFile:(NSString *)syncStateFilePath kEntitlementsPrefixFilterKey : array, kEntitlementsTeamIDFilterKey : array, kEnabledProcessAnnotations : array, + kTelemetryKey : array, }; _syncStateFilePath = syncStateFilePath; @@ -622,6 +624,10 @@ + (NSSet *)keyPathsForValuesAffectingEntitlementsTeamIDFilter { return [self configStateSet]; } ++ (NSSet *)keyPathsForValuesAffectingTelemetry { + return [self configStateSet]; +} + #pragma mark Public Interface - (SNTClientMode)clientMode { @@ -1038,6 +1044,18 @@ - (BOOL)enableForkAndExitLogging { return number ? [number boolValue] : NO; } +- (NSArray *)telemetry { + NSArray *events = self.configState[kTelemetryKey]; + + for (id event in events) { + if (![event isKindOfClass:[NSString class]]) { + return nil; + } + } + + return events; +} + - (BOOL)ignoreOtherEndpointSecurityClients { NSNumber *number = self.configState[kIgnoreOtherEndpointSecurityClients]; return number ? [number boolValue] : NO; @@ -1285,9 +1303,9 @@ - (void)applyOverrides:(NSMutableDictionary *)forcedConfig { NSDictionary *overrides = [NSDictionary dictionaryWithContentsOfFile:kConfigOverrideFilePath]; for (NSString *key in overrides) { id obj = overrides[key]; - if (![obj isKindOfClass:self.forcedConfigKeyTypes[key]] || - ([self.forcedConfigKeyTypes[key] isKindOfClass:[NSRegularExpression class]] && - ![obj isKindOfClass:[NSString class]])) { + if (![obj isKindOfClass:self.forcedConfigKeyTypes[key]] && + !(self.forcedConfigKeyTypes[key] == [NSRegularExpression class] && + [obj isKindOfClass:[NSString class]])) { continue; } diff --git a/Source/common/TelemetryEventMap.h b/Source/common/TelemetryEventMap.h new file mode 100644 index 00000000..f85bf3d4 --- /dev/null +++ b/Source/common/TelemetryEventMap.h @@ -0,0 +1,88 @@ +/// Copyright 2024 North Pole Security, Inc. +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. + +#ifndef SANTA__COMMON__TELEMETRYEVENTMAP_H +#define SANTA__COMMON__TELEMETRYEVENTMAP_H + +#import +#import + +namespace santa { + +// clang-format off +enum class TelemetryEvent : uint64_t { + kNone = 0, + kExecution = 1 << 0, + kFork = 1 << 1, + kExit = 1 << 2, + kClose = 1 << 3, + kRename = 1 << 4, + kUnlink = 1 << 5, + kLink = 1 << 6, + kExchangeData = 1 << 7, + kDisk = 1 << 8, + kBundle = 1 << 9, + kAllowlist = 1 << 10, + kFileAccess = 1 << 11, + kCodesigningInvalidated = 1 << 12, + kLoginWindowSession = 1 << 13, + kLoginLogout = 1 << 14, + kScreenSharing = 1 << 15, + kOpenSSH = 1 << 16, + kAuthentication = 1 << 17, + kEverything = ~0ULL, +}; +// clang-format on + +inline TelemetryEvent operator|(TelemetryEvent lhs, TelemetryEvent rhs) { + return static_cast(static_cast>(lhs) | + static_cast>(rhs)); +} + +inline TelemetryEvent &operator|=(TelemetryEvent &lhs, TelemetryEvent rhs) { + lhs = lhs | rhs; + return lhs; +} + +inline TelemetryEvent operator&(TelemetryEvent lhs, TelemetryEvent rhs) { + return static_cast(static_cast>(lhs) & + static_cast>(rhs)); +} + +inline TelemetryEvent &operator&=(TelemetryEvent &lhs, TelemetryEvent rhs) { + lhs = lhs & rhs; + return lhs; +} + +inline TelemetryEvent operator~(TelemetryEvent rhs) { + return static_cast(~static_cast>(rhs)); +} + +// Create a `TelemetryEvent` bitmask based on the `Telemetry` and +// `EnableForkAndExitLogging` configuration values. The `Telemetry` event +// array takes precedence over `EnableForkAndExitLogging`. +// +// If `Telemetry` is set, the events specified will be used. +// If `Telemetry` is not set, `everything` (all events) are assumed. +// When `Telemetry` is not set, `EnableForkAndExitLogging` willbe checked. If +// `false`, the `FORK` and `EXIT` bits will be cleared from the mask. +TelemetryEvent TelemetryConfigToBitmask(NSArray *telemetry, + BOOL enableForkAndExitLogging); + +// Returns the appropriate `TelemetryEvent` enum value for a given ES event +TelemetryEvent ESEventToTelemetryEvent(es_event_type_t event); + +} // namespace santa + +#endif diff --git a/Source/common/TelemetryEventMap.mm b/Source/common/TelemetryEventMap.mm new file mode 100644 index 00000000..c62034e7 --- /dev/null +++ b/Source/common/TelemetryEventMap.mm @@ -0,0 +1,103 @@ +/// Copyright 2024 North Pole Security, Inc. +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. + +#include "Source/common/TelemetryEventMap.h" + +#include + +#include "Source/common/String.h" +#include "absl/container/flat_hash_map.h" + +namespace santa { + +static inline TelemetryEvent EventNameToMask(std::string_view event) { + static absl::flat_hash_map event_name_to_mask = { + {"execution", TelemetryEvent::kExecution}, + {"fork", TelemetryEvent::kFork}, + {"exit", TelemetryEvent::kExit}, + {"close", TelemetryEvent::kClose}, + {"rename", TelemetryEvent::kRename}, + {"unlink", TelemetryEvent::kUnlink}, + {"link", TelemetryEvent::kLink}, + {"exchangedata", TelemetryEvent::kExchangeData}, + {"disk", TelemetryEvent::kDisk}, + {"bundle", TelemetryEvent::kBundle}, + {"allowlist", TelemetryEvent::kAllowlist}, + {"fileaccess", TelemetryEvent::kFileAccess}, + {"codesigninginvalidated", TelemetryEvent::kCodesigningInvalidated}, + {"loginwindowsession", TelemetryEvent::kLoginWindowSession}, + {"loginlogout", TelemetryEvent::kLoginLogout}, + {"screensharing", TelemetryEvent::kScreenSharing}, + {"openssh", TelemetryEvent::kOpenSSH}, + {"authentication", TelemetryEvent::kAuthentication}, + + // special cases + {"none", TelemetryEvent::kNone}, + {"everything", TelemetryEvent::kEverything}, + }; + + auto search = event_name_to_mask.find(event); + if (search != event_name_to_mask.end()) { + return search->second; + } else { + return TelemetryEvent::kNone; + } +} + +TelemetryEvent TelemetryConfigToBitmask(NSArray *telemetry, + BOOL enableForkAndExitLogging) { + TelemetryEvent mask = TelemetryEvent::kNone; + + if (telemetry) { + for (NSString *event_name in telemetry) { + mask |= EventNameToMask(santa::NSStringToUTF8StringView([event_name lowercaseString])); + } + } else { + mask = EventNameToMask("everything"); + + if (enableForkAndExitLogging == false) { + mask &= (~TelemetryEvent::kFork & ~TelemetryEvent::kExit); + } + } + + return mask; +} + +TelemetryEvent ESEventToTelemetryEvent(es_event_type_t event) { + switch (event) { + case ES_EVENT_TYPE_NOTIFY_CLOSE: return TelemetryEvent::kClose; + case ES_EVENT_TYPE_NOTIFY_CS_INVALIDATED: return TelemetryEvent::kCodesigningInvalidated; + case ES_EVENT_TYPE_NOTIFY_EXCHANGEDATA: return TelemetryEvent::kExchangeData; + case ES_EVENT_TYPE_NOTIFY_EXEC: return TelemetryEvent::kExecution; + case ES_EVENT_TYPE_NOTIFY_EXIT: return TelemetryEvent::kExit; + case ES_EVENT_TYPE_NOTIFY_FORK: return TelemetryEvent::kFork; + case ES_EVENT_TYPE_NOTIFY_LINK: return TelemetryEvent::kLink; + case ES_EVENT_TYPE_NOTIFY_RENAME: return TelemetryEvent::kRename; + case ES_EVENT_TYPE_NOTIFY_UNLINK: return TelemetryEvent::kUnlink; + case ES_EVENT_TYPE_NOTIFY_AUTHENTICATION: return TelemetryEvent::kAuthentication; + case ES_EVENT_TYPE_NOTIFY_LOGIN_LOGIN: return TelemetryEvent::kLoginLogout; + case ES_EVENT_TYPE_NOTIFY_LOGIN_LOGOUT: return TelemetryEvent::kLoginLogout; + case ES_EVENT_TYPE_NOTIFY_LW_SESSION_LOGIN: return TelemetryEvent::kLoginWindowSession; + case ES_EVENT_TYPE_NOTIFY_LW_SESSION_LOGOUT: return TelemetryEvent::kLoginWindowSession; + case ES_EVENT_TYPE_NOTIFY_LW_SESSION_LOCK: return TelemetryEvent::kLoginWindowSession; + case ES_EVENT_TYPE_NOTIFY_LW_SESSION_UNLOCK: return TelemetryEvent::kLoginWindowSession; + case ES_EVENT_TYPE_NOTIFY_SCREENSHARING_ATTACH: return TelemetryEvent::kScreenSharing; + case ES_EVENT_TYPE_NOTIFY_SCREENSHARING_DETACH: return TelemetryEvent::kScreenSharing; + case ES_EVENT_TYPE_NOTIFY_OPENSSH_LOGIN: return TelemetryEvent::kOpenSSH; + case ES_EVENT_TYPE_NOTIFY_OPENSSH_LOGOUT: return TelemetryEvent::kOpenSSH; + default: return TelemetryEvent::kNone; + } +} + +} // namespace santa diff --git a/Source/common/TelemetryEventMapTest.mm b/Source/common/TelemetryEventMapTest.mm new file mode 100644 index 00000000..50402a7d --- /dev/null +++ b/Source/common/TelemetryEventMapTest.mm @@ -0,0 +1,118 @@ +/// Copyright 2024 North Pole Security, Inc. +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. + +#include "Source/common/TelemetryEventMap.h" + +#import +#import + +#include + +using santa::ESEventToTelemetryEvent; +using santa::TelemetryConfigToBitmask; +using santa::TelemetryEvent; + +@interface TelemetryEventMapTest : XCTestCase +@end + +@implementation TelemetryEventMapTest + +- (void)testTelemetryConfigToBitmask { + // Ensure that each named event returns an expected flags value. + // Some items have mixed case to ensure case insensitive matching. + std::map eventNameToMask = { + {"ExeCUTion", TelemetryEvent::kExecution}, + {"FoRk", TelemetryEvent::kFork}, + {"eXIt", TelemetryEvent::kExit}, + {"close", TelemetryEvent::kClose}, + {"rename", TelemetryEvent::kRename}, + {"unlink", TelemetryEvent::kUnlink}, + {"link", TelemetryEvent::kLink}, + {"ExchangeData", TelemetryEvent::kExchangeData}, + {"disk", TelemetryEvent::kDisk}, + {"bundle", TelemetryEvent::kBundle}, + {"allowList", TelemetryEvent::kAllowlist}, + {"fileAccess", TelemetryEvent::kFileAccess}, + {"codesigninginvalidated", TelemetryEvent::kCodesigningInvalidated}, + {"loginwindowsession", TelemetryEvent::kLoginWindowSession}, + {"loginlogout", TelemetryEvent::kLoginLogout}, + {"screensharing", TelemetryEvent::kScreenSharing}, + {"openssh", TelemetryEvent::kOpenSSH}, + {"authentication", TelemetryEvent::kAuthentication}, + + // special cases + {"none", TelemetryEvent::kNone}, + {"everything", TelemetryEvent::kEverything}, + }; + + for (const auto &[event_name, want] : eventNameToMask) { + TelemetryEvent got = + TelemetryConfigToBitmask(@[ [NSString stringWithUTF8String:event_name.data()] ], false); + XCTAssertEqual(got, want); + } + + // Test some arbitrary sets of events return expected bitmasks + XCTAssertEqual(TelemetryConfigToBitmask(@[ @"everything" ], true), TelemetryEvent::kEverything); + XCTAssertEqual(TelemetryConfigToBitmask(@[ @"none" ], true), TelemetryEvent::kNone); + XCTAssertEqual(TelemetryConfigToBitmask(@[ @"execution", @"fork", @"exit" ], true), + TelemetryEvent::kExecution | TelemetryEvent::kFork | TelemetryEvent::kExit); + XCTAssertEqual(TelemetryConfigToBitmask(@[ @"bundle", @"close", @"allowList" ], false), + TelemetryEvent::kBundle | TelemetryEvent::kClose | TelemetryEvent::kAllowlist); + + // When telemetry config is nil, returned bitmask is dependent + // upon enableForkAndExitLogging being true or false + XCTAssertEqual(TelemetryConfigToBitmask(nil, true), TelemetryEvent::kEverything); + XCTAssertEqual(TelemetryConfigToBitmask(nil, false), + TelemetryEvent::kEverything & ~TelemetryEvent::kFork & ~TelemetryEvent::kExit); +} + +- (void)testESEventToTelemetryEvent { + std::map esEventToTelemetryEvent = { + {ES_EVENT_TYPE_NOTIFY_CLOSE, TelemetryEvent::kClose}, + {ES_EVENT_TYPE_NOTIFY_CS_INVALIDATED, TelemetryEvent::kCodesigningInvalidated}, + {ES_EVENT_TYPE_NOTIFY_EXCHANGEDATA, TelemetryEvent::kExchangeData}, + {ES_EVENT_TYPE_NOTIFY_EXEC, TelemetryEvent::kExecution}, + {ES_EVENT_TYPE_NOTIFY_EXIT, TelemetryEvent::kExit}, + {ES_EVENT_TYPE_NOTIFY_FORK, TelemetryEvent::kFork}, + {ES_EVENT_TYPE_NOTIFY_LINK, TelemetryEvent::kLink}, + {ES_EVENT_TYPE_NOTIFY_RENAME, TelemetryEvent::kRename}, + {ES_EVENT_TYPE_NOTIFY_UNLINK, TelemetryEvent::kUnlink}, + {ES_EVENT_TYPE_NOTIFY_AUTHENTICATION, TelemetryEvent::kAuthentication}, + {ES_EVENT_TYPE_NOTIFY_LOGIN_LOGIN, TelemetryEvent::kLoginLogout}, + {ES_EVENT_TYPE_NOTIFY_LOGIN_LOGOUT, TelemetryEvent::kLoginLogout}, + {ES_EVENT_TYPE_NOTIFY_LW_SESSION_LOGIN, TelemetryEvent::kLoginWindowSession}, + {ES_EVENT_TYPE_NOTIFY_LW_SESSION_LOGOUT, TelemetryEvent::kLoginWindowSession}, + {ES_EVENT_TYPE_NOTIFY_LW_SESSION_LOCK, TelemetryEvent::kLoginWindowSession}, + {ES_EVENT_TYPE_NOTIFY_LW_SESSION_UNLOCK, TelemetryEvent::kLoginWindowSession}, + {ES_EVENT_TYPE_NOTIFY_SCREENSHARING_ATTACH, TelemetryEvent::kScreenSharing}, + {ES_EVENT_TYPE_NOTIFY_SCREENSHARING_DETACH, TelemetryEvent::kScreenSharing}, + {ES_EVENT_TYPE_NOTIFY_OPENSSH_LOGIN, TelemetryEvent::kOpenSSH}, + {ES_EVENT_TYPE_NOTIFY_OPENSSH_LOGOUT, TelemetryEvent::kOpenSSH}, + }; + + // Ensure ESEventToTelemetryEvent returns TelemetryEvent::kNone for + // everything except for the events defined in the above map. + for (int event = 0; event < ES_EVENT_TYPE_LAST; event++) { + TelemetryEvent wantTelemetryEvent = TelemetryEvent::kNone; + + auto search = esEventToTelemetryEvent.find((es_event_type_t)event); + if (search != esEventToTelemetryEvent.end()) { + wantTelemetryEvent = search->second; + } + + XCTAssertEqual(ESEventToTelemetryEvent((es_event_type_t)event), wantTelemetryEvent); + } +} + +@end diff --git a/Source/santad/BUILD b/Source/santad/BUILD index 7c5d0b3f..6c96adb8 100644 --- a/Source/santad/BUILD +++ b/Source/santad/BUILD @@ -357,6 +357,7 @@ objc_library( "//Source/common:SNTConfigurator", "//Source/common:SNTLogging", "//Source/common:String", + "//Source/common:TelemetryEventMap", "//Source/common:Unit", "//Source/santad/ProcessTree:process_tree", ], @@ -505,6 +506,7 @@ objc_library( deps = [ ":EndpointSecurityMessage", "//Source/common:Platform", + "//Source/common:TelemetryEventMap", "//Source/santad/ProcessTree:process_tree_cc_proto", ], ) @@ -669,6 +671,7 @@ objc_library( "//Source/common:SNTCommonEnums", "//Source/common:SNTLogging", "//Source/common:SNTStoredEvent", + "//Source/common:TelemetryEventMap", ], ) @@ -786,6 +789,7 @@ objc_library( "//Source/common:SNTLogging", "//Source/common:SNTXPCNotifierInterface", "//Source/common:SNTXPCSyncServiceInterface", + "//Source/common:TelemetryEventMap", "//Source/common:Unit", "//Source/santad/ProcessTree:process_tree", "@MOLXPCConnection", @@ -818,6 +822,7 @@ objc_library( "//Source/common:SNTMetricSet", "//Source/common:SNTXPCControlInterface", "//Source/common:SNTXPCUnprivilegedControlInterface", + "//Source/common:TelemetryEventMap", "//Source/common:Unit", "//Source/santad/ProcessTree:process_tree", "//Source/santad/ProcessTree/annotations:originator", @@ -909,8 +914,10 @@ objc_library( testonly = 1, hdrs = ["Logs/EndpointSecurity/MockLogger.h"], deps = [ + ":EndpointSecurityEnrichedTypes", ":EndpointSecurityLogger", ":EndpointSecurityMessage", + "//Source/common:TelemetryEventMap", "@com_google_googletest//:gtest", ], ) @@ -1175,6 +1182,7 @@ santa_unit_test( ":EndpointSecurityWriterSyslog", ":MockEndpointSecurityAPI", "//Source/common:SNTCommonEnums", + "//Source/common:TelemetryEventMap", "//Source/common:TestUtils", "@OCMock", "@com_google_googletest//:gtest", @@ -1390,11 +1398,13 @@ santa_unit_test( ":EndpointSecurityMessage", ":Metrics", ":MockEndpointSecurityAPI", + ":MockLogger", ":SNTCompilerController", ":SNTEndpointSecurityRecorder", "//Source/common:Platform", "//Source/common:PrefixTree", "//Source/common:SNTConfigurator", + "//Source/common:TelemetryEventMap", "//Source/common:TestUtils", "//Source/common:Unit", "@OCMock", diff --git a/Source/santad/EventProviders/EndpointSecurity/EnrichedTypes.h b/Source/santad/EventProviders/EndpointSecurity/EnrichedTypes.h index 4f2c9560..22bd76ca 100644 --- a/Source/santad/EventProviders/EndpointSecurity/EnrichedTypes.h +++ b/Source/santad/EventProviders/EndpointSecurity/EnrichedTypes.h @@ -27,6 +27,7 @@ #include #include "Source/common/Platform.h" +#include "Source/common/TelemetryEventMap.h" #include "Source/santad/EventProviders/EndpointSecurity/Message.h" #include "Source/santad/ProcessTree/process_tree.pb.h" @@ -593,11 +594,23 @@ using EnrichedType = class EnrichedMessage { public: - EnrichedMessage(EnrichedType &&msg) : msg_(std::move(msg)) {} + // Note: For now, all EnrichedType variants have a base class of + // EnrichedEventType. If this changes in the future, we'll need a more + // comprehensive solution for grabbing the TelemetryEvent type of T. + template + EnrichedMessage(T &&event) + : telemetry_event_(ESEventToTelemetryEvent(event->event_type)), + msg_(std::move(event)) {} const EnrichedType &GetEnrichedMessage() { return msg_; } + inline TelemetryEvent GetTelemetryEvent() { return telemetry_event_; } + private: + // Because the constructor requires moving the given argument into msg_, + // telemetry_event_ should be declared first to ensure the argument isn't + // in an unspecified state. + TelemetryEvent telemetry_event_; EnrichedType msg_; }; diff --git a/Source/santad/EventProviders/SNTEndpointSecurityRecorder.mm b/Source/santad/EventProviders/SNTEndpointSecurityRecorder.mm index 239cef11..59944662 100644 --- a/Source/santad/EventProviders/SNTEndpointSecurityRecorder.mm +++ b/Source/santad/EventProviders/SNTEndpointSecurityRecorder.mm @@ -22,6 +22,7 @@ #import "Source/common/SNTConfigurator.h" #import "Source/common/SNTLogging.h" #include "Source/common/String.h" +#include "Source/common/TelemetryEventMap.h" #include "Source/santad/EventProviders/AuthResultCache.h" #include "Source/santad/EventProviders/EndpointSecurity/EnrichedTypes.h" #include "Source/santad/EventProviders/EndpointSecurity/Message.h" @@ -130,6 +131,13 @@ - (void)handleMessage:(Message &&)esMsg [self.compilerController handleEvent:esMsg withLogger:self->_logger]; + // The logger will take care of this, but we check early so we + // don't do any unnecessary work + if (!_logger->ShouldLog(santa::ESEventToTelemetryEvent(esMsg->event_type))) { + recordEventMetrics(EventDisposition::kDropped); + return; + } + switch (esMsg->event_type) { case ES_EVENT_TYPE_NOTIFY_CLOSE: OS_FALLTHROUGH; case ES_EVENT_TYPE_NOTIFY_EXCHANGEDATA: OS_FALLTHROUGH; @@ -163,15 +171,6 @@ - (void)handleMessage:(Message &&)esMsg break; } - case ES_EVENT_TYPE_NOTIFY_FORK: OS_FALLTHROUGH; - case ES_EVENT_TYPE_NOTIFY_EXIT: { - if (self.configurator.enableForkAndExitLogging == NO) { - recordEventMetrics(EventDisposition::kDropped); - return; - } - break; - } - default: break; } diff --git a/Source/santad/EventProviders/SNTEndpointSecurityRecorderTest.mm b/Source/santad/EventProviders/SNTEndpointSecurityRecorderTest.mm index e0a0b11b..7bfa9a09 100644 --- a/Source/santad/EventProviders/SNTEndpointSecurityRecorderTest.mm +++ b/Source/santad/EventProviders/SNTEndpointSecurityRecorderTest.mm @@ -27,6 +27,7 @@ #include "Source/common/Platform.h" #include "Source/common/PrefixTree.h" #import "Source/common/SNTConfigurator.h" +#include "Source/common/TelemetryEventMap.h" #include "Source/common/TestUtils.h" #include "Source/common/Unit.h" #import "Source/santad/EventProviders/AuthResultCache.h" @@ -36,7 +37,7 @@ #include "Source/santad/EventProviders/EndpointSecurity/Message.h" #include "Source/santad/EventProviders/EndpointSecurity/MockEndpointSecurityAPI.h" #import "Source/santad/EventProviders/SNTEndpointSecurityRecorder.h" -#include "Source/santad/Logs/EndpointSecurity/Logger.h" +#include "Source/santad/Logs/EndpointSecurity/MockLogger.h" #include "Source/santad/Metrics.h" #import "Source/santad/SNTCompilerController.h" @@ -48,6 +49,7 @@ using santa::Message; using santa::PrefixTree; using santa::Processor; +using santa::TelemetryEvent; using santa::Unit; class MockEnricher : public Enricher { @@ -62,13 +64,6 @@ MOCK_METHOD(void, RemoveFromCache, (const es_file_t *)); }; -class MockLogger : public Logger { - public: - using Logger::Logger; - - MOCK_METHOD(void, Log, (std::unique_ptr)); -}; - @interface SNTEndpointSecurityRecorderTest : XCTestCase @property id mockConfigurator; @end @@ -141,7 +136,8 @@ typedef void (^TestHelperBlock)(es_message_t *message, - (void)handleMessageShouldLog:(BOOL)shouldLog shouldRemoveFromCache:(BOOL)shouldRemoveFromCache - withBlock:(TestHelperBlock)testBlock { + withBlock:(TestHelperBlock)testBlock + telemetryMask:(TelemetryEvent)telemetryMask { es_file_t file = MakeESFile("foo"); es_process_t proc = MakeESProcess(&file); es_message_t esMsg = MakeESMessage(ES_EVENT_TYPE_NOTIFY_CLOSE, &proc, ActionType::Auth); @@ -162,6 +158,8 @@ - (void)handleMessageShouldLog:(BOOL)shouldLog auto mockAuthCache = std::make_shared(nullptr, nil); if (shouldRemoveFromCache) { EXPECT_CALL(*mockAuthCache, RemoveFromCache).Times(1); + } else { + EXPECT_CALL(*mockAuthCache, RemoveFromCache).Times(0); } dispatch_semaphore_t semaMetrics = dispatch_semaphore_create(0); @@ -170,12 +168,17 @@ - (void)handleMessageShouldLog:(BOOL)shouldLog // properly handle the `processEnrichedMessage:handler:` block. Instead this // test will mock the `Log` method that is called in the handler block. __block dispatch_semaphore_t sema = dispatch_semaphore_create(0); - auto mockLogger = std::make_shared(nullptr, nullptr); + auto mockLogger = std::make_shared(); + mockLogger->SetTelemetryMask(telemetryMask); + if (shouldLog) { EXPECT_CALL(*mockEnricher, Enrich).WillOnce(testing::Return(std::move(enrichedMsg))); EXPECT_CALL(*mockLogger, Log).WillOnce(testing::InvokeWithoutArgs(^() { dispatch_semaphore_signal(sema); })); + } else { + EXPECT_CALL(*mockEnricher, Enrich).Times(0); + EXPECT_CALL(*mockLogger, Log).Times(0); } auto prefixTree = std::make_shared>(); @@ -194,6 +197,11 @@ - (void)handleMessageShouldLog:(BOOL)shouldLog testBlock(&esMsg, mockESApi, mockCC, recorderClient, prefixTree, &sema, &semaMetrics); + // Ensure the deleter is called on the fake enriched message so that the underlying message gets + // released. Otherwise, various gmock warnings about uninteresting calls are printed because the + // object isn't deleted until after expectations are verified below. + enrichedMsg.reset(); + XCTAssertTrue(OCMVerifyAll(mockCC)); XCTBubbleMockVerifyAndClearExpectations(mockAuthCache.get()); @@ -204,6 +212,15 @@ - (void)handleMessageShouldLog:(BOOL)shouldLog [mockCC stopMocking]; } +- (void)handleMessageShouldLog:(BOOL)shouldLog + shouldRemoveFromCache:(BOOL)shouldRemoveFromCache + withBlock:(TestHelperBlock)testBlock { + [self handleMessageShouldLog:shouldLog + shouldRemoveFromCache:shouldRemoveFromCache + withBlock:testBlock + telemetryMask:TelemetryEvent::kEverything]; +} + - (void)testHandleEventCloseMappedWritableMatchesRegex { #if HAVE_MACOS_13 if (@available(macOS 13.0, *)) { @@ -399,7 +416,6 @@ - (void)testHandleMessage { Message msg(mockESApi, esMsg); OCMExpect([mockCC handleEvent:msg withLogger:nullptr]).ignoringNonObjectArgs(); - OCMExpect([self.mockConfigurator enableForkAndExitLogging]).andReturn(NO); [recorderClient handleMessage:std::move(msg) recordEventMetrics:^(EventDisposition d) { @@ -410,7 +426,10 @@ - (void)testHandleMessage { XCTAssertSemaTrue(*semaMetrics, 5, "Metrics not recorded within expected window"); }; - [self handleMessageShouldLog:NO shouldRemoveFromCache:NO withBlock:testBlock]; + [self handleMessageShouldLog:NO + shouldRemoveFromCache:NO + withBlock:testBlock + telemetryMask:santa::TelemetryConfigToBitmask(nil, false)]; // FORK, EnableForkAndExitLogging is true testBlock = @@ -423,7 +442,6 @@ - (void)testHandleMessage { Message msg(mockESApi, esMsg); OCMExpect([mockCC handleEvent:msg withLogger:nullptr]).ignoringNonObjectArgs(); - OCMExpect([self.mockConfigurator enableForkAndExitLogging]).andReturn(YES); [recorderClient handleMessage:std::move(msg) recordEventMetrics:^(EventDisposition d) { @@ -434,7 +452,10 @@ - (void)testHandleMessage { XCTAssertSemaTrue(*semaMetrics, 5, "Metrics not recorded within expected window"); }; - [self handleMessageShouldLog:YES shouldRemoveFromCache:NO withBlock:testBlock]; + [self handleMessageShouldLog:YES + shouldRemoveFromCache:NO + withBlock:testBlock + telemetryMask:santa::TelemetryConfigToBitmask(nil, true)]; XCTAssertTrue(OCMVerifyAll(self.mockConfigurator)); } diff --git a/Source/santad/Logs/EndpointSecurity/Logger.h b/Source/santad/Logs/EndpointSecurity/Logger.h index 8bedb75f..3c9f1f72 100644 --- a/Source/santad/Logs/EndpointSecurity/Logger.h +++ b/Source/santad/Logs/EndpointSecurity/Logger.h @@ -1,22 +1,24 @@ /// Copyright 2022 Google Inc. All rights reserved. +/// Copyright 2024 North Pole Security, Inc. /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. /// You may obtain a copy of the License at /// -/// http://www.apache.org/licenses/LICENSE-2.0 +/// http://www.apache.org/licenses/LICENSE-2.0 /// -/// Unless required by applicable law or agreed to in writing, software -/// distributed under the License is distributed on an "AS IS" BASIS, -/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -/// See the License for the specific language governing permissions and -/// limitations under the License. +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. #ifndef SANTA__SANTAD__LOGS_ENDPOINTSECURITY_LOGGER_H #define SANTA__SANTAD__LOGS_ENDPOINTSECURITY_LOGGER_H #include #include +#include "Source/common/TelemetryEventMap.h" #import @@ -39,13 +41,14 @@ namespace santa { class Logger { public: static std::unique_ptr Create(std::shared_ptr esapi, - SNTEventLogType log_type, SNTDecisionCache *decision_cache, - NSString *event_log_path, NSString *spool_log_path, - size_t spool_dir_size_threshold, + TelemetryEvent telemetry_mask, SNTEventLogType log_type, + SNTDecisionCache *decision_cache, NSString *event_log_path, + NSString *spool_log_path, size_t spool_dir_size_threshold, size_t spool_file_size_threshold, uint64_t spool_flush_timeout_ms); - Logger(std::shared_ptr serializer, std::shared_ptr writer); + Logger(TelemetryEvent telemetry_mask, std::shared_ptr serializer, + std::shared_ptr writer); virtual ~Logger() = default; @@ -65,9 +68,14 @@ class Logger { void Flush(); + void SetTelemetryMask(TelemetryEvent mask); + + inline bool ShouldLog(TelemetryEvent event) { return ((event & telemetry_mask_) == event); } + friend class santa::LoggerPeer; private: + TelemetryEvent telemetry_mask_; std::shared_ptr serializer_; std::shared_ptr writer_; }; diff --git a/Source/santad/Logs/EndpointSecurity/Logger.mm b/Source/santad/Logs/EndpointSecurity/Logger.mm index 5274bab2..06a474df 100644 --- a/Source/santad/Logs/EndpointSecurity/Logger.mm +++ b/Source/santad/Logs/EndpointSecurity/Logger.mm @@ -1,22 +1,24 @@ /// Copyright 2022 Google Inc. All rights reserved. +/// Copyright 2024 North Pole Security, Inc. /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. /// You may obtain a copy of the License at /// -/// http://www.apache.org/licenses/LICENSE-2.0 +/// http://www.apache.org/licenses/LICENSE-2.0 /// -/// Unless required by applicable law or agreed to in writing, software -/// distributed under the License is distributed on an "AS IS" BASIS, -/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -/// See the License for the specific language governing permissions and -/// limitations under the License. +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. #include "Source/santad/Logs/EndpointSecurity/Logger.h" #import "Source/common/SNTCommonEnums.h" #include "Source/common/SNTLogging.h" #include "Source/common/SNTStoredEvent.h" +#include "Source/common/TelemetryEventMap.h" #include "Source/santad/Logs/EndpointSecurity/Serializers/BasicString.h" #include "Source/santad/Logs/EndpointSecurity/Serializers/Empty.h" #include "Source/santad/Logs/EndpointSecurity/Serializers/Protobuf.h" @@ -27,18 +29,6 @@ #include "Source/santad/Logs/EndpointSecurity/Writers/Syslog.h" #include "Source/santad/SNTDecisionCache.h" -using santa::BasicString; -using santa::Empty; -using santa::EndpointSecurityAPI; -using santa::EnrichedMessage; -using santa::EnrichedProcess; -using santa::File; -using santa::Message; -using santa::Null; -using santa::Protobuf; -using santa::Spool; -using santa::Syslog; - namespace santa { // Flush the write buffer every 5 seconds @@ -50,68 +40,89 @@ // Translate configured log type to appropriate Serializer/Writer pairs std::unique_ptr Logger::Create(std::shared_ptr esapi, - SNTEventLogType log_type, SNTDecisionCache *decision_cache, - NSString *event_log_path, NSString *spool_log_path, - size_t spool_dir_size_threshold, + TelemetryEvent telemetry_mask, SNTEventLogType log_type, + SNTDecisionCache *decision_cache, NSString *event_log_path, + NSString *spool_log_path, size_t spool_dir_size_threshold, size_t spool_file_size_threshold, uint64_t spool_flush_timeout_ms) { switch (log_type) { case SNTEventLogTypeFilelog: return std::make_unique( - BasicString::Create(esapi, std::move(decision_cache)), + telemetry_mask, BasicString::Create(esapi, std::move(decision_cache)), File::Create(event_log_path, kFlushBufferTimeoutMS, kBufferBatchSizeBytes, kMaxExpectedWriteSizeBytes)); case SNTEventLogTypeSyslog: - return std::make_unique(BasicString::Create(esapi, std::move(decision_cache), false), + return std::make_unique(telemetry_mask, + BasicString::Create(esapi, std::move(decision_cache), false), Syslog::Create()); - case SNTEventLogTypeNull: return std::make_unique(Empty::Create(), Null::Create()); + case SNTEventLogTypeNull: + return std::make_unique(telemetry_mask, Empty::Create(), Null::Create()); case SNTEventLogTypeProtobuf: LOGW(@"The EventLogType value protobuf is currently in beta. The protobuf schema is subject " @"to change."); return std::make_unique( - Protobuf::Create(esapi, std::move(decision_cache)), + telemetry_mask, Protobuf::Create(esapi, std::move(decision_cache)), Spool::Create([spool_log_path UTF8String], spool_dir_size_threshold, spool_file_size_threshold, spool_flush_timeout_ms)); case SNTEventLogTypeJSON: return std::make_unique( - Protobuf::Create(esapi, std::move(decision_cache), true), + telemetry_mask, Protobuf::Create(esapi, std::move(decision_cache), true), File::Create(event_log_path, kFlushBufferTimeoutMS, kBufferBatchSizeBytes, kMaxExpectedWriteSizeBytes)); default: LOGE(@"Invalid log type: %ld", log_type); return nullptr; } } -Logger::Logger(std::shared_ptr serializer, std::shared_ptr writer) - : serializer_(std::move(serializer)), writer_(std::move(writer)) {} +Logger::Logger(TelemetryEvent telemetry_mask, std::shared_ptr serializer, + std::shared_ptr writer) + : telemetry_mask_(telemetry_mask), + serializer_(std::move(serializer)), + writer_(std::move(writer)) {} + +void Logger::SetTelemetryMask(TelemetryEvent mask) { + telemetry_mask_ = mask; +} void Logger::Log(std::unique_ptr msg) { - writer_->Write(serializer_->SerializeMessage(std::move(msg))); + if (ShouldLog(msg->GetTelemetryEvent())) { + writer_->Write(serializer_->SerializeMessage(std::move(msg))); + } } void Logger::LogAllowlist(const Message &msg, const std::string_view hash) { - writer_->Write(serializer_->SerializeAllowlist(msg, hash)); + if (ShouldLog(TelemetryEvent::kAllowlist)) { + writer_->Write(serializer_->SerializeAllowlist(msg, hash)); + } } void Logger::LogBundleHashingEvents(NSArray *events) { - for (SNTStoredEvent *se in events) { - writer_->Write(serializer_->SerializeBundleHashingEvent(se)); + if (ShouldLog(TelemetryEvent::kBundle)) { + for (SNTStoredEvent *se in events) { + writer_->Write(serializer_->SerializeBundleHashingEvent(se)); + } } } void Logger::LogDiskAppeared(NSDictionary *props) { - writer_->Write(serializer_->SerializeDiskAppeared(props)); + if (ShouldLog(TelemetryEvent::kDisk)) { + writer_->Write(serializer_->SerializeDiskAppeared(props)); + } } void Logger::LogDiskDisappeared(NSDictionary *props) { - writer_->Write(serializer_->SerializeDiskDisappeared(props)); + if (ShouldLog(TelemetryEvent::kDisk)) { + writer_->Write(serializer_->SerializeDiskDisappeared(props)); + } } void Logger::LogFileAccess(const std::string &policy_version, const std::string &policy_name, const santa::Message &msg, const santa::EnrichedProcess &enriched_process, const std::string &target, FileAccessPolicyDecision decision) { - writer_->Write(serializer_->SerializeFileAccess(policy_version, policy_name, msg, - enriched_process, target, decision)); + if (ShouldLog(TelemetryEvent::kFileAccess)) { + writer_->Write(serializer_->SerializeFileAccess(policy_version, policy_name, msg, + enriched_process, target, decision)); + } } void Logger::Flush() { diff --git a/Source/santad/Logs/EndpointSecurity/LoggerTest.mm b/Source/santad/Logs/EndpointSecurity/LoggerTest.mm index e6f9302c..e271e8e5 100644 --- a/Source/santad/Logs/EndpointSecurity/LoggerTest.mm +++ b/Source/santad/Logs/EndpointSecurity/LoggerTest.mm @@ -24,6 +24,7 @@ #include #import "Source/common/SNTCommonEnums.h" +#include "Source/common/TelemetryEventMap.h" #include "Source/common/TestUtils.h" #include "Source/santad/EventProviders/EndpointSecurity/EnrichedTypes.h" #include "Source/santad/EventProviders/EndpointSecurity/Message.h" @@ -52,6 +53,7 @@ using santa::Protobuf; using santa::Spool; using santa::Syslog; +using santa::TelemetryEvent; namespace santa { @@ -60,7 +62,8 @@ // Make base class constructors visible using Logger::Logger; - LoggerPeer(std::unique_ptr l) : Logger(l->serializer_, l->writer_) {} + LoggerPeer(std::unique_ptr l) + : Logger(TelemetryEvent::kEverything, l->serializer_, l->writer_) {} std::shared_ptr Serializer() { return serializer_; } @@ -101,31 +104,33 @@ - (void)testCreate { // Ensure that the factory method creates expected serializers/writers pairs auto mockESApi = std::make_shared(); - XCTAssertEqual(nullptr, Logger::Create(mockESApi, (SNTEventLogType)123, nil, @"/tmp/temppy", - @"/tmp/spool", 1, 1, 1)); + XCTAssertEqual(nullptr, + Logger::Create(mockESApi, TelemetryEvent::kEverything, (SNTEventLogType)123, nil, + @"/tmp/temppy", @"/tmp/spool", 1, 1, 1)); - LoggerPeer logger( - Logger::Create(mockESApi, SNTEventLogTypeFilelog, nil, @"/tmp/temppy", @"/tmp/spool", 1, 1, 1)); + LoggerPeer logger(Logger::Create(mockESApi, TelemetryEvent::kEverything, SNTEventLogTypeFilelog, + nil, @"/tmp/temppy", @"/tmp/spool", 1, 1, 1)); XCTAssertNotEqual(nullptr, std::dynamic_pointer_cast(logger.Serializer())); XCTAssertNotEqual(nullptr, std::dynamic_pointer_cast(logger.Writer())); - logger = LoggerPeer( - Logger::Create(mockESApi, SNTEventLogTypeSyslog, nil, @"/tmp/temppy", @"/tmp/spool", 1, 1, 1)); + logger = LoggerPeer(Logger::Create(mockESApi, TelemetryEvent::kEverything, SNTEventLogTypeSyslog, + nil, @"/tmp/temppy", @"/tmp/spool", 1, 1, 1)); XCTAssertNotEqual(nullptr, std::dynamic_pointer_cast(logger.Serializer())); XCTAssertNotEqual(nullptr, std::dynamic_pointer_cast(logger.Writer())); - logger = LoggerPeer( - Logger::Create(mockESApi, SNTEventLogTypeNull, nil, @"/tmp/temppy", @"/tmp/spool", 1, 1, 1)); + logger = LoggerPeer(Logger::Create(mockESApi, TelemetryEvent::kEverything, SNTEventLogTypeNull, + nil, @"/tmp/temppy", @"/tmp/spool", 1, 1, 1)); XCTAssertNotEqual(nullptr, std::dynamic_pointer_cast(logger.Serializer())); XCTAssertNotEqual(nullptr, std::dynamic_pointer_cast(logger.Writer())); - logger = LoggerPeer(Logger::Create(mockESApi, SNTEventLogTypeProtobuf, nil, @"/tmp/temppy", - @"/tmp/spool", 1, 1, 1)); + logger = + LoggerPeer(Logger::Create(mockESApi, TelemetryEvent::kEverything, SNTEventLogTypeProtobuf, nil, + @"/tmp/temppy", @"/tmp/spool", 1, 1, 1)); XCTAssertNotEqual(nullptr, std::dynamic_pointer_cast(logger.Serializer())); XCTAssertNotEqual(nullptr, std::dynamic_pointer_cast(logger.Writer())); - logger = LoggerPeer( - Logger::Create(mockESApi, SNTEventLogTypeJSON, nil, @"/tmp/temppy", @"/tmp/spool", 1, 1, 1)); + logger = LoggerPeer(Logger::Create(mockESApi, TelemetryEvent::kEverything, SNTEventLogTypeJSON, + nil, @"/tmp/temppy", @"/tmp/spool", 1, 1, 1)); XCTAssertNotEqual(nullptr, std::dynamic_pointer_cast(logger.Serializer())); XCTAssertNotEqual(nullptr, std::dynamic_pointer_cast(logger.Writer())); } @@ -150,7 +155,7 @@ - (void)testLog { EXPECT_CALL(*mockSerializer, SerializeMessage(testing::A())).Times(1); EXPECT_CALL(*mockWriter, Write).Times(1); - Logger(mockSerializer, mockWriter).Log(std::move(enrichedMsg)); + Logger(TelemetryEvent::kEverything, mockSerializer, mockWriter).Log(std::move(enrichedMsg)); } XCTBubbleMockVerifyAndClearExpectations(mockESApi.get()); @@ -169,7 +174,8 @@ - (void)testLogAllowList { EXPECT_CALL(*mockSerializer, SerializeAllowlist(testing::_, hash)); EXPECT_CALL(*mockWriter, Write); - Logger(mockSerializer, mockWriter).LogAllowlist(Message(mockESApi, &msg), hash); + Logger(TelemetryEvent::kEverything, mockSerializer, mockWriter) + .LogAllowlist(Message(mockESApi, &msg), hash); XCTBubbleMockVerifyAndClearExpectations(mockESApi.get()); XCTBubbleMockVerifyAndClearExpectations(mockSerializer.get()); @@ -184,7 +190,7 @@ - (void)testLogBundleHashingEvents { EXPECT_CALL(*mockSerializer, SerializeBundleHashingEvent).Times((int)[events count]); EXPECT_CALL(*mockWriter, Write).Times((int)[events count]); - Logger(mockSerializer, mockWriter).LogBundleHashingEvents(events); + Logger(TelemetryEvent::kEverything, mockSerializer, mockWriter).LogBundleHashingEvents(events); XCTBubbleMockVerifyAndClearExpectations(mockSerializer.get()); XCTBubbleMockVerifyAndClearExpectations(mockWriter.get()); @@ -197,7 +203,9 @@ - (void)testLogDiskAppeared { EXPECT_CALL(*mockSerializer, SerializeDiskAppeared); EXPECT_CALL(*mockWriter, Write); - Logger(mockSerializer, mockWriter).LogDiskAppeared(@{@"key" : @"value"}); + Logger(TelemetryEvent::kEverything, mockSerializer, mockWriter).LogDiskAppeared(@{ + @"key" : @"value" + }); XCTBubbleMockVerifyAndClearExpectations(mockSerializer.get()); XCTBubbleMockVerifyAndClearExpectations(mockWriter.get()); @@ -210,7 +218,9 @@ - (void)testLogDiskDisappeared { EXPECT_CALL(*mockSerializer, SerializeDiskDisappeared); EXPECT_CALL(*mockWriter, Write); - Logger(mockSerializer, mockWriter).LogDiskDisappeared(@{@"key" : @"value"}); + Logger(TelemetryEvent::kEverything, mockSerializer, mockWriter).LogDiskDisappeared(@{ + @"key" : @"value" + }); XCTBubbleMockVerifyAndClearExpectations(mockSerializer.get()); XCTBubbleMockVerifyAndClearExpectations(mockWriter.get()); @@ -226,7 +236,7 @@ - (void)testLogFileAccess { EXPECT_CALL(*mockSerializer, SerializeFileAccess); EXPECT_CALL(*mockWriter, Write); - Logger(mockSerializer, mockWriter) + Logger(TelemetryEvent::kEverything, mockSerializer, mockWriter) .LogFileAccess( "v1", "name", Message(mockESApi, &msg), EnrichedProcess(std::nullopt, std::nullopt, std::nullopt, std::nullopt, diff --git a/Source/santad/Logs/EndpointSecurity/MockLogger.h b/Source/santad/Logs/EndpointSecurity/MockLogger.h index 688f58ee..8b10519b 100644 --- a/Source/santad/Logs/EndpointSecurity/MockLogger.h +++ b/Source/santad/Logs/EndpointSecurity/MockLogger.h @@ -18,6 +18,8 @@ #include #include +#include "Source/common/TelemetryEventMap.h" +#include "Source/santad/EventProviders/EndpointSecurity/EnrichedTypes.h" #include "Source/santad/EventProviders/EndpointSecurity/Message.h" #include "Source/santad/Logs/EndpointSecurity/Logger.h" @@ -25,7 +27,9 @@ class MockLogger : public santa::Logger { public: using Logger::Logger; - MockLogger() : Logger(nullptr, nullptr) {} + MockLogger() : Logger(santa::TelemetryEvent::kEverything, nullptr, nullptr) {} + + MOCK_METHOD(void, Log, (std::unique_ptr)); MOCK_METHOD(void, LogFileAccess, (const std::string &policy_version, diff --git a/Source/santad/Santad.mm b/Source/santad/Santad.mm index fa6b422d..e492c97e 100644 --- a/Source/santad/Santad.mm +++ b/Source/santad/Santad.mm @@ -1,16 +1,17 @@ /// Copyright 2022 Google Inc. All rights reserved. +/// Copyright 2024 North Pole Security, Inc. /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. /// You may obtain a copy of the License at /// -/// http://www.apache.org/licenses/LICENSE-2.0 +/// http://www.apache.org/licenses/LICENSE-2.0 /// -/// Unless required by applicable law or agreed to in writing, software -/// distributed under the License is distributed on an "AS IS" BASIS, -/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -/// See the License for the specific language governing permissions and -/// limitations under the License. +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. #include "Source/santad/Santad.h" @@ -25,6 +26,7 @@ #import "Source/common/SNTLogging.h" #import "Source/common/SNTXPCNotifierInterface.h" #import "Source/common/SNTXPCSyncServiceInterface.h" +#include "Source/common/TelemetryEventMap.h" #include "Source/santad/DataLayer/WatchItems.h" #include "Source/santad/EventProviders/AuthResultCache.h" #include "Source/santad/EventProviders/EndpointSecurity/EndpointSecurityAPI.h" @@ -398,6 +400,47 @@ void SantadMain(std::shared_ptr esapi, std::shared_ptr %@", + [oldValue componentsJoinedByString:@","], + [newValue componentsJoinedByString:@","]); + logger->SetTelemetryMask(santa::TelemetryConfigToBitmask( + newValue, configurator.enableForkAndExitLogging)); + }], + [[SNTKVOManager alloc] initWithObject:configurator + selector:@selector(enableForkAndExitLogging) + type:[NSNumber class] + callback:^(NSNumber *oldValue, NSNumber *newValue) { + BOOL oldBool = [oldValue boolValue]; + BOOL newBool = [newValue boolValue]; + + if (oldBool == newBool) { + return; + } + + LOGI(@"enableForkAndExitLogging changed %d -> %d", oldBool, + newBool); + logger->SetTelemetryMask(santa::TelemetryConfigToBitmask( + configurator.telemetry, newBool)); + }], ]]; if (@available(macOS 13.0, *)) { diff --git a/Source/santad/SantadDeps.h b/Source/santad/SantadDeps.h index dc80d997..8fe77558 100644 --- a/Source/santad/SantadDeps.h +++ b/Source/santad/SantadDeps.h @@ -1,16 +1,17 @@ /// Copyright 2022 Google Inc. All rights reserved. +/// Copyright 2024 North Pole Security, Inc. /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. /// You may obtain a copy of the License at /// -/// http://www.apache.org/licenses/LICENSE-2.0 +/// http://www.apache.org/licenses/LICENSE-2.0 /// -/// Unless required by applicable law or agreed to in writing, software -/// distributed under the License is distributed on an "AS IS" BASIS, -/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -/// See the License for the specific language governing permissions and -/// limitations under the License. +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. #ifndef SANTA__SANTAD__SANTAD_DEPS_H #define SANTA__SANTAD__SANTAD_DEPS_H diff --git a/Source/santad/SantadDeps.mm b/Source/santad/SantadDeps.mm index 08af2fec..89d9a50e 100644 --- a/Source/santad/SantadDeps.mm +++ b/Source/santad/SantadDeps.mm @@ -20,6 +20,7 @@ #import "Source/common/SNTLogging.h" #import "Source/common/SNTMetricSet.h" #import "Source/common/SNTXPCControlInterface.h" +#include "Source/common/TelemetryEventMap.h" #import "Source/santad/DataLayer/SNTEventTable.h" #import "Source/santad/DataLayer/SNTRuleTable.h" #include "Source/santad/DataLayer/WatchItems.h" @@ -123,10 +124,11 @@ size_t spool_dir_threshold_bytes = [configurator spoolDirectorySizeThresholdMB] * 1024 * 1024; uint64_t spool_flush_timeout_ms = [configurator spoolDirectoryEventMaxFlushTimeSec] * 1000; - std::unique_ptr<::Logger> logger = - Logger::Create(esapi, [configurator eventLogType], [SNTDecisionCache sharedCache], - [configurator eventLogPath], [configurator spoolDirectory], - spool_dir_threshold_bytes, spool_file_threshold_bytes, spool_flush_timeout_ms); + std::unique_ptr<::Logger> logger = Logger::Create( + esapi, TelemetryConfigToBitmask([configurator telemetry], [configurator enableAllEventUpload]), + [configurator eventLogType], [SNTDecisionCache sharedCache], [configurator eventLogPath], + [configurator spoolDirectory], spool_dir_threshold_bytes, spool_file_threshold_bytes, + spool_flush_timeout_ms); if (!logger) { LOGE(@"Failed to create logger."); exit(EXIT_FAILURE);