diff --git a/Source/common/PrefixTree.h b/Source/common/PrefixTree.h index 7873787cf..e05a412f2 100644 --- a/Source/common/PrefixTree.h +++ b/Source/common/PrefixTree.h @@ -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]; @@ -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: diff --git a/Source/common/SNTCachedDecision.h b/Source/common/SNTCachedDecision.h index 4ca4752b1..0242bb222 100644 --- a/Source/common/SNTCachedDecision.h +++ b/Source/common/SNTCachedDecision.h @@ -39,6 +39,7 @@ @property NSString *teamID; @property NSString *signingID; @property NSDictionary *entitlements; +@property BOOL entitlementsFiltered; @property NSString *quarantineURL; diff --git a/Source/common/SNTConfigurator.h b/Source/common/SNTConfigurator.h index 30a5c43b7..5df87ee2a 100644 --- a/Source/common/SNTConfigurator.h +++ b/Source/common/SNTConfigurator.h @@ -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 *entitlementsPrefixFilter; + +/// +/// List of TeamIDs for which entitlements should not be logged. Use the string +/// "platform" to refer to platform binaries. +/// +@property(readonly, nonatomic) NSArray *entitlementsTeamIDFilter; + /// /// Retrieve an initialized singleton configurator object using the default file path. /// diff --git a/Source/common/SNTConfigurator.m b/Source/common/SNTConfigurator.m index 474727c9d..f2d71f37a 100644 --- a/Source/common/SNTConfigurator.m +++ b/Source/common/SNTConfigurator.m @@ -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 *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; @@ -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"; @@ -240,6 +258,8 @@ - (instancetype)init { kEnableAllEventUploadKey : number, kDisableUnknownEventUploadKey : number, kOverrideFileAccessActionKey : string, + kEntitlementsPrefixFilterKey : array, + kEntitlementsTeamIDFilterKey : array, }; _defaults = [NSUserDefaults standardUserDefaults]; [_defaults addSuiteNamed:@"com.google.santa"]; @@ -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 { @@ -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 { diff --git a/Source/common/santa.proto b/Source/common/santa.proto index 7636e656a..504476de8 100644 --- a/Source/common/santa.proto +++ b/Source/common/santa.proto @@ -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 @@ -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 diff --git a/Source/santad/BUILD b/Source/santad/BUILD index 423c777bd..79d64b271 100644 --- a/Source/santad/BUILD +++ b/Source/santad/BUILD @@ -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", @@ -249,7 +251,9 @@ objc_library( "//Source/common:SNTStoredEvent", "//Source/common:SantaVnode", "//Source/common:String", + "//Source/common:Unit", "@MOLCodesignChecker", + "@com_google_absl//absl/synchronization", ], ) diff --git a/Source/santad/EventProviders/AuthResultCache.h b/Source/santad/EventProviders/AuthResultCache.h index 0bec8f40d..0a3c6383d 100644 --- a/Source/santad/EventProviders/AuthResultCache.h +++ b/Source/santad/EventProviders/AuthResultCache.h @@ -41,6 +41,8 @@ enum class FlushCacheReason { kStaticRulesChanged, kExplicitCommand, kFilesystemUnmounted, + kEntitlementsPrefixFilterChanged, + kEntitlementsTeamIDFilterChanged, }; class AuthResultCache { diff --git a/Source/santad/EventProviders/AuthResultCache.mm b/Source/santad/EventProviders/AuthResultCache.mm index 13ab4e26c..192e51a4f 100644 --- a/Source/santad/EventProviders/AuthResultCache.mm +++ b/Source/santad/EventProviders/AuthResultCache.mm @@ -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 { @@ -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(reason)]; diff --git a/Source/santad/EventProviders/AuthResultCacheTest.mm b/Source/santad/EventProviders/AuthResultCacheTest.mm index d14831c8a..5133a24b0 100644 --- a/Source/santad/EventProviders/AuthResultCacheTest.mm +++ b/Source/santad/EventProviders/AuthResultCacheTest.mm @@ -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(FlushCacheReason::kEntitlementsTeamIDFilterChanged) + 1))); } @end diff --git a/Source/santad/Logs/EndpointSecurity/Serializers/Protobuf.mm b/Source/santad/Logs/EndpointSecurity/Serializers/Protobuf.mm index cc768c998..ab402a28b 100644 --- a/Source/santad/Logs/EndpointSecurity/Serializers/Protobuf.mm +++ b/Source/santad/Logs/EndpointSecurity/Serializers/Protobuf.mm @@ -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; } @@ -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(); }, @@ -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); } diff --git a/Source/santad/Logs/EndpointSecurity/Serializers/ProtobufTest.mm b/Source/santad/Logs/EndpointSecurity/Serializers/ProtobufTest.mm index 12561f724..050dc3be5 100644 --- a/Source/santad/Logs/EndpointSecurity/Serializers/ProtobufTest.mm +++ b/Source/santad/Logs/EndpointSecurity/Serializers/ProtobufTest.mm @@ -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); @@ -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 { diff --git a/Source/santad/SNTExecutionController.h b/Source/santad/SNTExecutionController.h index 8a06edcc2..1e95799c2 100644 --- a/Source/santad/SNTExecutionController.h +++ b/Source/santad/SNTExecutionController.h @@ -57,7 +57,9 @@ const static NSString *kBlockLongPath = @"BlockLongPath"; eventTable:(SNTEventTable *)eventTable notifierQueue:(SNTNotificationQueue *)notifierQueue syncdQueue:(SNTSyncdQueue *)syncdQueue - ttyWriter:(std::shared_ptr)ttyWriter; + ttyWriter:(std::shared_ptr)ttyWriter + entitlementsPrefixFilter:(NSArray *)prefixFilter + entitlementsTeamIDFilter:(NSArray *)teamIDFilter; /// /// Handles the logic of deciding whether to allow the binary to run or not, sends the response to @@ -82,4 +84,6 @@ const static NSString *kBlockLongPath = @"BlockLongPath"; - (bool)synchronousShouldProcessExecEvent: (const santa::santad::event_providers::endpoint_security::Message &)esMsg; +- (void)updateEntitlementsPrefixFilter:(NSArray *)filter; +- (void)updateEntitlementsTeamIDFilter:(NSArray *)filter; @end diff --git a/Source/santad/SNTExecutionController.mm b/Source/santad/SNTExecutionController.mm index b67ec98aa..73628603f 100644 --- a/Source/santad/SNTExecutionController.mm +++ b/Source/santad/SNTExecutionController.mm @@ -14,6 +14,7 @@ /// limitations under the License. #import "Source/santad/SNTExecutionController.h" +#include #import #include @@ -24,12 +25,16 @@ #include #include +#include +#include #include "Source/common/BranchPrediction.h" +#include "Source/common/PrefixTree.h" #import "Source/common/SNTBlockMessage.h" #import "Source/common/SNTCachedDecision.h" #import "Source/common/SNTCommonEnums.h" #import "Source/common/SNTConfigurator.h" +#import "Source/common/SNTDeepCopy.h" #import "Source/common/SNTDropRootPrivs.h" #import "Source/common/SNTFileInfo.h" #import "Source/common/SNTLogging.h" @@ -38,18 +43,39 @@ #import "Source/common/SNTStoredEvent.h" #include "Source/common/SantaVnode.h" #include "Source/common/String.h" +#include "Source/common/Unit.h" #import "Source/santad/DataLayer/SNTEventTable.h" #import "Source/santad/DataLayer/SNTRuleTable.h" #import "Source/santad/SNTDecisionCache.h" #import "Source/santad/SNTNotificationQueue.h" #import "Source/santad/SNTPolicyProcessor.h" #import "Source/santad/SNTSyncdQueue.h" +#include "absl/synchronization/mutex.h" +using santa::common::PrefixTree; +using santa::common::Unit; using santa::santad::TTYWriter; using santa::santad::event_providers::endpoint_security::Message; static const size_t kMaxAllowedPathLength = MAXPATHLEN - 1; // -1 to account for null terminator +void UpdateTeamIDFilterLocked(std::set &filterSet, NSArray *filter) { + filterSet.clear(); + + for (NSString *prefix in filter) { + filterSet.insert(santa::common::NSStringToUTF8String(prefix)); + } +} + +void UpdatePrefixFilterLocked(std::unique_ptr> &tree, + NSArray *filter) { + tree->Reset(); + + for (NSString *item in filter) { + tree->InsertPrefix(item.UTF8String, Unit{}); + } +} + @interface SNTExecutionController () @property SNTEventTable *eventTable; @property SNTNotificationQueue *notifierQueue; @@ -63,6 +89,9 @@ @interface SNTExecutionController () @implementation SNTExecutionController { std::shared_ptr _ttyWriter; + absl::Mutex _entitlementFilterMutex; + std::set _entitlementsTeamIDFilter; + std::unique_ptr> _entitlementsPrefixFilter; } static NSString *const kPrinterProxyPreMonterey = @@ -79,7 +108,9 @@ - (instancetype)initWithRuleTable:(SNTRuleTable *)ruleTable eventTable:(SNTEventTable *)eventTable notifierQueue:(SNTNotificationQueue *)notifierQueue syncdQueue:(SNTSyncdQueue *)syncdQueue - ttyWriter:(std::shared_ptr)ttyWriter { + ttyWriter:(std::shared_ptr)ttyWriter + entitlementsPrefixFilter:(NSArray *)entitlementsPrefixFilter + entitlementsTeamIDFilter:(NSArray *)entitlementsTeamIDFilter { self = [super init]; if (self) { _ruleTable = ruleTable; @@ -100,10 +131,25 @@ - (instancetype)initWithRuleTable:(SNTRuleTable *)ruleTable _events = [metricSet counterWithName:@"/santa/events" fieldNames:@[ @"action_response" ] helpText:@"Events processed by Santa per response"]; + + self->_entitlementsPrefixFilter = std::make_unique>(); + + UpdatePrefixFilterLocked(self->_entitlementsPrefixFilter, entitlementsPrefixFilter); + UpdateTeamIDFilterLocked(self->_entitlementsTeamIDFilter, entitlementsTeamIDFilter); } return self; } +- (void)updateEntitlementsPrefixFilter:(NSArray *)filter { + absl::MutexLock lock(&self->_entitlementFilterMutex); + UpdatePrefixFilterLocked(self->_entitlementsPrefixFilter, filter); +} + +- (void)updateEntitlementsTeamIDFilter:(NSArray *)filter { + absl::MutexLock lock(&self->_entitlementFilterMutex); + UpdateTeamIDFilterLocked(self->_entitlementsTeamIDFilter, filter); +} + - (void)incrementEventCounters:(SNTEventState)eventType { const NSString *eventTypeStr; @@ -208,8 +254,41 @@ - (void)validateExecEvent:(const Message &)esMsg postAction:(bool (^)(SNTAction) // TODO(markowsky): Maybe add a metric here for how many large executables we're seeing. // if (binInfo.fileSize > SomeUpperLimit) ... - SNTCachedDecision *cd = [self.policyProcessor decisionForFileInfo:binInfo - targetProcess:targetProc]; + SNTCachedDecision *cd = [self.policyProcessor + decisionForFileInfo:binInfo + targetProcess:targetProc + entitlementsFilterCallback:^NSDictionary *(const char *teamID, NSDictionary *entitlements) { + if (!entitlements) { + return nil; + } + + absl::ReaderMutexLock lock(&self->_entitlementFilterMutex); + + if (teamID && self->_entitlementsTeamIDFilter.count(std::string(teamID)) > 0) { + LOGD(@"Dropping entitlement logging for configured TeamID: %s", teamID); + return nil; + } + + if (self->_entitlementsPrefixFilter->NodeCount() == 0) { + LOGD(@"Copying full entitlements for tid: %s", teamID); + return [entitlements sntDeepCopy]; + } else { + LOGD(@"Filtering entitlements for tid: %s", teamID); + NSMutableDictionary *filtered = [NSMutableDictionary dictionary]; + + [entitlements enumerateKeysAndObjectsUsingBlock:^(NSString *key, id obj, BOOL *stop) { + if (!self->_entitlementsPrefixFilter->HasPrefix(key.UTF8String)) { + if ([obj isKindOfClass:[NSArray class]] || [obj isKindOfClass:[NSDictionary class]]) { + [filtered setObject:[obj sntDeepCopy] forKey:key]; + } else { + [filtered setObject:[obj copy] forKey:key]; + } + } + }]; + + return filtered.count > 0 ? filtered : nil; + } + }]; cd.vnodeId = SantaVnode::VnodeForFile(targetProc->executable); diff --git a/Source/santad/SNTExecutionControllerTest.mm b/Source/santad/SNTExecutionControllerTest.mm index 4a960051e..24d836d16 100644 --- a/Source/santad/SNTExecutionControllerTest.mm +++ b/Source/santad/SNTExecutionControllerTest.mm @@ -96,7 +96,9 @@ - (void)setUp { eventTable:self.mockEventDatabase notifierQueue:nil syncdQueue:nil - ttyWriter:nullptr]; + ttyWriter:nullptr + entitlementsPrefixFilter:nil + entitlementsTeamIDFilter:nil]; } - (void)tearDown { diff --git a/Source/santad/SNTPolicyProcessor.h b/Source/santad/SNTPolicyProcessor.h index 7620e7587..10170a4e9 100644 --- a/Source/santad/SNTPolicyProcessor.h +++ b/Source/santad/SNTPolicyProcessor.h @@ -36,22 +36,19 @@ - (nullable instancetype)initWithRuleTable:(nonnull SNTRuleTable *)ruleTable; /// -/// @param fileInfo A SNTFileInfo object. -/// @param fileSHA256 The pre-calculated SHA256 hash for the file, can be nil. If nil the hash will -/// be calculated by this method from the filePath. -/// @param certificateSHA256 The pre-calculated SHA256 hash of the leaf certificate. If nil, the -/// signature will be validated on the binary represented by fileInfo. -/// -- (nonnull SNTCachedDecision *)decisionForFileInfo:(nonnull SNTFileInfo *)fileInfo - fileSHA256:(nullable NSString *)fileSHA256 - certificateSHA256:(nullable NSString *)certificateSHA256 - teamID:(nullable NSString *)teamID - signingID:(nullable NSString *)signingID; - /// Convenience initializer. Will obtain the teamID and construct the signingID /// identifier if able. +/// +/// IMPORTANT: The lifetimes of arguments to `entitlementsFilterCallback` are +/// only guaranteed for the duration of the call to the block. Do not perform +/// any async processing without extending their lifetimes. +/// - (nonnull SNTCachedDecision *)decisionForFileInfo:(nonnull SNTFileInfo *)fileInfo - targetProcess:(nonnull const es_process_t *)targetProc; + targetProcess:(nonnull const es_process_t *)targetProc + entitlementsFilterCallback: + (NSDictionary *_Nullable (^_Nonnull)( + const char *_Nullable teamID, + NSDictionary *_Nullable entitlements))entitlementsFilterCallback; /// /// A wrapper for decisionForFileInfo:fileSHA256:certificateSHA256:. This method is slower as it diff --git a/Source/santad/SNTPolicyProcessor.m b/Source/santad/SNTPolicyProcessor.m index 9c9b836a4..0004ee0c2 100644 --- a/Source/santad/SNTPolicyProcessor.m +++ b/Source/santad/SNTPolicyProcessor.m @@ -13,6 +13,7 @@ /// limitations under the License. #import "Source/santad/SNTPolicyProcessor.h" +#include #import #import @@ -46,7 +47,10 @@ - (nonnull SNTCachedDecision *)decisionForFileInfo:(nonnull SNTFileInfo *)fileIn fileSHA256:(nullable NSString *)fileSHA256 certificateSHA256:(nullable NSString *)certificateSHA256 teamID:(nullable NSString *)teamID - signingID:(nullable NSString *)signingID { + signingID:(nullable NSString *)signingID + entitlementsFilterCallback: + (NSDictionary *_Nullable (^_Nullable)( + NSDictionary *_Nullable entitlements))entitlementsFilterCallback { SNTCachedDecision *cd = [[SNTCachedDecision alloc] init]; cd.sha256 = fileSHA256 ?: fileInfo.SHA256; cd.teamID = teamID; @@ -94,8 +98,16 @@ - (nonnull SNTCachedDecision *)decisionForFileInfo:(nonnull SNTFileInfo *)fileIn } } - cd.entitlements = - [csInfo.signingInformation[(__bridge NSString *)kSecCodeInfoEntitlementsDict] sntDeepCopy]; + NSDictionary *entitlements = + csInfo.signingInformation[(__bridge NSString *)kSecCodeInfoEntitlementsDict]; + + if (entitlementsFilterCallback) { + cd.entitlements = entitlementsFilterCallback(entitlements); + cd.entitlementsFiltered = (cd.entitlements.count == entitlements.count); + } else { + cd.entitlements = [entitlements sntDeepCopy]; + cd.entitlementsFiltered = NO; + } } } cd.quarantineURL = fileInfo.quarantineDataURL; @@ -225,17 +237,25 @@ - (nonnull SNTCachedDecision *)decisionForFileInfo:(nonnull SNTFileInfo *)fileIn } - (nonnull SNTCachedDecision *)decisionForFileInfo:(nonnull SNTFileInfo *)fileInfo - targetProcess:(nonnull const es_process_t *)targetProc { + targetProcess:(nonnull const es_process_t *)targetProc + entitlementsFilterCallback: + (NSDictionary *_Nullable (^_Nonnull)( + const char *_Nullable teamID, + NSDictionary *_Nullable entitlements))entitlementsFilterCallback { NSString *signingID; NSString *teamID; + const char *entitlementsFilterTeamID = NULL; + if (targetProc->signing_id.length > 0) { if (targetProc->team_id.length > 0) { + entitlementsFilterTeamID = targetProc->team_id.data; teamID = [NSString stringWithUTF8String:targetProc->team_id.data]; signingID = [NSString stringWithFormat:@"%@:%@", teamID, [NSString stringWithUTF8String:targetProc->signing_id.data]]; } else if (targetProc->is_platform_binary) { + entitlementsFilterTeamID = "platform"; signingID = [NSString stringWithFormat:@"platform:%@", [NSString stringWithUTF8String:targetProc->signing_id.data]]; @@ -246,7 +266,10 @@ - (nonnull SNTCachedDecision *)decisionForFileInfo:(nonnull SNTFileInfo *)fileIn fileSHA256:nil certificateSHA256:nil teamID:teamID - signingID:signingID]; + signingID:signingID + entitlementsFilterCallback:^NSDictionary *(NSDictionary *entitlements) { + return entitlementsFilterCallback(entitlementsFilterTeamID, entitlements); + }]; } // Used by `$ santactl fileinfo`. @@ -263,7 +286,8 @@ - (nonnull SNTCachedDecision *)decisionForFilePath:(nonnull NSString *)filePath fileSHA256:fileSHA256 certificateSHA256:certificateSHA256 teamID:teamID - signingID:signingID]; + signingID:signingID + entitlementsFilterCallback:nil]; } /// diff --git a/Source/santad/Santad.mm b/Source/santad/Santad.mm index fa24170db..77b2bb41a 100644 --- a/Source/santad/Santad.mm +++ b/Source/santad/Santad.mm @@ -358,6 +358,52 @@ void SantadMain(std::shared_ptr esapi, std::shared_ptr *oldValue, NSArray *newValue) { + if ((!oldValue && !newValue) || [oldValue isEqualToArray:newValue]) { + return; + } + + LOGI(@"EntitlementsTeamIDFilter changed. '%@' --> '%@'. Flushing caches.", oldValue, + newValue); + + // Get the value from the configurator since that method ensures proper structure + [exec_controller + updateEntitlementsTeamIDFilter:[configurator entitlementsTeamIDFilter]]; + + // Clear the AuthResultCache, then clear the ES cache to ensure + // future execs get SNTCachedDecision entitlement values filtered + // with the new settings. + auth_result_cache->FlushCache(FlushCacheMode::kAllCaches, + FlushCacheReason::kEntitlementsTeamIDFilterChanged); + [authorizer_client clearCache]; + }], + [[SNTKVOManager alloc] + initWithObject:configurator + selector:@selector(entitlementsPrefixFilter) + type:[NSArray class] + callback:^(NSArray *oldValue, NSArray *newValue) { + if ((!oldValue && !newValue) || [oldValue isEqualToArray:newValue]) { + return; + } + + LOGI(@"EntitlementsPrefixFilter changed. '%@' --> '%@'. Flushing caches.", oldValue, + newValue); + + // Get the value from the configurator since that method ensures proper structure + [exec_controller + updateEntitlementsPrefixFilter:[configurator entitlementsPrefixFilter]]; + + // Clear the AuthResultCache, then clear the ES cache to ensure + // future execs get SNTCachedDecision entitlement values filtered + // with the new settings. + auth_result_cache->FlushCache(FlushCacheMode::kAllCaches, + FlushCacheReason::kEntitlementsPrefixFilterChanged); + [authorizer_client clearCache]; + }], ]]; if (@available(macOS 13.0, *)) { diff --git a/Source/santad/SantadDeps.mm b/Source/santad/SantadDeps.mm index 25643aba3..d2c4b2d60 100644 --- a/Source/santad/SantadDeps.mm +++ b/Source/santad/SantadDeps.mm @@ -94,7 +94,9 @@ eventTable:event_table notifierQueue:notifier_queue syncdQueue:syncd_queue - ttyWriter:tty_writer]; + ttyWriter:tty_writer + entitlementsPrefixFilter:[configurator entitlementsPrefixFilter] + entitlementsTeamIDFilter:[configurator entitlementsTeamIDFilter]]; if (!exec_controller) { LOGE(@"Failed to initialize exec controller."); exit(EXIT_FAILURE); diff --git a/Source/santad/testdata/protobuf/v1/exec.json b/Source/santad/testdata/protobuf/v1/exec.json index 72f1760cb..7ae9e156a 100644 --- a/Source/santad/testdata/protobuf/v1/exec.json +++ b/Source/santad/testdata/protobuf/v1/exec.json @@ -121,42 +121,45 @@ }, "explain": "extra!", "quarantine_url": "google.com", - "entitlements": [ - { - "key": "key_with_arr_val_multitype", - "value": "[\"v1\",\"v2\",\"v3\",123,\"2023-11-07T17:00:02.000Z\"]" - }, - { - "key": "key_with_data_val", - "value": "\"SGVsbG8gV29ybGQ=\"" - }, - { - "key": "key_with_str_val", - "value": "\"bar\"" - }, - { - "key": "key_with_arr_val_nested", - "value": "[\"v1\",\"v2\",\"v3\",[\"nv1\",\"nv2\"]]" - }, - { - "key": "key_with_dict_val", - "value": "{\"k2\":\"v2\",\"k1\":\"v1\"}" - }, - { - "key": "key_with_date_val", - "value": "\"2023-11-07T17:00:02.000Z\"" - }, - { - "key": "key_with_dict_val_nested", - "value": "{\"k3\":{\"nk1\":\"nv1\",\"nk2\":\"2023-11-07T17:00:02.000Z\"},\"k2\":\"v2\",\"k1\":\"v1\"}" - }, - { - "key": "key_with_num_val", - "value": "1234" - }, - { - "key": "key_with_arr_val", - "value": "[\"v1\",\"v2\",\"v3\"]" - } - ] + "entitlement_info": { + "entitlements_filtered": false, + "entitlements": [ + { + "key": "key_with_arr_val_multitype", + "value": "[\"v1\",\"v2\",\"v3\",123,\"2023-11-07T17:00:02.000Z\"]" + }, + { + "key": "key_with_data_val", + "value": "\"SGVsbG8gV29ybGQ=\"" + }, + { + "key": "key_with_str_val", + "value": "\"bar\"" + }, + { + "key": "key_with_arr_val_nested", + "value": "[\"v1\",\"v2\",\"v3\",[\"nv1\",\"nv2\"]]" + }, + { + "key": "key_with_dict_val", + "value": "{\"k2\":\"v2\",\"k1\":\"v1\"}" + }, + { + "key": "key_with_date_val", + "value": "\"2023-11-07T17:00:02.000Z\"" + }, + { + "key": "key_with_dict_val_nested", + "value": "{\"k3\":{\"nk1\":\"nv1\",\"nk2\":\"2023-11-07T17:00:02.000Z\"},\"k2\":\"v2\",\"k1\":\"v1\"}" + }, + { + "key": "key_with_num_val", + "value": "1234" + }, + { + "key": "key_with_arr_val", + "value": "[\"v1\",\"v2\",\"v3\"]" + } + ] + } } diff --git a/Source/santad/testdata/protobuf/v2/exec.json b/Source/santad/testdata/protobuf/v2/exec.json index 95f2d81ac..92b030dad 100644 --- a/Source/santad/testdata/protobuf/v2/exec.json +++ b/Source/santad/testdata/protobuf/v2/exec.json @@ -149,42 +149,45 @@ }, "explain": "extra!", "quarantine_url": "google.com", - "entitlements": [ - { - "key": "key_with_arr_val_multitype", - "value": "[\"v1\",\"v2\",\"v3\",123,\"2023-11-07T17:00:02.000Z\"]" - }, - { - "key": "key_with_data_val", - "value": "\"SGVsbG8gV29ybGQ=\"" - }, - { - "key": "key_with_str_val", - "value": "\"bar\"" - }, - { - "key": "key_with_arr_val_nested", - "value": "[\"v1\",\"v2\",\"v3\",[\"nv1\",\"nv2\"]]" - }, - { - "key": "key_with_dict_val", - "value": "{\"k2\":\"v2\",\"k1\":\"v1\"}" - }, - { - "key": "key_with_date_val", - "value": "\"2023-11-07T17:00:02.000Z\"" - }, - { - "key": "key_with_dict_val_nested", - "value": "{\"k3\":{\"nk1\":\"nv1\",\"nk2\":\"2023-11-07T17:00:02.000Z\"},\"k2\":\"v2\",\"k1\":\"v1\"}" - }, - { - "key": "key_with_num_val", - "value": "1234" - }, - { - "key": "key_with_arr_val", - "value": "[\"v1\",\"v2\",\"v3\"]" - } - ] + "entitlement_info": { + "entitlements_filtered": false, + "entitlements": [ + { + "key": "key_with_arr_val_multitype", + "value": "[\"v1\",\"v2\",\"v3\",123,\"2023-11-07T17:00:02.000Z\"]" + }, + { + "key": "key_with_data_val", + "value": "\"SGVsbG8gV29ybGQ=\"" + }, + { + "key": "key_with_str_val", + "value": "\"bar\"" + }, + { + "key": "key_with_arr_val_nested", + "value": "[\"v1\",\"v2\",\"v3\",[\"nv1\",\"nv2\"]]" + }, + { + "key": "key_with_dict_val", + "value": "{\"k2\":\"v2\",\"k1\":\"v1\"}" + }, + { + "key": "key_with_date_val", + "value": "\"2023-11-07T17:00:02.000Z\"" + }, + { + "key": "key_with_dict_val_nested", + "value": "{\"k3\":{\"nk1\":\"nv1\",\"nk2\":\"2023-11-07T17:00:02.000Z\"},\"k2\":\"v2\",\"k1\":\"v1\"}" + }, + { + "key": "key_with_num_val", + "value": "1234" + }, + { + "key": "key_with_arr_val", + "value": "[\"v1\",\"v2\",\"v3\"]" + } + ] + } } diff --git a/Source/santad/testdata/protobuf/v4/exec.json b/Source/santad/testdata/protobuf/v4/exec.json index 4781ef27b..684c4a86e 100644 --- a/Source/santad/testdata/protobuf/v4/exec.json +++ b/Source/santad/testdata/protobuf/v4/exec.json @@ -198,42 +198,45 @@ }, "explain": "extra!", "quarantine_url": "google.com", - "entitlements": [ - { - "key": "key_with_arr_val_multitype", - "value": "[\"v1\",\"v2\",\"v3\",123,\"2023-11-07T17:00:02.000Z\"]" - }, - { - "key": "key_with_data_val", - "value": "\"SGVsbG8gV29ybGQ=\"" - }, - { - "key": "key_with_str_val", - "value": "\"bar\"" - }, - { - "key": "key_with_arr_val_nested", - "value": "[\"v1\",\"v2\",\"v3\",[\"nv1\",\"nv2\"]]" - }, - { - "key": "key_with_dict_val", - "value": "{\"k2\":\"v2\",\"k1\":\"v1\"}" - }, - { - "key": "key_with_date_val", - "value": "\"2023-11-07T17:00:02.000Z\"" - }, - { - "key": "key_with_dict_val_nested", - "value": "{\"k3\":{\"nk1\":\"nv1\",\"nk2\":\"2023-11-07T17:00:02.000Z\"},\"k2\":\"v2\",\"k1\":\"v1\"}" - }, - { - "key": "key_with_num_val", - "value": "1234" - }, - { - "key": "key_with_arr_val", - "value": "[\"v1\",\"v2\",\"v3\"]" - } - ] + "entitlement_info": { + "entitlements_filtered": false, + "entitlements": [ + { + "key": "key_with_arr_val_multitype", + "value": "[\"v1\",\"v2\",\"v3\",123,\"2023-11-07T17:00:02.000Z\"]" + }, + { + "key": "key_with_data_val", + "value": "\"SGVsbG8gV29ybGQ=\"" + }, + { + "key": "key_with_str_val", + "value": "\"bar\"" + }, + { + "key": "key_with_arr_val_nested", + "value": "[\"v1\",\"v2\",\"v3\",[\"nv1\",\"nv2\"]]" + }, + { + "key": "key_with_dict_val", + "value": "{\"k2\":\"v2\",\"k1\":\"v1\"}" + }, + { + "key": "key_with_date_val", + "value": "\"2023-11-07T17:00:02.000Z\"" + }, + { + "key": "key_with_dict_val_nested", + "value": "{\"k3\":{\"nk1\":\"nv1\",\"nk2\":\"2023-11-07T17:00:02.000Z\"},\"k2\":\"v2\",\"k1\":\"v1\"}" + }, + { + "key": "key_with_num_val", + "value": "1234" + }, + { + "key": "key_with_arr_val", + "value": "[\"v1\",\"v2\",\"v3\"]" + } + ] + } } diff --git a/Source/santad/testdata/protobuf/v5/exec.json b/Source/santad/testdata/protobuf/v5/exec.json index e6b6ba94f..814e6e365 100644 --- a/Source/santad/testdata/protobuf/v5/exec.json +++ b/Source/santad/testdata/protobuf/v5/exec.json @@ -198,42 +198,45 @@ }, "explain": "extra!", "quarantine_url": "google.com", - "entitlements": [ - { - "key": "key_with_arr_val_multitype", - "value": "[\"v1\",\"v2\",\"v3\",123,\"2023-11-07T17:00:02.000Z\"]" - }, - { - "key": "key_with_data_val", - "value": "\"SGVsbG8gV29ybGQ=\"" - }, - { - "key": "key_with_str_val", - "value": "\"bar\"" - }, - { - "key": "key_with_arr_val_nested", - "value": "[\"v1\",\"v2\",\"v3\",[\"nv1\",\"nv2\"]]" - }, - { - "key": "key_with_dict_val", - "value": "{\"k2\":\"v2\",\"k1\":\"v1\"}" - }, - { - "key": "key_with_date_val", - "value": "\"2023-11-07T17:00:02.000Z\"" - }, - { - "key": "key_with_dict_val_nested", - "value": "{\"k3\":{\"nk1\":\"nv1\",\"nk2\":\"2023-11-07T17:00:02.000Z\"},\"k2\":\"v2\",\"k1\":\"v1\"}" - }, - { - "key": "key_with_num_val", - "value": "1234" - }, - { - "key": "key_with_arr_val", - "value": "[\"v1\",\"v2\",\"v3\"]" - } - ] + "entitlement_info": { + "entitlements_filtered": false, + "entitlements": [ + { + "key": "key_with_arr_val_multitype", + "value": "[\"v1\",\"v2\",\"v3\",123,\"2023-11-07T17:00:02.000Z\"]" + }, + { + "key": "key_with_data_val", + "value": "\"SGVsbG8gV29ybGQ=\"" + }, + { + "key": "key_with_str_val", + "value": "\"bar\"" + }, + { + "key": "key_with_arr_val_nested", + "value": "[\"v1\",\"v2\",\"v3\",[\"nv1\",\"nv2\"]]" + }, + { + "key": "key_with_dict_val", + "value": "{\"k2\":\"v2\",\"k1\":\"v1\"}" + }, + { + "key": "key_with_date_val", + "value": "\"2023-11-07T17:00:02.000Z\"" + }, + { + "key": "key_with_dict_val_nested", + "value": "{\"k3\":{\"nk1\":\"nv1\",\"nk2\":\"2023-11-07T17:00:02.000Z\"},\"k2\":\"v2\",\"k1\":\"v1\"}" + }, + { + "key": "key_with_num_val", + "value": "1234" + }, + { + "key": "key_with_arr_val", + "value": "[\"v1\",\"v2\",\"v3\"]" + } + ] + } } diff --git a/Source/santad/testdata/protobuf/v6/exec.json b/Source/santad/testdata/protobuf/v6/exec.json index 4781ef27b..684c4a86e 100644 --- a/Source/santad/testdata/protobuf/v6/exec.json +++ b/Source/santad/testdata/protobuf/v6/exec.json @@ -198,42 +198,45 @@ }, "explain": "extra!", "quarantine_url": "google.com", - "entitlements": [ - { - "key": "key_with_arr_val_multitype", - "value": "[\"v1\",\"v2\",\"v3\",123,\"2023-11-07T17:00:02.000Z\"]" - }, - { - "key": "key_with_data_val", - "value": "\"SGVsbG8gV29ybGQ=\"" - }, - { - "key": "key_with_str_val", - "value": "\"bar\"" - }, - { - "key": "key_with_arr_val_nested", - "value": "[\"v1\",\"v2\",\"v3\",[\"nv1\",\"nv2\"]]" - }, - { - "key": "key_with_dict_val", - "value": "{\"k2\":\"v2\",\"k1\":\"v1\"}" - }, - { - "key": "key_with_date_val", - "value": "\"2023-11-07T17:00:02.000Z\"" - }, - { - "key": "key_with_dict_val_nested", - "value": "{\"k3\":{\"nk1\":\"nv1\",\"nk2\":\"2023-11-07T17:00:02.000Z\"},\"k2\":\"v2\",\"k1\":\"v1\"}" - }, - { - "key": "key_with_num_val", - "value": "1234" - }, - { - "key": "key_with_arr_val", - "value": "[\"v1\",\"v2\",\"v3\"]" - } - ] + "entitlement_info": { + "entitlements_filtered": false, + "entitlements": [ + { + "key": "key_with_arr_val_multitype", + "value": "[\"v1\",\"v2\",\"v3\",123,\"2023-11-07T17:00:02.000Z\"]" + }, + { + "key": "key_with_data_val", + "value": "\"SGVsbG8gV29ybGQ=\"" + }, + { + "key": "key_with_str_val", + "value": "\"bar\"" + }, + { + "key": "key_with_arr_val_nested", + "value": "[\"v1\",\"v2\",\"v3\",[\"nv1\",\"nv2\"]]" + }, + { + "key": "key_with_dict_val", + "value": "{\"k2\":\"v2\",\"k1\":\"v1\"}" + }, + { + "key": "key_with_date_val", + "value": "\"2023-11-07T17:00:02.000Z\"" + }, + { + "key": "key_with_dict_val_nested", + "value": "{\"k3\":{\"nk1\":\"nv1\",\"nk2\":\"2023-11-07T17:00:02.000Z\"},\"k2\":\"v2\",\"k1\":\"v1\"}" + }, + { + "key": "key_with_num_val", + "value": "1234" + }, + { + "key": "key_with_arr_val", + "value": "[\"v1\",\"v2\",\"v3\"]" + } + ] + } } diff --git a/docs/deployment/configuration.md b/docs/deployment/configuration.md index 6147103ba..a87ccafd3 100644 --- a/docs/deployment/configuration.md +++ b/docs/deployment/configuration.md @@ -77,7 +77,9 @@ also known as mobileconfig files, which are in an Apple-specific XML format. | FileAccessPolicyUpdateIntervalSec | Integer | Number of seconds between re-reading the file access policy config and policies/monitored paths updated. | | SyncClientContentEncoding | String | Sets the Content-Encoding header for requests sent to the sync service. Acceptable values are "deflate", "gzip", "none" (Defaults to deflate.) | | SyncExtraHeaders | Dictionary | Dictionary of additional headers to include in all requests made to the sync server. System managed headers such as Content-Length, Host, WWW-Authenticate etc will be ignored. | -| EnableDebugLogging | Bool | If YES, the client will log additional debug messages to the Apple Unified Log. For example, transitive rule creation logs can be viewed with `log stream --predicate 'sender=="com.google.santa.daemon"'`. Defaults to NO. | +| EnableDebugLogging | Bool | If YES, the client will log additional debug messages to the Apple Unified Log. For example, transitive rule creation logs can be viewed with `log stream --predicate 'sender=="com.google.santa.daemon"'`. Defaults to NO. | +| EntitlementsPrefixFilter | Array | Array of strings of entitlement prefixes that should not be logged (for example: `com.apple.private`). No default. | +| EntitlementsTeamIDFilter | Array | Array of TeamID strings. Entitlements from processes with a matching TeamID in the code signature will not be logged. Use the value `platform` to filter entitlements from platform binaries. No default. | \*overridable by the sync server: run `santactl status` to check the current