mirror of
https://github.com/google/santa.git
synced 2026-01-19 19:19:38 -05:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9e124f4c51 | ||
|
|
cd719ccef4 | ||
|
|
dde42ee686 | ||
|
|
d144e27798 | ||
|
|
afc2c216b8 | ||
|
|
03d7556f22 | ||
|
|
020827b091 | ||
|
|
baa31a5db0 | ||
|
|
9ba7075596 | ||
|
|
5d08538639 | ||
|
|
e73bafb596 | ||
|
|
1e92d109a7 | ||
|
|
6a6aa6dce8 | ||
|
|
0715033d6a | ||
|
|
123d7a2d6a | ||
|
|
7b4d997589 |
4
.bazelrc
4
.bazelrc
@@ -3,6 +3,10 @@ build --apple_generate_dsym --define=apple.propagate_embedded_extra_outputs=yes
|
||||
build --copt=-Werror
|
||||
build --copt=-Wall
|
||||
build --copt=-Wno-error=deprecated-declarations
|
||||
# Disable -Wunknown-warning-option because deprecated-non-prototype
|
||||
# isn't recognized on older SDKs
|
||||
build --copt=-Wno-unknown-warning-option
|
||||
build --copt=-Wno-error=deprecated-non-prototype
|
||||
build --per_file_copt=.*\.mm\$@-std=c++17
|
||||
build --cxxopt=-std=c++17
|
||||
|
||||
|
||||
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
@@ -24,7 +24,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [macos-11, macos-12]
|
||||
os: [macos-11, macos-12, macos-13]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [macos-11, macos-12]
|
||||
os: [macos-11, macos-12, macos-13]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
|
||||
# Example NOTARIZATION_TOOL wrapper.
|
||||
|
||||
/usr/bin/xcrun altool --notarize-app "${2}" --primary-bundle-id "${4}" \
|
||||
-u "${NOTARIZATION_USERNAME}" -p "${NOTARIZATION_PASSWORD}"
|
||||
/usr/bin/xcrun notarytool submit "${2}" --wait \
|
||||
--apple-id "${NOTARIZATION_USERNAME}" --password "${NOTARIZATION_PASSWORD}"
|
||||
|
||||
@@ -28,8 +28,6 @@
|
||||
# tool around the tool to use for notarization. The tool must take 2 flags:
|
||||
# --file
|
||||
# - pointing at a zip file containing the artifact to notarize
|
||||
# --primary-bundle-id
|
||||
# - specifying the CFBundleID of the artifact being notarized
|
||||
[[ -n "${NOTARIZATION_TOOL}" ]] || die "NOTARIZATION_TOOL unset"
|
||||
|
||||
# ARTIFACTS_DIR is a required environment variable pointing at a directory to
|
||||
@@ -92,7 +90,7 @@ for ARTIFACT in "${INPUT_SYSX}" "${INPUT_APP}"; do
|
||||
|
||||
echo "notarizing ${BN}"
|
||||
PBID=$(/usr/bin/defaults read "${ARTIFACT}/Contents/Info.plist" CFBundleIdentifier)
|
||||
"${NOTARIZATION_TOOL}" --file "${SCRATCH}/${BN}.zip" --primary-bundle-id "${PBID}"
|
||||
"${NOTARIZATION_TOOL}" --file "${SCRATCH}/${BN}.zip"
|
||||
done
|
||||
|
||||
# Staple the App.
|
||||
@@ -166,8 +164,7 @@ echo "verifying pkg signature"
|
||||
/usr/sbin/pkgutil --check-signature "${SCRATCH}/${RELEASE_NAME}/${RELEASE_NAME}.pkg" || die "bad pkg signature"
|
||||
|
||||
echo "notarizing pkg"
|
||||
"${NOTARIZATION_TOOL}" --file "${SCRATCH}/${RELEASE_NAME}/${RELEASE_NAME}.pkg" \
|
||||
--primary-bundle-id "com.google.santa"
|
||||
"${NOTARIZATION_TOOL}" --file "${SCRATCH}/${RELEASE_NAME}/${RELEASE_NAME}.pkg"
|
||||
|
||||
echo "stapling pkg"
|
||||
/usr/bin/xcrun stapler staple "${SCRATCH}/${RELEASE_NAME}/${RELEASE_NAME}.pkg" || die "failed to staple pkg"
|
||||
@@ -179,7 +176,7 @@ echo "wrapping pkg in dmg"
|
||||
-srcfolder "${SCRATCH}/${RELEASE_NAME}/" "${DMG_PATH}" || die "failed to wrap pkg in dmg"
|
||||
|
||||
echo "notarizing dmg"
|
||||
"${NOTARIZATION_TOOL}" --file "${DMG_PATH}" --primary-bundle-id "com.google.santa"
|
||||
"${NOTARIZATION_TOOL}" --file "${DMG_PATH}"
|
||||
|
||||
echo "stapling dmg"
|
||||
/usr/bin/xcrun stapler staple "${DMG_PATH}" || die "failed to staple dmg"
|
||||
|
||||
@@ -133,6 +133,19 @@ objc_library(
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTFileAccessEvent",
|
||||
srcs = ["SNTFileAccessEvent.m"],
|
||||
hdrs = ["SNTFileAccessEvent.h"],
|
||||
module_name = "santa_common_SNTFileAccessEvent",
|
||||
sdk_frameworks = [
|
||||
"Foundation",
|
||||
],
|
||||
deps = [
|
||||
"@MOLCertificate",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTCommonEnums",
|
||||
textual_hdrs = ["SNTCommonEnums.h"],
|
||||
|
||||
@@ -36,13 +36,20 @@ typedef NS_ENUM(NSInteger, SNTAction) {
|
||||
#define RESPONSE_VALID(x) \
|
||||
(x == SNTActionRespondAllow || x == SNTActionRespondDeny || x == SNTActionRespondAllowCompiler)
|
||||
|
||||
// Supported Rule Types
|
||||
//
|
||||
// Note: These enum values should be in order of decreasing precedence as
|
||||
// evaluated by Santa. When adding new enum values, leave some space so that
|
||||
// additional rules can be added without violating this. The ordering isn't
|
||||
// strictly necessary but improves readability and may preemptively prevent
|
||||
// issues should SQLite behavior change.
|
||||
typedef NS_ENUM(NSInteger, SNTRuleType) {
|
||||
SNTRuleTypeUnknown,
|
||||
SNTRuleTypeUnknown = 0,
|
||||
|
||||
SNTRuleTypeBinary = 1,
|
||||
SNTRuleTypeCertificate = 2,
|
||||
SNTRuleTypeTeamID = 3,
|
||||
SNTRuleTypeSigningID = 4,
|
||||
SNTRuleTypeBinary = 1000,
|
||||
SNTRuleTypeSigningID = 2000,
|
||||
SNTRuleTypeCertificate = 3000,
|
||||
SNTRuleTypeTeamID = 4000,
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, SNTRuleState) {
|
||||
@@ -114,6 +121,7 @@ typedef NS_ENUM(NSInteger, SNTEventLogType) {
|
||||
SNTEventLogTypeSyslog,
|
||||
SNTEventLogTypeFilelog,
|
||||
SNTEventLogTypeProtobuf,
|
||||
SNTEventLogTypeJSON,
|
||||
SNTEventLogTypeNull,
|
||||
};
|
||||
|
||||
|
||||
@@ -194,6 +194,7 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
kModeNotificationLockdown : string,
|
||||
kStaticRules : array,
|
||||
kSyncBaseURLKey : string,
|
||||
kSyncEnableCleanSyncEventUpload : number,
|
||||
kSyncProxyConfigKey : dictionary,
|
||||
kClientAuthCertificateFileKey : string,
|
||||
kClientAuthCertificatePasswordKey : string,
|
||||
@@ -314,6 +315,10 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingEnableCleanSyncEventUpload {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingEnablePageZeroProtection {
|
||||
return [self configStateSet];
|
||||
}
|
||||
@@ -794,6 +799,8 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
return SNTEventLogTypeSyslog;
|
||||
} else if ([logType isEqualToString:@"null"]) {
|
||||
return SNTEventLogTypeNull;
|
||||
} else if ([logType isEqualToString:@"json"]) {
|
||||
return SNTEventLogTypeJSON;
|
||||
} else if ([logType isEqualToString:@"file"]) {
|
||||
return SNTEventLogTypeFilelog;
|
||||
} else {
|
||||
|
||||
83
Source/common/SNTFileAccessEvent.h
Normal file
83
Source/common/SNTFileAccessEvent.h
Normal file
@@ -0,0 +1,83 @@
|
||||
/// 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>
|
||||
|
||||
#import <MOLCertificate/MOLCertificate.h>
|
||||
|
||||
///
|
||||
/// Represents an event stored in the database.
|
||||
///
|
||||
@interface SNTFileAccessEvent : NSObject <NSSecureCoding>
|
||||
|
||||
///
|
||||
/// The watched path that was accessed
|
||||
///
|
||||
@property NSString *accessedPath;
|
||||
|
||||
///
|
||||
/// The rule version and name that were violated
|
||||
///
|
||||
@property NSString *ruleVersion;
|
||||
@property NSString *ruleName;
|
||||
|
||||
///
|
||||
/// The SHA256 of the process that accessed the path
|
||||
///
|
||||
@property NSString *fileSHA256;
|
||||
|
||||
///
|
||||
/// The path of the process that accessed the watched path
|
||||
///
|
||||
@property NSString *filePath;
|
||||
|
||||
///
|
||||
/// If the process is part of a bundle, the name of the application
|
||||
///
|
||||
@property NSString *application;
|
||||
|
||||
///
|
||||
/// If the executed file was signed, this is the Team ID if present in the signature information.
|
||||
///
|
||||
@property NSString *teamID;
|
||||
|
||||
///
|
||||
/// If the executed file was signed, this is the Signing ID if present in the signature information.
|
||||
///
|
||||
@property NSString *signingID;
|
||||
|
||||
///
|
||||
/// The user who executed the binary.
|
||||
///
|
||||
@property NSString *executingUser;
|
||||
|
||||
///
|
||||
/// The process ID of the binary being executed.
|
||||
///
|
||||
@property NSNumber *pid;
|
||||
|
||||
///
|
||||
/// The parent process ID of the binary being executed.
|
||||
///
|
||||
@property NSNumber *ppid;
|
||||
|
||||
///
|
||||
/// The name of the parent process.
|
||||
///
|
||||
@property NSString *parentName;
|
||||
|
||||
// TODO(mlw): Store signing chain info
|
||||
// @property NSArray<MOLCertificate*> *signingChain;
|
||||
|
||||
@end
|
||||
79
Source/common/SNTFileAccessEvent.m
Normal file
79
Source/common/SNTFileAccessEvent.m
Normal file
@@ -0,0 +1,79 @@
|
||||
/// 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/SNTFileAccessEvent.h"
|
||||
|
||||
@implementation SNTFileAccessEvent
|
||||
|
||||
#define ENCODE(o) \
|
||||
do { \
|
||||
if (self.o) { \
|
||||
[coder encodeObject:self.o forKey:@(#o)]; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define DECODE(o, c) \
|
||||
do { \
|
||||
_##o = [decoder decodeObjectOfClass:[c class] forKey:@(#o)]; \
|
||||
} while (0)
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (BOOL)supportsSecureCoding {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)coder {
|
||||
ENCODE(accessedPath);
|
||||
ENCODE(ruleVersion);
|
||||
ENCODE(ruleName);
|
||||
ENCODE(fileSHA256);
|
||||
ENCODE(filePath);
|
||||
ENCODE(application);
|
||||
ENCODE(teamID);
|
||||
ENCODE(teamID);
|
||||
ENCODE(pid);
|
||||
ENCODE(ppid);
|
||||
ENCODE(parentName);
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)decoder {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
DECODE(accessedPath, NSString);
|
||||
DECODE(ruleVersion, NSString);
|
||||
DECODE(ruleName, NSString);
|
||||
DECODE(fileSHA256, NSString);
|
||||
DECODE(filePath, NSString);
|
||||
DECODE(application, NSString);
|
||||
DECODE(teamID, NSString);
|
||||
DECODE(teamID, NSString);
|
||||
DECODE(pid, NSNumber);
|
||||
DECODE(ppid, NSNumber);
|
||||
DECODE(parentName, NSString);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSString *)description {
|
||||
return [NSString
|
||||
stringWithFormat:@"SNTFileAccessEvent: Accessed: %@, By: %@", self.accessedPath, self.filePath];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -17,13 +17,16 @@
|
||||
#import "Source/common/SNTCommonEnums.h"
|
||||
#import "Source/common/SNTXPCBundleServiceInterface.h"
|
||||
|
||||
@class SNTStoredEvent;
|
||||
@class SNTDeviceEvent;
|
||||
@class SNTFileAccessEvent;
|
||||
@class SNTStoredEvent;
|
||||
|
||||
/// Protocol implemented by SantaGUI and utilized by santad
|
||||
@protocol SNTNotifierXPC
|
||||
- (void)postBlockNotification:(SNTStoredEvent *)event withCustomMessage:(NSString *)message;
|
||||
- (void)postUSBBlockNotification:(SNTDeviceEvent *)event withCustomMessage:(NSString *)message;
|
||||
- (void)postFileAccessBlockNotification:(SNTFileAccessEvent *)event
|
||||
withCustomMessage:(NSString *)message API_AVAILABLE(macos(13.0));
|
||||
- (void)postClientModeNotification:(SNTClientMode)clientmode;
|
||||
- (void)postRuleSyncNotificationWithCustomMessage:(NSString *)message;
|
||||
- (void)updateCountsForEvent:(SNTStoredEvent *)event
|
||||
|
||||
@@ -30,6 +30,14 @@ static inline std::string NSStringToUTF8String(NSString *str) {
|
||||
return std::string(str.UTF8String, [str lengthOfBytesUsingEncoding:NSUTF8StringEncoding]);
|
||||
}
|
||||
|
||||
static inline NSString *StringToNSString(const std::string &str) {
|
||||
return [NSString stringWithUTF8String:str.c_str()];
|
||||
}
|
||||
|
||||
static inline NSString *StringToNSString(const char *str) {
|
||||
return [NSString stringWithUTF8String:str];
|
||||
}
|
||||
|
||||
} // namespace santa::common
|
||||
|
||||
#endif
|
||||
|
||||
@@ -38,9 +38,12 @@
|
||||
// Pretty print C++ string match errors
|
||||
#define XCTAssertCppStringEqual(got, want) XCTAssertCStringEqual((got).c_str(), (want).c_str())
|
||||
|
||||
// Note: Delta between local formatter and the one run on Github. Disable for now.
|
||||
// clang-format off
|
||||
#define XCTAssertSemaTrue(s, sec, m) \
|
||||
XCTAssertEqual( \
|
||||
0, dispatch_semaphore_wait((s), dispatch_time(DISPATCH_TIME_NOW, (sec)*NSEC_PER_SEC)), m)
|
||||
0, dispatch_semaphore_wait((s), dispatch_time(DISPATCH_TIME_NOW, (sec) * NSEC_PER_SEC)), m)
|
||||
// clang-format on
|
||||
|
||||
// Helper to ensure at least `ms` milliseconds are slept, even if the sleep
|
||||
// function returns early due to interrupts.
|
||||
|
||||
@@ -31,6 +31,17 @@ swift_library(
|
||||
],
|
||||
)
|
||||
|
||||
swift_library(
|
||||
name = "SNTFileAccessMessageWindowView",
|
||||
srcs = [
|
||||
"SNTFileAccessMessageWindowView.swift",
|
||||
],
|
||||
generates_header = 1,
|
||||
deps = [
|
||||
"//Source/common:SNTFileAccessEvent",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SantaGUI_lib",
|
||||
srcs = [
|
||||
@@ -44,6 +55,8 @@ objc_library(
|
||||
"SNTBinaryMessageWindowController.m",
|
||||
"SNTDeviceMessageWindowController.h",
|
||||
"SNTDeviceMessageWindowController.m",
|
||||
"SNTFileAccessMessageWindowController.h",
|
||||
"SNTFileAccessMessageWindowController.m",
|
||||
"SNTMessageWindowController.h",
|
||||
"SNTMessageWindowController.m",
|
||||
"SNTNotificationManager.h",
|
||||
@@ -65,9 +78,11 @@ objc_library(
|
||||
deps = [
|
||||
":SNTAboutWindowView",
|
||||
":SNTDeviceMessageWindowView",
|
||||
":SNTFileAccessMessageWindowView",
|
||||
"//Source/common:SNTBlockMessage_SantaGUI",
|
||||
"//Source/common:SNTConfigurator",
|
||||
"//Source/common:SNTDeviceEvent",
|
||||
"//Source/common:SNTFileAccessEvent",
|
||||
"//Source/common:SNTLogging",
|
||||
"//Source/common:SNTStoredEvent",
|
||||
"//Source/common:SNTStrengthify",
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
/// 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 SwiftUI
|
||||
|
||||
import santa_common_SNTConfigurator
|
||||
|
||||
35
Source/gui/SNTFileAccessMessageWindowController.h
Normal file
35
Source/gui/SNTFileAccessMessageWindowController.h
Normal file
@@ -0,0 +1,35 @@
|
||||
/// 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 <Cocoa/Cocoa.h>
|
||||
|
||||
#import "Source/gui/SNTMessageWindowController.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class SNTFileAccessEvent;
|
||||
|
||||
///
|
||||
/// Controller for a single message window.
|
||||
///
|
||||
API_AVAILABLE(macos(13.0))
|
||||
@interface SNTFileAccessMessageWindowController : SNTMessageWindowController <NSWindowDelegate>
|
||||
|
||||
- (instancetype)initWithEvent:(SNTFileAccessEvent *)event message:(nullable NSString *)message;
|
||||
|
||||
@property(readonly) SNTFileAccessEvent *event;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
79
Source/gui/SNTFileAccessMessageWindowController.m
Normal file
79
Source/gui/SNTFileAccessMessageWindowController.m
Normal file
@@ -0,0 +1,79 @@
|
||||
/// 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/gui/SNTFileAccessMessageWindowController.h"
|
||||
#import "Source/gui/SNTFileAccessMessageWindowView-Swift.h"
|
||||
|
||||
#import "Source/common/SNTBlockMessage.h"
|
||||
#import "Source/common/SNTFileAccessEvent.h"
|
||||
#import "Source/common/SNTLogging.h"
|
||||
|
||||
@interface SNTFileAccessMessageWindowController ()
|
||||
@property NSString *customMessage;
|
||||
@property SNTFileAccessEvent *event;
|
||||
@end
|
||||
|
||||
@implementation SNTFileAccessMessageWindowController
|
||||
|
||||
- (instancetype)initWithEvent:(SNTFileAccessEvent *)event message:(nullable NSString *)message {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_customMessage = message;
|
||||
_event = event;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)showWindow:(id)sender {
|
||||
if (self.window) {
|
||||
[self.window orderOut:sender];
|
||||
}
|
||||
|
||||
self.window =
|
||||
[[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 0, 0)
|
||||
styleMask:NSWindowStyleMaskClosable | NSWindowStyleMaskTitled
|
||||
backing:NSBackingStoreBuffered
|
||||
defer:NO];
|
||||
|
||||
self.window.contentViewController =
|
||||
[SNTFileAccessMessageWindowViewFactory createWithWindow:self.window
|
||||
event:self.event
|
||||
customMsg:self.attributedCustomMessage];
|
||||
|
||||
self.window.delegate = self;
|
||||
|
||||
// Add app to Cmd+Tab and Dock.
|
||||
NSApp.activationPolicy = NSApplicationActivationPolicyRegular;
|
||||
|
||||
[super showWindow:sender];
|
||||
}
|
||||
|
||||
- (void)windowWillClose:(NSNotification *)notification {
|
||||
// Remove app from Cmd+Tab and Dock.
|
||||
NSApp.activationPolicy = NSApplicationActivationPolicyAccessory;
|
||||
[super windowWillClose:notification];
|
||||
}
|
||||
|
||||
- (NSAttributedString *)attributedCustomMessage {
|
||||
return [SNTBlockMessage formatMessage:self.customMessage];
|
||||
}
|
||||
|
||||
- (NSString *)messageHash {
|
||||
// TODO(mlw): This is not the final form. As this feature is expanded this
|
||||
// hash will need to be revisted to ensure it meets our needs.
|
||||
return [NSString stringWithFormat:@"%@|%@|%d", self.event.ruleName, self.event.ruleVersion,
|
||||
[self.event.pid intValue]];
|
||||
}
|
||||
|
||||
@end
|
||||
158
Source/gui/SNTFileAccessMessageWindowView.swift
Normal file
158
Source/gui/SNTFileAccessMessageWindowView.swift
Normal file
@@ -0,0 +1,158 @@
|
||||
/// 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 SwiftUI
|
||||
|
||||
import santa_common_SNTFileAccessEvent
|
||||
|
||||
@available(macOS 13, *)
|
||||
@objc public class SNTFileAccessMessageWindowViewFactory : NSObject {
|
||||
@objc public static func createWith(window: NSWindow, event: SNTFileAccessEvent, customMsg: NSAttributedString?) -> NSViewController {
|
||||
return NSHostingController(rootView:SNTFileAccessMessageWindowView(window:window, event:event, customMsg:customMsg)
|
||||
.frame(width:800, height:600))
|
||||
}
|
||||
}
|
||||
|
||||
@available(macOS 13, *)
|
||||
struct Property : View {
|
||||
var lbl: String
|
||||
var val: String
|
||||
|
||||
var body: some View {
|
||||
let width: CGFloat? = 150
|
||||
|
||||
HStack(spacing: 5) {
|
||||
Text(lbl + ":")
|
||||
.frame(width: width, alignment: .trailing)
|
||||
.lineLimit(1)
|
||||
.font(.system(size: 12, weight: .bold))
|
||||
.padding(Edge.Set.horizontal, 10)
|
||||
|
||||
Text(val)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@available(macOS 13, *)
|
||||
struct Event: View {
|
||||
let e: SNTFileAccessEvent
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing:10) {
|
||||
Property(lbl: "Path Accessed", val: e.accessedPath)
|
||||
Property(lbl: "Rule Name", val: e.ruleName)
|
||||
Property(lbl: "Rule Version", val: e.ruleVersion)
|
||||
|
||||
Divider()
|
||||
.frame(width: 700)
|
||||
|
||||
if let app = e.application {
|
||||
Property(lbl: "Application", val: app)
|
||||
}
|
||||
|
||||
Property(lbl: "Name", val: (e.filePath as NSString).lastPathComponent)
|
||||
Property(lbl: "Path", val: e.filePath)
|
||||
Property(lbl: "Identifier", val: e.fileSHA256)
|
||||
Property(lbl: "Parent", val: e.parentName + " (" + e.ppid.stringValue + ")")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@available(macOS 13, *)
|
||||
struct SNTFileAccessMessageWindowView: View {
|
||||
let window: NSWindow?
|
||||
let event: SNTFileAccessEvent?
|
||||
let customMsg: NSAttributedString?
|
||||
|
||||
@State private var checked = false
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing:20.0) {
|
||||
Spacer()
|
||||
Text("Santa").font(Font.custom("HelveticaNeue-UltraLight", size: 34.0))
|
||||
|
||||
if let msg = customMsg {
|
||||
Text(AttributedString(msg)).multilineTextAlignment(.center).padding(15.0)
|
||||
} else {
|
||||
Text("Access to a protected resource was denied.").multilineTextAlignment(.center).padding(15.0)
|
||||
}
|
||||
|
||||
Event(e: event!)
|
||||
|
||||
Toggle(isOn: $checked) {
|
||||
Text("Prevent future notifications for this application for a day")
|
||||
.font(Font.system(size: 11.0));
|
||||
}
|
||||
|
||||
VStack(spacing:15) {
|
||||
Button(action: openButton, label: {
|
||||
Text("Open Event Info...").frame(maxWidth:.infinity)
|
||||
})
|
||||
Button(action: dismissButton, label: {
|
||||
Text("Dismiss").frame(maxWidth:.infinity)
|
||||
})
|
||||
.keyboardShortcut(.return)
|
||||
}.frame(width: 220)
|
||||
|
||||
Spacer()
|
||||
|
||||
}.frame(maxWidth:800.0).fixedSize()
|
||||
}
|
||||
|
||||
func publisherInfo() {
|
||||
// TODO(mlw): Will hook up in a separate PR
|
||||
print("showing publisher popup...")
|
||||
}
|
||||
|
||||
func openButton() {
|
||||
// TODO(mlw): Will hook up in a separate PR
|
||||
print("opening event info...")
|
||||
}
|
||||
|
||||
func dismissButton() {
|
||||
window?.close()
|
||||
print("close window")
|
||||
}
|
||||
}
|
||||
|
||||
@available(macOS 13, *)
|
||||
func testFileAccessEvent() -> SNTFileAccessEvent {
|
||||
let faaEvent = SNTFileAccessEvent()
|
||||
|
||||
faaEvent.accessedPath = "/accessed/path"
|
||||
faaEvent.ruleVersion = "watched_path.v1"
|
||||
faaEvent.ruleName = "watched_path"
|
||||
faaEvent.fileSHA256 = "b7c1e3fd640c5f211c89b02c2c6122f78ce322aa5c56eb0bb54bc422a8f8b670"
|
||||
faaEvent.filePath = "/Applications/gShoe.app/Contents/MacOS/gShoe"
|
||||
faaEvent.application = "gShoe"
|
||||
faaEvent.teamID = "EQHXZ8M8AV"
|
||||
faaEvent.signingID = "com.google.gShoe"
|
||||
faaEvent.executingUser = "nobody"
|
||||
faaEvent.pid = 456
|
||||
faaEvent.ppid = 123
|
||||
faaEvent.parentName = "gLauncher"
|
||||
|
||||
return faaEvent
|
||||
}
|
||||
|
||||
// Enable previews in Xcode.
|
||||
@available(macOS 13, *)
|
||||
struct SNTFileAccessMessageWindowView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
SNTFileAccessMessageWindowView(window: nil, event: testFileAccessEvent(), customMsg: nil)
|
||||
}
|
||||
}
|
||||
@@ -15,8 +15,6 @@
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
#import "Source/common/SNTXPCNotifierInterface.h"
|
||||
#import "Source/gui/SNTBinaryMessageWindowController.h"
|
||||
#import "Source/gui/SNTDeviceMessageWindowController.h"
|
||||
#import "Source/gui/SNTMessageWindowController.h"
|
||||
|
||||
///
|
||||
|
||||
@@ -26,6 +26,9 @@
|
||||
#import "Source/common/SNTStrengthify.h"
|
||||
#import "Source/common/SNTSyncConstants.h"
|
||||
#import "Source/common/SNTXPCControlInterface.h"
|
||||
#import "Source/gui/SNTBinaryMessageWindowController.h"
|
||||
#import "Source/gui/SNTDeviceMessageWindowController.h"
|
||||
#import "Source/gui/SNTFileAccessMessageWindowController.h"
|
||||
#import "Source/gui/SNTMessageWindowController.h"
|
||||
|
||||
@interface SNTNotificationManager ()
|
||||
@@ -342,6 +345,19 @@ static NSString *const silencedNotificationsKey = @"SilencedNotifications";
|
||||
[self queueMessage:pendingMsg];
|
||||
}
|
||||
|
||||
- (void)postFileAccessBlockNotification:(SNTFileAccessEvent *)event
|
||||
withCustomMessage:(NSString *)message API_AVAILABLE(macos(13.0)) {
|
||||
if (!event) {
|
||||
LOGI(@"Error: Missing event object in message received from daemon!");
|
||||
return;
|
||||
}
|
||||
|
||||
SNTFileAccessMessageWindowController *pendingMsg =
|
||||
[[SNTFileAccessMessageWindowController alloc] initWithEvent:event message:message];
|
||||
|
||||
[self queueMessage:pendingMsg];
|
||||
}
|
||||
|
||||
#pragma mark SNTBundleNotifierXPC protocol methods
|
||||
|
||||
- (void)updateCountsForEvent:(SNTStoredEvent *)event
|
||||
|
||||
@@ -209,6 +209,16 @@ objc_library(
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "TTYWriter",
|
||||
srcs = ["TTYWriter.mm"],
|
||||
hdrs = ["TTYWriter.h"],
|
||||
deps = [
|
||||
"//Source/common:SNTLogging",
|
||||
"//Source/common:String",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTExecutionController",
|
||||
srcs = ["SNTExecutionController.mm"],
|
||||
@@ -221,6 +231,7 @@ objc_library(
|
||||
":SNTPolicyProcessor",
|
||||
":SNTRuleTable",
|
||||
":SNTSyncdQueue",
|
||||
":TTYWriter",
|
||||
"//Source/common:BranchPrediction",
|
||||
"//Source/common:SNTBlockMessage",
|
||||
"//Source/common:SNTCachedDecision",
|
||||
@@ -286,7 +297,9 @@ objc_library(
|
||||
":SNTEndpointSecurityClient",
|
||||
":SNTEndpointSecurityEventHandler",
|
||||
"//Source/common:PrefixTree",
|
||||
"//Source/common:SNTConfigurator",
|
||||
"//Source/common:SNTLogging",
|
||||
"//Source/common:String",
|
||||
"//Source/common:Unit",
|
||||
],
|
||||
)
|
||||
@@ -332,6 +345,9 @@ objc_library(
|
||||
name = "SNTEndpointSecurityFileAccessAuthorizer",
|
||||
srcs = ["EventProviders/SNTEndpointSecurityFileAccessAuthorizer.mm"],
|
||||
hdrs = ["EventProviders/SNTEndpointSecurityFileAccessAuthorizer.h"],
|
||||
sdk_dylibs = [
|
||||
"bsm",
|
||||
],
|
||||
deps = [
|
||||
":EndpointSecurityAPI",
|
||||
":EndpointSecurityEnrichedTypes",
|
||||
@@ -348,11 +364,13 @@ objc_library(
|
||||
"//Source/common:Platform",
|
||||
"//Source/common:SNTCommonEnums",
|
||||
"//Source/common:SNTConfigurator",
|
||||
"//Source/common:SNTFileAccessEvent",
|
||||
"//Source/common:SNTMetricSet",
|
||||
"//Source/common:SNTStrengthify",
|
||||
"//Source/common:SantaCache",
|
||||
"//Source/common:SantaVnode",
|
||||
"//Source/common:SantaVnodeHash",
|
||||
"//Source/common:String",
|
||||
"@MOLCertificate",
|
||||
"@MOLCodesignChecker",
|
||||
],
|
||||
@@ -678,6 +696,7 @@ objc_library(
|
||||
"//Source/common:PrefixTree",
|
||||
"//Source/common:SNTCommonEnums",
|
||||
"//Source/common:SNTConfigurator",
|
||||
"//Source/common:SNTFileAccessEvent",
|
||||
"//Source/common:SNTKVOManager",
|
||||
"//Source/common:SNTLogging",
|
||||
"//Source/common:SNTXPCNotifierInterface",
|
||||
@@ -705,6 +724,7 @@ objc_library(
|
||||
":SNTNotificationQueue",
|
||||
":SNTRuleTable",
|
||||
":SNTSyncdQueue",
|
||||
":TTYWriter",
|
||||
":WatchItems",
|
||||
"//Source/common:PrefixTree",
|
||||
"//Source/common:SNTConfigurator",
|
||||
@@ -1257,6 +1277,7 @@ santa_unit_test(
|
||||
":SNTCompilerController",
|
||||
":SNTEndpointSecurityRecorder",
|
||||
"//Source/common:PrefixTree",
|
||||
"//Source/common:SNTConfigurator",
|
||||
"//Source/common:TestUtils",
|
||||
"//Source/common:Unit",
|
||||
"@OCMock",
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
}
|
||||
[self closeDeleteReopenDatabase:db];
|
||||
} else if ([db userVersion] > [self currentSupportedVersion]) {
|
||||
LOGW(@"Database version newer than supported. Deleting.");
|
||||
[self closeDeleteReopenDatabase:db];
|
||||
}
|
||||
}];
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
#import "Source/common/SNTLogging.h"
|
||||
#import "Source/common/SNTRule.h"
|
||||
|
||||
static const uint32_t kRuleTableCurrentVersion = 4;
|
||||
static const uint32_t kRuleTableCurrentVersion = 5;
|
||||
|
||||
// TODO(nguyenphillip): this should be configurable.
|
||||
// How many rules must be in database before we start trying to remove transitive rules.
|
||||
@@ -210,12 +210,25 @@ static void addPathsFromDefaultMuteSet(NSMutableSet *criticalPaths) API_AVAILABL
|
||||
[db executeUpdate:@"ALTER TABLE 'rules' ADD 'timestamp' INTEGER"];
|
||||
newVersion = 3;
|
||||
}
|
||||
|
||||
if (version < 4) {
|
||||
// Rename `shasum` column to `identifier`.
|
||||
[db executeUpdate:@"ALTER TABLE 'rules' RENAME COLUMN 'shasum' TO 'identifier'"];
|
||||
newVersion = 4;
|
||||
}
|
||||
|
||||
if (version < 5) {
|
||||
// Migrate SNTRuleType enum values
|
||||
// Note: The reordering is intentional so that the type values are in order
|
||||
// of precedence.
|
||||
[db executeUpdate:@"UPDATE rules SET type = 1000 WHERE type = 1"];
|
||||
[db executeUpdate:@"UPDATE rules SET type = 3000 WHERE type = 2"];
|
||||
[db executeUpdate:@"UPDATE rules SET type = 4000 WHERE type = 3"];
|
||||
[db executeUpdate:@"UPDATE rules SET type = 2000 WHERE type = 4"];
|
||||
|
||||
newVersion = 5;
|
||||
}
|
||||
|
||||
// Save signing info for launchd and santad. Used to ensure they are always allowed.
|
||||
self.santadCSInfo = [[MOLCodesignChecker alloc] initWithSelf];
|
||||
self.launchdCSInfo = [[MOLCodesignChecker alloc] initWithPID:1];
|
||||
@@ -236,20 +249,20 @@ static void addPathsFromDefaultMuteSet(NSMutableSet *criticalPaths) API_AVAILABL
|
||||
return count;
|
||||
}
|
||||
|
||||
- (NSUInteger)binaryRuleCount {
|
||||
- (NSUInteger)ruleCountForRuleType:(SNTRuleType)ruleType {
|
||||
__block NSUInteger count = 0;
|
||||
[self inDatabase:^(FMDatabase *db) {
|
||||
count = [db longForQuery:@"SELECT COUNT(*) FROM rules WHERE type=1"];
|
||||
count = [db longForQuery:@"SELECT COUNT(*) FROM rules WHERE type=?", @(ruleType)];
|
||||
}];
|
||||
return count;
|
||||
}
|
||||
|
||||
- (NSUInteger)binaryRuleCount {
|
||||
return [self ruleCountForRuleType:SNTRuleTypeBinary];
|
||||
}
|
||||
|
||||
- (NSUInteger)certificateRuleCount {
|
||||
__block NSUInteger count = 0;
|
||||
[self inDatabase:^(FMDatabase *db) {
|
||||
count = [db longForQuery:@"SELECT COUNT(*) FROM rules WHERE type=2"];
|
||||
}];
|
||||
return count;
|
||||
return [self ruleCountForRuleType:SNTRuleTypeCertificate];
|
||||
}
|
||||
|
||||
- (NSUInteger)compilerRuleCount {
|
||||
@@ -271,19 +284,11 @@ static void addPathsFromDefaultMuteSet(NSMutableSet *criticalPaths) API_AVAILABL
|
||||
}
|
||||
|
||||
- (NSUInteger)teamIDRuleCount {
|
||||
__block NSUInteger count = 0;
|
||||
[self inDatabase:^(FMDatabase *db) {
|
||||
count = [db longForQuery:@"SELECT COUNT(*) FROM rules WHERE type=3"];
|
||||
}];
|
||||
return count;
|
||||
return [self ruleCountForRuleType:SNTRuleTypeTeamID];
|
||||
}
|
||||
|
||||
- (NSUInteger)signingIDRuleCount {
|
||||
__block NSUInteger count = 0;
|
||||
[self inDatabase:^(FMDatabase *db) {
|
||||
count = [db longForQuery:@"SELECT COUNT(*) FROM rules WHERE type=4"];
|
||||
}];
|
||||
return count;
|
||||
return [self ruleCountForRuleType:SNTRuleTypeSigningID];
|
||||
}
|
||||
|
||||
- (SNTRule *)ruleFromResultSet:(FMResultSet *)rs {
|
||||
@@ -347,10 +352,10 @@ static void addPathsFromDefaultMuteSet(NSMutableSet *criticalPaths) API_AVAILABL
|
||||
//
|
||||
[self inDatabase:^(FMDatabase *db) {
|
||||
FMResultSet *rs = [db executeQuery:@"SELECT * FROM rules WHERE "
|
||||
@" (identifier=? and type=1) "
|
||||
@"OR (identifier=? AND type=4) "
|
||||
@"OR (identifier=? AND type=2) "
|
||||
@"OR (identifier=? AND type=3) LIMIT 1",
|
||||
@" (identifier=? and type=1000) "
|
||||
@"OR (identifier=? AND type=2000) "
|
||||
@"OR (identifier=? AND type=3000) "
|
||||
@"OR (identifier=? AND type=4000) LIMIT 1",
|
||||
binarySHA256, signingID, certificateSHA256, teamID];
|
||||
if ([rs next]) {
|
||||
rule = [self ruleFromResultSet:rs];
|
||||
|
||||
@@ -100,6 +100,10 @@ struct WatchItemPolicy {
|
||||
bool audit_only;
|
||||
bool invert_process_exceptions;
|
||||
std::vector<Process> processes;
|
||||
|
||||
// WIP - No current way to control via config
|
||||
bool silent = true;
|
||||
std::string version = "temp_version";
|
||||
};
|
||||
|
||||
} // namespace santa::santad::data_layer
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
#import <XCTest/XCTest.h>
|
||||
#include <dispatch/dispatch.h>
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "Source/santad/EventProviders/EndpointSecurity/Client.h"
|
||||
|
||||
using santa::santad::event_providers::endpoint_security::Client;
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
#include <memory>
|
||||
|
||||
#import "Source/common/SNTFileAccessEvent.h"
|
||||
#include "Source/santad/DataLayer/WatchItems.h"
|
||||
#include "Source/santad/EventProviders/EndpointSecurity/EndpointSecurityAPI.h"
|
||||
#include "Source/santad/EventProviders/EndpointSecurity/Enricher.h"
|
||||
@@ -25,6 +26,8 @@
|
||||
#include "Source/santad/Metrics.h"
|
||||
#import "Source/santad/SNTDecisionCache.h"
|
||||
|
||||
typedef void (^SNTFileAccessBlockCallback)(SNTFileAccessEvent *event);
|
||||
|
||||
@interface SNTEndpointSecurityFileAccessAuthorizer
|
||||
: SNTEndpointSecurityClient <SNTEndpointSecurityDynamicEventHandler>
|
||||
|
||||
@@ -38,4 +41,6 @@
|
||||
(std::shared_ptr<santa::santad::event_providers::endpoint_security::Enricher>)enricher
|
||||
decisionCache:(SNTDecisionCache *)decisionCache;
|
||||
|
||||
@property SNTFileAccessBlockCallback fileAccessBlockCallback;
|
||||
|
||||
@end
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include <Kernel/kern/cs_blobs.h>
|
||||
#import <MOLCertificate/MOLCertificate.h>
|
||||
#import <MOLCodesignChecker/MOLCodesignChecker.h>
|
||||
#include <bsm/libbsm.h>
|
||||
#include <sys/fcntl.h>
|
||||
|
||||
#include <algorithm>
|
||||
@@ -32,17 +33,20 @@
|
||||
#include "Source/common/Platform.h"
|
||||
#import "Source/common/SNTCommonEnums.h"
|
||||
#import "Source/common/SNTConfigurator.h"
|
||||
#include "Source/common/SNTFileAccessEvent.h"
|
||||
#import "Source/common/SNTMetricSet.h"
|
||||
#import "Source/common/SNTStrengthify.h"
|
||||
#include "Source/common/SantaCache.h"
|
||||
#include "Source/common/SantaVnode.h"
|
||||
#include "Source/common/SantaVnodeHash.h"
|
||||
#include "Source/common/String.h"
|
||||
#include "Source/santad/DataLayer/WatchItemPolicy.h"
|
||||
#include "Source/santad/DataLayer/WatchItems.h"
|
||||
#include "Source/santad/EventProviders/EndpointSecurity/EnrichedTypes.h"
|
||||
#include "Source/santad/EventProviders/EndpointSecurity/Message.h"
|
||||
#include "Source/santad/EventProviders/RateLimiter.h"
|
||||
|
||||
using santa::common::StringToNSString;
|
||||
using santa::santad::EventDisposition;
|
||||
using santa::santad::data_layer::WatchItemPathType;
|
||||
using santa::santad::data_layer::WatchItemPolicy;
|
||||
@@ -481,6 +485,27 @@ void PopulatePathTargets(const Message &msg, std::vector<PathTarget> &targets) {
|
||||
targetPathCopy, policyDecision);
|
||||
}];
|
||||
}
|
||||
|
||||
if (!optionalPolicy.value()->silent && self.fileAccessBlockCallback) {
|
||||
SNTCachedDecision *cd =
|
||||
[self.decisionCache cachedDecisionForFile:msg->process->executable->stat];
|
||||
|
||||
SNTFileAccessEvent *event = [[SNTFileAccessEvent alloc] init];
|
||||
|
||||
event.accessedPath = StringToNSString(target.path);
|
||||
event.ruleVersion = StringToNSString(optionalPolicy.value()->version);
|
||||
event.ruleName = StringToNSString(optionalPolicy.value()->name);
|
||||
|
||||
event.fileSHA256 = cd.sha256 ?: @"<unknown sha>";
|
||||
event.filePath = StringToNSString(msg->process->executable->path.data);
|
||||
event.teamID = cd.teamID ?: @"<unknown team id>";
|
||||
event.teamID = cd.signingID ?: @"<unknown signing id>";
|
||||
event.pid = @(audit_token_to_pid(msg->process->audit_token));
|
||||
event.ppid = @(audit_token_to_pid(msg->process->parent_audit_token));
|
||||
event.parentName = StringToNSString(msg.ParentProcessName());
|
||||
|
||||
self.fileAccessBlockCallback(event);
|
||||
}
|
||||
}
|
||||
|
||||
return policyDecision;
|
||||
|
||||
@@ -16,7 +16,9 @@
|
||||
|
||||
#include <EndpointSecurity/EndpointSecurity.h>
|
||||
|
||||
#import "Source/common/SNTConfigurator.h"
|
||||
#import "Source/common/SNTLogging.h"
|
||||
#include "Source/common/String.h"
|
||||
#include "Source/santad/EventProviders/AuthResultCache.h"
|
||||
#include "Source/santad/EventProviders/EndpointSecurity/EnrichedTypes.h"
|
||||
#include "Source/santad/EventProviders/EndpointSecurity/Message.h"
|
||||
@@ -44,6 +46,7 @@ es_file_t *GetTargetFileForPrefixTree(const es_message_t *msg) {
|
||||
|
||||
@interface SNTEndpointSecurityRecorder ()
|
||||
@property SNTCompilerController *compilerController;
|
||||
@property SNTConfigurator *configurator;
|
||||
@end
|
||||
|
||||
@implementation SNTEndpointSecurityRecorder {
|
||||
@@ -69,6 +72,7 @@ es_file_t *GetTargetFileForPrefixTree(const es_message_t *msg) {
|
||||
_compilerController = compilerController;
|
||||
_authResultCache = authResultCache;
|
||||
_prefixTree = prefixTree;
|
||||
_configurator = [SNTConfigurator configurator];
|
||||
|
||||
[self establishClientOrDie];
|
||||
}
|
||||
@@ -83,7 +87,7 @@ es_file_t *GetTargetFileForPrefixTree(const es_message_t *msg) {
|
||||
recordEventMetrics:(void (^)(EventDisposition))recordEventMetrics {
|
||||
// Pre-enrichment processing
|
||||
switch (esMsg->event_type) {
|
||||
case ES_EVENT_TYPE_NOTIFY_CLOSE:
|
||||
case ES_EVENT_TYPE_NOTIFY_CLOSE: {
|
||||
// TODO(mlw): Once we move to building with the macOS 13 SDK, we should also check
|
||||
// the `was_mapped_writable` field
|
||||
if (esMsg->event.close.modified == false) {
|
||||
@@ -95,7 +99,23 @@ es_file_t *GetTargetFileForPrefixTree(const es_message_t *msg) {
|
||||
}
|
||||
|
||||
self->_authResultCache->RemoveFromCache(esMsg->event.close.target);
|
||||
|
||||
// Only log file changes that match the given regex
|
||||
NSString *targetPath = santa::common::StringToNSString(esMsg->event.close.target->path.data);
|
||||
if (![[self.configurator fileChangesRegex]
|
||||
numberOfMatchesInString:targetPath
|
||||
options:0
|
||||
range:NSMakeRange(0, targetPath.length)]) {
|
||||
// Note: Do not record metrics in this case. These are not considered "drops"
|
||||
// because this is not a failure case.
|
||||
// TODO(mlw): Consider changes to configuration that would allow muting paths
|
||||
// to filter on the kernel side rather than in user space.
|
||||
return;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default: break;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include <set>
|
||||
|
||||
#include "Source/common/PrefixTree.h"
|
||||
#import "Source/common/SNTConfigurator.h"
|
||||
#include "Source/common/TestUtils.h"
|
||||
#include "Source/common/Unit.h"
|
||||
#import "Source/santad/EventProviders/AuthResultCache.h"
|
||||
@@ -66,10 +67,21 @@ class MockLogger : public Logger {
|
||||
};
|
||||
|
||||
@interface SNTEndpointSecurityRecorderTest : XCTestCase
|
||||
@property id mockConfigurator;
|
||||
@end
|
||||
|
||||
@implementation SNTEndpointSecurityRecorderTest
|
||||
|
||||
- (void)setUp {
|
||||
self.mockConfigurator = OCMClassMock([SNTConfigurator class]);
|
||||
OCMStub([self.mockConfigurator configurator]).andReturn(self.mockConfigurator);
|
||||
NSString *testPattern = @"^/foo/match.*";
|
||||
NSRegularExpression *re = [NSRegularExpression regularExpressionWithPattern:testPattern
|
||||
options:0
|
||||
error:NULL];
|
||||
OCMStub([self.mockConfigurator fileChangesRegex]).andReturn(re);
|
||||
}
|
||||
|
||||
- (void)testEnable {
|
||||
// Ensure the client subscribes to expected event types
|
||||
std::set<es_event_type_t> expectedEventSubs{
|
||||
@@ -94,7 +106,8 @@ class MockLogger : public Logger {
|
||||
es_file_t file = MakeESFile("foo");
|
||||
es_process_t proc = MakeESProcess(&file);
|
||||
es_message_t esMsg = MakeESMessage(ES_EVENT_TYPE_NOTIFY_CLOSE, &proc, ActionType::Auth);
|
||||
es_file_t targetFile = MakeESFile("bar");
|
||||
es_file_t targetFileMatchesRegex = MakeESFile("/foo/matches");
|
||||
es_file_t targetFileMissesRegex = MakeESFile("/foo/misses");
|
||||
|
||||
auto mockESApi = std::make_shared<MockEndpointSecurityAPI>();
|
||||
mockESApi->SetExpectationsESNewClient();
|
||||
@@ -106,7 +119,8 @@ class MockLogger : public Logger {
|
||||
EXPECT_CALL(*mockEnricher, Enrich).WillOnce(testing::Return(std::move(enrichedMsg)));
|
||||
|
||||
auto mockAuthCache = std::make_shared<MockAuthResultCache>(nullptr, nil);
|
||||
EXPECT_CALL(*mockAuthCache, RemoveFromCache(&targetFile)).Times(1);
|
||||
EXPECT_CALL(*mockAuthCache, RemoveFromCache(&targetFileMatchesRegex)).Times(1);
|
||||
EXPECT_CALL(*mockAuthCache, RemoveFromCache(&targetFileMissesRegex)).Times(1);
|
||||
|
||||
dispatch_semaphore_t semaMetrics = dispatch_semaphore_create(0);
|
||||
|
||||
@@ -145,11 +159,11 @@ class MockLogger : public Logger {
|
||||
}]);
|
||||
}
|
||||
|
||||
// CLOSE modified, remove from cache
|
||||
// CLOSE modified, remove from cache, and matches fileChangesRegex
|
||||
{
|
||||
esMsg.event_type = ES_EVENT_TYPE_NOTIFY_CLOSE;
|
||||
esMsg.event.close.modified = true;
|
||||
esMsg.event.close.target = &targetFile;
|
||||
esMsg.event.close.target = &targetFileMatchesRegex;
|
||||
Message msg(mockESApi, &esMsg);
|
||||
|
||||
OCMExpect([mockCC handleEvent:msg withLogger:nullptr]).ignoringNonObjectArgs();
|
||||
@@ -164,10 +178,22 @@ class MockLogger : public Logger {
|
||||
XCTAssertSemaTrue(sema, 5, "Log wasn't called within expected time window");
|
||||
}
|
||||
|
||||
// CLOSE modified, remove from cache, but doesn't match fileChangesRegex
|
||||
{
|
||||
esMsg.event_type = ES_EVENT_TYPE_NOTIFY_CLOSE;
|
||||
esMsg.event.close.modified = true;
|
||||
esMsg.event.close.target = &targetFileMissesRegex;
|
||||
Message msg(mockESApi, &esMsg);
|
||||
XCTAssertNoThrow([recorderClient handleMessage:Message(mockESApi, &esMsg)
|
||||
recordEventMetrics:^(EventDisposition d) {
|
||||
XCTFail("Metrics record callback should not be called here");
|
||||
}]);
|
||||
}
|
||||
|
||||
// LINK, Prefix match, bail early
|
||||
{
|
||||
esMsg.event_type = ES_EVENT_TYPE_NOTIFY_LINK;
|
||||
esMsg.event.link.source = &targetFile;
|
||||
esMsg.event.link.source = &targetFileMatchesRegex;
|
||||
prefixTree->InsertPrefix(esMsg.event.link.source->path.data, Unit{});
|
||||
Message msg(mockESApi, &esMsg);
|
||||
|
||||
|
||||
@@ -72,6 +72,11 @@ std::unique_ptr<Logger> Logger::Create(std::shared_ptr<EndpointSecurityAPI> esap
|
||||
Protobuf::Create(esapi, std::move(decision_cache)),
|
||||
Spool::Create([spool_log_path UTF8String], spool_dir_size_threshold,
|
||||
spool_file_size_threshold, spool_flush_timeout_ms));
|
||||
case SNTEventLogTypeJSON:
|
||||
return std::make_unique<Logger>(
|
||||
Protobuf::Create(esapi, std::move(decision_cache), true),
|
||||
File::Create(event_log_path, kFlushBufferTimeoutMS, kBufferBatchSizeBytes,
|
||||
kMaxExpectedWriteSizeBytes));
|
||||
default: LOGE(@"Invalid log type: %ld", log_type); return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,6 +125,11 @@ class MockWriter : public Null {
|
||||
@"/tmp/spool", 1, 1, 1));
|
||||
XCTAssertNotEqual(nullptr, std::dynamic_pointer_cast<Protobuf>(logger.Serializer()));
|
||||
XCTAssertNotEqual(nullptr, std::dynamic_pointer_cast<Spool>(logger.Writer()));
|
||||
|
||||
logger = LoggerPeer(
|
||||
Logger::Create(mockESApi, SNTEventLogTypeJSON, nil, @"/tmp/temppy", @"/tmp/spool", 1, 1, 1));
|
||||
XCTAssertNotEqual(nullptr, std::dynamic_pointer_cast<Protobuf>(logger.Serializer()));
|
||||
XCTAssertNotEqual(nullptr, std::dynamic_pointer_cast<File>(logger.Writer()));
|
||||
}
|
||||
|
||||
- (void)testLog {
|
||||
|
||||
@@ -33,11 +33,11 @@ class Protobuf : public Serializer {
|
||||
public:
|
||||
static std::shared_ptr<Protobuf> Create(
|
||||
std::shared_ptr<santa::santad::event_providers::endpoint_security::EndpointSecurityAPI> esapi,
|
||||
SNTDecisionCache *decision_cache);
|
||||
SNTDecisionCache *decision_cache, bool json = false);
|
||||
|
||||
Protobuf(
|
||||
std::shared_ptr<santa::santad::event_providers::endpoint_security::EndpointSecurityAPI> esapi,
|
||||
SNTDecisionCache *decision_cache);
|
||||
SNTDecisionCache *decision_cache, bool json = false);
|
||||
|
||||
std::vector<uint8_t> SerializeMessage(
|
||||
const santa::santad::event_providers::endpoint_security::EnrichedClose &) override;
|
||||
@@ -87,6 +87,9 @@ class Protobuf : public Serializer {
|
||||
std::vector<uint8_t> FinalizeProto(::santa::pb::v1::SantaMessage *santa_msg);
|
||||
|
||||
std::shared_ptr<santa::santad::event_providers::endpoint_security::EndpointSecurityAPI> esapi_;
|
||||
// Toggle for transforming protobuf output to its JSON form.
|
||||
// See https://protobuf.dev/programming-guides/proto3/#json
|
||||
bool json_;
|
||||
};
|
||||
|
||||
} // namespace santa::santad::logs::endpoint_security::serializers
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
#include <EndpointSecurity/EndpointSecurity.h>
|
||||
#include <Kernel/kern/cs_blobs.h>
|
||||
#include <bsm/libbsm.h>
|
||||
#include <google/protobuf/stubs/status.h>
|
||||
#include <google/protobuf/util/json_util.h>
|
||||
#include <mach/message.h>
|
||||
#include <math.h>
|
||||
#include <sys/proc_info.h>
|
||||
@@ -38,6 +40,8 @@
|
||||
|
||||
using google::protobuf::Arena;
|
||||
using google::protobuf::Timestamp;
|
||||
using google::protobuf::util::JsonPrintOptions;
|
||||
using google::protobuf::util::MessageToJsonString;
|
||||
|
||||
using santa::common::NSStringToUTF8StringView;
|
||||
using santa::santad::event_providers::endpoint_security::EndpointSecurityAPI;
|
||||
@@ -66,12 +70,13 @@ namespace pbv1 = ::santa::pb::v1;
|
||||
namespace santa::santad::logs::endpoint_security::serializers {
|
||||
|
||||
std::shared_ptr<Protobuf> Protobuf::Create(std::shared_ptr<EndpointSecurityAPI> esapi,
|
||||
SNTDecisionCache *decision_cache) {
|
||||
return std::make_shared<Protobuf>(esapi, std::move(decision_cache));
|
||||
SNTDecisionCache *decision_cache, bool json) {
|
||||
return std::make_shared<Protobuf>(esapi, std::move(decision_cache), json);
|
||||
}
|
||||
|
||||
Protobuf::Protobuf(std::shared_ptr<EndpointSecurityAPI> esapi, SNTDecisionCache *decision_cache)
|
||||
: Serializer(std::move(decision_cache)), esapi_(esapi) {}
|
||||
Protobuf::Protobuf(std::shared_ptr<EndpointSecurityAPI> esapi, SNTDecisionCache *decision_cache,
|
||||
bool json)
|
||||
: Serializer(std::move(decision_cache)), esapi_(esapi), json_(json) {}
|
||||
|
||||
static inline void EncodeTimestamp(Timestamp *timestamp, struct timespec ts) {
|
||||
timestamp->set_seconds(ts.tv_sec);
|
||||
@@ -387,6 +392,26 @@ static inline void EncodeCertificateInfo(::pbv1::CertificateInfo *pb_cert_info,
|
||||
}
|
||||
|
||||
std::vector<uint8_t> Protobuf::FinalizeProto(::pbv1::SantaMessage *santa_msg) {
|
||||
if (this->json_) {
|
||||
// TODO: Profile this. It's probably not the most efficient way to do this.
|
||||
JsonPrintOptions options;
|
||||
options.always_print_enums_as_ints = false;
|
||||
options.always_print_primitive_fields = true;
|
||||
options.preserve_proto_field_names = true;
|
||||
std::string json;
|
||||
|
||||
google::protobuf::util::Status status = MessageToJsonString(*santa_msg, &json, options);
|
||||
|
||||
if (!status.ok()) {
|
||||
LOGE(@"Failed to convert protobuf to JSON: %s", status.ToString().c_str());
|
||||
}
|
||||
|
||||
std::vector<uint8_t> vec(json.begin(), json.end());
|
||||
// Add a newline to the end of the JSON row.
|
||||
vec.push_back('\n');
|
||||
return vec;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> vec(santa_msg->ByteSizeLong());
|
||||
santa_msg->SerializeWithCachedSizesToArray(vec.data());
|
||||
return vec;
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#import <OCMock/OCMock.h>
|
||||
#import <XCTest/XCTest.h>
|
||||
#include <gmock/gmock.h>
|
||||
#include <google/protobuf/util/json_util.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <sys/proc_info.h>
|
||||
#include <sys/signal.h>
|
||||
@@ -26,8 +27,6 @@
|
||||
#include <uuid/uuid.h>
|
||||
#include <cstring>
|
||||
|
||||
#include <google/protobuf/util/json_util.h>
|
||||
|
||||
#import "Source/common/SNTCachedDecision.h"
|
||||
#include "Source/common/SNTCommonEnums.h"
|
||||
#import "Source/common/SNTConfigurator.h"
|
||||
@@ -46,6 +45,7 @@
|
||||
|
||||
using google::protobuf::Timestamp;
|
||||
using google::protobuf::util::JsonPrintOptions;
|
||||
using google::protobuf::util::JsonStringToMessage;
|
||||
using santa::santad::event_providers::endpoint_security::EnrichedEventType;
|
||||
using santa::santad::event_providers::endpoint_security::EnrichedMessage;
|
||||
using santa::santad::event_providers::endpoint_security::Enricher;
|
||||
@@ -161,10 +161,34 @@ std::string ConvertMessageToJsonString(const ::pbv1::SantaMessage &santaMsg) {
|
||||
return json;
|
||||
}
|
||||
|
||||
NSDictionary *findDelta(NSDictionary *a, NSDictionary *b) {
|
||||
NSMutableDictionary *delta = 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];
|
||||
|
||||
if (![obj isEqual:otherObj]) {
|
||||
delta[key] = obj;
|
||||
}
|
||||
}];
|
||||
|
||||
// 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];
|
||||
|
||||
if (!aObj) {
|
||||
delta[key] = obj;
|
||||
}
|
||||
}];
|
||||
|
||||
return delta;
|
||||
}
|
||||
|
||||
void SerializeAndCheck(es_event_type_t eventType,
|
||||
void (^messageSetup)(std::shared_ptr<MockEndpointSecurityAPI>,
|
||||
es_message_t *),
|
||||
SNTDecisionCache *decisionCache) {
|
||||
SNTDecisionCache *decisionCache, bool json = false) {
|
||||
std::shared_ptr<MockEndpointSecurityAPI> mockESApi = std::make_shared<MockEndpointSecurityAPI>();
|
||||
|
||||
for (uint32_t cur_version = 1; cur_version <= MaxSupportedESMessageVersionForCurrentOS();
|
||||
@@ -185,7 +209,7 @@ void SerializeAndCheck(es_event_type_t eventType,
|
||||
|
||||
messageSetup(mockESApi, &esMsg);
|
||||
|
||||
std::shared_ptr<Serializer> bs = Protobuf::Create(mockESApi, decisionCache);
|
||||
std::shared_ptr<Serializer> bs = Protobuf::Create(mockESApi, decisionCache, json);
|
||||
std::unique_ptr<EnrichedMessage> enrichedMsg = Enricher().Enrich(Message(mockESApi, &esMsg));
|
||||
|
||||
// Copy some values we need to check later before the object is moved out of this funciton
|
||||
@@ -204,14 +228,43 @@ void SerializeAndCheck(es_event_type_t eventType,
|
||||
std::vector<uint8_t> vec = bs->SerializeMessage(std::move(enrichedMsg));
|
||||
std::string protoStr(vec.begin(), vec.end());
|
||||
|
||||
// if we're checking against JSON then we should already have a jsonified string and just need
|
||||
// to
|
||||
::pbv1::SantaMessage santaMsg;
|
||||
XCTAssertTrue(santaMsg.ParseFromString(protoStr));
|
||||
std::string gotData;
|
||||
|
||||
std::string gotData = ConvertMessageToJsonString(santaMsg);
|
||||
if (json) {
|
||||
// Parse the jsonified string into the protobuf
|
||||
// gotData = protoStr;
|
||||
google::protobuf::util::JsonParseOptions options;
|
||||
options.ignore_unknown_fields = true;
|
||||
google::protobuf::util::Status status = JsonStringToMessage(protoStr, &santaMsg, options);
|
||||
gotData = ConvertMessageToJsonString(santaMsg);
|
||||
} else {
|
||||
XCTAssertTrue(santaMsg.ParseFromString(protoStr));
|
||||
gotData = ConvertMessageToJsonString(santaMsg);
|
||||
}
|
||||
|
||||
XCTAssertTrue(CompareTime(santaMsg.processed_time(), enrichmentTime));
|
||||
XCTAssertTrue(CompareTime(santaMsg.event_time(), msgTime));
|
||||
XCTAssertEqualObjects([NSString stringWithUTF8String:gotData.c_str()], wantData);
|
||||
|
||||
// Convert JSON strings to objects and compare each key-value set.
|
||||
NSError *jsonError;
|
||||
NSData *objectData = [wantData dataUsingEncoding:NSUTF8StringEncoding];
|
||||
NSDictionary *wantJSONDict =
|
||||
[NSJSONSerialization JSONObjectWithData:objectData
|
||||
options:NSJSONReadingMutableContainers
|
||||
error:&jsonError];
|
||||
XCTAssertNil(jsonError, @"failed to parse want data as JSON");
|
||||
NSDictionary *gotJSONDict = [NSJSONSerialization
|
||||
JSONObjectWithData:[NSData dataWithBytes:gotData.data() length:gotData.length()]
|
||||
options:NSJSONReadingMutableContainers
|
||||
error:&jsonError];
|
||||
XCTAssertNil(jsonError, @"failed to parse got data as JSON");
|
||||
|
||||
// XCTAssertEqualObjects([NSString stringWithUTF8String:gotData.c_str()], wantData);
|
||||
NSDictionary *delta = findDelta(wantJSONDict, gotJSONDict);
|
||||
XCTAssertEqualObjects(@{}, delta);
|
||||
}
|
||||
|
||||
XCTBubbleMockVerifyAndClearExpectations(mockESApi.get());
|
||||
@@ -294,8 +347,9 @@ void SerializeAndCheckNonESEvents(
|
||||
|
||||
- (void)serializeAndCheckEvent:(es_event_type_t)eventType
|
||||
messageSetup:(void (^)(std::shared_ptr<MockEndpointSecurityAPI>,
|
||||
es_message_t *))messageSetup {
|
||||
SerializeAndCheck(eventType, messageSetup, self.mockDecisionCache);
|
||||
es_message_t *))messageSetup
|
||||
json:(BOOL)json {
|
||||
SerializeAndCheck(eventType, messageSetup, self.mockDecisionCache, (bool)json);
|
||||
}
|
||||
|
||||
- (void)testSerializeMessageClose {
|
||||
@@ -306,7 +360,8 @@ void SerializeAndCheckNonESEvents(
|
||||
es_message_t *esMsg) {
|
||||
esMsg->event.close.modified = true;
|
||||
esMsg->event.close.target = &file;
|
||||
}];
|
||||
}
|
||||
json:NO];
|
||||
}
|
||||
|
||||
- (void)testSerializeMessageExchange {
|
||||
@@ -318,7 +373,8 @@ void SerializeAndCheckNonESEvents(
|
||||
es_message_t *esMsg) {
|
||||
esMsg->event.exchangedata.file1 = &file1;
|
||||
esMsg->event.exchangedata.file2 = &file2;
|
||||
}];
|
||||
}
|
||||
json:NO];
|
||||
}
|
||||
|
||||
- (void)testGetDecisionEnum {
|
||||
@@ -455,7 +511,60 @@ void SerializeAndCheckNonESEvents(
|
||||
.WillOnce(testing::Return(&fd2))
|
||||
.WillOnce(testing::Return(&fd3));
|
||||
}
|
||||
}];
|
||||
}
|
||||
json:NO];
|
||||
}
|
||||
|
||||
- (void)testSerializeMessageExecJSON {
|
||||
es_file_t procFileTarget = MakeESFile("fooexec", MakeStat(300));
|
||||
__block es_process_t procTarget =
|
||||
MakeESProcess(&procFileTarget, MakeAuditToken(23, 45), MakeAuditToken(67, 89));
|
||||
__block es_file_t fileCwd = MakeESFile("cwd", MakeStat(400));
|
||||
__block es_file_t fileScript = MakeESFile("script.sh", MakeStat(500));
|
||||
__block es_fd_t fd1 = {.fd = 1, .fdtype = PROX_FDTYPE_VNODE};
|
||||
__block es_fd_t fd2 = {.fd = 2, .fdtype = PROX_FDTYPE_SOCKET};
|
||||
__block es_fd_t fd3 = {.fd = 3, .fdtype = PROX_FDTYPE_PIPE, .pipe = {.pipe_id = 123}};
|
||||
|
||||
procTarget.codesigning_flags = CS_SIGNED | CS_HARD | CS_KILL;
|
||||
memset(procTarget.cdhash, 'A', sizeof(procTarget.cdhash));
|
||||
procTarget.signing_id = MakeESStringToken("my_signing_id");
|
||||
procTarget.team_id = MakeESStringToken("my_team_id");
|
||||
|
||||
[self serializeAndCheckEvent:ES_EVENT_TYPE_NOTIFY_EXEC
|
||||
messageSetup:^(std::shared_ptr<MockEndpointSecurityAPI> mockESApi,
|
||||
es_message_t *esMsg) {
|
||||
esMsg->event.exec.target = &procTarget;
|
||||
esMsg->event.exec.cwd = &fileCwd;
|
||||
esMsg->event.exec.script = &fileScript;
|
||||
|
||||
// For version 5, simulate a "truncated" set of FDs
|
||||
if (esMsg->version == 5) {
|
||||
esMsg->event.exec.last_fd = 123;
|
||||
} else {
|
||||
esMsg->event.exec.last_fd = 3;
|
||||
}
|
||||
|
||||
EXPECT_CALL(*mockESApi, ExecArgCount).WillOnce(testing::Return(3));
|
||||
EXPECT_CALL(*mockESApi, ExecArg)
|
||||
.WillOnce(testing::Return(MakeESStringToken("exec_path")))
|
||||
.WillOnce(testing::Return(MakeESStringToken("-l")))
|
||||
.WillOnce(testing::Return(MakeESStringToken("--foo")));
|
||||
|
||||
EXPECT_CALL(*mockESApi, ExecEnvCount).WillOnce(testing::Return(2));
|
||||
EXPECT_CALL(*mockESApi, ExecEnv)
|
||||
.WillOnce(
|
||||
testing::Return(MakeESStringToken("ENV_PATH=/path/to/bin:/and/another")))
|
||||
.WillOnce(testing::Return(MakeESStringToken("DEBUG=1")));
|
||||
|
||||
if (esMsg->version >= 4) {
|
||||
EXPECT_CALL(*mockESApi, ExecFDCount).WillOnce(testing::Return(3));
|
||||
EXPECT_CALL(*mockESApi, ExecFD)
|
||||
.WillOnce(testing::Return(&fd1))
|
||||
.WillOnce(testing::Return(&fd2))
|
||||
.WillOnce(testing::Return(&fd3));
|
||||
}
|
||||
}
|
||||
json:YES];
|
||||
}
|
||||
|
||||
- (void)testSerializeMessageExit {
|
||||
@@ -463,7 +572,8 @@ void SerializeAndCheckNonESEvents(
|
||||
messageSetup:^(std::shared_ptr<MockEndpointSecurityAPI> mockESApi,
|
||||
es_message_t *esMsg) {
|
||||
esMsg->event.exit.stat = W_EXITCODE(1, 0);
|
||||
}];
|
||||
}
|
||||
json:NO];
|
||||
}
|
||||
|
||||
- (void)testEncodeExitStatus {
|
||||
@@ -500,7 +610,8 @@ void SerializeAndCheckNonESEvents(
|
||||
messageSetup:^(std::shared_ptr<MockEndpointSecurityAPI> mockESApi,
|
||||
es_message_t *esMsg) {
|
||||
esMsg->event.fork.child = &procChild;
|
||||
}];
|
||||
}
|
||||
json:NO];
|
||||
}
|
||||
|
||||
- (void)testSerializeMessageLink {
|
||||
@@ -514,7 +625,8 @@ void SerializeAndCheckNonESEvents(
|
||||
esMsg->event.link.source = &fileSource;
|
||||
esMsg->event.link.target_dir = &fileTargetDir;
|
||||
esMsg->event.link.target_filename = targetTok;
|
||||
}];
|
||||
}
|
||||
json:NO];
|
||||
}
|
||||
|
||||
- (void)testSerializeMessageRename {
|
||||
@@ -535,7 +647,8 @@ void SerializeAndCheckNonESEvents(
|
||||
esMsg->event.rename.destination.new_path.filename = targetTok;
|
||||
esMsg->event.rename.destination_type = ES_DESTINATION_TYPE_NEW_PATH;
|
||||
}
|
||||
}];
|
||||
}
|
||||
json:NO];
|
||||
}
|
||||
|
||||
- (void)testSerializeMessageUnlink {
|
||||
@@ -547,7 +660,8 @@ void SerializeAndCheckNonESEvents(
|
||||
es_message_t *esMsg) {
|
||||
esMsg->event.unlink.target = &fileTarget;
|
||||
esMsg->event.unlink.parent_dir = &fileTargetParent;
|
||||
}];
|
||||
}
|
||||
json:NO];
|
||||
}
|
||||
|
||||
- (void)testGetAccessType {
|
||||
|
||||
@@ -56,7 +56,12 @@ absl::Status FsSpoolLogBatchWriter::FlushNoLock() {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
cache_.mutable_records()->Clear();
|
||||
// We assign a new LogBatch() object here instead of calling Clear() method to
|
||||
// make sure the memory used by the cache_ is actually freed. It seems that
|
||||
// internal implementation of protobuf has some very generous way of managing
|
||||
// memory allocations and in certain scenarios it keeps objects for a very
|
||||
// long time (forever?).
|
||||
cache_ = santa::fsspool::binaryproto::LogBatch();
|
||||
cache_.mutable_records()->Reserve(max_batch_size_);
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ class FsSpoolLogBatchWriter {
|
||||
absl::Mutex cache_mutex_;
|
||||
santa::fsspool::binaryproto::LogBatch cache_ ABSL_GUARDED_BY(cache_mutex_);
|
||||
|
||||
absl::Status FlushNoLock() ABSL_SHARED_LOCKS_REQUIRED(cache_mutex_);
|
||||
absl::Status FlushNoLock() ABSL_EXCLUSIVE_LOCKS_REQUIRED(cache_mutex_);
|
||||
};
|
||||
|
||||
} // namespace fsspool
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#include "Source/santad/EventProviders/EndpointSecurity/Message.h"
|
||||
|
||||
#import "Source/common/SNTCommonEnums.h"
|
||||
#include "Source/santad/EventProviders/EndpointSecurity/Message.h"
|
||||
#include "Source/santad/TTYWriter.h"
|
||||
|
||||
const static NSString *kBlockBinary = @"BlockBinary";
|
||||
const static NSString *kAllowBinary = @"AllowBinary";
|
||||
@@ -56,7 +56,8 @@ const static NSString *kBlockLongPath = @"BlockLongPath";
|
||||
- (instancetype)initWithRuleTable:(SNTRuleTable *)ruleTable
|
||||
eventTable:(SNTEventTable *)eventTable
|
||||
notifierQueue:(SNTNotificationQueue *)notifierQueue
|
||||
syncdQueue:(SNTSyncdQueue *)syncdQueue;
|
||||
syncdQueue:(SNTSyncdQueue *)syncdQueue
|
||||
ttyWriter:(std::shared_ptr<santa::santad::TTYWriter>)ttyWriter;
|
||||
|
||||
///
|
||||
/// Handles the logic of deciding whether to allow the binary to run or not, sends the response to
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
|
||||
#import "Source/santad/SNTExecutionController.h"
|
||||
|
||||
#import <MOLCodesignChecker/MOLCodesignChecker.h>
|
||||
#include <bsm/libbsm.h>
|
||||
#include <copyfile.h>
|
||||
#include <libproc.h>
|
||||
@@ -22,7 +23,7 @@
|
||||
#include <sys/param.h>
|
||||
#include <utmpx.h>
|
||||
|
||||
#import <MOLCodesignChecker/MOLCodesignChecker.h>
|
||||
#include <memory>
|
||||
|
||||
#include "Source/common/BranchPrediction.h"
|
||||
#import "Source/common/SNTBlockMessage.h"
|
||||
@@ -44,6 +45,7 @@
|
||||
#import "Source/santad/SNTPolicyProcessor.h"
|
||||
#import "Source/santad/SNTSyncdQueue.h"
|
||||
|
||||
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
|
||||
@@ -59,7 +61,9 @@ static const size_t kMaxAllowedPathLength = MAXPATHLEN - 1; // -1 to account fo
|
||||
@property dispatch_queue_t eventQueue;
|
||||
@end
|
||||
|
||||
@implementation SNTExecutionController
|
||||
@implementation SNTExecutionController {
|
||||
std::shared_ptr<TTYWriter> _ttyWriter;
|
||||
}
|
||||
|
||||
static NSString *const kPrinterProxyPreMonterey =
|
||||
(@"/System/Library/Frameworks/Carbon.framework/Versions/Current/"
|
||||
@@ -74,13 +78,15 @@ static NSString *const kPrinterProxyPostMonterey =
|
||||
- (instancetype)initWithRuleTable:(SNTRuleTable *)ruleTable
|
||||
eventTable:(SNTEventTable *)eventTable
|
||||
notifierQueue:(SNTNotificationQueue *)notifierQueue
|
||||
syncdQueue:(SNTSyncdQueue *)syncdQueue {
|
||||
syncdQueue:(SNTSyncdQueue *)syncdQueue
|
||||
ttyWriter:(std::shared_ptr<TTYWriter>)ttyWriter {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_ruleTable = ruleTable;
|
||||
_eventTable = eventTable;
|
||||
_notifierQueue = notifierQueue;
|
||||
_syncdQueue = syncdQueue;
|
||||
_ttyWriter = std::move(ttyWriter);
|
||||
_policyProcessor = [[SNTPolicyProcessor alloc] initWithRuleTable:_ruleTable];
|
||||
|
||||
_eventQueue =
|
||||
@@ -298,7 +304,8 @@ static NSString *const kPrinterProxyPostMonterey =
|
||||
NSAttributedString *s = [SNTBlockMessage attributedBlockMessageForEvent:se
|
||||
customMessage:cd.customMsg];
|
||||
|
||||
if (targetProc->tty && targetProc->tty->path.length > 0 && !config.enableSilentTTYMode) {
|
||||
if (targetProc->tty && targetProc->tty->path.length > 0 && !config.enableSilentTTYMode &&
|
||||
self->_ttyWriter) {
|
||||
NSMutableString *msg = [NSMutableString stringWithCapacity:1024];
|
||||
[msg appendFormat:@"\n\033[1mSanta\033[0m\n\n%@\n\n", s.string];
|
||||
[msg appendFormat:@"\033[1mPath: \033[0m %@\n"
|
||||
@@ -310,7 +317,7 @@ static NSString *const kPrinterProxyPostMonterey =
|
||||
[msg appendFormat:@"More info:\n%@\n\n", detailURL.absoluteString];
|
||||
}
|
||||
|
||||
[self printMessage:msg toTTY:targetProc->tty->path.data];
|
||||
self->_ttyWriter->Write(targetProc->tty->path.data, msg);
|
||||
}
|
||||
|
||||
[self.notifierQueue addEvent:se customMessage:cd.customMsg];
|
||||
@@ -363,13 +370,6 @@ static NSString *const kPrinterProxyPostMonterey =
|
||||
return proxyInfo;
|
||||
}
|
||||
|
||||
- (void)printMessage:(NSString *)msg toTTY:(const char *)path {
|
||||
int fd = open(path, O_WRONLY | O_NOCTTY);
|
||||
std::string_view str = santa::common::NSStringToUTF8StringView(msg);
|
||||
write(fd, str.data(), str.length());
|
||||
close(fd);
|
||||
}
|
||||
|
||||
- (void)loggedInUsers:(NSArray **)users sessions:(NSArray **)sessions {
|
||||
NSMutableSet *loggedInUsers = [NSMutableSet set];
|
||||
NSMutableArray *loggedInHosts = [NSMutableArray array];
|
||||
|
||||
@@ -95,7 +95,8 @@ VerifyPostActionBlock verifyPostAction = ^PostActionBlock(SNTAction wantAction)
|
||||
self.sut = [[SNTExecutionController alloc] initWithRuleTable:self.mockRuleDatabase
|
||||
eventTable:self.mockEventDatabase
|
||||
notifierQueue:nil
|
||||
syncdQueue:nil];
|
||||
syncdQueue:nil
|
||||
ttyWriter:nullptr];
|
||||
}
|
||||
|
||||
- (void)tearDown {
|
||||
|
||||
@@ -65,6 +65,8 @@
|
||||
csInfo = nil;
|
||||
cd.decisionExtra =
|
||||
[NSString stringWithFormat:@"Signature ignored due to error: %ld", (long)csInfoError.code];
|
||||
cd.teamID = nil;
|
||||
cd.signingID = nil;
|
||||
} else {
|
||||
cd.certSHA256 = csInfo.leafCertificate.SHA256;
|
||||
cd.certCommonName = csInfo.leafCertificate.commonName;
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "Source/common/PrefixTree.h"
|
||||
#import "Source/common/SNTCommonEnums.h"
|
||||
#import "Source/common/SNTConfigurator.h"
|
||||
#import "Source/common/SNTFileAccessEvent.h"
|
||||
#import "Source/common/SNTKVOManager.h"
|
||||
#import "Source/common/SNTLogging.h"
|
||||
#import "Source/common/SNTXPCNotifierInterface.h"
|
||||
@@ -140,6 +141,12 @@ void SantadMain(std::shared_ptr<EndpointSecurityAPI> esapi, std::shared_ptr<Logg
|
||||
enricher:enricher
|
||||
decisionCache:[SNTDecisionCache sharedCache]];
|
||||
watch_items->RegisterClient(access_authorizer_client);
|
||||
|
||||
access_authorizer_client.fileAccessBlockCallback = ^(SNTFileAccessEvent *event) {
|
||||
[[notifier_queue.notifierConnection remoteObjectProxy]
|
||||
postFileAccessBlockNotification:event
|
||||
withCustomMessage:@"Access to the resource has been denied!"];
|
||||
};
|
||||
}
|
||||
|
||||
EstablishSyncServiceConnection(syncd_queue);
|
||||
@@ -302,7 +309,13 @@ void SantadMain(std::shared_ptr<EndpointSecurityAPI> esapi, std::shared_ptr<Logg
|
||||
selector:@selector(staticRules)
|
||||
type:[NSArray class]
|
||||
callback:^(NSArray *oldValue, NSArray *newValue) {
|
||||
if ([oldValue isEqual:newValue]) return;
|
||||
NSSet *oldValueSet = [NSSet setWithArray:oldValue ?: @[]];
|
||||
NSSet *newValueSet = [NSSet setWithArray:newValue ?: @[]];
|
||||
|
||||
if ([oldValueSet isEqualToSet:newValueSet]) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOGI(@"StaticRules set has changed, flushing cache.");
|
||||
auth_result_cache->FlushCache(
|
||||
FlushCacheMode::kAllCaches,
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
#include "Source/santad/SantadDeps.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <memory>
|
||||
|
||||
#import "Source/common/SNTLogging.h"
|
||||
@@ -25,10 +26,12 @@
|
||||
#include "Source/santad/EventProviders/EndpointSecurity/EndpointSecurityAPI.h"
|
||||
#import "Source/santad/SNTDatabaseController.h"
|
||||
#include "Source/santad/SNTDecisionCache.h"
|
||||
#include "Source/santad/TTYWriter.h"
|
||||
|
||||
using santa::common::PrefixTree;
|
||||
using santa::common::Unit;
|
||||
using santa::santad::Metrics;
|
||||
using santa::santad::TTYWriter;
|
||||
using santa::santad::data_layer::WatchItems;
|
||||
using santa::santad::event_providers::AuthResultCache;
|
||||
using santa::santad::event_providers::endpoint_security::EndpointSecurityAPI;
|
||||
@@ -81,11 +84,17 @@ std::unique_ptr<SantadDeps> SantadDeps::Create(SNTConfigurator *configurator,
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
std::shared_ptr<TTYWriter> tty_writer = TTYWriter::Create();
|
||||
if (!tty_writer) {
|
||||
LOGW(@"Unable to initialize TTY writer");
|
||||
}
|
||||
|
||||
SNTExecutionController *exec_controller =
|
||||
[[SNTExecutionController alloc] initWithRuleTable:rule_table
|
||||
eventTable:event_table
|
||||
notifierQueue:notifier_queue
|
||||
syncdQueue:syncd_queue];
|
||||
syncdQueue:syncd_queue
|
||||
ttyWriter:tty_writer];
|
||||
if (!exec_controller) {
|
||||
LOGE(@"Failed to initialize exec controller.");
|
||||
exit(EXIT_FAILURE);
|
||||
|
||||
48
Source/santad/TTYWriter.h
Normal file
48
Source/santad/TTYWriter.h
Normal file
@@ -0,0 +1,48 @@
|
||||
/// 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.
|
||||
|
||||
#ifndef SANTA__SANTAD__TTYWRITER_H
|
||||
#define SANTA__SANTAD__TTYWRITER_H
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#include <dispatch/dispatch.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace santa::santad {
|
||||
|
||||
// Small helper class to synchronize writing to TTYs
|
||||
class TTYWriter {
|
||||
public:
|
||||
static std::unique_ptr<TTYWriter> Create();
|
||||
|
||||
TTYWriter(dispatch_queue_t q);
|
||||
|
||||
// Moves can be safe, but not currently needed/implemented
|
||||
TTYWriter(TTYWriter &&other) = delete;
|
||||
TTYWriter &operator=(TTYWriter &&rhs) = delete;
|
||||
|
||||
// No copies
|
||||
TTYWriter(const TTYWriter &other) = delete;
|
||||
TTYWriter &operator=(const TTYWriter &other) = delete;
|
||||
|
||||
void Write(const char *tty, NSString *msg);
|
||||
|
||||
private:
|
||||
dispatch_queue_t q_;
|
||||
};
|
||||
|
||||
} // namespace santa::santad
|
||||
|
||||
#endif
|
||||
56
Source/santad/TTYWriter.mm
Normal file
56
Source/santad/TTYWriter.mm
Normal file
@@ -0,0 +1,56 @@
|
||||
/// 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.
|
||||
|
||||
#include "Source/santad/TTYWriter.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <sys/errno.h>
|
||||
|
||||
#import "Source/common/SNTLogging.h"
|
||||
#include "Source/common/String.h"
|
||||
|
||||
namespace santa::santad {
|
||||
|
||||
std::unique_ptr<TTYWriter> TTYWriter::Create() {
|
||||
dispatch_queue_t q = dispatch_queue_create_with_target(
|
||||
"com.google.santa.ttywriter", DISPATCH_QUEUE_SERIAL,
|
||||
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0));
|
||||
|
||||
if (!q) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return std::make_unique<TTYWriter>(q);
|
||||
}
|
||||
|
||||
TTYWriter::TTYWriter(dispatch_queue_t q) : q_(q) {}
|
||||
|
||||
void TTYWriter::Write(const char *tty, NSString *msg) {
|
||||
dispatch_async(q_, ^{
|
||||
@autoreleasepool {
|
||||
int fd = open(tty, O_WRONLY | O_NOCTTY);
|
||||
if (fd == -1) {
|
||||
LOGW(@"Failed to open TTY for writing: %s", strerror(errno));
|
||||
return;
|
||||
}
|
||||
|
||||
std::string_view str = santa::common::NSStringToUTF8StringView(msg);
|
||||
write(fd, str.data(), str.length());
|
||||
|
||||
close(fd);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace santa::santad
|
||||
BIN
Source/santad/testdata/binaryrules/rules.db
vendored
BIN
Source/santad/testdata/binaryrules/rules.db
vendored
Binary file not shown.
@@ -72,6 +72,7 @@ JSON blob. Here is an example of Firefox being blocked and sent for upload:
|
||||
}
|
||||
],
|
||||
"team_id": "43AQ936H96",
|
||||
"signing_id": "org.mozilla.firefox",
|
||||
"file_bundle_name": "Firefox",
|
||||
"executing_user": "bur",
|
||||
"ppid": 1,
|
||||
|
||||
@@ -4,17 +4,31 @@ parent: Concepts
|
||||
|
||||
# Rules
|
||||
|
||||
Rules provide the primary evaluation mechanism for allowing and blocking
|
||||
binaries with Santa on macOS. There are three types of rules: binary,
|
||||
certificate, and TeamID.
|
||||
## Rule Types
|
||||
|
||||
##### Binary Rules
|
||||
Rules provide the primary evaluation mechanism for allowing and blocking
|
||||
binaries with Santa on macOS. There are four types of rules: binary, signing ID,
|
||||
certificate, and Team ID.
|
||||
|
||||
### Binary Rules
|
||||
|
||||
Binary rules use the SHA-256 hash of the entire binary as an identifier. This is
|
||||
the most specific rule in Santa. Even a small change in the binary will alter
|
||||
the SHA-256 hash, invalidating the rule.
|
||||
|
||||
##### Certificate Rules
|
||||
### Signing ID Rules
|
||||
|
||||
Signing IDs are arbitrary identifiers under developer control that are given to
|
||||
a binary at signing time. Typically, these use reverse domain name notation and
|
||||
include the name of the binary (e.g. `com.google.Chrome`). Because the signing
|
||||
IDs are arbitrary, the Santa rule identifier must be prefixed with the Team ID
|
||||
associated with the Apple developer certificate used to sign the application.
|
||||
For example, a signing ID rule for Google Chrome would be:
|
||||
`EQHXZ8M8AV:com.google.Chrome`. For platform binaries (i.e. those binaries
|
||||
shipped by Apple with the OS) which do not have a Team ID, the string `platform`
|
||||
must be used (e.g. `platform:com.apple.curl`).
|
||||
|
||||
### Certificate Rules
|
||||
|
||||
Certificate rules are formed from the SHA-256 fingerprint of an X.509 leaf
|
||||
signing certificate. This is a powerful rule type that has a much broader reach
|
||||
@@ -64,49 +78,88 @@ chain's intermediates or roots has no effect on binaries signing by a leaf.
|
||||
Santa ignores the chain and is only concerned with the leaf certificate's
|
||||
SHA-256 hash.
|
||||
|
||||
##### Apple Developer Team ID Rules
|
||||
### Apple Developer Team ID Rules
|
||||
|
||||
The Apple Developer Program Team ID is a 10-character identifier issued by Apple
|
||||
and tied to developer accounts/organizations. This is distinct from Certificates,
|
||||
as a single developer account can and frequently will request/rotate between
|
||||
multiple different signing certificates and entitlements. This is an even more
|
||||
powerful rule with broader reach than individual certificate rules.
|
||||
|
||||
##### Rule Evaluation
|
||||
## Rule Evaluation
|
||||
|
||||
When a process is trying to `execve()` `santad` retrieves information on the
|
||||
binary, including a hash of the entire file and the signing chain (if any). The
|
||||
hash and signing leaf certificate are then passed through the
|
||||
When a process is trying to execute, `santad` retrieves information on the
|
||||
binary, including a hash of the entire file, signing ID, the signing chain (if
|
||||
any), and the team ID. The collected info is then passed through the
|
||||
[SNTPolicyProcessor](https://github.com/google/santa/blob/master/Source/santad/SNTPolicyProcessor.h).
|
||||
Rules are evaluated from most specific to least specific. First binary (either
|
||||
allow or block), then certificate (either allow or block), then team ID (either allow or block). If no rules are found
|
||||
that apply, scopes are then searched. See the [scopes.md](scopes.md) document
|
||||
for more information on scopes.
|
||||
|
||||
Rules (both ALLOW and BLOCK) are evaluated in the following order, from most
|
||||
specific to least specific:
|
||||
|
||||
```
|
||||
Most Specific Least Specific
|
||||
|
||||
Binary --> Signing ID --> Certificate --> Team ID
|
||||
```
|
||||
|
||||
If no rules are found that apply, scopes are then searched. See the
|
||||
[scopes.md](scopes.md) document for more information on scopes.
|
||||
|
||||
### Rule Examples
|
||||
|
||||
You can use the `santactl fileinfo` command to check the status of any given
|
||||
binary on the filesystem.
|
||||
|
||||
###### Allowed with a Binary Rule
|
||||
#### Allowed with a Binary Rule
|
||||
|
||||
```sh
|
||||
⇒ santactl fileinfo /Applications/Hex\ Fiend.app --key Rule
|
||||
Allowed (Binary)
|
||||
```
|
||||
|
||||
###### Allowed with a Certificate Rule
|
||||
#### Allowed with a Signing ID Rule
|
||||
|
||||
```sh
|
||||
⇒ santactl fileinfo /Applications/Example.app --key Rule
|
||||
Allowed (SigningID)
|
||||
```
|
||||
|
||||
#### Allowed with a Certificate Rule
|
||||
|
||||
```sh
|
||||
⇒ santactl fileinfo /Applications/Safari.app --key Rule
|
||||
Allowed (Certificate)
|
||||
```
|
||||
|
||||
###### Blocked with a Binary Rule
|
||||
#### Allowed with a Team ID rule
|
||||
|
||||
```sh
|
||||
⇒ santactl fileinfo /Applications/Spotify.app --key Rule
|
||||
Allowed (TeamID)
|
||||
```
|
||||
|
||||
For checking the Team ID of `/Applications/Microsoft\ Remote\ Desktop.app`
|
||||
|
||||
```sh
|
||||
⇒ santactl fileinfo /Applications/Spotify.app --key "Team ID"
|
||||
2FNC3A47ZF
|
||||
```
|
||||
|
||||
#### Blocked with a Binary Rule
|
||||
|
||||
```sh
|
||||
⇒ santactl fileinfo /usr/bin/yes --key Rule
|
||||
Blocked (Binary)
|
||||
```
|
||||
|
||||
###### Blocked with a Certificate Rule
|
||||
#### Blocked with a Signing ID Rule
|
||||
|
||||
```sh
|
||||
⇒ santactl fileinfo /Applications/Example.app --key Rule
|
||||
Blocked (SigningID)
|
||||
```
|
||||
|
||||
#### Blocked with a Certificate Rule
|
||||
|
||||
```sh
|
||||
⇒ santactl fileinfo /Applications/Malware.app --key Rule
|
||||
@@ -130,20 +183,6 @@ For checking the SHA-256 hash of `/usr/bin/yes` signing certificate:
|
||||
Allowed (Certificate)
|
||||
```
|
||||
|
||||
##### Allowed with a Team ID rule
|
||||
|
||||
```sh
|
||||
⇒ santactl fileinfo /Applications/Spotify.app --key Rule
|
||||
Allowed (TeamID)
|
||||
```
|
||||
|
||||
For checking the Team ID of `/Applications/Microsoft\ Remote\ Desktop.app`
|
||||
|
||||
```sh
|
||||
⇒ santactl fileinfo /Applications/Spotify.app --key "Team ID"
|
||||
2FNC3A47ZF
|
||||
```
|
||||
|
||||
#### Blocked with a Team ID rule
|
||||
|
||||
```sh
|
||||
@@ -158,7 +197,7 @@ For checking the Team ID of `/Applications/Microsoft\ Remote\ Desktop.app`
|
||||
UBF8T346G9
|
||||
```
|
||||
|
||||
##### Built-in rules
|
||||
### Built-in rules
|
||||
|
||||
To avoid blocking any Apple system binaries or Santa binaries, `santad` will
|
||||
create 2 immutable certificate rules at startup:
|
||||
|
||||
@@ -54,7 +54,8 @@ also known as mobileconfig files, which are in an Apple-specific XML format.
|
||||
| MachineOwnerKey | String | The key to use on MachineOwnerPlist. |
|
||||
| MachineIDPlist | String | The path to a plist that contains the MachineOwnerKey / value pair. |
|
||||
| MachineIDKey | String | The key to use on MachineIDPlist. |
|
||||
| EventLogType | String | Defines how event logs are stored. Options are 1) syslog: Sent to ASL or ULS (if built with the 10.12 SDK or later). 2) filelog: Sent to a file on disk. Use EventLogPath to specify a path. 3) protobuf (BETA): Sent to file on disk using a maildir-like format. 4) null: Don't output any event logs. Defaults to filelog. |
|
||||
| EventLogType | String | Defines how event logs are stored. Options are 1) syslog: Sent to ULS. 2) filelog: Sent to a file on disk. Use EventLogPath to specify a path. 3) protobuf (BETA): Sent to file on disk using a maildir-like format. 4) json (BETA): Same as file but output is one JSON object per line 5) null: Don't output any event logs. Defaults to filelog. |
|
||||
| EventLogPath | String | If EventLogType is set to filelog or json, EventLogPath will provide the path to save logs. Defaults to /var/db/santa/santa.log. If you change this value ensure you also update com.google.santa.newsyslog.conf with the new path. |
|
||||
| EventLogPath | String | If EventLogType is set to filelog, EventLogPath will provide the path to save logs. Defaults to /var/db/santa/santa.log. If you change this value ensure you also update com.google.santa.newsyslog.conf with the new path. |
|
||||
| SpoolDirectory | String | If EventLogType is set to protobuf, SpoolDirectory will provide the the base directory used to save files according to a maildir-like format. Defaults to /var/db/santa/spool. |
|
||||
| SpoolDirectoryFileSizeThresholdKB | Integer | If EventLogType is set to protobuf, SpoolDirectoryFileSizeThresholdKB defines the per-file size limit for files stored in the spool directory. Events are buffered in memory until this threshold would be exceeded (or SpoolDirectoryEventMaxFlushTimeSec is exceeded). Defaults to 100. |
|
||||
|
||||
Reference in New Issue
Block a user