Skip to content

Commit

Permalink
Entitlements logging config options (#1233)
Browse files Browse the repository at this point in the history
* WIP add config support to filter logged entitlements

* Add EntitlementInfo proto message to store if entitlements were filtered

* Log cleanup

* Address PR feedback

* Address PR feedback
  • Loading branch information
mlw committed Nov 13, 2023
1 parent edac42e commit a5e8d77
Show file tree
Hide file tree
Showing 24 changed files with 543 additions and 241 deletions.
10 changes: 5 additions & 5 deletions Source/common/PrefixTree.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,11 @@ class PrefixTree {
node_count_ = 0;
}

uint32_t NodeCount() {
absl::ReaderMutexLock lock(&lock_);
return node_count_;
}

#if SANTA_PREFIX_TREE_DEBUG
void Print() {
char buf[max_depth_ + 1];
Expand All @@ -82,11 +87,6 @@ class PrefixTree {
absl::ReaderMutexLock lock(&lock_);
PrintLocked(root_, buf, 0);
}

uint32_t NodeCount() {
absl::ReaderMutexLock lock(&lock_);
return node_count_;
}
#endif

private:
Expand Down
1 change: 1 addition & 0 deletions Source/common/SNTCachedDecision.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
@property NSString *teamID;
@property NSString *signingID;
@property NSDictionary *entitlements;
@property BOOL entitlementsFiltered;

@property NSString *quarantineURL;

Expand Down
12 changes: 12 additions & 0 deletions Source/common/SNTConfigurator.h
Original file line number Diff line number Diff line change
Expand Up @@ -642,6 +642,18 @@
///
@property(readonly, nonatomic) NSUInteger metricExportTimeout;

///
/// List of prefix strings for which individual entitlement keys with a matching
/// prefix should not be logged.
///
@property(readonly, nonatomic) NSArray<NSString *> *entitlementsPrefixFilter;

///
/// List of TeamIDs for which entitlements should not be logged. Use the string
/// "platform" to refer to platform binaries.
///
@property(readonly, nonatomic) NSArray<NSString *> *entitlementsTeamIDFilter;

///
/// Retrieve an initialized singleton configurator object using the default file path.
///
Expand Down
36 changes: 36 additions & 0 deletions Source/common/SNTConfigurator.m
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,21 @@
#import "Source/common/SNTStrengthify.h"
#import "Source/common/SNTSystemInfo.h"

// Ensures the given object is an NSArray and only contains NSString value types
static NSArray<NSString *> *EnsureArrayOfStrings(id obj) {
if (![obj isKindOfClass:[NSArray class]]) {
return nil;
}

for (id item in obj) {
if (![item isKindOfClass:[NSString class]]) {
return nil;
}
}

return obj;
}

@interface SNTConfigurator ()
/// A NSUserDefaults object set to use the com.google.santa suite.
@property(readonly, nonatomic) NSUserDefaults *defaults;
Expand Down Expand Up @@ -116,6 +131,9 @@ @implementation SNTConfigurator
static NSString *const kFCMEntity = @"FCMEntity";
static NSString *const kFCMAPIKey = @"FCMAPIKey";

static NSString *const kEntitlementsPrefixFilterKey = @"EntitlementsPrefixFilter";
static NSString *const kEntitlementsTeamIDFilterKey = @"EntitlementsTeamIDFilter";

// The keys managed by a sync server or mobileconfig.
static NSString *const kClientModeKey = @"ClientMode";
static NSString *const kFailClosedKey = @"FailClosed";
Expand Down Expand Up @@ -240,6 +258,8 @@ - (instancetype)init {
kEnableAllEventUploadKey : number,
kDisableUnknownEventUploadKey : number,
kOverrideFileAccessActionKey : string,
kEntitlementsPrefixFilterKey : array,
kEntitlementsTeamIDFilterKey : array,
};
_defaults = [NSUserDefaults standardUserDefaults];
[_defaults addSuiteNamed:@"com.google.santa"];
Expand Down Expand Up @@ -527,6 +547,14 @@ + (NSSet *)keyPathsForValuesAffectingOverrideFileAccessActionKey {
return [self syncAndConfigStateSet];
}

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

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

#pragma mark Public Interface

- (SNTClientMode)clientMode {
Expand Down Expand Up @@ -1111,6 +1139,14 @@ - (void)clearSyncState {
self.syncState = [NSMutableDictionary dictionary];
}

- (NSArray *)entitlementsPrefixFilter {
return EnsureArrayOfStrings(self.configState[kEntitlementsPrefixFilterKey]);
}

- (NSArray *)entitlementsTeamIDFilter {
return EnsureArrayOfStrings(self.configState[kEntitlementsTeamIDFilterKey]);
}

#pragma mark Private Defaults Methods

- (NSRegularExpression *)expressionForPattern:(NSString *)pattern {
Expand Down
18 changes: 14 additions & 4 deletions Source/common/santa.proto
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,18 @@ message Entitlement {
optional string value = 2;
}

// Information about entitlements
message EntitlementInfo {
// Whether or not the set of reported entilements is complete or has been
// filtered (e.g. by configuration or clipped because too many to log).
optional bool entitlements_filtered = 1;

// The set of entitlements associated with the target executable
// Only top level keys are represented
// Values (including nested keys) are JSON serialized
repeated Entitlement entitlements = 2;
}

// Information about a process execution event
message Execution {
// The process that executed the new image (e.g. the process that called
Expand Down Expand Up @@ -296,10 +308,8 @@ message Execution {
// Applies when executables are translocated
optional string original_path = 15;

// The set of entitlements associated with the target executable
// Only top level keys are represented
// Values (including nested keys) are JSON serialized
repeated Entitlement entitlements = 16;
// Entitlement information about the target executbale
optional EntitlementInfo entitlement_info = 16;
}

// Information about a fork event
Expand Down
4 changes: 4 additions & 0 deletions Source/santad/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -237,10 +237,12 @@ objc_library(
":SNTSyncdQueue",
":TTYWriter",
"//Source/common:BranchPrediction",
"//Source/common:PrefixTree",
"//Source/common:SNTBlockMessage",
"//Source/common:SNTCachedDecision",
"//Source/common:SNTCommonEnums",
"//Source/common:SNTConfigurator",
"//Source/common:SNTDeepCopy",
"//Source/common:SNTDropRootPrivs",
"//Source/common:SNTFileInfo",
"//Source/common:SNTLogging",
Expand All @@ -249,7 +251,9 @@ objc_library(
"//Source/common:SNTStoredEvent",
"//Source/common:SantaVnode",
"//Source/common:String",
"//Source/common:Unit",
"@MOLCodesignChecker",
"@com_google_absl//absl/synchronization",
],
)

Expand Down
2 changes: 2 additions & 0 deletions Source/santad/EventProviders/AuthResultCache.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ enum class FlushCacheReason {
kStaticRulesChanged,
kExplicitCommand,
kFilesystemUnmounted,
kEntitlementsPrefixFilterChanged,
kEntitlementsTeamIDFilterChanged,
};

class AuthResultCache {
Expand Down
8 changes: 8 additions & 0 deletions Source/santad/EventProviders/AuthResultCache.mm
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@
static NSString *const kFlushCacheReasonStaticRulesChanged = @"StaticRulesChanged";
static NSString *const kFlushCacheReasonExplicitCommand = @"ExplicitCommand";
static NSString *const kFlushCacheReasonFilesystemUnmounted = @"FilesystemUnmounted";
static NSString *const kFlushCacheReasonEntitlementsPrefixFilterChanged =
@"EntitlementsPrefixFilterChanged";
static NSString *const kFlushCacheReasonEntitlementsTeamIDFilterChanged =
@"EntitlementsTeamIDFilterChanged";

namespace santa::santad::event_providers {

Expand Down Expand Up @@ -59,6 +63,10 @@ static inline uint64_t TimestampFromCachedValue(uint64_t cachedValue) {
case FlushCacheReason::kStaticRulesChanged: return kFlushCacheReasonStaticRulesChanged;
case FlushCacheReason::kExplicitCommand: return kFlushCacheReasonExplicitCommand;
case FlushCacheReason::kFilesystemUnmounted: return kFlushCacheReasonFilesystemUnmounted;
case FlushCacheReason::kEntitlementsPrefixFilterChanged:
return kFlushCacheReasonEntitlementsPrefixFilterChanged;
case FlushCacheReason::kEntitlementsTeamIDFilterChanged:
return kFlushCacheReasonEntitlementsTeamIDFilterChanged;
default:
[NSException raise:@"Invalid reason"
format:@"Unknown reason value: %d", static_cast<int>(reason)];
Expand Down
5 changes: 4 additions & 1 deletion Source/santad/EventProviders/AuthResultCacheTest.mm
Original file line number Diff line number Diff line change
Expand Up @@ -230,13 +230,16 @@ - (void)testFlushCacheReasonToString {
{FlushCacheReason::kStaticRulesChanged, @"StaticRulesChanged"},
{FlushCacheReason::kExplicitCommand, @"ExplicitCommand"},
{FlushCacheReason::kFilesystemUnmounted, @"FilesystemUnmounted"},
{FlushCacheReason::kEntitlementsPrefixFilterChanged, @"EntitlementsPrefixFilterChanged"},
{FlushCacheReason::kEntitlementsTeamIDFilterChanged, @"EntitlementsTeamIDFilterChanged"},
};

for (const auto &kv : reasonToString) {
XCTAssertEqualObjects(FlushCacheReasonToString(kv.first), kv.second);
}

XCTAssertThrows(FlushCacheReasonToString((FlushCacheReason)12345));
XCTAssertThrows(FlushCacheReasonToString(
(FlushCacheReason)(static_cast<int>(FlushCacheReason::kEntitlementsTeamIDFilterChanged) + 1)));
}

@end
19 changes: 13 additions & 6 deletions Source/santad/Logs/EndpointSecurity/Serializers/Protobuf.mm
Original file line number Diff line number Diff line change
Expand Up @@ -485,22 +485,29 @@ id StandardizedNestedObjects(id obj, int level) {
}
}

void EncodeEntitlements(::pbv1::Execution *pb_exec, NSDictionary *entitlements) {
if (!entitlements) {
void EncodeEntitlements(::pbv1::Execution *pb_exec, SNTCachedDecision *cd) {
::pbv1::EntitlementInfo *pb_entitlement_info = pb_exec->mutable_entitlement_info();

pb_entitlement_info->set_entitlements_filtered(cd.entitlementsFiltered != NO);

if (!cd.entitlements) {
return;
}

// Since nested objects with varying types is hard for the API to serialize to
// JSON, first go through and standardize types to ensure better serialization
// as well as a consitent view of data.
entitlements = StandardizedNestedObjects(entitlements, kMaxEncodeObjectLevels);
NSDictionary *entitlements = StandardizedNestedObjects(cd.entitlements, kMaxEncodeObjectLevels);

__block int numObjectsToEncode = (int)std::min(kMaxEncodeObjectEntries, entitlements.count);

pb_exec->mutable_entitlements()->Reserve(numObjectsToEncode);
pb_entitlement_info->mutable_entitlements()->Reserve(numObjectsToEncode);

[entitlements enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
if (numObjectsToEncode-- == 0) {
// Because entitlements are being clipped, ensure that we update that
// the set of entitlements were filtered.
pb_entitlement_info->set_entitlements_filtered(true);
*stop = YES;
return;
}
Expand Down Expand Up @@ -554,7 +561,7 @@ void EncodeEntitlements(::pbv1::Execution *pb_exec, NSDictionary *entitlements)
return;
}

::pbv1::Entitlement *pb_entitlement = pb_exec->add_entitlements();
::pbv1::Entitlement *pb_entitlement = pb_entitlement_info->add_entitlements();
EncodeString([pb_entitlement] { return pb_entitlement->mutable_key(); },
NSStringToUTF8StringView(key));
EncodeString([pb_entitlement] { return pb_entitlement->mutable_value(); },
Expand Down Expand Up @@ -639,7 +646,7 @@ void EncodeEntitlements(::pbv1::Execution *pb_exec, NSDictionary *entitlements)
NSString *orig_path = Utilities::OriginalPathForTranslocation(msg.es_msg().event.exec.target);
EncodeString([pb_exec] { return pb_exec->mutable_original_path(); }, orig_path);

EncodeEntitlements(pb_exec, cd.entitlements);
EncodeEntitlements(pb_exec, cd);

return FinalizeProto(santa_msg);
}
Expand Down
66 changes: 57 additions & 9 deletions Source/santad/Logs/EndpointSecurity/Serializers/ProtobufTest.mm
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@

namespace santa::santad::logs::endpoint_security::serializers {
extern void EncodeExitStatus(::pbv1::Exit *pbExit, int exitStatus);
extern void EncodeEntitlements(::pbv1::Execution *pb_exec, NSDictionary *entitlements);
extern void EncodeEntitlements(::pbv1::Execution *pb_exec, SNTCachedDecision *cd);
extern ::pbv1::Execution::Decision GetDecisionEnum(SNTEventState event_state);
extern ::pbv1::Execution::Reason GetReasonEnum(SNTEventState event_state);
extern ::pbv1::Execution::Mode GetModeEnum(SNTClientMode mode);
Expand Down Expand Up @@ -599,19 +599,67 @@ - (void)testSerializeMessageExecJSON {
}

- (void)testEncodeEntitlements {
::pbv1::Execution pbExec;
NSMutableDictionary *ents = [NSMutableDictionary dictionary];
int kMaxEncodeObjectEntries = 64; // From Protobuf.mm
// Test basic encoding without filtered entitlements
{
::pbv1::Execution pbExec;

SNTCachedDecision *cd = [[SNTCachedDecision alloc] init];
cd.entitlements = @{@"com.google.test" : @(YES)};

XCTAssertEqual(0, pbExec.entitlement_info().entitlements_size());
XCTAssertFalse(cd.entitlementsFiltered);
XCTAssertEqual(1, cd.entitlements.count);

EncodeEntitlements(&pbExec, cd);

for (int i = 0; i < 100; i++) {
ents[[NSString stringWithFormat:@"k%d", i]] = @(i);
XCTAssertEqual(1, pbExec.entitlement_info().entitlements_size());
XCTAssertTrue(pbExec.entitlement_info().has_entitlements_filtered());
XCTAssertFalse(pbExec.entitlement_info().entitlements_filtered());
}

XCTAssertEqual(0, pbExec.entitlements_size());
// Test basic encoding with filtered entitlements
{
::pbv1::Execution pbExec;

EncodeEntitlements(&pbExec, ents);
SNTCachedDecision *cd = [[SNTCachedDecision alloc] init];
cd.entitlements = @{@"com.google.test" : @(YES), @"com.google.test2" : @(NO)};
cd.entitlementsFiltered = YES;

int kMaxEncodeObjectEntries = 64; // From Protobuf.mm
XCTAssertEqual(kMaxEncodeObjectEntries, pbExec.entitlements_size());
XCTAssertEqual(0, pbExec.entitlement_info().entitlements_size());
XCTAssertTrue(cd.entitlementsFiltered);
XCTAssertEqual(2, cd.entitlements.count);

EncodeEntitlements(&pbExec, cd);

XCTAssertEqual(2, pbExec.entitlement_info().entitlements_size());
XCTAssertTrue(pbExec.entitlement_info().has_entitlements_filtered());
XCTAssertTrue(pbExec.entitlement_info().entitlements_filtered());
}

// Test max number of entitlements logged
// When entitlements are clipped, `entitlements_filtered` is set to true
{
::pbv1::Execution pbExec;
NSMutableDictionary *ents = [NSMutableDictionary dictionary];

for (int i = 0; i < 100; i++) {
ents[[NSString stringWithFormat:@"k%d", i]] = @(i);
}

SNTCachedDecision *cd = [[SNTCachedDecision alloc] init];
cd.entitlements = ents;

XCTAssertEqual(0, pbExec.entitlement_info().entitlements_size());
XCTAssertFalse(cd.entitlementsFiltered);
XCTAssertGreaterThan(cd.entitlements.count, kMaxEncodeObjectEntries);

EncodeEntitlements(&pbExec, cd);

XCTAssertEqual(kMaxEncodeObjectEntries, pbExec.entitlement_info().entitlements_size());
XCTAssertTrue(pbExec.entitlement_info().has_entitlements_filtered());
XCTAssertTrue(pbExec.entitlement_info().entitlements_filtered());
}
}

- (void)testSerializeMessageExit {
Expand Down
6 changes: 5 additions & 1 deletion Source/santad/SNTExecutionController.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,9 @@ const static NSString *kBlockLongPath = @"BlockLongPath";
eventTable:(SNTEventTable *)eventTable
notifierQueue:(SNTNotificationQueue *)notifierQueue
syncdQueue:(SNTSyncdQueue *)syncdQueue
ttyWriter:(std::shared_ptr<santa::santad::TTYWriter>)ttyWriter;
ttyWriter:(std::shared_ptr<santa::santad::TTYWriter>)ttyWriter
entitlementsPrefixFilter:(NSArray<NSString *> *)prefixFilter
entitlementsTeamIDFilter:(NSArray<NSString *> *)teamIDFilter;

///
/// Handles the logic of deciding whether to allow the binary to run or not, sends the response to
Expand All @@ -82,4 +84,6 @@ const static NSString *kBlockLongPath = @"BlockLongPath";
- (bool)synchronousShouldProcessExecEvent:
(const santa::santad::event_providers::endpoint_security::Message &)esMsg;

- (void)updateEntitlementsPrefixFilter:(NSArray<NSString *> *)filter;
- (void)updateEntitlementsTeamIDFilter:(NSArray<NSString *> *)filter;
@end
Loading

0 comments on commit a5e8d77

Please sign in to comment.