Skip to content

Commit

Permalink
Configurable telemetry event filtering (northpolesec#149)
Browse files Browse the repository at this point in the history
Adds support for a new `Telemetry` configuration key that allows admins
to specify the event types that should be logged.

This PR deprecated the `EnableForkAndExitLogging` key.
  • Loading branch information
mlw authored Nov 26, 2024
1 parent 174ebe8 commit 1a0107c
Show file tree
Hide file tree
Showing 18 changed files with 626 additions and 135 deletions.
17 changes: 17 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 Expand Up @@ -529,6 +545,7 @@ test_suite(
":SantaCacheTest",
":ScopedCFTypeRefTest",
":ScopedIOObjectRefTest",
":TelemetryEventMapTest",
],
visibility = ["//:santa_package_group"],
)
Expand Down
20 changes: 14 additions & 6 deletions Source/common/SNTConfigurator.h
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
/// Copyright 2015-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.

#import <Foundation/Foundation.h>

Expand Down Expand Up @@ -210,6 +211,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
47 changes: 38 additions & 9 deletions Source/common/SNTConfigurator.m
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
/// Copyright 2014-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.

#import "Source/common/SNTConfigurator.h"

Expand Down Expand Up @@ -132,6 +133,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 +286,7 @@ - (instancetype)initWithSyncStateFile:(NSString *)syncStateFilePath
kEntitlementsPrefixFilterKey : array,
kEntitlementsTeamIDFilterKey : array,
kEnabledProcessAnnotations : array,
kTelemetryKey : array,
};

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

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

#pragma mark Public Interface

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

// This method returns only the values that are of the expected string type.
// The reasoning is that if a filter is attempted to be set, this method should
// return some subset rather than `nil`. Since `nil` effectively means to log
// everything, returning it would be akin to "failing open" even though some
// filter configuration was attempted.
- (NSArray<NSString *> *)telemetry {
NSArray *configuredEvents = self.configState[kTelemetryKey];
if (!configuredEvents) {
return nil;
}

NSMutableArray *events = [[NSMutableArray alloc] initWithCapacity:configuredEvents.count];

for (id event in configuredEvents) {
if ([event isKindOfClass:[NSString class]]) {
[events addObject:event];
}
}

return events;
}

- (BOOL)ignoreOtherEndpointSecurityClients {
NSNumber *number = self.configState[kIgnoreOtherEndpointSecurityClients];
return number ? [number boolValue] : NO;
Expand Down Expand Up @@ -1285,9 +1314,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
88 changes: 88 additions & 0 deletions Source/common/TelemetryEventMap.h
Original file line number Diff line number Diff line change
@@ -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 <EndpointSecurity/ESTypes.h>
#import <Foundation/Foundation.h>

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<TelemetryEvent>(static_cast<std::underlying_type_t<TelemetryEvent>>(lhs) |
static_cast<std::underlying_type_t<TelemetryEvent>>(rhs));
}

inline TelemetryEvent &operator|=(TelemetryEvent &lhs, TelemetryEvent rhs) {
lhs = lhs | rhs;
return lhs;
}

inline TelemetryEvent operator&(TelemetryEvent lhs, TelemetryEvent rhs) {
return static_cast<TelemetryEvent>(static_cast<std::underlying_type_t<TelemetryEvent>>(lhs) &
static_cast<std::underlying_type_t<TelemetryEvent>>(rhs));
}

inline TelemetryEvent &operator&=(TelemetryEvent &lhs, TelemetryEvent rhs) {
lhs = lhs & rhs;
return lhs;
}

inline TelemetryEvent operator~(TelemetryEvent rhs) {
return static_cast<TelemetryEvent>(~static_cast<std::underlying_type_t<TelemetryEvent>>(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<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
103 changes: 103 additions & 0 deletions Source/common/TelemetryEventMap.mm
Original file line number Diff line number Diff line change
@@ -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 <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", 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<NSString *> *telemetry,
BOOL enableForkAndExitLogging) {
TelemetryEvent mask = TelemetryEvent::kNone;

if (telemetry) {
for (NSString *event_name in telemetry) {
mask |= EventNameToMask(santa::NSStringToUTF8StringView([event_name lowercaseString]));
}
} else {
mask = TelemetryEvent::kEverything;

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
Loading

0 comments on commit 1a0107c

Please sign in to comment.