mirror of
https://github.com/google/santa.git
synced 2026-01-17 18:28:01 -05:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
221664436f | ||
|
|
65c660298c | ||
|
|
2b5d55781c | ||
|
|
84e6d6ccff | ||
|
|
c16f90f5f9 | ||
|
|
d503eae4d9 | ||
|
|
818518bb38 | ||
|
|
f499654951 | ||
|
|
a5e8d77d06 | ||
|
|
edac42e8b8 | ||
|
|
ce5e3d0ee4 | ||
|
|
3e51ec6b8a | ||
|
|
ed227f43d4 | ||
|
|
056ed75bf1 |
9
.github/workflows/check-markdown.yml
vendored
9
.github/workflows/check-markdown.yml
vendored
@@ -9,6 +9,9 @@ jobs:
|
||||
markdown-check:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- uses: gaurav-nelson/github-action-markdown-link-check@v1
|
||||
- run: "! git grep -EIn $'[ \t]+$' -- ':(exclude)*.patch'"
|
||||
- name: "Checkout Santa"
|
||||
uses: actions/checkout@61b9e3751b92087fd0b06925ba6dd6314e06f089 # ratchet:actions/checkout@master
|
||||
- name: "Check for deadlinks"
|
||||
uses: gaurav-nelson/github-action-markdown-link-check@5c5dfc0ac2e225883c0e5f03a85311ec2830d368 # ratchet:gaurav-nelson/github-action-markdown-link-check@v1
|
||||
- name: "Check for trailing whitespace and newlines"
|
||||
run: "! git grep -EIn $'[ \t]+$' -- ':(exclude)*.patch'"
|
||||
|
||||
5
.github/workflows/e2e.yml
vendored
5
.github/workflows/e2e.yml
vendored
@@ -34,7 +34,10 @@ jobs:
|
||||
bazel run //Testing/integration:allow_sysex
|
||||
sudo santactl sync --debug
|
||||
- name: Run integration test binaries
|
||||
run: bazel test //Testing/integration:integration_tests
|
||||
run: |
|
||||
bazel test //Testing/integration:integration_tests
|
||||
sleep 3
|
||||
bazel run //Testing/integration:dismiss_santa_popup || true
|
||||
- name: Test config changes
|
||||
run: ./Testing/integration/test_config_changes.sh
|
||||
- name: Test sync server changes
|
||||
|
||||
@@ -40,6 +40,12 @@ objc_library(
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTDeepCopy",
|
||||
srcs = ["SNTDeepCopy.m"],
|
||||
hdrs = ["SNTDeepCopy.h"],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "SantaCache",
|
||||
hdrs = ["SantaCache.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:
|
||||
|
||||
@@ -38,6 +38,8 @@
|
||||
@property NSArray<MOLCertificate *> *certChain;
|
||||
@property NSString *teamID;
|
||||
@property NSString *signingID;
|
||||
@property NSDictionary *entitlements;
|
||||
@property BOOL entitlementsFiltered;
|
||||
|
||||
@property NSString *quarantineURL;
|
||||
|
||||
|
||||
@@ -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.
|
||||
///
|
||||
|
||||
@@ -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;
|
||||
@@ -116,6 +131,9 @@ static NSString *const kFCMProject = @"FCMProject";
|
||||
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 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
kEnableAllEventUploadKey : number,
|
||||
kDisableUnknownEventUploadKey : number,
|
||||
kOverrideFileAccessActionKey : string,
|
||||
kEntitlementsPrefixFilterKey : array,
|
||||
kEntitlementsTeamIDFilterKey : array,
|
||||
};
|
||||
_defaults = [NSUserDefaults standardUserDefaults];
|
||||
[_defaults addSuiteNamed:@"com.google.santa"];
|
||||
@@ -527,6 +547,14 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
return [self syncAndConfigStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingEntitlementsPrefixFilter {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingEntitlementsTeamIDFilter {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
#pragma mark Public Interface
|
||||
|
||||
- (SNTClientMode)clientMode {
|
||||
@@ -1111,6 +1139,14 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
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 {
|
||||
|
||||
27
Source/common/SNTDeepCopy.h
Normal file
27
Source/common/SNTDeepCopy.h
Normal file
@@ -0,0 +1,27 @@
|
||||
/// Copyright 2023 Google LLC
|
||||
///
|
||||
/// 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
|
||||
///
|
||||
/// https://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.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface NSArray (SNTDeepCopy)
|
||||
|
||||
- (instancetype)sntDeepCopy;
|
||||
|
||||
@end
|
||||
|
||||
@interface NSDictionary (SNTDeepCopy)
|
||||
|
||||
- (instancetype)sntDeepCopy;
|
||||
|
||||
@end
|
||||
53
Source/common/SNTDeepCopy.m
Normal file
53
Source/common/SNTDeepCopy.m
Normal file
@@ -0,0 +1,53 @@
|
||||
/// Copyright 2023 Google LLC
|
||||
///
|
||||
/// 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
|
||||
///
|
||||
/// https://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.
|
||||
|
||||
#import "Source/common/SNTDeepCopy.h"
|
||||
|
||||
@implementation NSArray (SNTDeepCopy)
|
||||
|
||||
- (instancetype)sntDeepCopy {
|
||||
NSMutableArray<__kindof NSObject *> *deepCopy = [NSMutableArray arrayWithCapacity:self.count];
|
||||
for (id object in self) {
|
||||
if ([object respondsToSelector:@selector(sntDeepCopy)]) {
|
||||
[deepCopy addObject:[object sntDeepCopy]];
|
||||
} else if ([object respondsToSelector:@selector(copyWithZone:)]) {
|
||||
[deepCopy addObject:[object copy]];
|
||||
} else {
|
||||
[deepCopy addObject:object];
|
||||
}
|
||||
}
|
||||
return deepCopy;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation NSDictionary (SNTDeepCopy)
|
||||
|
||||
- (instancetype)sntDeepCopy {
|
||||
NSMutableDictionary<__kindof NSObject *, __kindof NSObject *> *deepCopy =
|
||||
[NSMutableDictionary dictionary];
|
||||
for (id key in self) {
|
||||
id value = self[key];
|
||||
if ([value respondsToSelector:@selector(sntDeepCopy)]) {
|
||||
deepCopy[key] = [value sntDeepCopy];
|
||||
} else if ([value respondsToSelector:@selector(copyWithZone:)]) {
|
||||
deepCopy[key] = [value copy];
|
||||
} else {
|
||||
deepCopy[key] = value;
|
||||
}
|
||||
}
|
||||
return deepCopy;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -71,6 +71,8 @@
|
||||
- (void)syncCleanRequired:(void (^)(BOOL))reply;
|
||||
- (void)enableBundles:(void (^)(BOOL))reply;
|
||||
- (void)enableTransitiveRules:(void (^)(BOOL))reply;
|
||||
- (void)blockUSBMount:(void (^)(BOOL))reply;
|
||||
- (void)remountUSBMode:(void (^)(NSArray<NSString *> *))reply;
|
||||
|
||||
///
|
||||
/// Metrics ops
|
||||
|
||||
@@ -245,7 +245,7 @@ struct S {
|
||||
uint64_t first_val;
|
||||
uint64_t second_val;
|
||||
|
||||
bool operator==(const S &rhs) {
|
||||
bool operator==(const S &rhs) const {
|
||||
return first_val == rhs.first_val && second_val == rhs.second_val;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -92,7 +92,11 @@ es_process_t MakeESProcess(es_file_t *file, audit_token_t tok, audit_token_t par
|
||||
}
|
||||
|
||||
uint32_t MaxSupportedESMessageVersionForCurrentOS() {
|
||||
// Note: ES message v3 was only in betas.
|
||||
// Notes:
|
||||
// 1. ES message v3 was only in betas.
|
||||
// 2. Message version 7 appeared in macOS 13.3, but features from that are
|
||||
// not currently used. Leaving off support here so as to not require
|
||||
// adding v7 test JSON files.
|
||||
if (@available(macOS 13.0, *)) {
|
||||
return 6;
|
||||
} else if (@available(macOS 12.3, *)) {
|
||||
|
||||
@@ -213,6 +213,27 @@ message CertificateInfo {
|
||||
optional string common_name = 2;
|
||||
}
|
||||
|
||||
// Information about a single entitlement key/value pair
|
||||
message Entitlement {
|
||||
// The name of an entitlement
|
||||
optional string key = 1;
|
||||
|
||||
// The value of an 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
|
||||
@@ -286,6 +307,9 @@ message Execution {
|
||||
// The original path on disk of the target executable
|
||||
// Applies when executables are translocated
|
||||
optional string original_path = 15;
|
||||
|
||||
// Entitlement information about the target executbale
|
||||
optional EntitlementInfo entitlement_info = 16;
|
||||
}
|
||||
|
||||
// Information about a fork event
|
||||
|
||||
@@ -35,10 +35,6 @@ macos_command_line_application(
|
||||
],
|
||||
infoplists = ["Info.plist"],
|
||||
minimum_os_version = "11.0",
|
||||
provisioning_profile = select({
|
||||
"//:adhoc_build": None,
|
||||
"//conditions:default": "//profiles:santa_dev",
|
||||
}),
|
||||
version = "//:version",
|
||||
visibility = ["//:santa_package_group"],
|
||||
deps = [":santabs_lib"],
|
||||
|
||||
@@ -92,10 +92,6 @@ macos_command_line_application(
|
||||
],
|
||||
infoplists = ["Info.plist"],
|
||||
minimum_os_version = "11.0",
|
||||
provisioning_profile = select({
|
||||
"//:adhoc_build": None,
|
||||
"//conditions:default": "//profiles:santa_dev",
|
||||
}),
|
||||
version = "//:version",
|
||||
deps = [":santactl_lib"],
|
||||
)
|
||||
|
||||
@@ -56,7 +56,6 @@ REGISTER_COMMAND_NAME(@"status")
|
||||
}
|
||||
|
||||
- (void)runWithArguments:(NSArray *)arguments {
|
||||
dispatch_group_t group = dispatch_group_create();
|
||||
id<SNTDaemonControlXPC> rop = [self.daemonConn synchronousRemoteObjectProxy];
|
||||
|
||||
// Daemon status
|
||||
@@ -169,10 +168,15 @@ REGISTER_COMMAND_NAME(@"status")
|
||||
}
|
||||
}];
|
||||
|
||||
// Wait a maximum of 5s for stats collected from daemon to arrive.
|
||||
if (dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 5))) {
|
||||
fprintf(stderr, "Failed to retrieve some stats from daemon\n\n");
|
||||
}
|
||||
__block BOOL blockUSBMount = NO;
|
||||
[rop blockUSBMount:^(BOOL response) {
|
||||
blockUSBMount = response;
|
||||
}];
|
||||
|
||||
__block NSArray<NSString *> *remountUSBMode;
|
||||
[rop remountUSBMode:^(NSArray<NSString *> *response) {
|
||||
remountUSBMode = response;
|
||||
}];
|
||||
|
||||
// Format dates
|
||||
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
|
||||
@@ -202,10 +206,8 @@ REGISTER_COMMAND_NAME(@"status")
|
||||
@"watchdog_ram_events" : @(ramEvents),
|
||||
@"watchdog_cpu_peak" : @(cpuPeak),
|
||||
@"watchdog_ram_peak" : @(ramPeak),
|
||||
@"block_usb" : @(configurator.blockUSBMount),
|
||||
@"remount_usb_mode" : (configurator.blockUSBMount && configurator.remountUSBMode.count
|
||||
? configurator.remountUSBMode
|
||||
: @""),
|
||||
@"block_usb" : @(blockUSBMount),
|
||||
@"remount_usb_mode" : (blockUSBMount && remountUSBMode.count ? remountUSBMode : @""),
|
||||
@"on_start_usb_options" : StartupOptionToString(configurator.onStartUSBOptions),
|
||||
},
|
||||
@"database" : @{
|
||||
@@ -262,10 +264,10 @@ REGISTER_COMMAND_NAME(@"status")
|
||||
printf(" %-25s | %s\n", "Mode", [clientMode UTF8String]);
|
||||
printf(" %-25s | %s\n", "Log Type", [eventLogType UTF8String]);
|
||||
printf(" %-25s | %s\n", "File Logging", (fileLogging ? "Yes" : "No"));
|
||||
printf(" %-25s | %s\n", "USB Blocking", (configurator.blockUSBMount ? "Yes" : "No"));
|
||||
if (configurator.blockUSBMount && configurator.remountUSBMode.count > 0) {
|
||||
printf(" %-25s | %s\n", "USB Blocking", (blockUSBMount ? "Yes" : "No"));
|
||||
if (blockUSBMount && remountUSBMode.count > 0) {
|
||||
printf(" %-25s | %s\n", "USB Remounting Mode",
|
||||
[[configurator.remountUSBMode componentsJoinedByString:@", "] UTF8String]);
|
||||
[[remountUSBMode componentsJoinedByString:@", "] UTF8String]);
|
||||
}
|
||||
printf(" %-25s | %s\n", "On Start USB Options",
|
||||
StartupOptionToString(configurator.onStartUSBOptions).UTF8String);
|
||||
|
||||
@@ -199,6 +199,7 @@ objc_library(
|
||||
"//Source/common:SNTCachedDecision",
|
||||
"//Source/common:SNTCommonEnums",
|
||||
"//Source/common:SNTConfigurator",
|
||||
"//Source/common:SNTDeepCopy",
|
||||
"//Source/common:SNTFileInfo",
|
||||
"//Source/common:SNTLogging",
|
||||
"//Source/common:SNTRule",
|
||||
@@ -236,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",
|
||||
@@ -248,7 +251,9 @@ objc_library(
|
||||
"//Source/common:SNTStoredEvent",
|
||||
"//Source/common:SantaVnode",
|
||||
"//Source/common:String",
|
||||
"//Source/common:Unit",
|
||||
"@MOLCodesignChecker",
|
||||
"@com_google_absl//absl/synchronization",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -41,6 +41,8 @@ enum class FlushCacheReason {
|
||||
kStaticRulesChanged,
|
||||
kExplicitCommand,
|
||||
kFilesystemUnmounted,
|
||||
kEntitlementsPrefixFilterChanged,
|
||||
kEntitlementsTeamIDFilterChanged,
|
||||
};
|
||||
|
||||
class AuthResultCache {
|
||||
|
||||
@@ -31,6 +31,10 @@ static NSString *const kFlushCacheReasonRulesChanged = @"RulesChanged";
|
||||
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 @@ NSString *const FlushCacheReasonToString(FlushCacheReason reason) {
|
||||
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)];
|
||||
|
||||
@@ -230,13 +230,16 @@ static inline void AssertCacheCounts(std::shared_ptr<AuthResultCache> cache, uin
|
||||
{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
|
||||
|
||||
@@ -322,11 +322,14 @@ using santa::santad::event_providers::endpoint_security::Message;
|
||||
|
||||
// Ensure all paths are attempted to be muted even if some fail.
|
||||
// Ensure if any paths fail the overall result is false.
|
||||
EXPECT_CALL(*mockESApi, MuteTargetPath(testing::_, "a", WatchItemPathType::kLiteral))
|
||||
EXPECT_CALL(*mockESApi,
|
||||
MuteTargetPath(testing::_, std::string_view("a"), WatchItemPathType::kLiteral))
|
||||
.WillOnce(testing::Return(true));
|
||||
EXPECT_CALL(*mockESApi, MuteTargetPath(testing::_, "b", WatchItemPathType::kLiteral))
|
||||
EXPECT_CALL(*mockESApi,
|
||||
MuteTargetPath(testing::_, std::string_view("b"), WatchItemPathType::kLiteral))
|
||||
.WillOnce(testing::Return(false));
|
||||
EXPECT_CALL(*mockESApi, MuteTargetPath(testing::_, "c", WatchItemPathType::kPrefix))
|
||||
EXPECT_CALL(*mockESApi,
|
||||
MuteTargetPath(testing::_, std::string_view("c"), WatchItemPathType::kPrefix))
|
||||
.WillOnce(testing::Return(true));
|
||||
|
||||
std::vector<std::pair<std::string, WatchItemPathType>> paths = {
|
||||
@@ -349,11 +352,14 @@ using santa::santad::event_providers::endpoint_security::Message;
|
||||
|
||||
// Ensure all paths are attempted to be unmuted even if some fail.
|
||||
// Ensure if any paths fail the overall result is false.
|
||||
EXPECT_CALL(*mockESApi, UnmuteTargetPath(testing::_, "a", WatchItemPathType::kLiteral))
|
||||
EXPECT_CALL(*mockESApi,
|
||||
UnmuteTargetPath(testing::_, std::string_view("a"), WatchItemPathType::kLiteral))
|
||||
.WillOnce(testing::Return(true));
|
||||
EXPECT_CALL(*mockESApi, UnmuteTargetPath(testing::_, "b", WatchItemPathType::kLiteral))
|
||||
EXPECT_CALL(*mockESApi,
|
||||
UnmuteTargetPath(testing::_, std::string_view("b"), WatchItemPathType::kLiteral))
|
||||
.WillOnce(testing::Return(false));
|
||||
EXPECT_CALL(*mockESApi, UnmuteTargetPath(testing::_, "c", WatchItemPathType::kPrefix))
|
||||
EXPECT_CALL(*mockESApi,
|
||||
UnmuteTargetPath(testing::_, std::string_view("c"), WatchItemPathType::kPrefix))
|
||||
.WillOnce(testing::Return(true));
|
||||
|
||||
std::vector<std::pair<std::string, WatchItemPathType>> paths = {
|
||||
|
||||
@@ -71,6 +71,9 @@ namespace pbv1 = ::santa::pb::v1;
|
||||
|
||||
namespace santa::santad::logs::endpoint_security::serializers {
|
||||
|
||||
static constexpr NSUInteger kMaxEncodeObjectEntries = 64;
|
||||
static constexpr NSUInteger kMaxEncodeObjectLevels = 5;
|
||||
|
||||
std::shared_ptr<Protobuf> Protobuf::Create(std::shared_ptr<EndpointSecurityAPI> esapi,
|
||||
SNTDecisionCache *decision_cache, bool json) {
|
||||
return std::make_shared<Protobuf>(esapi, std::move(decision_cache), json);
|
||||
@@ -449,6 +452,124 @@ std::vector<uint8_t> Protobuf::SerializeMessage(const EnrichedExchange &msg) {
|
||||
return FinalizeProto(santa_msg);
|
||||
}
|
||||
|
||||
id StandardizedNestedObjects(id obj, int level) {
|
||||
if (level-- == 0) {
|
||||
return [obj description];
|
||||
}
|
||||
|
||||
if ([obj isKindOfClass:[NSNumber class]] || [obj isKindOfClass:[NSString class]]) {
|
||||
return obj;
|
||||
} else if ([obj isKindOfClass:[NSArray class]]) {
|
||||
NSMutableArray *arr = [NSMutableArray array];
|
||||
for (id item in obj) {
|
||||
[arr addObject:StandardizedNestedObjects(item, level)];
|
||||
}
|
||||
return arr;
|
||||
} else if ([obj isKindOfClass:[NSDictionary class]]) {
|
||||
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
|
||||
for (id key in obj) {
|
||||
[dict setObject:StandardizedNestedObjects(obj[key], level) forKey:key];
|
||||
}
|
||||
return dict;
|
||||
} else if ([obj isKindOfClass:[NSData class]]) {
|
||||
return [obj base64EncodedStringWithOptions:0];
|
||||
} else if ([obj isKindOfClass:[NSDate class]]) {
|
||||
return [NSISO8601DateFormatter stringFromDate:obj
|
||||
timeZone:[NSTimeZone timeZoneWithAbbreviation:@"UTC"]
|
||||
formatOptions:NSISO8601DateFormatWithFractionalSeconds |
|
||||
NSISO8601DateFormatWithInternetDateTime];
|
||||
|
||||
} else {
|
||||
LOGW(@"Unexpected object encountered: %@", obj);
|
||||
return [obj description];
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
NSDictionary *entitlements = StandardizedNestedObjects(cd.entitlements, kMaxEncodeObjectLevels);
|
||||
|
||||
__block int numObjectsToEncode = (int)std::min(kMaxEncodeObjectEntries, entitlements.count);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (![key isKindOfClass:[NSString class]]) {
|
||||
LOGW(@"Skipping entitlement key with unexpected key type: %@", key);
|
||||
return;
|
||||
}
|
||||
|
||||
NSError *err;
|
||||
NSData *jsonData;
|
||||
@try {
|
||||
jsonData = [NSJSONSerialization dataWithJSONObject:obj
|
||||
options:NSJSONWritingFragmentsAllowed
|
||||
error:&err];
|
||||
} @catch (NSException *e) {
|
||||
LOGW(@"Encountered entitlement that cannot directly convert to JSON: %@: %@", key, obj);
|
||||
}
|
||||
|
||||
if (!jsonData) {
|
||||
// If the first attempt to serialize to JSON failed, get a string
|
||||
// representation of the object via the `description` method and attempt
|
||||
// to serialize that instead. Serialization can fail for a number of
|
||||
// reasons, such as arrays including invalid types.
|
||||
@try {
|
||||
jsonData = [NSJSONSerialization dataWithJSONObject:[obj description]
|
||||
options:NSJSONWritingFragmentsAllowed
|
||||
error:&err];
|
||||
} @catch (NSException *e) {
|
||||
LOGW(@"Unable to create fallback string: %@: %@", key, obj);
|
||||
}
|
||||
|
||||
if (!jsonData) {
|
||||
@try {
|
||||
// As a final fallback, simply serialize an error message so that the
|
||||
// entitlement key is still logged.
|
||||
jsonData = [NSJSONSerialization dataWithJSONObject:@"JSON Serialization Failed"
|
||||
options:NSJSONWritingFragmentsAllowed
|
||||
error:&err];
|
||||
} @catch (NSException *e) {
|
||||
// This shouldn't be able to happen...
|
||||
LOGW(@"Failed to serialize fallback error message");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This shouldn't be possible given the fallback code above. But handle it
|
||||
// just in case to prevent a crash.
|
||||
if (!jsonData) {
|
||||
LOGW(@"Failed to create valid JSON for entitlement: %@", key);
|
||||
return;
|
||||
}
|
||||
|
||||
::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(); },
|
||||
NSStringToUTF8StringView([[NSString alloc] initWithData:jsonData
|
||||
encoding:NSUTF8StringEncoding]));
|
||||
}];
|
||||
}
|
||||
|
||||
std::vector<uint8_t> Protobuf::SerializeMessage(const EnrichedExec &msg, SNTCachedDecision *cd) {
|
||||
Arena arena;
|
||||
::pbv1::SantaMessage *santa_msg = CreateDefaultProto(&arena, msg);
|
||||
@@ -525,6 +646,8 @@ std::vector<uint8_t> Protobuf::SerializeMessage(const EnrichedExec &msg, SNTCach
|
||||
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);
|
||||
|
||||
return FinalizeProto(santa_msg);
|
||||
}
|
||||
|
||||
|
||||
@@ -60,6 +60,7 @@ namespace pbv1 = ::santa::pb::v1;
|
||||
|
||||
namespace santa::santad::logs::endpoint_security::serializers {
|
||||
extern void EncodeExitStatus(::pbv1::Exit *pbExit, int exitStatus);
|
||||
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);
|
||||
@@ -68,6 +69,7 @@ extern ::pbv1::FileAccess::AccessType GetAccessType(es_event_type_t event_type);
|
||||
extern ::pbv1::FileAccess::PolicyDecision GetPolicyDecision(FileAccessPolicyDecision decision);
|
||||
} // namespace santa::santad::logs::endpoint_security::serializers
|
||||
|
||||
using santa::santad::logs::endpoint_security::serializers::EncodeEntitlements;
|
||||
using santa::santad::logs::endpoint_security::serializers::EncodeExitStatus;
|
||||
using santa::santad::logs::endpoint_security::serializers::GetAccessType;
|
||||
using santa::santad::logs::endpoint_security::serializers::GetDecisionEnum;
|
||||
@@ -166,28 +168,35 @@ std::string ConvertMessageToJsonString(const ::pbv1::SantaMessage &santaMsg) {
|
||||
return json;
|
||||
}
|
||||
|
||||
NSDictionary *findDelta(NSDictionary *a, NSDictionary *b) {
|
||||
NSMutableDictionary *delta = NSMutableDictionary.dictionary;
|
||||
NSDictionary *FindDelta(NSDictionary *want, NSDictionary *got) {
|
||||
NSMutableDictionary *delta = [NSMutableDictionary dictionary];
|
||||
delta[@"want"] = [NSMutableDictionary dictionary];
|
||||
delta[@"got"] = [NSMutableDictionary dictionary];
|
||||
|
||||
// Find objects in a that don't exist or are different in b.
|
||||
[a enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL *_Nonnull stop) {
|
||||
id otherObj = b[key];
|
||||
// Find objects in `want` that don't exist or are different in `got`.
|
||||
[want enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
|
||||
id otherObj = got[key];
|
||||
|
||||
if (![obj isEqual:otherObj]) {
|
||||
delta[key] = obj;
|
||||
if (!otherObj) {
|
||||
delta[@"want"][key] = obj;
|
||||
delta[@"got"][key] = @"Key missing";
|
||||
} else if (![obj isEqual:otherObj]) {
|
||||
delta[@"want"][key] = obj;
|
||||
delta[@"got"][key] = otherObj;
|
||||
}
|
||||
}];
|
||||
|
||||
// Find objects in the other dictionary that don't exist in self
|
||||
[b enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL *_Nonnull stop) {
|
||||
id aObj = a[key];
|
||||
// Find objects in `got` that don't exist in `want`
|
||||
[got enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
|
||||
id aObj = want[key];
|
||||
|
||||
if (!aObj) {
|
||||
delta[key] = obj;
|
||||
delta[@"want"][key] = @"Key missing";
|
||||
delta[@"got"][key] = obj;
|
||||
}
|
||||
}];
|
||||
|
||||
return delta;
|
||||
return [delta[@"want"] count] > 0 ? delta : nil;
|
||||
}
|
||||
|
||||
void SerializeAndCheck(es_event_type_t eventType,
|
||||
@@ -268,9 +277,9 @@ void SerializeAndCheck(es_event_type_t eventType,
|
||||
error:&jsonError];
|
||||
XCTAssertNil(jsonError, @"failed to parse got data as JSON");
|
||||
|
||||
XCTAssertNil(FindDelta(wantJSONDict, gotJSONDict));
|
||||
// Note: Uncomment this line to help create testfile JSON when the assert above fails
|
||||
// XCTAssertEqualObjects([NSString stringWithUTF8String:gotData.c_str()], wantData);
|
||||
NSDictionary *delta = findDelta(wantJSONDict, gotJSONDict);
|
||||
XCTAssertEqualObjects(@{}, delta);
|
||||
}
|
||||
|
||||
XCTBubbleMockVerifyAndClearExpectations(mockESApi.get());
|
||||
@@ -338,6 +347,22 @@ void SerializeAndCheckNonESEvents(
|
||||
self.testCachedDecision.quarantineURL = @"google.com";
|
||||
self.testCachedDecision.certSHA256 = @"5678_cert_hash";
|
||||
self.testCachedDecision.decisionClientMode = SNTClientModeLockdown;
|
||||
self.testCachedDecision.entitlements = @{
|
||||
@"key_with_str_val" : @"bar",
|
||||
@"key_with_num_val" : @(1234),
|
||||
@"key_with_date_val" : [NSDate dateWithTimeIntervalSince1970:1699376402],
|
||||
@"key_with_data_val" : [@"Hello World" dataUsingEncoding:NSUTF8StringEncoding],
|
||||
@"key_with_arr_val" : @[ @"v1", @"v2", @"v3" ],
|
||||
@"key_with_arr_val_nested" : @[ @"v1", @"v2", @"v3", @[ @"nv1", @"nv2" ] ],
|
||||
@"key_with_arr_val_multitype" :
|
||||
@[ @"v1", @"v2", @"v3", @(123), [NSDate dateWithTimeIntervalSince1970:1699376402] ],
|
||||
@"key_with_dict_val" : @{@"k1" : @"v1", @"k2" : @"v2"},
|
||||
@"key_with_dict_val_nested" : @{
|
||||
@"k1" : @"v1",
|
||||
@"k2" : @"v2",
|
||||
@"k3" : @{@"nk1" : @"nv1", @"nk2" : [NSDate dateWithTimeIntervalSince1970:1699376402]}
|
||||
},
|
||||
};
|
||||
|
||||
self.mockDecisionCache = OCMClassMock([SNTDecisionCache class]);
|
||||
OCMStub([self.mockDecisionCache sharedCache]).andReturn(self.mockDecisionCache);
|
||||
@@ -573,6 +598,70 @@ void SerializeAndCheckNonESEvents(
|
||||
json:YES];
|
||||
}
|
||||
|
||||
- (void)testEncodeEntitlements {
|
||||
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);
|
||||
|
||||
XCTAssertEqual(1, pbExec.entitlement_info().entitlements_size());
|
||||
XCTAssertTrue(pbExec.entitlement_info().has_entitlements_filtered());
|
||||
XCTAssertFalse(pbExec.entitlement_info().entitlements_filtered());
|
||||
}
|
||||
|
||||
// Test basic encoding with filtered entitlements
|
||||
{
|
||||
::pbv1::Execution pbExec;
|
||||
|
||||
SNTCachedDecision *cd = [[SNTCachedDecision alloc] init];
|
||||
cd.entitlements = @{@"com.google.test" : @(YES), @"com.google.test2" : @(NO)};
|
||||
cd.entitlementsFiltered = YES;
|
||||
|
||||
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 {
|
||||
[self serializeAndCheckEvent:ES_EVENT_TYPE_NOTIFY_EXIT
|
||||
messageSetup:^(std::shared_ptr<MockEndpointSecurityAPI> mockESApi,
|
||||
|
||||
@@ -132,8 +132,8 @@ static constexpr std::string_view kIgnoredCompilerProcessPathPrefix = "/dev/";
|
||||
NSError *error = nil;
|
||||
SNTFileInfo *fi = [[SNTFileInfo alloc] initWithEndpointSecurityFile:targetFile error:&error];
|
||||
if (error) {
|
||||
LOGD(@"Unable to create SNTFileInfo while attempting to create transitive rule. Path: %@",
|
||||
@(targetFile->path.data));
|
||||
LOGD(@"Unable to create SNTFileInfo while attempting to create transitive rule. Event: %d | Path: %@ | Error: %@",
|
||||
(int)esMsg->event_type, @(targetFile->path.data), error);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -258,10 +258,19 @@ double watchdogRAMPeak = 0;
|
||||
reply();
|
||||
}
|
||||
|
||||
- (void)blockUSBMount:(void (^)(BOOL))reply {
|
||||
reply([[SNTConfigurator configurator] blockUSBMount]);
|
||||
}
|
||||
|
||||
- (void)setBlockUSBMount:(BOOL)enabled reply:(void (^)(void))reply {
|
||||
[[SNTConfigurator configurator] setBlockUSBMount:enabled];
|
||||
reply();
|
||||
}
|
||||
|
||||
- (void)remountUSBMode:(void (^)(NSArray<NSString *> *))reply {
|
||||
reply([[SNTConfigurator configurator] remountUSBMode]);
|
||||
}
|
||||
|
||||
- (void)setRemountUSBMode:(NSArray *)remountUSBMode reply:(void (^)(void))reply {
|
||||
[[SNTConfigurator configurator] setRemountUSBMode:remountUSBMode];
|
||||
reply();
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
/// limitations under the License.
|
||||
|
||||
#import "Source/santad/SNTExecutionController.h"
|
||||
#include <Foundation/Foundation.h>
|
||||
|
||||
#import <MOLCodesignChecker/MOLCodesignChecker.h>
|
||||
#include <bsm/libbsm.h>
|
||||
@@ -24,12 +25,16 @@
|
||||
#include <utmpx.h>
|
||||
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <string>
|
||||
|
||||
#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<std::string> &filterSet, NSArray<NSString *> *filter) {
|
||||
filterSet.clear();
|
||||
|
||||
for (NSString *prefix in filter) {
|
||||
filterSet.insert(santa::common::NSStringToUTF8String(prefix));
|
||||
}
|
||||
}
|
||||
|
||||
void UpdatePrefixFilterLocked(std::unique_ptr<PrefixTree<Unit>> &tree,
|
||||
NSArray<NSString *> *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 @@ static const size_t kMaxAllowedPathLength = MAXPATHLEN - 1; // -1 to account fo
|
||||
|
||||
@implementation SNTExecutionController {
|
||||
std::shared_ptr<TTYWriter> _ttyWriter;
|
||||
absl::Mutex _entitlementFilterMutex;
|
||||
std::set<std::string> _entitlementsTeamIDFilter;
|
||||
std::unique_ptr<PrefixTree<Unit>> _entitlementsPrefixFilter;
|
||||
}
|
||||
|
||||
static NSString *const kPrinterProxyPreMonterey =
|
||||
@@ -79,7 +108,9 @@ static NSString *const kPrinterProxyPostMonterey =
|
||||
eventTable:(SNTEventTable *)eventTable
|
||||
notifierQueue:(SNTNotificationQueue *)notifierQueue
|
||||
syncdQueue:(SNTSyncdQueue *)syncdQueue
|
||||
ttyWriter:(std::shared_ptr<TTYWriter>)ttyWriter {
|
||||
ttyWriter:(std::shared_ptr<TTYWriter>)ttyWriter
|
||||
entitlementsPrefixFilter:(NSArray<NSString *> *)entitlementsPrefixFilter
|
||||
entitlementsTeamIDFilter:(NSArray<NSString *> *)entitlementsTeamIDFilter {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_ruleTable = ruleTable;
|
||||
@@ -100,10 +131,25 @@ static NSString *const kPrinterProxyPostMonterey =
|
||||
_events = [metricSet counterWithName:@"/santa/events"
|
||||
fieldNames:@[ @"action_response" ]
|
||||
helpText:@"Events processed by Santa per response"];
|
||||
|
||||
self->_entitlementsPrefixFilter = std::make_unique<PrefixTree<Unit>>();
|
||||
|
||||
UpdatePrefixFilterLocked(self->_entitlementsPrefixFilter, entitlementsPrefixFilter);
|
||||
UpdateTeamIDFilterLocked(self->_entitlementsTeamIDFilter, entitlementsTeamIDFilter);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)updateEntitlementsPrefixFilter:(NSArray<NSString *> *)filter {
|
||||
absl::MutexLock lock(&self->_entitlementFilterMutex);
|
||||
UpdatePrefixFilterLocked(self->_entitlementsPrefixFilter, filter);
|
||||
}
|
||||
|
||||
- (void)updateEntitlementsTeamIDFilter:(NSArray<NSString *> *)filter {
|
||||
absl::MutexLock lock(&self->_entitlementFilterMutex);
|
||||
UpdateTeamIDFilterLocked(self->_entitlementsTeamIDFilter, filter);
|
||||
}
|
||||
|
||||
- (void)incrementEventCounters:(SNTEventState)eventType {
|
||||
const NSString *eventTypeStr;
|
||||
|
||||
@@ -208,8 +254,41 @@ static NSString *const kPrinterProxyPostMonterey =
|
||||
// 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) {
|
||||
// Dropping entitlement logging for configured TeamID
|
||||
return nil;
|
||||
}
|
||||
|
||||
if (self->_entitlementsPrefixFilter->NodeCount() == 0) {
|
||||
// Copying full entitlements for TeamID
|
||||
return [entitlements sntDeepCopy];
|
||||
} else {
|
||||
// Filtering entitlements for 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);
|
||||
|
||||
|
||||
@@ -96,7 +96,9 @@ VerifyPostActionBlock verifyPostAction = ^PostActionBlock(SNTAction wantAction)
|
||||
eventTable:self.mockEventDatabase
|
||||
notifierQueue:nil
|
||||
syncdQueue:nil
|
||||
ttyWriter:nullptr];
|
||||
ttyWriter:nullptr
|
||||
entitlementsPrefixFilter:nil
|
||||
entitlementsTeamIDFilter:nil];
|
||||
}
|
||||
|
||||
- (void)tearDown {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -13,15 +13,18 @@
|
||||
/// limitations under the License.
|
||||
|
||||
#import "Source/santad/SNTPolicyProcessor.h"
|
||||
#include <Foundation/Foundation.h>
|
||||
|
||||
#include <Kernel/kern/cs_blobs.h>
|
||||
#import <MOLCodesignChecker/MOLCodesignChecker.h>
|
||||
#import <Security/SecCode.h>
|
||||
|
||||
#include "Source/common/SNTLogging.h"
|
||||
#import <Security/Security.h>
|
||||
|
||||
#import "Source/common/SNTCachedDecision.h"
|
||||
#import "Source/common/SNTConfigurator.h"
|
||||
#import "Source/common/SNTDeepCopy.h"
|
||||
#import "Source/common/SNTFileInfo.h"
|
||||
#import "Source/common/SNTLogging.h"
|
||||
#import "Source/common/SNTRule.h"
|
||||
#import "Source/santad/DataLayer/SNTRuleTable.h"
|
||||
|
||||
@@ -45,7 +48,11 @@
|
||||
fileSHA256:(nullable NSString *)fileSHA256
|
||||
certificateSHA256:(nullable NSString *)certificateSHA256
|
||||
teamID:(nullable NSString *)teamID
|
||||
signingID:(nullable NSString *)signingID {
|
||||
signingID:(nullable NSString *)signingID
|
||||
isProdSignedCallback:(BOOL (^_Nonnull)())isProdSignedCallback
|
||||
entitlementsFilterCallback:
|
||||
(NSDictionary *_Nullable (^_Nullable)(
|
||||
NSDictionary *_Nullable entitlements))entitlementsFilterCallback {
|
||||
SNTCachedDecision *cd = [[SNTCachedDecision alloc] init];
|
||||
cd.sha256 = fileSHA256 ?: fileInfo.SHA256;
|
||||
cd.teamID = teamID;
|
||||
@@ -92,10 +99,34 @@
|
||||
cd.signingID = nil;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
// Do not evaluate TeamID/SigningID rules for dev-signed code based on the
|
||||
// assumption that orgs are generally more relaxed about dev signed cert
|
||||
// protections and users can more easily produce dev-signed code that
|
||||
// would otherwise be inadvertently allowed.
|
||||
// Note: Only perform the check if the SigningID is still set, otherwise
|
||||
// it is unsigned or had issues above that already cleared the values.
|
||||
if (cd.signingID && !isProdSignedCallback()) {
|
||||
LOGD(@"Ignoring TeamID and SigningID rules for code not signed with production cert: %@",
|
||||
cd.signingID);
|
||||
cd.teamID = nil;
|
||||
cd.signingID = nil;
|
||||
}
|
||||
|
||||
SNTRule *rule = [self.ruleTable ruleForBinarySHA256:cd.sha256
|
||||
signingID:cd.signingID
|
||||
certificateSHA256:cd.certSHA256
|
||||
@@ -221,17 +252,25 @@
|
||||
}
|
||||
|
||||
- (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]];
|
||||
@@ -239,10 +278,16 @@
|
||||
}
|
||||
|
||||
return [self decisionForFileInfo:fileInfo
|
||||
fileSHA256:nil
|
||||
certificateSHA256:nil
|
||||
teamID:teamID
|
||||
signingID:signingID];
|
||||
fileSHA256:nil
|
||||
certificateSHA256:nil
|
||||
teamID:teamID
|
||||
signingID:signingID
|
||||
isProdSignedCallback:^BOOL {
|
||||
return ((targetProc->codesigning_flags & CS_DEV_CODE) == 0);
|
||||
}
|
||||
entitlementsFilterCallback:^NSDictionary *(NSDictionary *entitlements) {
|
||||
return entitlementsFilterCallback(entitlementsFilterTeamID, entitlements);
|
||||
}];
|
||||
}
|
||||
|
||||
// Used by `$ santactl fileinfo`.
|
||||
@@ -251,15 +296,37 @@
|
||||
certificateSHA256:(nullable NSString *)certificateSHA256
|
||||
teamID:(nullable NSString *)teamID
|
||||
signingID:(nullable NSString *)signingID {
|
||||
SNTFileInfo *fileInfo;
|
||||
MOLCodesignChecker *csInfo;
|
||||
NSError *error;
|
||||
fileInfo = [[SNTFileInfo alloc] initWithPath:filePath error:&error];
|
||||
if (!fileInfo) LOGW(@"Failed to read file %@: %@", filePath, error.localizedDescription);
|
||||
|
||||
SNTFileInfo *fileInfo = [[SNTFileInfo alloc] initWithPath:filePath error:&error];
|
||||
if (!fileInfo) {
|
||||
LOGW(@"Failed to read file %@: %@", filePath, error.localizedDescription);
|
||||
} else {
|
||||
csInfo = [fileInfo codesignCheckerWithError:&error];
|
||||
if (error) {
|
||||
LOGW(@"Failed to get codesign ingo for file %@: %@", filePath, error.localizedDescription);
|
||||
}
|
||||
}
|
||||
|
||||
return [self decisionForFileInfo:fileInfo
|
||||
fileSHA256:fileSHA256
|
||||
certificateSHA256:certificateSHA256
|
||||
teamID:teamID
|
||||
signingID:signingID];
|
||||
signingID:signingID
|
||||
isProdSignedCallback:^BOOL {
|
||||
if (csInfo) {
|
||||
// Development OID values defined by Apple and used by the Security Framework
|
||||
// https://images.apple.com/certificateauthority/pdf/Apple_WWDR_CPS_v1.31.pdf
|
||||
NSArray *keys = @[ @"1.2.840.113635.100.6.1.2", @"1.2.840.113635.100.6.1.12" ];
|
||||
NSDictionary *vals = CFBridgingRelease(SecCertificateCopyValues(
|
||||
csInfo.leafCertificate.certRef, (__bridge CFArrayRef)keys, NULL));
|
||||
return vals.count == 0;
|
||||
} else {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
entitlementsFilterCallback:nil];
|
||||
}
|
||||
|
||||
///
|
||||
|
||||
@@ -358,6 +358,52 @@ void SantadMain(std::shared_ptr<EndpointSecurityAPI> esapi, std::shared_ptr<Logg
|
||||
// Forcefully exit. The daemon will be restarted immediately.
|
||||
exit(EXIT_SUCCESS);
|
||||
}],
|
||||
[[SNTKVOManager alloc]
|
||||
initWithObject:configurator
|
||||
selector:@selector(entitlementsTeamIDFilter)
|
||||
type:[NSArray class]
|
||||
callback:^(NSArray<NSString *> *oldValue, NSArray<NSString *> *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<NSString *> *oldValue, NSArray<NSString *> *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, *)) {
|
||||
|
||||
@@ -94,7 +94,9 @@ std::unique_ptr<SantadDeps> SantadDeps::Create(SNTConfigurator *configurator,
|
||||
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);
|
||||
|
||||
43
Source/santad/testdata/protobuf/v1/exec.json
vendored
43
Source/santad/testdata/protobuf/v1/exec.json
vendored
@@ -120,5 +120,46 @@
|
||||
}
|
||||
},
|
||||
"explain": "extra!",
|
||||
"quarantine_url": "google.com"
|
||||
"quarantine_url": "google.com",
|
||||
"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\"]"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
43
Source/santad/testdata/protobuf/v2/exec.json
vendored
43
Source/santad/testdata/protobuf/v2/exec.json
vendored
@@ -148,5 +148,46 @@
|
||||
}
|
||||
},
|
||||
"explain": "extra!",
|
||||
"quarantine_url": "google.com"
|
||||
"quarantine_url": "google.com",
|
||||
"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\"]"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
43
Source/santad/testdata/protobuf/v4/exec.json
vendored
43
Source/santad/testdata/protobuf/v4/exec.json
vendored
@@ -197,5 +197,46 @@
|
||||
}
|
||||
},
|
||||
"explain": "extra!",
|
||||
"quarantine_url": "google.com"
|
||||
"quarantine_url": "google.com",
|
||||
"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\"]"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
43
Source/santad/testdata/protobuf/v5/exec.json
vendored
43
Source/santad/testdata/protobuf/v5/exec.json
vendored
@@ -197,5 +197,46 @@
|
||||
}
|
||||
},
|
||||
"explain": "extra!",
|
||||
"quarantine_url": "google.com"
|
||||
"quarantine_url": "google.com",
|
||||
"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\"]"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
43
Source/santad/testdata/protobuf/v6/exec.json
vendored
43
Source/santad/testdata/protobuf/v6/exec.json
vendored
@@ -197,5 +197,46 @@
|
||||
}
|
||||
},
|
||||
"explain": "extra!",
|
||||
"quarantine_url": "google.com"
|
||||
"quarantine_url": "google.com",
|
||||
"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\"]"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,10 +66,6 @@ macos_command_line_application(
|
||||
],
|
||||
infoplists = ["Info.plist"],
|
||||
minimum_os_version = "11.0",
|
||||
provisioning_profile = select({
|
||||
"//:adhoc_build": None,
|
||||
"//conditions:default": "//profiles:santa_dev",
|
||||
}),
|
||||
version = "//:version",
|
||||
visibility = ["//:santa_package_group"],
|
||||
deps = [
|
||||
|
||||
@@ -168,10 +168,6 @@ macos_command_line_application(
|
||||
],
|
||||
infoplists = ["Info.plist"],
|
||||
minimum_os_version = "11.0",
|
||||
provisioning_profile = select({
|
||||
"//:adhoc_build": None,
|
||||
"//conditions:default": "//profiles:santa_dev",
|
||||
}),
|
||||
version = "//:version",
|
||||
visibility = ["//:santa_package_group"],
|
||||
deps = [":santass_lib"],
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user