Skip to content

Commit

Permalink
WIP configurable telemetry event filtering
Browse files Browse the repository at this point in the history
  • Loading branch information
mlw committed Nov 24, 2024
1 parent 5d4c9f0 commit 505c60a
Show file tree
Hide file tree
Showing 13 changed files with 479 additions and 69 deletions.
16 changes: 16 additions & 0 deletions Source/common/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -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"],
Expand Down
7 changes: 7 additions & 0 deletions Source/common/SNTConfigurator.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<NSString *> *telemetry;

///
/// If eventLogType is set to protobuf, spoolDirectory will provide the base path used for
/// saving logs using a maildir-like format.
Expand Down
24 changes: 21 additions & 3 deletions Source/common/SNTConfigurator.m
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -284,6 +285,7 @@ - (instancetype)initWithSyncStateFile:(NSString *)syncStateFilePath
kEntitlementsPrefixFilterKey : array,
kEntitlementsTeamIDFilterKey : array,
kEnabledProcessAnnotations : array,
kTelemetryKey : array,
};

_syncStateFilePath = syncStateFilePath;
Expand Down Expand Up @@ -622,6 +624,10 @@ + (NSSet *)keyPathsForValuesAffectingEntitlementsTeamIDFilter {
return [self configStateSet];
}

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

#pragma mark Public Interface

- (SNTClientMode)clientMode {
Expand Down Expand Up @@ -1038,6 +1044,18 @@ - (BOOL)enableForkAndExitLogging {
return number ? [number boolValue] : NO;
}

- (NSArray<NSString *> *)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;
Expand Down Expand Up @@ -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;
}

Expand Down
63 changes: 63 additions & 0 deletions Source/common/TelemetryEventMap.h
Original file line number Diff line number Diff line change
@@ -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 <EndpointSecurity/ESTypes.h>
#import <Foundation/Foundation.h>

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<NSString *> *telemetry, BOOL enableForkAndExitLogging);

// Returns the appropriate `TelemetryEvent` enum value for a given ES event
TelemetryEvent ESEventToTelemetryEvent(es_event_type_t event);

} // namespace santa

#endif
102 changes: 102 additions & 0 deletions Source/common/TelemetryEventMap.mm
Original file line number Diff line number Diff line change
@@ -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 <string_view>

#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<std::string_view, TelemetryEvent> 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<NSString *> *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
123 changes: 123 additions & 0 deletions Source/common/TelemetryEventMapTest.mm
Original file line number Diff line number Diff line change
@@ -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 <Foundation/Foundation.h>
#import <XCTest/XCTest.h>

#include <map>

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<std::string_view, TelemetryEvent> 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<es_event_type_t, TelemetryEvent> 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
Loading

0 comments on commit 505c60a

Please sign in to comment.