Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Source/common/SNTRule.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,8 @@
state:(SNTRuleState)state
type:(SNTRuleType)type
customMsg:(NSString *)customMsg
customURL:(NSString *)customURL;
customURL:(NSString *)customURL
celExpr:(NSString *)celExpr;

///
/// Initialize with a default timestamp: current time if rule state is transitive, 0 otherwise.
Expand Down
27 changes: 23 additions & 4 deletions Source/common/SNTRule.m
Original file line number Diff line number Diff line change
Expand Up @@ -166,15 +166,16 @@ - (instancetype)initWithIdentifier:(NSString *)identifier
state:(SNTRuleState)state
type:(SNTRuleType)type
customMsg:(NSString *)customMsg
customURL:(NSString *)customURL {
customURL:(NSString *)customURL
celExpr:(NSString *)celExpr {
self = [self initWithIdentifier:identifier
state:state
type:type
customMsg:customMsg
customURL:customURL
timestamp:0
comment:nil
celExpr:nil
celExpr:celExpr
error:nil];
// Initialize timestamp to current time if rule is transitive.
if (self && state == SNTRuleStateAllowTransitive) {
Expand All @@ -186,7 +187,15 @@ - (instancetype)initWithIdentifier:(NSString *)identifier
- (instancetype)initWithIdentifier:(NSString *)identifier
state:(SNTRuleState)state
type:(SNTRuleType)type {
return [self initWithIdentifier:identifier state:state type:type customMsg:nil customURL:nil];
return [self initWithIdentifier:identifier
state:state
type:type
customMsg:nil
customURL:nil
timestamp:0
comment:nil
celExpr:nil
error:nil];
}

// lowercase policy keys and upper case the policy decision.
Expand Down Expand Up @@ -254,6 +263,8 @@ - (instancetype)initWithDictionary:(NSDictionary *)rawDict error:(NSError **)err
state = SNTRuleStateSilentBlock;
} else if ([policyString isEqual:kRulePolicyRemove]) {
state = SNTRuleStateRemove;
} else if ([policyString isEqual:kRulePolicyCEL]) {
state = SNTRuleStateCEL;
} else {
[SNTError populateError:error
withCode:SNTErrorCodeRuleInvalidPolicy
Expand Down Expand Up @@ -301,14 +312,19 @@ - (instancetype)initWithDictionary:(NSDictionary *)rawDict error:(NSError **)err
comment = nil;
}

NSString *celExpr = dict[kRuleCELExpr];
if (![celExpr isKindOfClass:[NSString class]] || celExpr.length == 0) {
celExpr = nil;
}

return [self initWithIdentifier:identifier
state:state
type:type
customMsg:customMsg
customURL:customURL
timestamp:0
comment:comment
celExpr:nil
celExpr:celExpr
error:error];
}

Expand Down Expand Up @@ -354,6 +370,7 @@ - (NSString *)ruleStateToPolicyString:(SNTRuleState)state {
case SNTRuleStateAllowTransitive: return @"AllowTransitive";
case SNTRuleStateAllowLocalBinary: return kRulePolicyAllowlistLocalBinary;
case SNTRuleStateAllowLocalSigningID: return kRulePolicyAllowlistLocalSigningID;
case SNTRuleStateCEL: return kRulePolicyCEL;
// This should never be hit. But is here for completion.
default: return @"Unknown";
}
Expand Down Expand Up @@ -381,6 +398,7 @@ - (NSDictionary *)dictionaryRepresentation {
kRuleCustomMsg : self.customMsg ?: @"",
kRuleCustomURL : self.customURL ?: @"",
kRuleComment : self.comment ?: @"",
kRuleCELExpr : self.celExpr ?: @"",
};
}

Expand All @@ -399,6 +417,7 @@ - (NSUInteger)hash {
result = prime * result + self.state;
result = prime * result + self.type;
result = prime * result + [self.celExpr hash];
result = prime * result + [self.celExpr hash];
return result;
}

Expand Down
4 changes: 4 additions & 0 deletions Source/common/SNTRuleTest.mm
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,7 @@ - (void)testRuleDictionaryRepresentation {
@"custom_msg" : @"A custom block message",
@"custom_url" : @"https://example.com",
@"comment" : @"",
@"cel_expr" : @"",
};

SNTRule *sut = [[SNTRule alloc] initWithDictionary:expectedTeamID error:nil];
Expand All @@ -298,6 +299,7 @@ - (void)testRuleDictionaryRepresentation {
@"custom_msg" : @"",
@"custom_url" : @"",
@"comment" : @"",
@"cel_expr" : @"",
};

sut = [[SNTRule alloc] initWithDictionary:expectedBinary error:nil];
Expand All @@ -313,6 +315,7 @@ - (void)testRuleStateToPolicyString {
@"rule_type" : @"BINARY",
@"custom_msg" : @"A custom block message",
@"custom_url" : @"https://example.com",
@"cel_expr" : @"",
};

SNTRule *sut = [[SNTRule alloc] initWithDictionary:expected error:nil];
Expand All @@ -333,6 +336,7 @@ - (void)testKeyCaseForInitWithDictionary {
for (NSString *key in
@[ kRulePolicy, kRuleIdentifier, kRuleType, kRuleCustomMsg, kRuleCustomURL, kRuleComment ]) {
NSDictionary *expected = @{
@"cel_expr" : @"",
@"identifier" : @"84de9c61777ca36b13228e2446d53e966096e78db7a72c632b5c185b2ffe68a6",
@"policy" : @"ALLOWLIST",
@"rule_type" : @"BINARY",
Expand Down
2 changes: 2 additions & 0 deletions Source/common/SNTSyncConstants.h
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ extern NSString *const kRulePolicyBlocklistDeprecated;
extern NSString *const kRulePolicySilentBlocklist;
extern NSString *const kRulePolicySilentBlocklistDeprecated;
extern NSString *const kRulePolicyRemove;
extern NSString *const kRulePolicyCEL;
extern NSString *const kRuleType;
extern NSString *const kRuleTypeBinary;
extern NSString *const kRuleTypeCertificate;
Expand All @@ -135,6 +136,7 @@ extern NSString *const kRuleTypeCDHash;
extern NSString *const kRuleCustomMsg;
extern NSString *const kRuleCustomURL;
extern NSString *const kRuleComment;
extern NSString *const kRuleCELExpr;
extern NSString *const kCursor;

extern NSString *const kBackoffInterval;
Expand Down
2 changes: 2 additions & 0 deletions Source/common/SNTSyncConstants.m
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@
NSString *const kRulePolicySilentBlocklist = @"SILENT_BLOCKLIST";
NSString *const kRulePolicySilentBlocklistDeprecated = @"SILENT_BLACKLIST";
NSString *const kRulePolicyRemove = @"REMOVE";
NSString *const kRulePolicyCEL = @"CEL";
NSString *const kRuleType = @"rule_type";
NSString *const kRuleTypeBinary = @"BINARY";
NSString *const kRuleTypeCertificate = @"CERTIFICATE";
Expand All @@ -136,6 +137,7 @@
NSString *const kRuleCustomMsg = @"custom_msg";
NSString *const kRuleCustomURL = @"custom_url";
NSString *const kRuleComment = @"comment";
NSString *const kRuleCELExpr = @"cel_expr";
NSString *const kCursor = @"cursor";

NSString *const kBackoffInterval = @"backoff";
Expand Down
24 changes: 12 additions & 12 deletions Source/common/cel/Activation.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,26 +32,23 @@
#include "eval/public/activation.h"
#pragma clang diagnostic pop

namespace cel_runtime = ::google::api::expr::runtime;
namespace pbv1 = ::santa::cel::v1;

namespace santa {
namespace cel {

// SantaActivation is a CEL activation that provides lookups of values from the
// santa.pb.cel.v1.Context message, and easy access to variables for return values.
class Activation : public ::cel_runtime::BaseActivation {
class Activation : public ::google::api::expr::runtime::BaseActivation {
public:
Activation(std::unique_ptr<::pbv1::ExecutableFile> file, std::vector<std::string> (^args)(),
std::map<std::string, std::string> (^envs)())
Activation(std::unique_ptr<::santa::cel::v1::ExecutableFile> file,
std::vector<std::string> (^args)(), std::map<std::string, std::string> (^envs)())
: file_(std::move(file)), args_(args), envs_(envs) {};
~Activation() = default;

std::optional<cel_runtime::CelValue> FindValue(absl::string_view name,
google::protobuf::Arena *arena) const override;
std::optional<::google::api::expr::runtime::CelValue> FindValue(
absl::string_view name, google::protobuf::Arena *arena) const override;

// Activation does not support lazy-loaded functions.
std::vector<const cel_runtime::CelFunction *> FindFunctionOverloads(
std::vector<const ::google::api::expr::runtime::CelFunction *> FindFunctionOverloads(
absl::string_view) const override {
return {};
}
Expand All @@ -72,11 +69,14 @@ class Activation : public ::cel_runtime::BaseActivation {
const google::protobuf::Descriptor *messageType);

template <typename T>
static cel_runtime::CelValue CELValue(const T &v, google::protobuf::Arena *arena);
static ::google::api::expr::runtime::CelValue CELValue(const T &v,
google::protobuf::Arena *arena);
template <typename T>
static cel_runtime::CelValue CELValue(const std::vector<T> &v, google::protobuf::Arena *arena);
static ::google::api::expr::runtime::CelValue CELValue(const std::vector<T> &v,
google::protobuf::Arena *arena);
template <typename K, typename V>
static cel_runtime::CelValue CELValue(const std::map<K, V> &v, google::protobuf::Arena *arena);
static ::google::api::expr::runtime::CelValue CELValue(const std::map<K, V> &v,
google::protobuf::Arena *arena);
};

} // namespace cel
Expand Down
6 changes: 3 additions & 3 deletions Source/common/cel/Activation.mm
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@
std::optional<cel_runtime::CelValue> Activation::FindValue(absl::string_view name,
google::protobuf::Arena *arena) const {
// Handle the ReturnValue values.
auto retDescriptor = pbv1::ReturnValue_descriptor();
auto retDescriptor = santa::cel::v1::ReturnValue_descriptor();
auto retValue = retDescriptor->FindValueByName(name);
if (retValue != nullptr) {
return CELValue(retValue->number(), arena);
Expand All @@ -109,14 +109,14 @@
// ALLOWLIST or BLOCKLIST in their CEL expressions without having to use the
// proto package name prefix. Start from value number 1 to avoid the
// UNSPECIFIED value.
auto retDescriptor = pbv1::ReturnValue_descriptor();
auto retDescriptor = ::santa::cel::v1::ReturnValue_descriptor();
for (int i = 1; i < retDescriptor->value_count(); i++) {
auto value = retDescriptor->value(i);
v.push_back({value->name(), ::cel::IntType()});
}

// Now add all the fields from the CELContext message.
auto ctxDescriptor = pbv1::ExecutionContext::descriptor();
auto ctxDescriptor = santa::cel::v1::ExecutionContext::descriptor();
for (int i = 0; i < ctxDescriptor->field_count(); i++) {
auto field = ctxDescriptor->field(i);

Expand Down
7 changes: 7 additions & 0 deletions Source/santactl/Commands/SNTCommandRule.m
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ + (NSString *)longHelpText {
@" --block: add to block\n"
@" --silent-block: add to silent block\n"
@" --compiler: allow and mark as a compiler\n"
@" --cel {cel_expr}: add a CEL rule\n"
Comment thread
russellhancox marked this conversation as resolved.
@" --remove: remove existing rule\n"
@" --check: check for an existing rule\n"
@" --import {path}: import rules from a JSON file\n"
Expand Down Expand Up @@ -153,6 +154,12 @@ - (void)runWithArguments:(NSArray *)arguments {
newRule.state = SNTRuleStateSilentBlock;
} else if ([arg caseInsensitiveCompare:@"--compiler"] == NSOrderedSame) {
newRule.state = SNTRuleStateAllowCompiler;
} else if ([arg caseInsensitiveCompare:@"--cel"] == NSOrderedSame) {
if (++i > arguments.count - 1) {
[self printErrorUsageAndExit:@"--cel requires an argument"];
}
newRule.state = SNTRuleStateCEL;
newRule.celExpr = arguments[i];
} else if ([arg caseInsensitiveCompare:@"--remove"] == NSOrderedSame) {
newRule.state = SNTRuleStateRemove;
} else if ([arg caseInsensitiveCompare:@"--check"] == NSOrderedSame) {
Expand Down
2 changes: 1 addition & 1 deletion Source/santad/SNTPolicyProcessor.mm
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ - (BOOL)decision:(SNTCachedDecision *)cd
break;
default: break;
}
if (!(*evalResult).second) {
if (!evalResult->second) {
cd.cacheable = NO;
}
}
Expand Down
2 changes: 2 additions & 0 deletions Source/santasyncservice/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ objc_library(
"//Source/common:SNTXPCControlInterface",
"//Source/common:SNTXPCSyncServiceInterface",
"//Source/common:String",
"//Source/common/cel:CEL",
"@northpolesec_protos//sync:v1_cc_proto",
"@protobuf//src/google/protobuf/json",
],
Expand Down Expand Up @@ -185,6 +186,7 @@ santa_unit_test(
"//Source/common:SNTSystemInfo",
"//Source/common:SNTXPCControlInterface",
"//Source/common:String",
"//Source/common/cel:CEL",
"@OCMock",
"@northpolesec_protos//sync:v1_cc_proto",
"@protobuf//src/google/protobuf/json",
Expand Down
27 changes: 25 additions & 2 deletions Source/santasyncservice/SNTSyncRuleDownload.mm
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#import "Source/common/SNTSyncConstants.h"
#import "Source/common/SNTXPCControlInterface.h"
#import "Source/common/String.h"
#include "Source/common/cel/Evaluator.h"
#import "Source/santasyncservice/SNTPushNotificationsTracker.h"
#import "Source/santasyncservice/SNTSyncConfigBundle.h"
#import "Source/santasyncservice/SNTSyncLogging.h"
Expand All @@ -42,7 +43,17 @@ SNTRuleCleanup SyncTypeToRuleCleanup(SNTSyncType syncType) {
}
}

@implementation SNTSyncRuleDownload
@implementation SNTSyncRuleDownload {
std::unique_ptr<santa::cel::Evaluator> _celEvaluator;
}

- (nullable instancetype)initWithState:(nonnull SNTSyncState *)syncState {
self = [super initWithState:syncState];
if (self) {
_celEvaluator = santa::cel::Evaluator::Create().value();
Comment thread
russellhancox marked this conversation as resolved.
Outdated
}
return self;
}

- (NSURL *)stageURL {
NSString *stageName = [@"ruledownload" stringByAppendingFormat:@"/%@", self.syncState.machineID];
Expand Down Expand Up @@ -162,6 +173,7 @@ - (SNTRule *)ruleFromProtoRule:(::pbv1::Rule)rule {
case ::pbv1::BLOCKLIST: state = SNTRuleStateBlock; break;
case ::pbv1::SILENT_BLOCKLIST: state = SNTRuleStateSilentBlock; break;
case ::pbv1::REMOVE: state = SNTRuleStateRemove; break;
case ::pbv1::CEL: state = SNTRuleStateCEL; break;
default: LOGE(@"Failed to process rule with unknown policy: %d", rule.policy()); return nil;
}

Expand All @@ -181,11 +193,22 @@ - (SNTRule *)ruleFromProtoRule:(::pbv1::Rule)rule {
const std::string &custom_url = rule.custom_url();
NSString *customURL = (!custom_url.empty()) ? StringToNSString(custom_url) : nil;

const std::string &cel_expr = rule.cel_expr();
NSString *celExpr = (!cel_expr.empty()) ? StringToNSString(cel_expr) : nil;
if (celExpr && _celEvaluator) {
auto result = _celEvaluator->Compile(cel_expr);
if (!result.ok()) {
LOGE(@"Failed to compile CEL expression: %s", std::string(result.status().message()).c_str());
return nil;
}
}

return [[SNTRule alloc] initWithIdentifier:identifier
state:state
type:type
customMsg:customMsg
customURL:customURL];
customURL:customURL
celExpr:celExpr];
}

// Send out push notifications for allowed bundles/binaries whose rule download was preceded by
Expand Down
6 changes: 4 additions & 2 deletions Source/santasyncservice/SNTSyncTest.mm
Original file line number Diff line number Diff line change
Expand Up @@ -856,12 +856,14 @@ - (void)testRuleDownload {
state:SNTRuleStateBlock
type:SNTRuleTypeCertificate
customMsg:@"Hi There"
customURL:@"http://northpole.security"],
customURL:@"http://northpole.security"
celExpr:nil],
[[SNTRule alloc] initWithIdentifier:@"AAAAAAAAAA"
state:SNTRuleStateBlock
type:SNTRuleTypeTeamID
customMsg:@"Banned team ID"
customURL:@"http://northpole.security"],
customURL:@"http://northpole.security"
celExpr:nil],
];

OCMVerify([self.daemonConnRop databaseRuleAddRules:rules
Expand Down