mirror of
https://github.com/google/santa.git
synced 2026-04-24 03:00:12 -04:00
Adopt new ES APIs to monitor target paths (#975)
* WIP begin adopting new ES APIs inverting target mute paths * Track subscription status so as not to unnecessarily enable/disable * Properly chain call to invert target mute paths. Fix using wrong Message obj. * Add base client tests * Support compiling on older platforms * More changes to support compiling on older platforms * Only enable watch items periodic task on macOS 13 * Add more asserts to test * Disable ES caching for now * lint
This commit is contained in:
@@ -64,6 +64,11 @@ objc_library(
|
||||
hdrs = ["SantaVnode.h"],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "Platform",
|
||||
hdrs = ["Platform.h"],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SantaVnodeHash",
|
||||
srcs = ["SantaVnodeHash.mm"],
|
||||
|
||||
34
Source/common/Platform.h
Normal file
34
Source/common/Platform.h
Normal file
@@ -0,0 +1,34 @@
|
||||
/// Copyright 2022 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__COMMON__PLATFORM_H
|
||||
#define SANTA__COMMON__PLATFORM_H
|
||||
|
||||
#include <Availability.h>
|
||||
|
||||
#if defined(MAC_OS_VERSION_12_0) && \
|
||||
MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_12_0
|
||||
#define HAVE_MACOS_12 1
|
||||
#else
|
||||
#define HAVE_MACOS_12 0
|
||||
#endif
|
||||
|
||||
#if defined(MAC_OS_VERSION_13_0) && \
|
||||
MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_13_0
|
||||
#define HAVE_MACOS_13 1
|
||||
#else
|
||||
#define HAVE_MACOS_13 0
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -23,6 +23,7 @@ objc_library(
|
||||
hdrs = ["DataLayer/SNTRuleTable.h"],
|
||||
deps = [
|
||||
":SNTDatabaseTable",
|
||||
"//Source/common:Platform",
|
||||
"//Source/common:SNTCachedDecision",
|
||||
"//Source/common:SNTCommonEnums",
|
||||
"//Source/common:SNTConfigurator",
|
||||
@@ -92,6 +93,7 @@ objc_library(
|
||||
deps = [
|
||||
":EndpointSecurityMessage",
|
||||
":Metrics",
|
||||
":WatchItemPolicy",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -238,6 +240,7 @@ objc_library(
|
||||
":EndpointSecurityEnrichedTypes",
|
||||
":EndpointSecurityMessage",
|
||||
":Metrics",
|
||||
":WatchItemPolicy",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -553,6 +556,7 @@ objc_library(
|
||||
hdrs = ["EventProviders/EndpointSecurity/Message.h"],
|
||||
deps = [
|
||||
":EndpointSecurityClient",
|
||||
":WatchItemPolicy",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -568,6 +572,8 @@ objc_library(
|
||||
deps = [
|
||||
":EndpointSecurityClient",
|
||||
":EndpointSecurityMessage",
|
||||
":WatchItemPolicy",
|
||||
"//Source/common:Platform",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -739,6 +745,7 @@ objc_library(
|
||||
":EndpointSecurityAPI",
|
||||
":EndpointSecurityClient",
|
||||
":EndpointSecurityMessage",
|
||||
":WatchItemPolicy",
|
||||
"@com_google_googletest//:gtest",
|
||||
],
|
||||
)
|
||||
@@ -786,6 +793,7 @@ santa_unit_test(
|
||||
"EndpointSecurity",
|
||||
],
|
||||
deps = [
|
||||
"//Source/common:Platform",
|
||||
"//Source/common:SNTCachedDecision",
|
||||
"//Source/common:SNTCommonEnums",
|
||||
"//Source/common:SNTConfigurator",
|
||||
@@ -1082,6 +1090,7 @@ santa_unit_test(
|
||||
":Metrics",
|
||||
":MockEndpointSecurityAPI",
|
||||
":SNTEndpointSecurityClient",
|
||||
":WatchItemPolicy",
|
||||
"//Source/common:TestUtils",
|
||||
"@OCMock",
|
||||
"@com_google_googletest//:gtest",
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#import <MOLCertificate/MOLCertificate.h>
|
||||
#import <MOLCodesignChecker/MOLCodesignChecker.h>
|
||||
|
||||
#import "Source/common/Platform.h"
|
||||
#import "Source/common/SNTCachedDecision.h"
|
||||
#import "Source/common/SNTConfigurator.h"
|
||||
#import "Source/common/SNTFileInfo.h"
|
||||
@@ -36,7 +37,7 @@ static void addPathsFromDefaultMuteSet(NSMutableSet *criticalPaths) API_AVAILABL
|
||||
// instead we use the following preprocessor macros to conditionally compile these API. The
|
||||
// drawback here is that if a pre-macOS 12 SDK is used to build Santa and it is then deployed
|
||||
// on macOS 12 or later, the dynamic mute set will not be computed.
|
||||
#if defined(MAC_OS_VERSION_12_0) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_12_0
|
||||
#if HAVE_MACOS_12
|
||||
// Create a temporary ES client in order to grab the default set of muted paths.
|
||||
// TODO(mlw): Reorganize this code so that a temporary ES client doesn't need to be created
|
||||
es_client_t *client = NULL;
|
||||
|
||||
@@ -19,12 +19,16 @@
|
||||
#include <ctype.h>
|
||||
#include <glob.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstdlib>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#import "Source/common/PrefixTree.h"
|
||||
#import "Source/common/SNTLogging.h"
|
||||
@@ -322,8 +326,18 @@ void WatchItems::UpdateCurrentState(
|
||||
(current_config_ == nil && new_config != nil) ||
|
||||
(currently_monitored_paths_ != new_monitored_paths) ||
|
||||
(new_config && ![current_config_ isEqualToDictionary:new_config])) {
|
||||
// TODO(mlw): In upcoming PR, need to use ES API to stop watching removed paths,
|
||||
// and start watching newly configured paths.
|
||||
std::vector<std::pair<std::string, WatchItemPathType>> paths_to_watch;
|
||||
std::vector<std::pair<std::string, WatchItemPathType>> paths_to_stop_watching;
|
||||
|
||||
// New paths to watch are those that are in the new set, but not current
|
||||
std::set_difference(new_monitored_paths.begin(), new_monitored_paths.end(),
|
||||
currently_monitored_paths_.begin(), currently_monitored_paths_.end(),
|
||||
std::back_inserter(paths_to_watch));
|
||||
|
||||
// Paths to stop watching are in the current set, but not new
|
||||
std::set_difference(currently_monitored_paths_.begin(), currently_monitored_paths_.end(),
|
||||
new_monitored_paths.begin(), new_monitored_paths.end(),
|
||||
std::back_inserter(paths_to_stop_watching));
|
||||
|
||||
std::swap(watch_items_, new_tree);
|
||||
std::swap(currently_monitored_paths_, new_monitored_paths);
|
||||
@@ -334,18 +348,15 @@ void WatchItems::UpdateCurrentState(
|
||||
policy_version_ = "";
|
||||
}
|
||||
|
||||
bool anyPathsMonitored = currently_monitored_paths_.size() > 0;
|
||||
for (const id<SNTEndpointSecurityDynamicEventHandler> &client : registerd_clients_) {
|
||||
// Note: Enable clients on an async queue in case they perform any
|
||||
// synchronous work that could trigger ES events. Otherwise they might
|
||||
// trigger AUTH ES events that would attempt to re-enter this object and
|
||||
// potentially deadlock.
|
||||
dispatch_async(q_, ^{
|
||||
if (anyPathsMonitored) {
|
||||
[client enable];
|
||||
} else {
|
||||
[client disable];
|
||||
}
|
||||
[client watchItemsCount:currently_monitored_paths_.size()
|
||||
newPaths:paths_to_watch
|
||||
removedPaths:paths_to_stop_watching];
|
||||
});
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -19,7 +19,9 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#include <set>
|
||||
#include <string_view>
|
||||
|
||||
#include "Source/santad/DataLayer/WatchItemPolicy.h"
|
||||
#include "Source/santad/EventProviders/EndpointSecurity/Client.h"
|
||||
#include "Source/santad/EventProviders/EndpointSecurity/Message.h"
|
||||
|
||||
@@ -34,6 +36,17 @@ class EndpointSecurityAPI : public std::enable_shared_from_this<EndpointSecurity
|
||||
virtual bool Subscribe(const Client &client, const std::set<es_event_type_t> &);
|
||||
virtual bool UnsubscribeAll(const Client &client);
|
||||
|
||||
virtual bool UnmuteAllPaths(const Client &client);
|
||||
virtual bool UnmuteAllTargetPaths(const Client &client);
|
||||
|
||||
virtual bool IsTargetPathMutingInverted(const Client &client);
|
||||
virtual bool InvertTargetPathMuting(const Client &client);
|
||||
|
||||
virtual bool MuteTargetPath(const Client &client, std::string_view path,
|
||||
santa::santad::data_layer::WatchItemPathType path_type);
|
||||
virtual bool UnmuteTargetPath(const Client &client, std::string_view path,
|
||||
santa::santad::data_layer::WatchItemPathType path_type);
|
||||
|
||||
virtual void RetainMessage(const es_message_t *msg);
|
||||
virtual void ReleaseMessage(const es_message_t *msg);
|
||||
|
||||
|
||||
@@ -13,11 +13,14 @@
|
||||
/// limitations under the License.
|
||||
|
||||
#include "Source/santad/EventProviders/EndpointSecurity/EndpointSecurityAPI.h"
|
||||
#include <EndpointSecurity/ESTypes.h>
|
||||
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
#include "Source/common/Platform.h"
|
||||
|
||||
using santa::santad::data_layer::WatchItemPathType;
|
||||
|
||||
namespace santa::santad::event_providers::endpoint_security {
|
||||
|
||||
Client EndpointSecurityAPI::NewClient(void (^message_handler)(es_client_t *, Message)) {
|
||||
@@ -51,6 +54,73 @@ bool EndpointSecurityAPI::UnsubscribeAll(const Client &client) {
|
||||
return es_unsubscribe_all(client.Get()) == ES_RETURN_SUCCESS;
|
||||
}
|
||||
|
||||
bool EndpointSecurityAPI::UnmuteAllPaths(const Client &client) {
|
||||
return es_unmute_all_paths(client.Get()) == ES_RETURN_SUCCESS;
|
||||
}
|
||||
|
||||
bool EndpointSecurityAPI::UnmuteAllTargetPaths(const Client &client) {
|
||||
#if HAVE_MACOS_13
|
||||
if (@available(macOS 13.0, *)) {
|
||||
return es_unmute_all_target_paths(client.Get()) == ES_RETURN_SUCCESS;
|
||||
}
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EndpointSecurityAPI::IsTargetPathMutingInverted(const Client &client) {
|
||||
#if HAVE_MACOS_13
|
||||
if (@available(macOS 13.0, *)) {
|
||||
return es_muting_inverted(client.Get(), ES_MUTE_INVERSION_TYPE_TARGET_PATH) == ES_MUTE_INVERTED;
|
||||
}
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool EndpointSecurityAPI::InvertTargetPathMuting(const Client &client) {
|
||||
#if HAVE_MACOS_13
|
||||
if (@available(macOS 13.0, *)) {
|
||||
if (!IsTargetPathMutingInverted(client)) {
|
||||
return es_invert_muting(client.Get(), ES_MUTE_INVERSION_TYPE_TARGET_PATH) ==
|
||||
ES_RETURN_SUCCESS;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool EndpointSecurityAPI::MuteTargetPath(const Client &client, std::string_view path,
|
||||
WatchItemPathType path_type) {
|
||||
#if HAVE_MACOS_13
|
||||
if (@available(macOS 13.0, *)) {
|
||||
return es_mute_path(client.Get(), path.data(),
|
||||
path_type == WatchItemPathType::kPrefix
|
||||
? ES_MUTE_PATH_TYPE_TARGET_PREFIX
|
||||
: ES_MUTE_PATH_TYPE_TARGET_LITERAL) == ES_RETURN_SUCCESS;
|
||||
}
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool EndpointSecurityAPI::UnmuteTargetPath(const Client &client, std::string_view path,
|
||||
WatchItemPathType path_type) {
|
||||
#if HAVE_MACOS_13
|
||||
if (@available(macOS 13.0, *)) {
|
||||
return es_unmute_path(client.Get(), path.data(),
|
||||
path_type == WatchItemPathType::kPrefix
|
||||
? ES_MUTE_PATH_TYPE_TARGET_PREFIX
|
||||
: ES_MUTE_PATH_TYPE_TARGET_LITERAL) == ES_RETURN_SUCCESS;
|
||||
}
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EndpointSecurityAPI::RespondAuthResult(const Client &client, const Message &msg,
|
||||
es_auth_result_t result, bool cache) {
|
||||
return es_respond_auth_result(client.Get(), &(*msg), result, cache) == ES_RESPOND_RESULT_SUCCESS;
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
#include <set>
|
||||
|
||||
#include "Source/santad/DataLayer/WatchItemPolicy.h"
|
||||
#include "Source/santad/EventProviders/EndpointSecurity/Client.h"
|
||||
#include "Source/santad/EventProviders/EndpointSecurity/EndpointSecurityAPI.h"
|
||||
#include "Source/santad/EventProviders/EndpointSecurity/Message.h"
|
||||
@@ -40,6 +41,19 @@ class MockEndpointSecurityAPI
|
||||
const std::set<es_event_type_t> &));
|
||||
MOCK_METHOD(bool, UnsubscribeAll, (const Client &client));
|
||||
|
||||
MOCK_METHOD(bool, UnmuteAllPaths, (const Client &client));
|
||||
MOCK_METHOD(bool, UnmuteAllTargetPaths, (const Client &client));
|
||||
|
||||
MOCK_METHOD(bool, IsTargetPathMutingInverted, (const Client &client));
|
||||
MOCK_METHOD(bool, InvertTargetPathMuting, (const Client &client));
|
||||
|
||||
MOCK_METHOD(bool, MuteTargetPath,
|
||||
(const Client &client, std::string_view path,
|
||||
santa::santad::data_layer::WatchItemPathType path_type));
|
||||
MOCK_METHOD(bool, UnmuteTargetPath,
|
||||
(const Client &client, std::string_view path,
|
||||
santa::santad::data_layer::WatchItemPathType path_type));
|
||||
|
||||
MOCK_METHOD(void, RetainMessage, (const es_message_t *msg));
|
||||
MOCK_METHOD(void, ReleaseMessage, (const es_message_t *msg));
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#import "Source/common/SNTConfigurator.h"
|
||||
#import "Source/common/SNTLogging.h"
|
||||
#include "Source/common/SystemResources.h"
|
||||
#include "Source/santad/DataLayer/WatchItemPolicy.h"
|
||||
#include "Source/santad/EventProviders/EndpointSecurity/Client.h"
|
||||
#include "Source/santad/EventProviders/EndpointSecurity/EnrichedTypes.h"
|
||||
#include "Source/santad/EventProviders/EndpointSecurity/Message.h"
|
||||
@@ -33,6 +34,7 @@
|
||||
using santa::santad::EventDisposition;
|
||||
using santa::santad::Metrics;
|
||||
using santa::santad::Processor;
|
||||
using santa::santad::data_layer::WatchItemPathType;
|
||||
using santa::santad::event_providers::endpoint_security::Client;
|
||||
using santa::santad::event_providers::endpoint_security::EndpointSecurityAPI;
|
||||
using santa::santad::event_providers::endpoint_security::EnrichedMessage;
|
||||
@@ -185,6 +187,34 @@ using santa::santad::event_providers::endpoint_security::Message;
|
||||
return _esApi->UnsubscribeAll(_esClient);
|
||||
}
|
||||
|
||||
- (bool)unmuteEverything {
|
||||
bool result = _esApi->UnmuteAllPaths(_esClient);
|
||||
result = _esApi->UnmuteAllTargetPaths(_esClient) && result;
|
||||
return result;
|
||||
}
|
||||
|
||||
- (bool)enableTargetPathWatching {
|
||||
return _esApi->InvertTargetPathMuting(_esClient);
|
||||
}
|
||||
|
||||
- (bool)muteTargetPaths:(const std::vector<std::pair<std::string, WatchItemPathType>> &)paths {
|
||||
bool result = true;
|
||||
for (const auto &pathAndTypePair : paths) {
|
||||
result =
|
||||
_esApi->MuteTargetPath(_esClient, pathAndTypePair.first, pathAndTypePair.second) && result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
- (bool)unmuteTargetPaths:(const std::vector<std::pair<std::string, WatchItemPathType>> &)paths {
|
||||
bool result = true;
|
||||
for (const auto &pathAndTypePair : paths) {
|
||||
result =
|
||||
_esApi->UnmuteTargetPath(_esClient, pathAndTypePair.first, pathAndTypePair.second) && result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
- (bool)respondToMessage:(const Message &)msg
|
||||
withAuthResult:(es_auth_result_t)result
|
||||
cacheable:(bool)cacheable {
|
||||
|
||||
@@ -17,9 +17,11 @@
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#include "Source/santad/DataLayer/WatchItemPolicy.h"
|
||||
#include "Source/santad/EventProviders/EndpointSecurity/EndpointSecurityAPI.h"
|
||||
#include "Source/santad/EventProviders/EndpointSecurity/EnrichedTypes.h"
|
||||
#include "Source/santad/EventProviders/EndpointSecurity/Message.h"
|
||||
@@ -47,6 +49,12 @@
|
||||
- (bool)subscribeAndClearCache:(const std::set<es_event_type_t> &)events;
|
||||
|
||||
- (bool)unsubscribeAll;
|
||||
- (bool)unmuteEverything;
|
||||
- (bool)enableTargetPathWatching;
|
||||
- (bool)muteTargetPaths:
|
||||
(const std::vector<std::pair<std::string, santa::santad::data_layer::WatchItemPathType>> &)paths;
|
||||
- (bool)unmuteTargetPaths:
|
||||
(const std::vector<std::pair<std::string, santa::santad::data_layer::WatchItemPathType>> &)paths;
|
||||
|
||||
/// Responds to the Message with the given auth result
|
||||
///
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include <memory>
|
||||
|
||||
#include "Source/common/TestUtils.h"
|
||||
#include "Source/santad/DataLayer/WatchItemPolicy.h"
|
||||
#include "Source/santad/EventProviders/EndpointSecurity/Client.h"
|
||||
#include "Source/santad/EventProviders/EndpointSecurity/EnrichedTypes.h"
|
||||
#include "Source/santad/EventProviders/EndpointSecurity/Message.h"
|
||||
@@ -31,6 +32,7 @@
|
||||
#include "Source/santad/Metrics.h"
|
||||
|
||||
using santa::santad::Processor;
|
||||
using santa::santad::data_layer::WatchItemPathType;
|
||||
using santa::santad::event_providers::endpoint_security::Client;
|
||||
using santa::santad::event_providers::endpoint_security::EnrichedClose;
|
||||
using santa::santad::event_providers::endpoint_security::EnrichedFile;
|
||||
@@ -244,6 +246,117 @@ using santa::santad::event_providers::endpoint_security::Message;
|
||||
XCTBubbleMockVerifyAndClearExpectations(mockESApi.get());
|
||||
}
|
||||
|
||||
- (void)testUnsubscribeAll {
|
||||
auto mockESApi = std::make_shared<MockEndpointSecurityAPI>();
|
||||
SNTEndpointSecurityClient *client =
|
||||
[[SNTEndpointSecurityClient alloc] initWithESAPI:mockESApi
|
||||
metrics:nullptr
|
||||
processor:Processor::kUnknown];
|
||||
|
||||
// Test the underlying unsubscribe all impl returning both true and false
|
||||
EXPECT_CALL(*mockESApi, UnsubscribeAll)
|
||||
.WillOnce(testing::Return(true))
|
||||
.WillOnce(testing::Return(false));
|
||||
|
||||
XCTAssertTrue([client unsubscribeAll]);
|
||||
XCTAssertFalse([client unsubscribeAll]);
|
||||
|
||||
XCTBubbleMockVerifyAndClearExpectations(mockESApi.get());
|
||||
}
|
||||
|
||||
- (void)testUnmuteEverything {
|
||||
auto mockESApi = std::make_shared<MockEndpointSecurityAPI>();
|
||||
SNTEndpointSecurityClient *client =
|
||||
[[SNTEndpointSecurityClient alloc] initWithESAPI:mockESApi
|
||||
metrics:nullptr
|
||||
processor:Processor::kUnknown];
|
||||
|
||||
// Test variations of underlying unmute impls returning both true and false
|
||||
EXPECT_CALL(*mockESApi, UnmuteAllPaths)
|
||||
.WillOnce(testing::Return(true))
|
||||
.WillOnce(testing::Return(false));
|
||||
EXPECT_CALL(*mockESApi, UnmuteAllTargetPaths)
|
||||
.WillOnce(testing::Return(true))
|
||||
.WillOnce(testing::Return(true));
|
||||
|
||||
XCTAssertTrue([client unmuteEverything]);
|
||||
XCTAssertFalse([client unmuteEverything]);
|
||||
|
||||
XCTBubbleMockVerifyAndClearExpectations(mockESApi.get());
|
||||
}
|
||||
|
||||
- (void)testEnableTargetPathWatching {
|
||||
auto mockESApi = std::make_shared<MockEndpointSecurityAPI>();
|
||||
SNTEndpointSecurityClient *client =
|
||||
[[SNTEndpointSecurityClient alloc] initWithESAPI:mockESApi
|
||||
metrics:nullptr
|
||||
processor:Processor::kUnknown];
|
||||
|
||||
// Test the underlying invert nute impl returning both true and false
|
||||
EXPECT_CALL(*mockESApi, InvertTargetPathMuting)
|
||||
.WillOnce(testing::Return(true))
|
||||
.WillOnce(testing::Return(false));
|
||||
|
||||
XCTAssertTrue([client enableTargetPathWatching]);
|
||||
XCTAssertFalse([client enableTargetPathWatching]);
|
||||
|
||||
XCTBubbleMockVerifyAndClearExpectations(mockESApi.get());
|
||||
}
|
||||
|
||||
- (void)testMuteTargetPaths {
|
||||
auto mockESApi = std::make_shared<MockEndpointSecurityAPI>();
|
||||
SNTEndpointSecurityClient *client =
|
||||
[[SNTEndpointSecurityClient alloc] initWithESAPI:mockESApi
|
||||
metrics:nullptr
|
||||
processor:Processor::kUnknown];
|
||||
|
||||
// Ensure all paths are attempted to be muted even if some fail.
|
||||
// Ensure if any paths fail the overall result is false.
|
||||
EXPECT_CALL(*mockESApi, MuteTargetPath(testing::_, "a", WatchItemPathType::kLiteral))
|
||||
.WillOnce(testing::Return(true));
|
||||
EXPECT_CALL(*mockESApi, MuteTargetPath(testing::_, "b", WatchItemPathType::kLiteral))
|
||||
.WillOnce(testing::Return(false));
|
||||
EXPECT_CALL(*mockESApi, MuteTargetPath(testing::_, "c", WatchItemPathType::kPrefix))
|
||||
.WillOnce(testing::Return(true));
|
||||
|
||||
std::vector<std::pair<std::string, WatchItemPathType>> paths = {
|
||||
{"a", WatchItemPathType::kLiteral},
|
||||
{"b", WatchItemPathType::kLiteral},
|
||||
{"c", WatchItemPathType::kPrefix},
|
||||
};
|
||||
|
||||
XCTAssertFalse([client muteTargetPaths:paths]);
|
||||
|
||||
XCTBubbleMockVerifyAndClearExpectations(mockESApi.get());
|
||||
}
|
||||
|
||||
- (void)testUnmuteTargetPaths {
|
||||
auto mockESApi = std::make_shared<MockEndpointSecurityAPI>();
|
||||
SNTEndpointSecurityClient *client =
|
||||
[[SNTEndpointSecurityClient alloc] initWithESAPI:mockESApi
|
||||
metrics:nullptr
|
||||
processor:Processor::kUnknown];
|
||||
|
||||
// Ensure all paths are attempted to be unmuted even if some fail.
|
||||
// Ensure if any paths fail the overall result is false.
|
||||
EXPECT_CALL(*mockESApi, UnmuteTargetPath(testing::_, "a", WatchItemPathType::kLiteral))
|
||||
.WillOnce(testing::Return(true));
|
||||
EXPECT_CALL(*mockESApi, UnmuteTargetPath(testing::_, "b", WatchItemPathType::kLiteral))
|
||||
.WillOnce(testing::Return(false));
|
||||
EXPECT_CALL(*mockESApi, UnmuteTargetPath(testing::_, "c", WatchItemPathType::kPrefix))
|
||||
.WillOnce(testing::Return(true));
|
||||
|
||||
std::vector<std::pair<std::string, WatchItemPathType>> paths = {
|
||||
{"a", WatchItemPathType::kLiteral},
|
||||
{"b", WatchItemPathType::kLiteral},
|
||||
{"c", WatchItemPathType::kPrefix},
|
||||
};
|
||||
|
||||
XCTAssertFalse([client unmuteTargetPaths:paths]);
|
||||
|
||||
XCTBubbleMockVerifyAndClearExpectations(mockESApi.get());
|
||||
}
|
||||
|
||||
- (void)testRespondToMessageWithAuthResultCacheable {
|
||||
es_message_t esMsg;
|
||||
auto mockESApi = std::make_shared<MockEndpointSecurityAPI>();
|
||||
@@ -280,21 +393,22 @@ using santa::santad::event_providers::endpoint_security::Message;
|
||||
[[SNTEndpointSecurityClient alloc] initWithESAPI:mockESApi
|
||||
metrics:nullptr
|
||||
processor:Processor::kUnknown];
|
||||
{
|
||||
auto enrichedMsg = std::make_shared<EnrichedMessage>(
|
||||
EnrichedClose(Message(mockESApi, &esMsg),
|
||||
EnrichedProcess(std::nullopt, std::nullopt, std::nullopt, std::nullopt,
|
||||
EnrichedFile(std::nullopt, std::nullopt, std::nullopt)),
|
||||
EnrichedFile(std::nullopt, std::nullopt, std::nullopt)));
|
||||
|
||||
auto enrichedMsg = std::make_shared<EnrichedMessage>(
|
||||
EnrichedClose(Message(mockESApi, &esMsg),
|
||||
EnrichedProcess(std::nullopt, std::nullopt, std::nullopt, std::nullopt,
|
||||
EnrichedFile(std::nullopt, std::nullopt, std::nullopt)),
|
||||
EnrichedFile(std::nullopt, std::nullopt, std::nullopt)));
|
||||
[client processEnrichedMessage:enrichedMsg
|
||||
handler:^(std::shared_ptr<EnrichedMessage> msg) {
|
||||
dispatch_semaphore_signal(sema);
|
||||
}];
|
||||
|
||||
[client processEnrichedMessage:enrichedMsg
|
||||
handler:^(std::shared_ptr<EnrichedMessage> msg) {
|
||||
dispatch_semaphore_signal(sema);
|
||||
}];
|
||||
|
||||
XCTAssertEqual(0,
|
||||
dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC)),
|
||||
"Handler block not called within expected time window");
|
||||
XCTAssertEqual(
|
||||
0, dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC)),
|
||||
"Handler block not called within expected time window");
|
||||
}
|
||||
|
||||
XCTBubbleMockVerifyAndClearExpectations(mockESApi.get());
|
||||
}
|
||||
@@ -355,11 +469,11 @@ using santa::santad::event_providers::endpoint_security::Message;
|
||||
handler:^(const Message &msg) {
|
||||
dispatch_semaphore_signal(sema);
|
||||
}]);
|
||||
}
|
||||
|
||||
XCTAssertEqual(0,
|
||||
dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC)),
|
||||
"Handler block not called within expected time window");
|
||||
XCTAssertEqual(
|
||||
0, dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC)),
|
||||
"Handler block not called within expected time window");
|
||||
}
|
||||
|
||||
XCTBubbleMockVerifyAndClearExpectations(mockESApi.get());
|
||||
}
|
||||
|
||||
@@ -14,6 +14,10 @@
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "Source/santad/DataLayer/WatchItemPolicy.h"
|
||||
#include "Source/santad/EventProviders/EndpointSecurity/Message.h"
|
||||
#include "Source/santad/Metrics.h"
|
||||
|
||||
@@ -39,4 +43,13 @@
|
||||
// Called when a client should no longer receive events.
|
||||
- (void)disable;
|
||||
|
||||
- (void)
|
||||
watchItemsCount:(size_t)count
|
||||
newPaths:
|
||||
(const std::vector<std::pair<std::string, santa::santad::data_layer::WatchItemPathType>>
|
||||
&)newPaths
|
||||
removedPaths:
|
||||
(const std::vector<std::pair<std::string, santa::santad::data_layer::WatchItemPathType>> &)
|
||||
removedPaths;
|
||||
|
||||
@end
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
#include "Source/santad/EventProviders/EndpointSecurity/Message.h"
|
||||
|
||||
using santa::santad::EventDisposition;
|
||||
using santa::santad::data_layer::WatchItemPathType;
|
||||
using santa::santad::data_layer::WatchItemPolicy;
|
||||
using santa::santad::data_layer::WatchItems;
|
||||
using santa::santad::event_providers::endpoint_security::EndpointSecurityAPI;
|
||||
@@ -155,7 +156,7 @@ void PopulatePathTargets(const Message &msg, std::vector<std::string> &targets)
|
||||
|
||||
@interface SNTEndpointSecurityFileAccessAuthorizer ()
|
||||
@property SNTDecisionCache *decisionCache;
|
||||
@property BOOL isSubscribed;
|
||||
@property bool isSubscribed;
|
||||
@end
|
||||
|
||||
@implementation SNTEndpointSecurityFileAccessAuthorizer {
|
||||
@@ -185,6 +186,9 @@ void PopulatePathTargets(const Message &msg, std::vector<std::string> &targets)
|
||||
_decisionCache = decisionCache;
|
||||
|
||||
[self establishClientOrDie];
|
||||
|
||||
[super enableTargetPathWatching];
|
||||
[super unmuteEverything];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -347,7 +351,7 @@ void PopulatePathTargets(const Message &msg, std::vector<std::string> &targets)
|
||||
handler:^(Message &&esMsg) {
|
||||
self->_logger->LogFileAccess(
|
||||
policyVersionCopy, policyNameCopy, esMsg,
|
||||
self->_enricher->Enrich(*msg->process, EnrichOptions::kLocalOnly),
|
||||
self->_enricher->Enrich(*esMsg->process, EnrichOptions::kLocalOnly),
|
||||
targetCopy, policyDecision);
|
||||
}];
|
||||
|
||||
@@ -380,9 +384,7 @@ void PopulatePathTargets(const Message &msg, std::vector<std::string> &targets)
|
||||
prevDecision = curDecision;
|
||||
}
|
||||
|
||||
[self respondToMessage:msg
|
||||
withAuthResult:policyResult
|
||||
cacheable:(policyResult == ES_AUTH_RESULT_ALLOW)];
|
||||
[self respondToMessage:msg withAuthResult:policyResult cacheable:false];
|
||||
}
|
||||
|
||||
- (void)handleMessage:(santa::santad::event_providers::endpoint_security::Message &&)esMsg
|
||||
@@ -402,11 +404,37 @@ void PopulatePathTargets(const Message &msg, std::vector<std::string> &targets)
|
||||
// ES_EVENT_TYPE_AUTH_CLONE
|
||||
// ES_EVENT_TYPE_AUTH_EXCHANGEDATA
|
||||
// ES_EVENT_TYPE_AUTH_COPYFILE
|
||||
[super subscribeAndClearCache:{ES_EVENT_TYPE_AUTH_OPEN}];
|
||||
if (!self.isSubscribed) {
|
||||
self.isSubscribed = [super subscribe:{ES_EVENT_TYPE_AUTH_OPEN}];
|
||||
[super clearCache];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)disable {
|
||||
[super unsubscribeAll];
|
||||
if (self.isSubscribed) {
|
||||
if ([super unsubscribeAll]) {
|
||||
self.isSubscribed = false;
|
||||
}
|
||||
[super unmuteEverything];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)watchItemsCount:(size_t)count
|
||||
newPaths:(const std::vector<std::pair<std::string, WatchItemPathType>> &)newPaths
|
||||
removedPaths:
|
||||
(const std::vector<std::pair<std::string, WatchItemPathType>> &)removedPaths {
|
||||
if (count == 0) {
|
||||
[self disable];
|
||||
} else {
|
||||
// Stop watching removed paths
|
||||
[super unmuteTargetPaths:removedPaths];
|
||||
|
||||
// Begin watching the added paths
|
||||
[super muteTargetPaths:newPaths];
|
||||
|
||||
// begin receiving events (if not already)
|
||||
[self enable];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -50,6 +50,13 @@ extern es_auth_result_t FileAccessPolicyDecisionToESAuthResult(FileAccessPolicyD
|
||||
extern bool ShouldLogDecision(FileAccessPolicyDecision decision);
|
||||
extern es_auth_result_t CombinePolicyResults(es_auth_result_t result1, es_auth_result_t result2);
|
||||
|
||||
void SetExpectationsForFileAccessAuthorizerInit(
|
||||
std::shared_ptr<MockEndpointSecurityAPI> mockESApi) {
|
||||
EXPECT_CALL(*mockESApi, InvertTargetPathMuting).WillOnce(testing::Return(true));
|
||||
EXPECT_CALL(*mockESApi, UnmuteAllPaths).WillOnce(testing::Return(true));
|
||||
EXPECT_CALL(*mockESApi, UnmuteAllTargetPaths).WillOnce(testing::Return(true));
|
||||
}
|
||||
|
||||
@interface SNTEndpointSecurityFileAccessAuthorizer (Testing)
|
||||
- (NSString *)getCertificateHash:(es_file_t *)esFile;
|
||||
- (FileAccessPolicyDecision)specialCaseForPolicy:(std::shared_ptr<WatchItemPolicy>)policy
|
||||
@@ -57,6 +64,8 @@ extern es_auth_result_t CombinePolicyResults(es_auth_result_t result1, es_auth_r
|
||||
- (FileAccessPolicyDecision)applyPolicy:
|
||||
(std::optional<std::shared_ptr<WatchItemPolicy>>)optionalPolicy
|
||||
toMessage:(const Message &)msg;
|
||||
|
||||
@property bool isSubscribed;
|
||||
@end
|
||||
|
||||
@interface SNTEndpointSecurityFileAccessAuthorizerTest : XCTestCase
|
||||
@@ -98,6 +107,7 @@ extern es_auth_result_t CombinePolicyResults(es_auth_result_t result1, es_auth_r
|
||||
|
||||
auto mockESApi = std::make_shared<MockEndpointSecurityAPI>();
|
||||
mockESApi->SetExpectationsESNewClient();
|
||||
SetExpectationsForFileAccessAuthorizerInit(mockESApi);
|
||||
|
||||
SNTEndpointSecurityFileAccessAuthorizer *accessClient =
|
||||
[[SNTEndpointSecurityFileAccessAuthorizer alloc] initWithESAPI:mockESApi
|
||||
@@ -229,6 +239,7 @@ extern es_auth_result_t CombinePolicyResults(es_auth_result_t result1, es_auth_r
|
||||
auto mockESApi = std::make_shared<MockEndpointSecurityAPI>();
|
||||
mockESApi->SetExpectationsESNewClient();
|
||||
mockESApi->SetExpectationsRetainReleaseMessage();
|
||||
SetExpectationsForFileAccessAuthorizerInit(mockESApi);
|
||||
|
||||
SNTEndpointSecurityFileAccessAuthorizer *accessClient =
|
||||
[[SNTEndpointSecurityFileAccessAuthorizer alloc] initWithESAPI:mockESApi
|
||||
@@ -309,6 +320,7 @@ extern es_auth_result_t CombinePolicyResults(es_auth_result_t result1, es_auth_r
|
||||
auto mockESApi = std::make_shared<MockEndpointSecurityAPI>();
|
||||
mockESApi->SetExpectationsESNewClient();
|
||||
mockESApi->SetExpectationsRetainReleaseMessage();
|
||||
SetExpectationsForFileAccessAuthorizerInit(mockESApi);
|
||||
|
||||
SNTEndpointSecurityFileAccessAuthorizer *accessClient =
|
||||
[[SNTEndpointSecurityFileAccessAuthorizer alloc] initWithESAPI:mockESApi
|
||||
@@ -445,6 +457,7 @@ extern es_auth_result_t CombinePolicyResults(es_auth_result_t result1, es_auth_r
|
||||
- (void)testDisable {
|
||||
auto mockESApi = std::make_shared<MockEndpointSecurityAPI>();
|
||||
mockESApi->SetExpectationsESNewClient();
|
||||
SetExpectationsForFileAccessAuthorizerInit(mockESApi);
|
||||
|
||||
SNTEndpointSecurityFileAccessAuthorizer *accessClient =
|
||||
[[SNTEndpointSecurityFileAccessAuthorizer alloc] initWithESAPI:mockESApi
|
||||
@@ -455,7 +468,10 @@ extern es_auth_result_t CombinePolicyResults(es_auth_result_t result1, es_auth_r
|
||||
decisionCache:nil];
|
||||
|
||||
EXPECT_CALL(*mockESApi, UnsubscribeAll);
|
||||
EXPECT_CALL(*mockESApi, UnmuteAllPaths).WillOnce(testing::Return(true));
|
||||
EXPECT_CALL(*mockESApi, UnmuteAllTargetPaths).WillOnce(testing::Return(true));
|
||||
|
||||
accessClient.isSubscribed = true;
|
||||
[accessClient disable];
|
||||
|
||||
XCTBubbleMockVerifyAndClearExpectations(mockESApi.get());
|
||||
|
||||
@@ -321,8 +321,11 @@ void SantadMain(std::shared_ptr<EndpointSecurityAPI> esapi, std::shared_ptr<Logg
|
||||
// execution policy appropriately.
|
||||
[authorizer_client enable];
|
||||
[tamper_client enable];
|
||||
// Start monitoring any watched items
|
||||
watch_items->BeginPeriodicTask();
|
||||
if (@available(macOS 13.0, *)) {
|
||||
// Start monitoring any watched items
|
||||
// Note: This feature is only enabled on macOS 13.0+
|
||||
watch_items->BeginPeriodicTask();
|
||||
}
|
||||
[monitor_client enable];
|
||||
[device_client enable];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user