Skip to content

Commit 5b059d7

Browse files
celv2: expose AUDIT return value, trigger event upload with flag set (#956)
1 parent 38dca1b commit 5b059d7

13 files changed

Lines changed: 104 additions & 2 deletions

MODULE.bazel

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ bazel_dep(name = "boringssl", version = "0.20260211.0")
1818
bazel_dep(name = "protos", version = "1.0.1", repo_name = "northpolesec_protos")
1919
git_override(
2020
module_name = "protos",
21-
commit = "ae9f6e1cfca5cdbac04240cac6be46b58c4b1161",
21+
commit = "df6b5ea22bdfeb185b5a22d7090553ec80ce081f",
2222
remote = "https://github.com/northpolesec/protos",
2323
)
2424

Source/common/SNTCachedDecision.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,4 +75,11 @@
7575
@property BOOL silentTouchID;
7676
@property NSNumber* touchIDCooldownMinutes; // nil = no caching (prompt every time)
7777

78+
/// YES if the matching CEL rule returned AUDIT. The execution itself is
79+
/// allowed (the decision is set to the underlying allow event state, e.g.
80+
/// SNTEventStateAllowBinary or SNTEventStateAllowCELFallback) but the event
81+
/// is logged and uploaded with this flag set so audit-only matches can be
82+
/// distinguished from regular allow decisions on the server.
83+
@property BOOL auditReturn;
84+
7885
@end

Source/common/SNTCachedDecision.mm

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ - (id)copyWithZone:(NSZone*)zone {
8787
copy.holdAndAsk = _holdAndAsk;
8888
copy.silentTouchID = _silentTouchID;
8989
copy.touchIDCooldownMinutes = _touchIDCooldownMinutes;
90+
copy.auditReturn = _auditReturn;
9091
return copy;
9192
}
9293

Source/common/SNTStoredExecutionEvent.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,11 @@
8989
/// The decision santad returned.
9090
@property SNTEventState decision;
9191

92+
/// YES if the matching CEL rule returned AUDIT. The decision itself is an
93+
/// allow decision; this flag indicates the rule was audit-only and the event
94+
/// is uploaded so audit matches can be distinguished on the sync server.
95+
@property BOOL auditReturn;
96+
9297
/// The decision depends on the user approving execution.
9398
@property BOOL holdAndAsk;
9499

Source/common/SNTStoredExecutionEvent.mm

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ - (void)encodeWithCoder:(NSCoder*)coder {
8080

8181
ENCODE(coder, executingUser);
8282
ENCODE_BOXABLE(coder, decision);
83+
ENCODE_BOXABLE(coder, auditReturn);
8384
ENCODE_BOXABLE(coder, holdAndAsk);
8485
ENCODE_BOXABLE(coder, silentTouchID);
8586
ENCODE_BOXABLE(coder, seatbeltRequired);
@@ -128,6 +129,7 @@ - (instancetype)initWithCoder:(NSCoder*)decoder {
128129

129130
DECODE(decoder, executingUser, NSString);
130131
DECODE_SELECTOR(decoder, decision, NSNumber, unsignedLongLongValue);
132+
DECODE_SELECTOR(decoder, auditReturn, NSNumber, boolValue);
131133
DECODE_SELECTOR(decoder, holdAndAsk, NSNumber, boolValue);
132134
DECODE_SELECTOR(decoder, silentTouchID, NSNumber, boolValue);
133135
DECODE_SELECTOR(decoder, seatbeltRequired, NSNumber, boolValue);

Source/common/cel/CELProtoTraits.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ struct CELProtoTraits<true> {
7878
static constexpr ReturnValue REQUIRE_TOUCHID_ONLY =
7979
::santa::cel::v2::REQUIRE_TOUCHID_ONLY;
8080
static constexpr ReturnValue SEATBELT = ::santa::cel::v2::SEATBELT;
81+
static constexpr ReturnValue AUDIT = ::santa::cel::v2::AUDIT;
8182

8283
// Descriptor accessors
8384
static const google::protobuf::EnumDescriptor* ReturnValue_descriptor() {

Source/common/cel/Test.mm

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,66 @@ - (void)testBasic {
270270
}
271271
}
272272

273+
- (void)testAuditReturnValue {
274+
using ReturnValue = santa::cel::CELProtoTraits<true>::ReturnValue;
275+
using ExecutableFileT = santa::cel::CELProtoTraits<true>::ExecutableFileT;
276+
using AncestorT = santa::cel::CELProtoTraits<true>::AncestorT;
277+
using FileDescriptorT = santa::cel::CELProtoTraits<true>::FileDescriptorT;
278+
279+
XCTAssertEqual(santa::cel::CELProtoTraits<true>::AUDIT, ::santa::cel::v2::AUDIT);
280+
281+
auto f = std::make_unique<ExecutableFileT>();
282+
f->set_team_id("EQHXZ8M8AV");
283+
santa::cel::Activation<true> activation(
284+
std::move(f),
285+
^std::vector<std::string>() {
286+
return {"hello", "world"};
287+
},
288+
^std::map<std::string, std::string>() {
289+
return {};
290+
},
291+
^uid_t() {
292+
return 0;
293+
},
294+
^std::string() {
295+
return "/";
296+
},
297+
^std::string() {
298+
return "/usr/bin/test";
299+
},
300+
^std::vector<AncestorT>() {
301+
return {};
302+
},
303+
^std::vector<FileDescriptorT>() {
304+
return {};
305+
});
306+
307+
auto sut = santa::cel::Evaluator<true>::Create();
308+
XCTAssertTrue(sut.ok());
309+
310+
{
311+
// Static - AUDIT returned when team_id matches
312+
auto result = sut.value()->CompileAndEvaluate(
313+
"target.team_id == 'EQHXZ8M8AV' ? AUDIT : ALLOWLIST", activation);
314+
if (!result.ok()) {
315+
XCTFail("Failed to evaluate: %s", result.status().message().data());
316+
} else {
317+
XCTAssertEqual(result.value().value, ReturnValue::AUDIT);
318+
XCTAssertEqual(result.value().cacheable, true);
319+
}
320+
}
321+
{
322+
// Dynamic - AUDIT returned when args non-empty
323+
auto result = sut.value()->CompileAndEvaluate("size(args) > 0 ? AUDIT : ALLOWLIST", activation);
324+
if (!result.ok()) {
325+
XCTFail("Failed to evaluate: %s", result.status().message().data());
326+
} else {
327+
XCTAssertEqual(result.value().value, ReturnValue::AUDIT);
328+
XCTAssertEqual(result.value().cacheable, false);
329+
}
330+
}
331+
}
332+
273333
- (void)testV2Only {
274334
auto argsFn = ^std::vector<std::string>() {
275335
return {"hello", "world"};

Source/common/santa.proto

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,13 @@ message Execution {
350350

351351
// The server-assigned rule ID that was used to make the decision
352352
optional int64 rule_id = 18;
353+
354+
// True if the matching rule returned AUDIT from a CEL expression. The
355+
// execution was allowed (as if the rule had returned ALLOWLIST), but the
356+
// event is logged with this flag set so audit matches can be distinguished
357+
// from regular allow decisions. The `decision` and `reason` fields still
358+
// report the underlying allow decision (e.g. DECISION_ALLOW / REASON_BINARY).
359+
optional bool audit_return = 19;
353360
}
354361

355362
// Information about a fork event

Source/santad/Logs/EndpointSecurity/Serializers/BasicString.mm

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,10 @@ static inline void AppendSocketAddress(std::string& str, es_address_type_t type,
337337
str.append("|reason=");
338338
str.append(GetReasonString(cd.decision));
339339

340+
if (cd.auditReturn) {
341+
str.append("|audit=true");
342+
}
343+
340344
if (cd.decisionExtra) {
341345
str.append("|explain=");
342346
str.append([cd.decisionExtra UTF8String]);

Source/santad/Logs/EndpointSecurity/Serializers/Protobuf.mm

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -604,6 +604,10 @@ void EncodeEntitlements(::pbv1::Execution* pb_exec, SNTCachedDecision* cd) {
604604
pb_exec->set_rule_id(cd.ruleId);
605605
}
606606

607+
if (cd.auditReturn) {
608+
pb_exec->set_audit_return(true);
609+
}
610+
607611
return FinalizeProto(santa_msg);
608612
}
609613

0 commit comments

Comments
 (0)