Compare commits

...

14 Commits
2024.7 ... main

Author SHA1 Message Date
Günther Noack
261425aa64 docs: Colorize callout and make the link work (#1445) 2025-02-05 14:23:36 +01:00
Günther Noack
c17c890356 docs: Add deprecation note (#1444) 2025-02-05 14:08:38 +01:00
Günther Noack
e4e1704495 Add deprecation message (#1443) 2025-02-05 13:54:29 +01:00
Tom Burgin
737525b746 kvo static rules (#1425) 2024-09-12 19:23:30 -04:00
Matt W
8199348091 Use runtime platform binary check for exec evals (#1424)
* Use runtime platform binary check for exec evals

* PR Feedback

* Remove parens to mitigate insane clang-formatting
2024-09-10 09:07:50 -04:00
Pete Markowsky
9f41fbb124 Fix: Change uint64 fields in syncv1.proto to uint32 for backwards compatibility (#1422)
Change the uint64 fields in the syncv1.proto to uint32 to ensure backwards compatibility.

This also updates the SNTSyncEventUpload code to use the uint32 values and updates sync protocol docs.
2024-09-08 15:46:30 -04:00
Matt W
ff0efe952b Use proper CanWrite method to safeguard TTY struct access (#1420) 2024-08-21 16:29:33 -04:00
Tom Burgin
c711129ac9 s/NSDictionary/NSBundle/ (#1418) 2024-08-15 12:36:01 -04:00
Russell Hancox
a56f6c5447 Project: Update rules_apple to 3.8.0 (#1417) 2024-08-13 14:20:19 -04:00
Russell Hancox
fadc9b505b sync: Drop rules_* fields in postflight to uint32 (#1415)
* sync: Drop rules_* fields in postflight to uint32

This lets the protobuf json serializer to send the values as ints (like NSJSONSerialization did) instead of strings. This will cause problems if someone has 4B rules but that's probably a sign of bigger problems
2024-08-12 15:40:01 -04:00
Tom Burgin
c7766d5993 length and count check (#1413) 2024-08-09 17:17:13 -04:00
Russell Hancox
341abf044b sync: Fix Content-Type logic bug, add test (#1412) 2024-08-08 17:43:53 -04:00
Tom Burgin
b1cf83a7e3 switch to CFBundleCopyInfoDictionaryInDirectory (#1411) 2024-08-08 16:30:33 -04:00
Russell Hancox
013b0b40af sync: Remove debug logging of request JSON. (#1410)
This was helpful while switching to the protobuf lib but due to the way SLOGD works it isn't limited to just debug invocations and is quite noisy
2024-08-08 14:06:52 -04:00
16 changed files with 86 additions and 62 deletions

View File

@@ -4,7 +4,7 @@ bazel_dep(name = "apple_support", version = "1.15.1", repo_name = "build_bazel_a
bazel_dep(name = "abseil-cpp", version = "20240116.2", repo_name = "com_google_absl")
bazel_dep(name = "rules_python", version = "0.33.2")
bazel_dep(name = "rules_cc", version = "0.0.9")
bazel_dep(name = "rules_apple", version = "3.6.0", repo_name = "build_bazel_rules_apple")
bazel_dep(name = "rules_apple", version = "3.8.0", repo_name = "build_bazel_rules_apple")
bazel_dep(name = "rules_swift", version = "2.0.0-rc1", repo_name = "build_bazel_rules_swift")
bazel_dep(name = "rules_fuzzing", version = "0.5.2")
bazel_dep(name = "protobuf", version = "27.2", repo_name = "com_google_protobuf")

View File

@@ -1,5 +1,12 @@
# Santa
> [!NOTE]
> **As of 2025, Santa is no longer maintained by Google.** We encourage
> existing users to migrate to an actively maintained fork of Santa, such as
> https://github.com/northpolesec/santa.
---
[![license](https://img.shields.io/github/license/google/santa)](https://github.com/google/santa/blob/main/LICENSE)
[![CI](https://github.com/google/santa/actions/workflows/ci.yml/badge.svg)](https://github.com/google/santa/actions/workflows/ci.yml)
[![latest release](https://img.shields.io/github/v/release/google/santa.svg)](https://github.com/google/santa/releases/latest)

View File

@@ -368,7 +368,12 @@ static NSString *const kSyncTypeRequired = @"SyncTypeRequired";
}
+ (NSSet *)keyPathsForValuesAffectingStaticRules {
return [self configStateSet];
static NSSet *set;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
set = [NSSet setWithObject:NSStringFromSelector(@selector(cachedStaticRules))];
});
return set;
}
+ (NSSet *)keyPathsForValuesAffectingSyncBaseURL {

View File

@@ -423,8 +423,14 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
return self.infoDict;
}
d = self.bundle.infoDictionary;
if (d) {
// `-[NSBundle infoDictionary]` is heavily cached, changes to the Info.plist are not realized.
// Use `CFBundleCopyInfoDictionaryInDirectory` instead, which does not appear to cache.
NSString *bundlePath = [self bundlePath];
if (bundlePath.length) {
d = CFBridgingRelease(CFBundleCopyInfoDictionaryInDirectory(
(__bridge CFURLRef)[NSURL fileURLWithPath:bundlePath]));
}
if (d.count) {
self.infoDict = d;
return self.infoDict;
}

View File

@@ -688,7 +688,7 @@ bool ShouldMessageTTY(const std::shared_ptr<WatchItemPolicy> &policy, const Mess
// Notify users on block decisions
if (ShouldNotifyUserDecision(policyDecision) &&
(!policy->silent || (!policy->silent_tty && msg->process->tty->path.length > 0))) {
(!policy->silent || (!policy->silent_tty && TTYWriter::CanWrite(msg->process)))) {
SNTCachedDecision *cd =
[self.decisionCache cachedDecisionForFile:msg->process->executable->stat];

View File

@@ -45,12 +45,6 @@
/// 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
entitlementsFilterCallback:
(NSDictionary *_Nullable (^_Nonnull)(
const char *_Nullable teamID,
NSDictionary *_Nullable entitlements))entitlementsFilterCallback;
- (nonnull SNTCachedDecision *)decisionForFileInfo:(nonnull SNTFileInfo *)fileInfo
targetProcess:(nonnull const es_process_t *)targetProc
preCodesignCheckCallback:(void (^_Nullable)(void))preCodesignCheckCallback

View File

@@ -30,6 +30,12 @@
#import "Source/santad/DataLayer/SNTRuleTable.h"
#include "absl/container/flat_hash_map.h"
enum class PlatformBinaryState {
kRuntimeTrue = 0,
kRuntimeFalse,
kStaticCheck,
};
@interface SNTPolicyProcessor ()
@property SNTRuleTable *ruleTable;
@property SNTConfigurator *configurator;
@@ -129,7 +135,7 @@
}
static void UpdateCachedDecisionSigningInfo(
SNTCachedDecision *cd, MOLCodesignChecker *csInfo,
SNTCachedDecision *cd, MOLCodesignChecker *csInfo, PlatformBinaryState platformBinaryState,
NSDictionary *_Nullable (^entitlementsFilterCallback)(NSDictionary *_Nullable entitlements)) {
cd.certSHA256 = csInfo.leafCertificate.SHA256;
cd.certCommonName = csInfo.leafCertificate.commonName;
@@ -144,11 +150,18 @@ static void UpdateCachedDecisionSigningInfo(
cd.signingID = FormatSigningID(csInfo);
}
// Ensure that if no teamID exists that the signing info confirms it is a
// platform binary. If not, remove the signingID.
// Ensure that if no teamID exists but a signingID does exist, that the binary
// is a platform binary. If not, remove the signingID.
if (!cd.teamID && cd.signingID) {
if (!csInfo.platformBinary) {
cd.signingID = nil;
switch (platformBinaryState) {
case PlatformBinaryState::kRuntimeTrue: break;
case PlatformBinaryState::kStaticCheck:
if (!csInfo.platformBinary) {
cd.signingID = nil;
}
break;
case PlatformBinaryState::kRuntimeFalse: OS_FALLTHROUGH;
default: cd.signingID = nil; break;
}
}
@@ -170,6 +183,7 @@ static void UpdateCachedDecisionSigningInfo(
certificateSHA256:(nullable NSString *)certificateSHA256
teamID:(nullable NSString *)teamID
signingID:(nullable NSString *)signingID
platformBinaryState:(PlatformBinaryState)platformBinaryState
isProdSignedCallback:(BOOL (^_Nonnull)())isProdSignedCallback
entitlementsFilterCallback:(NSDictionary *_Nullable (^_Nullable)(
NSDictionary *_Nullable entitlements))entitlementsFilterCallback
@@ -215,7 +229,7 @@ static void UpdateCachedDecisionSigningInfo(
cd.signingID = nil;
cd.cdhash = nil;
} else {
UpdateCachedDecisionSigningInfo(cd, csInfo, entitlementsFilterCallback);
UpdateCachedDecisionSigningInfo(cd, csInfo, platformBinaryState, entitlementsFilterCallback);
}
}
@@ -276,18 +290,6 @@ static void UpdateCachedDecisionSigningInfo(
}
}
- (nonnull SNTCachedDecision *)decisionForFileInfo:(nonnull SNTFileInfo *)fileInfo
targetProcess:(nonnull const es_process_t *)targetProc
entitlementsFilterCallback:
(NSDictionary *_Nullable (^_Nonnull)(
const char *_Nullable teamID,
NSDictionary *_Nullable entitlements))entitlementsFilterCallback {
return [self decisionForFileInfo:fileInfo
targetProcess:targetProc
preCodesignCheckCallback:nil
entitlementsFilterCallback:entitlementsFilterCallback];
}
- (nonnull SNTCachedDecision *)decisionForFileInfo:(nonnull SNTFileInfo *)fileInfo
targetProcess:(nonnull const es_process_t *)targetProc
preCodesignCheckCallback:(void (^_Nullable)(void))preCodesignCheckCallback
@@ -338,6 +340,8 @@ static void UpdateCachedDecisionSigningInfo(
certificateSHA256:nil
teamID:teamID
signingID:signingID
platformBinaryState:targetProc->is_platform_binary ? PlatformBinaryState::kRuntimeTrue
: PlatformBinaryState::kRuntimeFalse
isProdSignedCallback:^BOOL {
return ((targetProc->codesigning_flags & CS_DEV_CODE) == 0);
}
@@ -369,6 +373,7 @@ static void UpdateCachedDecisionSigningInfo(
certificateSHA256:identifiers.certificateSHA256
teamID:identifiers.teamID
signingID:identifiers.signingID
platformBinaryState:PlatformBinaryState::kStaticCheck
isProdSignedCallback:^BOOL {
if (csInfo) {
// Development OID values defined by Apple and used by the Security Framework

View File

@@ -315,12 +315,9 @@ void SantadMain(std::shared_ptr<EndpointSecurityAPI> esapi, std::shared_ptr<Logg
}],
[[SNTKVOManager alloc] initWithObject:configurator
selector:@selector(staticRules)
type:[NSArray class]
callback:^(NSArray *oldValue, NSArray *newValue) {
NSSet *oldValueSet = [NSSet setWithArray:oldValue ?: @[]];
NSSet *newValueSet = [NSSet setWithArray:newValue ?: @[]];
if ([oldValueSet isEqualToSet:newValueSet]) {
type:[NSDictionary class]
callback:^(NSDictionary *oldValue, NSDictionary *newValue) {
if ([oldValue isEqualToDictionary:newValue]) {
return;
}

View File

@@ -150,8 +150,8 @@ using santa::NSStringToUTF8String;
e->set_file_bundle_version(NSStringToUTF8String(event.fileBundleVersion));
e->set_file_bundle_version_string(NSStringToUTF8String(event.fileBundleVersionString));
e->set_file_bundle_hash(NSStringToUTF8String(event.fileBundleHash));
e->set_file_bundle_hash_millis([event.fileBundleHashMilliseconds longLongValue]);
e->set_file_bundle_binary_count([event.fileBundleBinaryCount longLongValue]);
e->set_file_bundle_hash_millis([event.fileBundleHashMilliseconds unsignedIntValue]);
e->set_file_bundle_binary_count([event.fileBundleBinaryCount unsignedIntValue]);
e->set_pid([event.pid unsignedIntValue]);
e->set_ppid([event.ppid unsignedIntValue]);

View File

@@ -38,8 +38,8 @@ using santa::NSStringToUTF8String;
google::protobuf::Arena arena;
auto req = google::protobuf::Arena::Create<::pbv1::PostflightRequest>(&arena);
req->set_machine_id(NSStringToUTF8String(self.syncState.machineID));
req->set_rules_received(self.syncState.rulesReceived);
req->set_rules_processed(self.syncState.rulesProcessed);
req->set_rules_received(static_cast<uint32_t>(self.syncState.rulesReceived));
req->set_rules_processed(static_cast<uint32_t>(self.syncState.rulesProcessed));
::pbv1::PostflightResponse response;
[self performRequest:[self requestWithMessage:req] intoMessage:&response timeout:30];

View File

@@ -86,13 +86,12 @@ using santa::NSStringToUTF8String;
return nil;
}
SLOGD(@"Request JSON: %s", json.c_str());
return [self requestWithData:[NSData dataWithBytes:json.data() length:json.size()]
contentType:@"application/json"];
}
- (NSMutableURLRequest *)requestWithData:(NSData *)requestBody contentType:(NSString *)contentType {
if (contentType.length) contentType = @"application/octet-stream";
if (!contentType.length) contentType = @"application/octet-stream";
NSMutableURLRequest *req = [[NSMutableURLRequest alloc] initWithURL:[self stageURL]];
[req setHTTPMethod:@"POST"];

View File

@@ -280,15 +280,17 @@
SNTSyncPreflight *sut = [[SNTSyncPreflight alloc] initWithState:self.syncState];
NSData *respData = [self dataFromFixture:@"sync_preflight_basic.json"];
[self stubRequestBody:respData
response:nil
error:nil
validateBlock:^BOOL(NSURLRequest *req) {
NSData *gotReqData = [req HTTPBody];
NSData *expectedReqData = [self dataFromFixture:@"sync_preflight_request.json"];
XCTAssertEqualObjects(gotReqData, expectedReqData);
return YES;
}];
[self
stubRequestBody:respData
response:nil
error:nil
validateBlock:^BOOL(NSURLRequest *req) {
NSData *gotReqData = [req HTTPBody];
NSData *expectedReqData = [self dataFromFixture:@"sync_preflight_request.json"];
XCTAssertEqualObjects(gotReqData, expectedReqData);
XCTAssertEqualObjects([req valueForHTTPHeaderField:@"Content-Type"], @"application/json");
return YES;
}];
XCTAssertTrue([sut sync]);
XCTAssertEqual(self.syncState.clientMode, SNTClientModeMonitor);

View File

@@ -125,16 +125,16 @@ message PreflightResponse {
optional bool disable_unknown_event_upload = 7;
// Specifies the time interval in seconds between full syncs. Defaults to 600 (10 minutes). Cannot be set lower than 60.
uint64 full_sync_interval_seconds = 8 [json_name="full_sync_interval"];
uint32 full_sync_interval_seconds = 8 [json_name="full_sync_interval"];
// When push notifications are enabled, this overrides the full_sync_interval above. It is expected that Santa will not
// need to perform a full sync as frequently when push notifications are working. Defaults to 14400 (6 hours).
uint64 push_notification_full_sync_interval_seconds = 9 [json_name="push_notification_full_sync_interval"];
uint32 push_notification_full_sync_interval_seconds = 9 [json_name="push_notification_full_sync_interval"];
// The maximum number of seconds Santa can wait before triggering a rule sync after receiving a "global rule sync" notification.
// As these notifications cause every Santa client to try and sync, we add a random delay to each client to try and spread the
// load out on the sync server. This defaults to 600 (10 minutes).
uint64 push_notification_global_rule_sync_deadline_seconds = 10 [json_name="push_notification_global_rule_sync_deadline"];
uint32 push_notification_global_rule_sync_deadline_seconds = 10 [json_name="push_notification_global_rule_sync_deadline"];
// These two regexes are used to allow/block executions whose path matches. The provided regex must conform to ICU format.
// While this feature can be useful, its use should be very carefully considered as it is much riskier than real rules.
@@ -159,8 +159,8 @@ message PreflightResponse {
optional bool deprecated_enabled_transitive_whitelisting = 1000 [json_name="enabled_transitive_whitelisting", deprecated=true];
optional bool deprecated_transitive_whitelisting_enabled = 1001 [json_name="transitive_whitelisting_enabled", deprecated=true];
optional bool deprecated_bundles_enabled = 1002 [json_name="bundles_enabled", deprecated=true];
optional uint64 deprecated_fcm_full_sync_interval_seconds = 1003 [json_name="fcm_full_sync_interval", deprecated=true];
optional uint64 deprecated_fcm_global_rule_sync_deadline_seconds = 1004 [json_name="fcm_global_rule_sync_deadline", deprecated=true];
optional uint32 deprecated_fcm_full_sync_interval_seconds = 1003 [json_name="fcm_full_sync_interval", deprecated=true];
optional uint32 deprecated_fcm_global_rule_sync_deadline_seconds = 1004 [json_name="fcm_global_rule_sync_deadline", deprecated=true];
optional string deprecated_whitelist_regex = 1005 [json_name="whitelist_regex", deprecated=true];
optional string deprecated_blacklist_regex = 1006 [json_name="blacklist_regex", deprecated=true];
@@ -214,8 +214,8 @@ message Event {
string file_bundle_version = 13 [json_name="file_bundle_version"];
string file_bundle_version_string = 14 [json_name="file_bundle_version_string"];
string file_bundle_hash = 15 [json_name="file_bundle_hash"];
uint64 file_bundle_hash_millis = 16 [json_name="file_bundle_hash_millis"];
uint64 file_bundle_binary_count = 17 [json_name="file_bundle_binary_count"];
uint32 file_bundle_hash_millis = 16 [json_name="file_bundle_hash_millis"];
uint32 file_bundle_binary_count = 17 [json_name="file_bundle_binary_count"];
// pid_t is an int32
int32 pid = 18 [json_name="pid"];
@@ -305,8 +305,8 @@ message RuleDownloadResponse {
}
message PostflightRequest {
uint64 rules_received = 1 [json_name="rules_received"];
uint64 rules_processed = 2 [json_name="rules_processed"];
uint32 rules_received = 1 [json_name="rules_received"];
uint32 rules_processed = 2 [json_name="rules_processed"];
// The UUID of the machine that is sending this postflight.
string machine_id = 3 [json_name="machine_id"];
}

View File

@@ -14,3 +14,7 @@ gh_edit_branch: "main"
plugins:
- jekyll-redirect-from
callouts:
important:
title: Important
color: blue

View File

@@ -140,7 +140,7 @@ The JSON object has the following keys:
| enable_bundles | Use previous setting | boolean | Enable bundle scanning | true |
| enable_transitive_rules | Use previous setting | boolean | Whether or not to enable transitive allowlisting | true |
| batch_size | Use a Santa-defined default value | integer | Number of events to upload at a time | 128 |
| full_sync_interval | Defaults to 600 seconds | integer | Number of seconds between full syncs. Note: Santa enforces a minimum value of 60. The default value will be used if a smaller value is provided. | 600 |
| full_sync_interval | Defaults to 600 seconds | uint32 | Number of seconds between full syncs. Note: Santa enforces a minimum value of 60. The default value will be used if a smaller value is provided. | 600 |
| client_mode | Use previous setting | string | Operating mode to set for the client | either `MONITOR` or `LOCKDOWN` |
| allowed_path_regex | Use previous setting | string | Regular expression to allow a binary to execute from a path | "/Users/markowsk/foo/.\*" |
| blocked_path_regex | Use previous setting | string | Regular expression to block a binary from executing by path | "/tmp/" |
@@ -223,8 +223,8 @@ sequenceDiagram
| file_bundle_version | NO | string | The bundle version string | "9999.1.1" |
| file_bundle_version_string | NO | string | Bundle short version string | "2.3.4" |
| file_bundle_hash | NO | string | SHA256 hash of all executables in the bundle | "7466e3687f540bcb7792c6d14d5a186667dbe18a85021857b42effe9f0370805" |
| file_bundle_hash_millis | NO | float64 | The time in milliseconds it took to find all of the binaries, hash and produce the bundle_hash | 1234775 |
| file_bundle_binary_count | NO | integer | The number of binaries in a bundle | 13 |
| file_bundle_hash_millis | NO | uint32 | The time in milliseconds it took to find all of the binaries, hash and produce the bundle_hash | 1234775 |
| file_bundle_binary_count | NO | uint32 | The number of binaries in a bundle | 13 |
| pid | NO | int | Process id of the executable that was blocked | 1234 |
| ppid | NO | int | Parent process id of the executable that was blocked | 456 |
| parent_name | NO | Parent process short command name of the executable that was blocked | "bar" |

View File

@@ -3,6 +3,11 @@ title: Home
nav_order: 1
---
{: .important }
**As of 2025, Santa is no longer maintained by Google.** We encourage existing
users to migrate to an actively maintained fork of Santa, such as
[https://github.com/northpolesec/santa](https://github.com/northpolesec/santa).
# Welcome to the Santa documentation
Santa is a binary and file access authorization system for macOS. It consists of a system extension that allows or denies attempted executions using a set of rules stored in a local database, a GUI agent that notifies the user in case of a block decision, a sync daemon responsible for syncing the database, and a server, and a command-line utility for managing the system.