Skip to content

Commit

Permalink
Add new preflight response sync type key, lots of tests
Browse files Browse the repository at this point in the history
  • Loading branch information
mlw committed Jan 19, 2024
1 parent 490f5ac commit faeb387
Show file tree
Hide file tree
Showing 4 changed files with 142 additions and 37 deletions.
3 changes: 2 additions & 1 deletion Source/common/SNTSyncConstants.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ extern NSString *const kClientModeMonitor;
extern NSString *const kClientModeLockdown;
extern NSString *const kBlockUSBMount;
extern NSString *const kRemountUSBMode;
extern NSString *const kCleanSync;
extern NSString *const kCleanSyncDeprecated;
extern NSString *const kSyncType;
extern NSString *const kAllowedPathRegex;
extern NSString *const kAllowedPathRegexDeprecated;
extern NSString *const kBlockedPathRegex;
Expand Down
3 changes: 2 additions & 1 deletion Source/common/SNTSyncConstants.m
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@
NSString *const kRemountUSBMode = @"remount_usb_mode";
NSString *const kClientModeMonitor = @"MONITOR";
NSString *const kClientModeLockdown = @"LOCKDOWN";
NSString *const kCleanSync = @"clean_sync";
NSString *const kCleanSyncDeprecated = @"clean_sync";
NSString *const kSyncType = @"sync_type";
NSString *const kAllowedPathRegex = @"allowed_path_regex";
NSString *const kAllowedPathRegexDeprecated = @"whitelist_regex";
NSString *const kBlockedPathRegex = @"blocked_path_regex";
Expand Down
40 changes: 35 additions & 5 deletions Source/santasyncservice/SNTSyncPreflight.m
Original file line number Diff line number Diff line change
Expand Up @@ -139,19 +139,49 @@ - (BOOL)sync {
EnsureType(resp[kOverrideFileAccessAction], [NSString class]);

// Default sync type is SNTSyncTypeNormal
//
// Logic overview:
// The requested sync type (clean or normal) is merely informative. The server
// can choose to respond with a normal, clean or clean_all.
//
// If the server responds that it will perform a clean sync, santa will
// treat it as either a clean or clean_all depending on which was requested.
//
// The server can also "override" the requested clean operation. If a normal
// sync was requested, but the server responded that it was doing a clean or
// clean_all sync, that will take precedence. Similarly, if only a clean sync
// was requested, the server can force a "clean_all" operation to take place.
self.syncState.syncType = SNTSyncTypeNormal;

if ([EnsureType(resp[kCleanSync], [NSNumber class]) boolValue]) {
// If the server responded that it's doing a clean sync, ensure that the sync type
// is stored as either Clean or Clean All based on what was requested.
// The syncType must be set appropriately so that it can be propagated during
// the Rule Download stage so SNTRuleTable knows which rules to delete.
// If kSyncType response key exists, it overrides the kCleanSyncDeprecated value
// First check if the kSyncType reponse key exists. If so, it takes precedence
// over the kCleanSyncDeprecated key.
NSString *responseSyncType = [EnsureType(resp[kSyncType], [NSString class]) lowercaseString];
if (responseSyncType) {
if ([responseSyncType isEqualToString:@"clean"]) {
// If the client wants to Clean All, this takes precedence. The server
// cannot override the client wanting to remove all rules.
if (requestSyncType == SNTSyncTypeCleanAll) {
self.syncState.syncType = SNTSyncTypeCleanAll;
} else {
self.syncState.syncType = SNTSyncTypeClean;
}
} else if ([responseSyncType isEqualToString:@"clean_all"]) {
self.syncState.syncType = SNTSyncTypeCleanAll;
}
} else if ([EnsureType(resp[kCleanSyncDeprecated], [NSNumber class]) boolValue]) {
// If the deprecated key is set, the type of sync clean performed should be
// the type that was requested. This must be set appropriately so that it
// can be propagated during the Rule Download stage so SNTRuleTable knows
// which rules to delete.
if (requestSyncType == SNTSyncTypeCleanAll) {
self.syncState.syncType = SNTSyncTypeCleanAll;
} else {
self.syncState.syncType = SNTSyncTypeClean;
}
}

if (self.syncState.syncType != SNTSyncTypeNormal) {
SLOGD(@"Clean sync requested by server");
}

Expand Down
133 changes: 103 additions & 30 deletions Source/santasyncservice/SNTSyncTest.m
Original file line number Diff line number Diff line change
Expand Up @@ -379,7 +379,12 @@ - (void)testPreflightDatabaseCounts {
[sut sync];
}

- (void)testPreflightCleanSync {
// This method is designed to help facilitate easy testing of many different
// permutations of clean sync request / response values and how syncType gets set.
- (void)cleanSyncPreflightRequiredSyncType:(SNTSyncType)requestedSyncType
expectcleanSyncRequest:(BOOL)expectcleanSyncRequest
expectedSyncType:(SNTSyncType)expectedSyncType
response:(NSDictionary *)resp {
SNTSyncPreflight *sut = [[SNTSyncPreflight alloc] initWithState:self.syncState];

OCMStub([self.daemonConnRop
Expand All @@ -393,52 +398,120 @@ - (void)testPreflightCleanSync {
OCMStub([self.daemonConnRop
clientMode:([OCMArg invokeBlockWithArgs:OCMOCK_VALUE(SNTClientModeMonitor), nil])]);
OCMStub([self.daemonConnRop
syncTypeRequired:([OCMArg invokeBlockWithArgs:OCMOCK_VALUE(SNTSyncTypeClean), nil])]);
syncTypeRequired:([OCMArg invokeBlockWithArgs:OCMOCK_VALUE(requestedSyncType), nil])]);

NSData *respData = [self dataFromDict:@{kCleanSync : @YES}];
NSData *respData = [self dataFromDict:resp];
[self stubRequestBody:respData
response:nil
error:nil
validateBlock:^BOOL(NSURLRequest *req) {
NSDictionary *requestDict = [self dictFromRequest:req];
XCTAssertEqualObjects(requestDict[kRequestCleanSync], @YES);
if (expectcleanSyncRequest) {
XCTAssertEqualObjects(requestDict[kRequestCleanSync], @YES);
} else {
XCTAssertNil(requestDict[kRequestCleanSync]);
}
return YES;
}];

[sut sync];

XCTAssertEqual(self.syncState.syncType, SNTSyncTypeClean);
XCTAssertEqual(self.syncState.syncType, expectedSyncType);
}

- (void)testPreflightCleanAllSync {
SNTSyncPreflight *sut = [[SNTSyncPreflight alloc] initWithState:self.syncState];
- (void)testPreflightCleanSyncNone {
[self cleanSyncPreflightRequiredSyncType:SNTSyncTypeNormal
expectcleanSyncRequest:NO
expectedSyncType:SNTSyncTypeNormal
response:@{}];
}

OCMStub([self.daemonConnRop
databaseRuleCounts:([OCMArg invokeBlockWithArgs:OCMOCK_VALUE(0), // binary
OCMOCK_VALUE(0), // cert
OCMOCK_VALUE(0), // compiler
OCMOCK_VALUE(0), // transitive
OCMOCK_VALUE(0), // teamID
OCMOCK_VALUE(0), // signingID
nil])]);
OCMStub([self.daemonConnRop
clientMode:([OCMArg invokeBlockWithArgs:OCMOCK_VALUE(SNTClientModeMonitor), nil])]);
OCMStub([self.daemonConnRop
syncTypeRequired:([OCMArg invokeBlockWithArgs:OCMOCK_VALUE(SNTSyncTypeCleanAll), nil])]);
- (void)testPreflightCleanSyncDeprecatedForced {
[self cleanSyncPreflightRequiredSyncType:SNTSyncTypeNormal
expectcleanSyncRequest:NO
expectedSyncType:SNTSyncTypeClean
response:@{kCleanSyncDeprecated : @YES}];
}

NSData *respData = [self dataFromDict:@{kCleanSync : @YES}];
[self stubRequestBody:respData
response:nil
error:nil
validateBlock:^BOOL(NSURLRequest *req) {
NSDictionary *requestDict = [self dictFromRequest:req];
XCTAssertEqualObjects(requestDict[kRequestCleanSync], @YES);
return YES;
}];
- (void)testPreflightCleanSyncCleanReqCleanDeprecatedResponse {
[self cleanSyncPreflightRequiredSyncType:SNTSyncTypeClean
expectcleanSyncRequest:YES
expectedSyncType:SNTSyncTypeClean
response:@{kCleanSyncDeprecated : @YES}];
}

[sut sync];
- (void)testPreflightCleanSyncCleanAllReqCleanDeprecatedResponse {
[self cleanSyncPreflightRequiredSyncType:SNTSyncTypeCleanAll
expectcleanSyncRequest:YES
expectedSyncType:SNTSyncTypeCleanAll
response:@{kCleanSyncDeprecated : @YES}];
}

- (void)testPreflightCleanSyncCleanAllReqCleanResponse {
[self cleanSyncPreflightRequiredSyncType:SNTSyncTypeCleanAll
expectcleanSyncRequest:YES
expectedSyncType:SNTSyncTypeCleanAll
response:@{kSyncType : @"clean"}];
}

- (void)testPreflightCleanSyncCleanReqCleanResponse {
[self cleanSyncPreflightRequiredSyncType:SNTSyncTypeClean
expectcleanSyncRequest:YES
expectedSyncType:SNTSyncTypeClean
response:@{kSyncType : @"clean"}];
}

- (void)testPreflightCleanSyncCleanAllReqCleanAllResponse {
[self cleanSyncPreflightRequiredSyncType:SNTSyncTypeCleanAll
expectcleanSyncRequest:YES
expectedSyncType:SNTSyncTypeCleanAll
response:@{kSyncType : @"clean_all"}];
}

- (void)testPreflightCleanSyncCleanReqCleanAllResponse {
// Note: The server can override the clean/clean all operation
[self cleanSyncPreflightRequiredSyncType:SNTSyncTypeClean
expectcleanSyncRequest:YES
expectedSyncType:SNTSyncTypeCleanAll
response:@{kSyncType : @"clean_all"}];
}

- (void)testPreflightCleanSyncForcedCleanResponse {
// Note: The server can override the clean/clean all operation
[self cleanSyncPreflightRequiredSyncType:SNTSyncTypeNormal
expectcleanSyncRequest:NO
expectedSyncType:SNTSyncTypeClean
response:@{kSyncType : @"clean"}];
}

- (void)testPreflightCleanSyncForcedCleanAllResponse {
// Note: The server can override the clean/clean all operation
[self cleanSyncPreflightRequiredSyncType:SNTSyncTypeNormal
expectcleanSyncRequest:NO
expectedSyncType:SNTSyncTypeCleanAll
response:@{kSyncType : @"clean_all"}];
}

- (void)testPreflightCleanSyncCleanAllReqNormalResponse {
[self cleanSyncPreflightRequiredSyncType:SNTSyncTypeCleanAll
expectcleanSyncRequest:YES
expectedSyncType:SNTSyncTypeNormal
response:@{kSyncType : @"normal"}];
}

- (void)testPreflightCleanSyncCleanAllReqUnknownValueResponse {
[self cleanSyncPreflightRequiredSyncType:SNTSyncTypeCleanAll
expectcleanSyncRequest:YES
expectedSyncType:SNTSyncTypeNormal
response:@{kSyncType : @"foo"}];
}

XCTAssertEqual(self.syncState.syncType, SNTSyncTypeCleanAll);
- (void)testPreflightCleanSyncCleanAllReqBothMethodsResponse {
// Note: The kSyncType key takes precedence over kCleanSyncDeprecated if both are set
[self cleanSyncPreflightRequiredSyncType:SNTSyncTypeCleanAll
expectcleanSyncRequest:YES
expectedSyncType:SNTSyncTypeNormal
response:@{kSyncType : @"normal", kCleanSyncDeprecated : @YES}];
}

- (void)testPreflightLockdown {
Expand Down

0 comments on commit faeb387

Please sign in to comment.