From 921a221d0170a345da090e93dce3baf4f40ae349 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] WIP configurable telemetry event filtering --- Source/common/BUILD | 16 +++ Source/common/SNTConfigurator.h | 7 + Source/common/SNTConfigurator.m | 24 +++- Source/common/TelemetryEventMap.h | 63 +++++++++ Source/common/TelemetryEventMap.mm | 102 +++++++++++++++ Source/common/TelemetryEventMapTest.mm | 123 ++++++++++++++++++ Source/santad/BUILD | 10 ++ .../EndpointSecurity/EnrichedTypes.h | 15 ++- .../SNTEndpointSecurityRecorder.mm | 17 ++- .../SNTEndpointSecurityRecorderTest.mm | 54 ++++++-- Source/santad/Logs/EndpointSecurity/Logger.h | 28 ++-- Source/santad/Logs/EndpointSecurity/Logger.mm | 83 +++++++----- .../Logs/EndpointSecurity/LoggerTest.mm | 53 +++++--- .../santad/Logs/EndpointSecurity/MockLogger.h | 8 +- Source/santad/Santad.mm | 55 +++++++- Source/santad/SantadDeps.h | 13 +- Source/santad/SantadDeps.mm | 10 +- 17 files changed, 572 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..af087dd6 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"], 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..38856a6b --- /dev/null +++ b/Source/common/TelemetryEventMap.h @@ -0,0 +1,63 @@ +/// 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 +typedef enum : uint64_t { + TELEMETRY_EVENT_NONE = 0, + TELEMETRY_EVENT_EXECUTION = 1 << 0, + TELEMETRY_EVENT_FORK = 1 << 1, + TELEMETRY_EVENT_EXIT = 1 << 2, + TELEMETRY_EVENT_CLOSE = 1 << 3, + TELEMETRY_EVENT_RENAME = 1 << 4, + TELEMETRY_EVENT_UNLINK = 1 << 5, + TELEMETRY_EVENT_LINK = 1 << 6, + TELEMETRY_EVENT_EXCHANGE_DATA = 1 << 7, + TELEMETRY_EVENT_DISK = 1 << 8, + TELEMETRY_EVENT_BUNDLE = 1 << 9, + TELEMETRY_EVENT_ALLOWLIST = 1 << 10, + TELEMETRY_EVENT_FILE_ACCESS = 1 << 11, + TELEMETRY_EVENT_CODESIGNING_INVALIDATED = 1 << 12, + TELEMETRY_EVENT_LOGIN_WINDOW_SESSION = 1 << 13, + TELEMETRY_EVENT_LOGIN_LOGOUT = 1 << 14, + TELEMETRY_EVENT_SCREEN_SHARING = 1 << 15, + TELEMETRY_EVENT_OPEN_SSH = 1 << 16, + TELEMETRY_EVENT_AUTHENTICATION = 1 << 17, + TELEMETRY_EVENT_EVERYTHING = ~0ULL, +} TelemetryEvent; +// clang-format on + +// 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. +uint64_t 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..b095a296 --- /dev/null +++ b/Source/common/TelemetryEventMap.mm @@ -0,0 +1,102 @@ +/// 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", TELEMETRY_EVENT_EXECUTION}, + {"fork", TELEMETRY_EVENT_FORK}, + {"exit", TELEMETRY_EVENT_EXIT}, + {"close", TELEMETRY_EVENT_CLOSE}, + {"rename", TELEMETRY_EVENT_RENAME}, + {"unlink", TELEMETRY_EVENT_UNLINK}, + {"link", TELEMETRY_EVENT_LINK}, + {"exchangedata", TELEMETRY_EVENT_EXCHANGE_DATA}, + {"disk", TELEMETRY_EVENT_DISK}, + {"bundle", TELEMETRY_EVENT_BUNDLE}, + {"allowlist", TELEMETRY_EVENT_ALLOWLIST}, + {"fileaccess", TELEMETRY_EVENT_FILE_ACCESS}, + {"codesigninginvalidated", TELEMETRY_EVENT_CODESIGNING_INVALIDATED}, + {"loginwindowsession", TELEMETRY_EVENT_LOGIN_WINDOW_SESSION}, + {"loginlogout", TELEMETRY_EVENT_LOGIN_LOGOUT}, + {"screensharing", TELEMETRY_EVENT_SCREEN_SHARING}, + {"openssh", TELEMETRY_EVENT_OPEN_SSH}, + {"authentication", TELEMETRY_EVENT_AUTHENTICATION}, + + // special cases + {"none", TELEMETRY_EVENT_NONE}, + {"everything", TELEMETRY_EVENT_EVERYTHING}, + }; + + auto search = event_name_to_mask.find(event); + if (search != event_name_to_mask.end()) { + return search->second; + } else { + return TELEMETRY_EVENT_NONE; + } +} + +uint64_t TelemetryConfigToBitmask(NSArray *telemetry, BOOL enableForkAndExitLogging) { + uint64_t mask = 0; + + if (telemetry) { + for (NSString *event_name in telemetry) { + mask |= EventNameToMask(santa::NSStringToUTF8StringView([event_name lowercaseString])); + } + } else { + mask = EventNameToMask("everything"); + + if (enableForkAndExitLogging == false) { + mask &= (~TELEMETRY_EVENT_FORK & ~TELEMETRY_EVENT_EXIT); + } + } + + return mask; +} + +TelemetryEvent ESEventToTelemetryEvent(es_event_type_t event) { + switch (event) { + case ES_EVENT_TYPE_NOTIFY_CLOSE: return TELEMETRY_EVENT_CLOSE; + case ES_EVENT_TYPE_NOTIFY_CS_INVALIDATED: return TELEMETRY_EVENT_CODESIGNING_INVALIDATED; + case ES_EVENT_TYPE_NOTIFY_EXCHANGEDATA: return TELEMETRY_EVENT_EXCHANGE_DATA; + case ES_EVENT_TYPE_NOTIFY_EXEC: return TELEMETRY_EVENT_EXECUTION; + case ES_EVENT_TYPE_NOTIFY_EXIT: return TELEMETRY_EVENT_EXIT; + case ES_EVENT_TYPE_NOTIFY_FORK: return TELEMETRY_EVENT_FORK; + case ES_EVENT_TYPE_NOTIFY_LINK: return TELEMETRY_EVENT_LINK; + case ES_EVENT_TYPE_NOTIFY_RENAME: return TELEMETRY_EVENT_RENAME; + case ES_EVENT_TYPE_NOTIFY_UNLINK: return TELEMETRY_EVENT_UNLINK; + case ES_EVENT_TYPE_NOTIFY_AUTHENTICATION: return TELEMETRY_EVENT_AUTHENTICATION; + case ES_EVENT_TYPE_NOTIFY_LOGIN_LOGIN: return TELEMETRY_EVENT_LOGIN_LOGOUT; + case ES_EVENT_TYPE_NOTIFY_LOGIN_LOGOUT: return TELEMETRY_EVENT_LOGIN_LOGOUT; + case ES_EVENT_TYPE_NOTIFY_LW_SESSION_LOGIN: return TELEMETRY_EVENT_LOGIN_WINDOW_SESSION; + case ES_EVENT_TYPE_NOTIFY_LW_SESSION_LOGOUT: return TELEMETRY_EVENT_LOGIN_WINDOW_SESSION; + case ES_EVENT_TYPE_NOTIFY_LW_SESSION_LOCK: return TELEMETRY_EVENT_LOGIN_WINDOW_SESSION; + case ES_EVENT_TYPE_NOTIFY_LW_SESSION_UNLOCK: return TELEMETRY_EVENT_LOGIN_WINDOW_SESSION; + case ES_EVENT_TYPE_NOTIFY_SCREENSHARING_ATTACH: return TELEMETRY_EVENT_SCREEN_SHARING; + case ES_EVENT_TYPE_NOTIFY_SCREENSHARING_DETACH: return TELEMETRY_EVENT_SCREEN_SHARING; + case ES_EVENT_TYPE_NOTIFY_OPENSSH_LOGIN: return TELEMETRY_EVENT_OPEN_SSH; + case ES_EVENT_TYPE_NOTIFY_OPENSSH_LOGOUT: return TELEMETRY_EVENT_OPEN_SSH; + default: return TELEMETRY_EVENT_NONE; + } +} + +} // namespace santa diff --git a/Source/common/TelemetryEventMapTest.mm b/Source/common/TelemetryEventMapTest.mm new file mode 100644 index 00000000..69a23438 --- /dev/null +++ b/Source/common/TelemetryEventMapTest.mm @@ -0,0 +1,123 @@ +/// 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::TELEMETRY_EVENT_EXECUTION}, + {"FoRk", TelemetryEvent::TELEMETRY_EVENT_FORK}, + {"eXIt", TelemetryEvent::TELEMETRY_EVENT_EXIT}, + {"close", TelemetryEvent::TELEMETRY_EVENT_CLOSE}, + {"rename", TelemetryEvent::TELEMETRY_EVENT_RENAME}, + {"unlink", TelemetryEvent::TELEMETRY_EVENT_UNLINK}, + {"link", TelemetryEvent::TELEMETRY_EVENT_LINK}, + {"ExchangeData", TelemetryEvent::TELEMETRY_EVENT_EXCHANGE_DATA}, + {"disk", TelemetryEvent::TELEMETRY_EVENT_DISK}, + {"bundle", TelemetryEvent::TELEMETRY_EVENT_BUNDLE}, + {"allowList", TelemetryEvent::TELEMETRY_EVENT_ALLOWLIST}, + {"fileAccess", TelemetryEvent::TELEMETRY_EVENT_FILE_ACCESS}, + {"codesigninginvalidated", TelemetryEvent::TELEMETRY_EVENT_CODESIGNING_INVALIDATED}, + {"loginwindowsession", TelemetryEvent::TELEMETRY_EVENT_LOGIN_WINDOW_SESSION}, + {"loginlogout", TelemetryEvent::TELEMETRY_EVENT_LOGIN_LOGOUT}, + {"screensharing", TelemetryEvent::TELEMETRY_EVENT_SCREEN_SHARING}, + {"openssh", TelemetryEvent::TELEMETRY_EVENT_OPEN_SSH}, + {"authentication", TelemetryEvent::TELEMETRY_EVENT_AUTHENTICATION}, + + // special cases + {"none", TelemetryEvent::TELEMETRY_EVENT_NONE}, + {"everything", TelemetryEvent::TELEMETRY_EVENT_EVERYTHING}, + }; + + for (const auto &[event_name, want] : eventNameToMask) { + uint64_t 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::TELEMETRY_EVENT_EVERYTHING); + XCTAssertEqual(TelemetryConfigToBitmask(@[ @"none" ], true), + TelemetryEvent::TELEMETRY_EVENT_NONE); + XCTAssertEqual(TelemetryConfigToBitmask(@[ @"execution", @"fork", @"exit" ], true), + TelemetryEvent::TELEMETRY_EVENT_EXECUTION | TelemetryEvent::TELEMETRY_EVENT_FORK | + TelemetryEvent::TELEMETRY_EVENT_EXIT); + XCTAssertEqual(TelemetryConfigToBitmask(@[ @"bundle", @"close", @"allowList" ], false), + TelemetryEvent::TELEMETRY_EVENT_BUNDLE | TelemetryEvent::TELEMETRY_EVENT_CLOSE | + TelemetryEvent::TELEMETRY_EVENT_ALLOWLIST); + + // When telemetry config is nil, returned bitmask is dependent + // upon enableForkAndExitLogging being true or false + XCTAssertEqual(TelemetryConfigToBitmask(nil, true), TelemetryEvent::TELEMETRY_EVENT_EVERYTHING); + XCTAssertEqual(TelemetryConfigToBitmask(nil, false), TelemetryEvent::TELEMETRY_EVENT_EVERYTHING & + ~TelemetryEvent::TELEMETRY_EVENT_FORK & + ~TelemetryEvent::TELEMETRY_EVENT_EXIT); +} + +- (void)testESEventToTelemetryEvent { + std::map esEventToTelemetryEvent = { + {ES_EVENT_TYPE_NOTIFY_CLOSE, TelemetryEvent::TELEMETRY_EVENT_CLOSE}, + {ES_EVENT_TYPE_NOTIFY_CS_INVALIDATED, TelemetryEvent::TELEMETRY_EVENT_CODESIGNING_INVALIDATED}, + {ES_EVENT_TYPE_NOTIFY_EXCHANGEDATA, TelemetryEvent::TELEMETRY_EVENT_EXCHANGE_DATA}, + {ES_EVENT_TYPE_NOTIFY_EXEC, TelemetryEvent::TELEMETRY_EVENT_EXECUTION}, + {ES_EVENT_TYPE_NOTIFY_EXIT, TelemetryEvent::TELEMETRY_EVENT_EXIT}, + {ES_EVENT_TYPE_NOTIFY_FORK, TelemetryEvent::TELEMETRY_EVENT_FORK}, + {ES_EVENT_TYPE_NOTIFY_LINK, TelemetryEvent::TELEMETRY_EVENT_LINK}, + {ES_EVENT_TYPE_NOTIFY_RENAME, TelemetryEvent::TELEMETRY_EVENT_RENAME}, + {ES_EVENT_TYPE_NOTIFY_UNLINK, TelemetryEvent::TELEMETRY_EVENT_UNLINK}, + {ES_EVENT_TYPE_NOTIFY_AUTHENTICATION, TelemetryEvent::TELEMETRY_EVENT_AUTHENTICATION}, + {ES_EVENT_TYPE_NOTIFY_LOGIN_LOGIN, TelemetryEvent::TELEMETRY_EVENT_LOGIN_LOGOUT}, + {ES_EVENT_TYPE_NOTIFY_LOGIN_LOGOUT, TelemetryEvent::TELEMETRY_EVENT_LOGIN_LOGOUT}, + {ES_EVENT_TYPE_NOTIFY_LW_SESSION_LOGIN, TelemetryEvent::TELEMETRY_EVENT_LOGIN_WINDOW_SESSION}, + {ES_EVENT_TYPE_NOTIFY_LW_SESSION_LOGOUT, TelemetryEvent::TELEMETRY_EVENT_LOGIN_WINDOW_SESSION}, + {ES_EVENT_TYPE_NOTIFY_LW_SESSION_LOCK, TelemetryEvent::TELEMETRY_EVENT_LOGIN_WINDOW_SESSION}, + {ES_EVENT_TYPE_NOTIFY_LW_SESSION_UNLOCK, TelemetryEvent::TELEMETRY_EVENT_LOGIN_WINDOW_SESSION}, + {ES_EVENT_TYPE_NOTIFY_SCREENSHARING_ATTACH, TelemetryEvent::TELEMETRY_EVENT_SCREEN_SHARING}, + {ES_EVENT_TYPE_NOTIFY_SCREENSHARING_DETACH, TelemetryEvent::TELEMETRY_EVENT_SCREEN_SHARING}, + {ES_EVENT_TYPE_NOTIFY_OPENSSH_LOGIN, TelemetryEvent::TELEMETRY_EVENT_OPEN_SSH}, + {ES_EVENT_TYPE_NOTIFY_OPENSSH_LOGOUT, TelemetryEvent::TELEMETRY_EVENT_OPEN_SSH}, + }; + + // Ensure ESEventToTelemetryEvent returns TELEMETRY_EVENT_NONE for + // everything except for the events defined in the above map. + for (int event = 0; event < ES_EVENT_TYPE_LAST; event++) { + TelemetryEvent wantTelemetryEvent = TelemetryEvent::TELEMETRY_EVENT_NONE; + + 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..35009908 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:(uint64_t)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,11 @@ - (void)handleMessageShouldLog:(BOOL)shouldLog auto mockAuthCache = std::make_shared(nullptr, nil); if (shouldRemoveFromCache) { EXPECT_CALL(*mockAuthCache, RemoveFromCache).Times(1); + } else { + ON_CALL(*mockAuthCache, RemoveFromCache).WillByDefault(^{ + XCTFail("RemoveFromCache should not be called"); + return nullptr; + }); } dispatch_semaphore_t semaMetrics = dispatch_semaphore_create(0); @@ -170,12 +171,19 @@ - (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 { + ON_CALL(*mockEnricher, Enrich).WillByDefault(^{ + XCTFail("Enrich should not be called"); + return nullptr; + }); } auto prefixTree = std::make_shared>(); @@ -194,6 +202,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 +217,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::TELEMETRY_EVENT_EVERYTHING]; +} + - (void)testHandleEventCloseMappedWritableMatchesRegex { #if HAVE_MACOS_13 if (@available(macOS 13.0, *)) { @@ -399,7 +421,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 +431,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 +447,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 +457,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..914fc5f8 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, + uint64_t 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(uint64_t telemetry_mask, std::shared_ptr serializer, + std::shared_ptr writer); virtual ~Logger() = default; @@ -65,9 +68,14 @@ class Logger { void Flush(); + void SetTelemetryMask(uint64_t mask); + + inline bool ShouldLog(TelemetryEvent event) { return ((event & telemetry_mask_) == event); } + friend class santa::LoggerPeer; private: + uint64_t 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..dd44f08f 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, + uint64_t 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(uint64_t 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(uint64_t 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(TELEMETRY_EVENT_ALLOWLIST)) { + writer_->Write(serializer_->SerializeAllowlist(msg, hash)); + } } void Logger::LogBundleHashingEvents(NSArray *events) { - for (SNTStoredEvent *se in events) { - writer_->Write(serializer_->SerializeBundleHashingEvent(se)); + if (ShouldLog(TELEMETRY_EVENT_BUNDLE)) { + for (SNTStoredEvent *se in events) { + writer_->Write(serializer_->SerializeBundleHashingEvent(se)); + } } } void Logger::LogDiskAppeared(NSDictionary *props) { - writer_->Write(serializer_->SerializeDiskAppeared(props)); + if (ShouldLog(TELEMETRY_EVENT_DISK)) { + writer_->Write(serializer_->SerializeDiskAppeared(props)); + } } void Logger::LogDiskDisappeared(NSDictionary *props) { - writer_->Write(serializer_->SerializeDiskDisappeared(props)); + if (ShouldLog(TELEMETRY_EVENT_DISK)) { + 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(TELEMETRY_EVENT_FILE_ACCESS)) { + 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..692d6359 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::TELEMETRY_EVENT_EVERYTHING, l->serializer_, l->writer_) {} std::shared_ptr Serializer() { return serializer_; } @@ -101,31 +104,37 @@ - (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::TELEMETRY_EVENT_EVERYTHING, + (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::TELEMETRY_EVENT_EVERYTHING, + 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::TELEMETRY_EVENT_EVERYTHING, + 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::TELEMETRY_EVENT_EVERYTHING, + 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::TELEMETRY_EVENT_EVERYTHING, + 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::TELEMETRY_EVENT_EVERYTHING, + 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 +159,8 @@ - (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::TELEMETRY_EVENT_EVERYTHING, mockSerializer, mockWriter) + .Log(std::move(enrichedMsg)); } XCTBubbleMockVerifyAndClearExpectations(mockESApi.get()); @@ -169,7 +179,8 @@ - (void)testLogAllowList { EXPECT_CALL(*mockSerializer, SerializeAllowlist(testing::_, hash)); EXPECT_CALL(*mockWriter, Write); - Logger(mockSerializer, mockWriter).LogAllowlist(Message(mockESApi, &msg), hash); + Logger(TelemetryEvent::TELEMETRY_EVENT_EVERYTHING, mockSerializer, mockWriter) + .LogAllowlist(Message(mockESApi, &msg), hash); XCTBubbleMockVerifyAndClearExpectations(mockESApi.get()); XCTBubbleMockVerifyAndClearExpectations(mockSerializer.get()); @@ -184,7 +195,8 @@ - (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::TELEMETRY_EVENT_EVERYTHING, mockSerializer, mockWriter) + .LogBundleHashingEvents(events); XCTBubbleMockVerifyAndClearExpectations(mockSerializer.get()); XCTBubbleMockVerifyAndClearExpectations(mockWriter.get()); @@ -197,7 +209,9 @@ - (void)testLogDiskAppeared { EXPECT_CALL(*mockSerializer, SerializeDiskAppeared); EXPECT_CALL(*mockWriter, Write); - Logger(mockSerializer, mockWriter).LogDiskAppeared(@{@"key" : @"value"}); + Logger(TelemetryEvent::TELEMETRY_EVENT_EVERYTHING, mockSerializer, mockWriter).LogDiskAppeared(@{ + @"key" : @"value" + }); XCTBubbleMockVerifyAndClearExpectations(mockSerializer.get()); XCTBubbleMockVerifyAndClearExpectations(mockWriter.get()); @@ -210,7 +224,8 @@ - (void)testLogDiskDisappeared { EXPECT_CALL(*mockSerializer, SerializeDiskDisappeared); EXPECT_CALL(*mockWriter, Write); - Logger(mockSerializer, mockWriter).LogDiskDisappeared(@{@"key" : @"value"}); + Logger(TelemetryEvent::TELEMETRY_EVENT_EVERYTHING, mockSerializer, mockWriter) + .LogDiskDisappeared(@{@"key" : @"value"}); XCTBubbleMockVerifyAndClearExpectations(mockSerializer.get()); XCTBubbleMockVerifyAndClearExpectations(mockWriter.get()); @@ -226,7 +241,7 @@ - (void)testLogFileAccess { EXPECT_CALL(*mockSerializer, SerializeFileAccess); EXPECT_CALL(*mockWriter, Write); - Logger(mockSerializer, mockWriter) + Logger(TelemetryEvent::TELEMETRY_EVENT_EVERYTHING, 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..24bd9afe 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,11 @@ class MockLogger : public santa::Logger { public: using Logger::Logger; - MockLogger() : Logger(nullptr, nullptr) {} + MockLogger() + : Logger(santa::TelemetryEvent::TELEMETRY_EVENT_EVERYTHING, 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);