Apr 27th Update

This commit is contained in:
Ned Wright
2023-04-27 19:05:49 +00:00
parent cd4fb6e3e8
commit fd2d9fa081
89 changed files with 2175 additions and 544 deletions

View File

@@ -0,0 +1,3 @@
add_library(l7_access_control layer_7_access_control.cc)
add_subdirectory(layer_7_access_control_ut)

View File

@@ -0,0 +1,348 @@
#include "layer_7_access_control.h"
#include <string>
#include <unordered_set>
#include <boost/algorithm/string/case_conv.hpp>
#include "config.h"
#include "cache.h"
#include "http_inspection_events.h"
#include "http_transaction_common.h"
#include "nginx_attachment_common.h"
#include "intelligence_comp_v2.h"
#include "intelligence_is_v2/intelligence_query_v2.h"
#include "intelligence_is_v2/query_request_v2.h"
#include "log_generator.h"
USE_DEBUG_FLAG(D_L7_ACCESS_CONTROL);
using namespace std;
using namespace Intelligence_IS_V2;
static const string crowdsec_enabled_value = "true";
static const string crowdsec_asset_type = "data-cloud-ip-crowdSec";
class IntelligenceIpReputation
{
public:
template <class Archive>
void
load(Archive &ar)
{
try {
vector<string> ipv4_addresses;
ar(cereal::make_nvp("type", type));
ar(cereal::make_nvp("scenario", scenario));
ar(cereal::make_nvp("origin", origin));
ar(cereal::make_nvp("crowdsecId", crowdsec_event_id));
ar(cereal::make_nvp("ipv4Addresses", ipv4_addresses));
if (!ipv4_addresses.empty()) ipv4_address = ipv4_addresses.front();
} catch (const cereal::Exception &e) {
dbgWarning(D_L7_ACCESS_CONTROL) << "Failed to load IP reputation data JSON. Error: " << e.what();
}
}
Maybe<LogField>
getType() const
{
if (type.empty()) return genError("Empty type");
return LogField("externalVendorRecommendedAction", type);
}
Maybe<LogField>
getScenario() const
{
if (scenario.empty()) return genError("Empty scenario");
return LogField("externalVendorRecommendationOriginDetails", scenario);
}
Maybe<LogField>
getOrigin() const
{
if (origin.empty()) return genError("Empty origin");
return LogField("externalVendorRecommendationOrigin", origin);
}
Maybe<LogField>
getIpv4Address() const
{
if (ipv4_address.empty()) return genError("Empty ipv4 address");
return LogField("externalVendorRecommendedAffectedScope", ipv4_address);
}
Maybe<LogField>
getCrowdsecEventId() const
{
if (!crowdsec_event_id) return genError("Empty ID");
return LogField("externalVendorRecommendationId", crowdsec_event_id);
}
bool isMalicious() const { return type == "ban"; }
void
print(std::ostream &out) const
{
out
<< "Crowdsec event ID: "
<< crowdsec_event_id
<< ", IPV4 address: "
<< ipv4_address
<< ", type: "
<< type
<< ", origin: "
<< origin
<< ", scenario: "
<< scenario;
}
private:
string type;
string scenario;
string origin;
string ipv4_address;
unsigned int crowdsec_event_id;
};
class Layer7AccessControl::Impl : public Listener<HttpRequestHeaderEvent>
{
public:
void init();
void fini();
string getListenerName() const override { return "Layer-7 Access Control app"; }
EventVerdict
respond(const HttpRequestHeaderEvent &event) override
{
dbgTrace(D_L7_ACCESS_CONTROL) << "Handling a new layer-7 access control event: " << event;
if (!isAppEnabled()) {
dbgTrace(D_L7_ACCESS_CONTROL) << "Returning Accept verdict as the Layer-7 Access Control app is disabled";
return ngx_http_cp_verdict_e::TRAFFIC_VERDICT_ACCEPT;
}
if (!event.isLastHeader()) {
dbgTrace(D_L7_ACCESS_CONTROL) << "Returning Inspect verdict";
return ngx_http_cp_verdict_e::TRAFFIC_VERDICT_INSPECT;
}
auto source_identifier = i_env->get<string>(HttpTransactionData::source_identifier);
if (source_identifier.ok() && IPAddr::createIPAddr(source_identifier.unpack()).ok()) {
dbgTrace(D_L7_ACCESS_CONTROL) << "Found a valid source identifier value: " << source_identifier.unpack();
return checkReputation(source_identifier.unpack());
}
auto orig_source_ip = i_env->get<IPAddr>(HttpTransactionData::client_ip_ctx);
if (!orig_source_ip.ok()) {
dbgWarning(D_L7_ACCESS_CONTROL) << "Could not extract the Client IP address from context";
return ngx_http_cp_verdict_e::TRAFFIC_VERDICT_ACCEPT;
}
stringstream ss_client_ip;
ss_client_ip << orig_source_ip.unpack();
return checkReputation(ss_client_ip.str());
}
private:
Maybe<IntelligenceIpReputation> getIpReputation(const string &ip);
ngx_http_cp_verdict_e checkReputation(const string &source_ip);
void generateLog(const string &source_ip, const IntelligenceIpReputation &ip_reputation) const;
bool isAppEnabled() const;
bool isPrevent() const;
Maybe<LogField, Context::Error> genLogField(const string &log_key, const string &env_key) const;
Maybe<LogField, Context::Error> genLogIPField(const string &log_key, const string &env_key) const;
I_Environment *i_env = nullptr;
I_Intelligence_IS_V2 *i_intelligence = nullptr;
TemporaryCache<string, IntelligenceIpReputation> ip_reputation_cache;
};
bool
Layer7AccessControl::Impl::isAppEnabled() const
{
bool enabled = getenv("CROWDSEC_ENABLED") ? string(getenv("CROWDSEC_ENABLED")) == crowdsec_enabled_value : false;
return getProfileAgentSettingWithDefault<bool>(enabled, "layer7AccessControl.crowdsec.enabled");
}
bool
Layer7AccessControl::Impl::isPrevent() const
{
string security_mode_env = getenv("CROWDSEC_MODE") ? getenv("CROWDSEC_MODE") : "prevent";
string mode = getProfileAgentSettingWithDefault(security_mode_env, "layer7AccessControl.securityMode");
dbgTrace(D_L7_ACCESS_CONTROL) << "Selected security mode: " << mode;
return mode == "prevent";
}
Maybe<IntelligenceIpReputation>
Layer7AccessControl::Impl::getIpReputation(const string &ip)
{
dbgFlow(D_L7_ACCESS_CONTROL) << "Getting reputation of IP " << ip;
if (ip_reputation_cache.doesKeyExists(ip)) return ip_reputation_cache.getEntry(ip);
dbgTrace(D_L7_ACCESS_CONTROL) << "Not found in cache - about to query intelligence";
QueryRequest request = QueryRequest(
Condition::EQUALS,
"ipv4Addresses",
ip,
true,
AttributeKeyType::REGULAR
);
auto response = i_intelligence->queryIntelligence<IntelligenceIpReputation>(request);
if (!response.ok()) {
dbgWarning(D_L7_ACCESS_CONTROL) << "Failed to query intelligence about reputation of IP: " << ip;
return genError("Failed to query intelligence");
}
auto &unpacked_response = response.unpack();
if (unpacked_response.empty()) {
dbgTrace(D_L7_ACCESS_CONTROL) << "Intelligence reputation response collection is empty. IP is clean.";
return IntelligenceIpReputation();
}
for (const auto &intelligence_reply : unpacked_response) {
if (intelligence_reply.getAssetType() == crowdsec_asset_type && !intelligence_reply.getData().empty()){
dbgTrace(D_L7_ACCESS_CONTROL) << intelligence_reply.getData().front();
return intelligence_reply.getData().front();
}
}
return IntelligenceIpReputation();
}
ngx_http_cp_verdict_e
Layer7AccessControl::Impl::checkReputation(const string &source_ip)
{
auto ip_reputation = getIpReputation(source_ip);
if (!ip_reputation.ok()) {
dbgWarning(D_L7_ACCESS_CONTROL) << "Could not query intelligence. Retruning default verdict";
bool is_drop_by_default = getProfileAgentSettingWithDefault<bool>(false, "layer7AccessControl.dropByDefault");
if (!(is_drop_by_default && isPrevent())) return ngx_http_cp_verdict_e::TRAFFIC_VERDICT_ACCEPT;
generateLog(source_ip, IntelligenceIpReputation());
return ngx_http_cp_verdict_e::TRAFFIC_VERDICT_DROP;
}
if (!ip_reputation.unpack().isMalicious()) {
dbgTrace(D_L7_ACCESS_CONTROL) << "Accepting IP: " << source_ip;
return ngx_http_cp_verdict_e::TRAFFIC_VERDICT_ACCEPT;
}
ip_reputation_cache.emplaceEntry(source_ip, ip_reputation.unpack());
if (isPrevent()) {
dbgTrace(D_L7_ACCESS_CONTROL) << "Dropping IP: " << source_ip;
generateLog(source_ip, ip_reputation.unpack());
return ngx_http_cp_verdict_e::TRAFFIC_VERDICT_DROP;
}
dbgTrace(D_L7_ACCESS_CONTROL) << "Detecting IP: " << source_ip;
generateLog(source_ip, ip_reputation.unpack());
return ngx_http_cp_verdict_e::TRAFFIC_VERDICT_ACCEPT;
}
void
Layer7AccessControl::Impl::generateLog(const string &source_ip, const IntelligenceIpReputation &ip_reputation) const
{
dbgFlow(D_L7_ACCESS_CONTROL) << "About to generate Layer-7 Access Control log";
string security_action = isPrevent() ? "Prevent" : "Detect";
LogGen log(
"Access Control External Vendor Reputation",
ReportIS::Audience::SECURITY,
ReportIS::Severity::CRITICAL,
ReportIS::Priority::HIGH,
ReportIS::Tags::LAYER_7_ACCESS_CONTROL
);
log
<< genLogField("sourcePort", HttpTransactionData::client_port_ctx)
<< genLogField("httpHostName", HttpTransactionData::host_name_ctx)
<< genLogField("httpUriPath", HttpTransactionData::uri_ctx)
<< genLogField("httpMethod", HttpTransactionData::method_ctx)
<< genLogField("ipProtocol", HttpTransactionData::http_proto_ctx)
<< genLogField("destinationPort", HttpTransactionData::listening_port_ctx)
<< genLogField("proxyIP", HttpTransactionData::proxy_ip_ctx)
<< genLogField("httpSourceId", HttpTransactionData::source_identifier)
<< genLogField("httpUriPath", HttpTransactionData::uri_path_decoded)
<< genLogField("httpUriQuery", HttpTransactionData::uri_query_decoded)
<< genLogField("httpRequestHeaders", HttpTransactionData::req_headers)
<< genLogIPField("destinationIP", HttpTransactionData::listening_ip_ctx)
<< LogField("securityAction", security_action)
<< LogField("sourceIP", source_ip)
<< LogField("externalVendorName", "crowdsec")
<< ip_reputation.getCrowdsecEventId()
<< ip_reputation.getType()
<< ip_reputation.getOrigin()
<< ip_reputation.getIpv4Address()
<< ip_reputation.getScenario();
}
Maybe<LogField, Context::Error>
Layer7AccessControl::Impl::genLogField(const string &log_key, const string &env_key) const
{
auto value = i_env->get<string>(env_key);
if (value.ok()) return LogField(log_key, *value);
return value.passErr();
}
Maybe<LogField, Context::Error>
Layer7AccessControl::Impl::genLogIPField(const string &log_key, const string &env_key) const
{
auto value = i_env->get<IPAddr>(env_key);
if (value.ok()) {
stringstream value_str;
value_str << value.unpack();
return LogField(log_key, value_str.str());
}
return value.passErr();
}
void
Layer7AccessControl::Impl::init()
{
registerListener();
i_env = Singleton::Consume<I_Environment>::by<Layer7AccessControl>();
i_intelligence = Singleton::Consume<I_Intelligence_IS_V2>::by<Layer7AccessControl>();
chrono::minutes expiration(
getProfileAgentSettingWithDefault<uint>(60u, "layer7AccessControl.crowdsec.cacheExpiration")
);
ip_reputation_cache.startExpiration(
expiration,
Singleton::Consume<I_MainLoop>::by<Layer7AccessControl>(),
Singleton::Consume<I_TimeGet>::by<Layer7AccessControl>()
);
}
void
Layer7AccessControl::Impl::fini()
{
unregisterListener();
ip_reputation_cache.endExpiration();
}
Layer7AccessControl::Layer7AccessControl() : Component("Layer-7 Access Control"), pimpl(make_unique<Impl>()) {}
Layer7AccessControl::~Layer7AccessControl() {}
void
Layer7AccessControl::init()
{
pimpl->init();
}
void
Layer7AccessControl::fini()
{
pimpl->fini();
}

View File

@@ -0,0 +1,7 @@
file(COPY data DESTINATION .)
add_unit_test(
layer_7_access_control_ut
"layer_7_access_control_ut.cc"
"l7_access_control;logging;agent_details;table;singleton;time_proxy;metric;event_is;connkey;http_transaction_data;generic_rulebase;generic_rulebase_evaluators;ip_utilities;intelligence_is_v2"
)

View File

@@ -0,0 +1,87 @@
{
"assetCollections": [
{
"schemaVersion": 1,
"assetType": "not-crowdsec",
"assetTypeSchemaVersion": 1,
"permissionType": "allTenants",
"name": "050 Plus",
"objectType": "asset",
"class": "appiApplication",
"category": "cloud",
"family": "applicationsAndCategories",
"group": "appiObjects",
"order": "application",
"mainAttributes": {
"appiObjUuid": "00FA9E4440350F65E05308241DC22DA2"
},
"sources": [
{
"tenantId": "27278218-0e7f-4cd8-bdfe-2a5897d68fd0",
"sourceId": "434eabf4-651f-4a45-a9f7-159dc8183b78",
"assetId": "2222222222222222222222222222222",
"ttl": 86400,
"expirationTime": "2023-03-29T15:16:11.367873254Z",
"confidence": 900,
"attributes": {
"appType": "core",
"blockOnAny": false,
"categoryId": 40000060,
"categoryName": "VoIP",
"categoryUuid": "00FA9E44404E0F65E05308241DC22DA2",
"cpId": 60517839,
"dataType": false,
"type": "appfw_application"
}
}
]
},
{
"schemaVersion": 1,
"assetType": "data-cloud-ip-crowdSec",
"assetTypeSchemaVersion": 1,
"permissionType": "tenant",
"name": "1.2.3.4",
"objectType": "asset",
"class": "data",
"category": "cloud",
"family": "ip",
"group": "crowdSec",
"mainAttributes": {
"ipv4Addresses": [
"1.2.3.4"
]
},
"sources": [
{
"tenantId": "c6f606b1-e59b-4f94-b829-ce597bd03067",
"sourceId": "529185bd-9c91-4853-9c26-3140950ecad7",
"assetId": "YXNzZXQ7OztkYXRhOzs7Y2xvdWQ7OztpcDs7O2Nyb3dkU2VjOzs7Ozs7Ozs7eyJpcHY0QWRkcmVzc2VzIjpbIjEuMi4zLjQiXX0=",
"ttl": 86400,
"expirationTime": "2023-03-27T12:08:00.279Z",
"confidence": 500,
"attributes": {
"crowdsecId": 2253734,
"duration": "29m33.009472691s",
"ipv4Addresses": [
"1.2.3.4"
],
"ipv4AddressesRange": [
{
"max": "1.2.3.4",
"min": "1.2.3.4"
}
],
"origin": "cscli",
"scenario": "manual 'ban' from 'localhost'",
"scope": "Ip",
"type": "ban"
}
}
]
}
],
"status": "done",
"totalNumAssets": 1,
"cursor": ""
}

View File

@@ -0,0 +1,6 @@
{
"assetCollections": [],
"status": "done",
"totalNumAssets": 0,
"cursor": ""
}

View File

@@ -0,0 +1,413 @@
#include "layer_7_access_control.h"
#include "cptest.h"
#include "config_component.h"
#include "mock/mock_mainloop.h"
#include "mock/mock_time_get.h"
#include "mock/mock_http_manager.h"
#include "mock/mock_logging.h"
#include "mock/mock_messaging.h"
#include "intelligence_comp_v2.h"
#include "agent_details.h"
using namespace std;
using namespace testing;
USE_DEBUG_FLAG(D_L7_ACCESS_CONTROL);
class Layer7AccessControlTest : public Test
{
public:
Layer7AccessControlTest()
{
Debug::setUnitTestFlag(D_L7_ACCESS_CONTROL, Debug::DebugLevel::TRACE);
EXPECT_CALL(mock_logging, getCurrentLogId()).Times(AnyNumber());
EXPECT_CALL(mock_time, getWalltimeStr(_)).WillRepeatedly(Return(string("2016-11-13T17:31:24.087")));
EXPECT_CALL(mock_time, getWalltime()).WillRepeatedly(Return(chrono::seconds(0)));
EXPECT_CALL(mock_time, getMonotonicTime()).WillRepeatedly(Return(chrono::seconds(60)));
EXPECT_CALL(mock_ml, doesRoutineExist(_)).WillRepeatedly(Return(true));
EXPECT_CALL(mock_ml, stop(_)).WillRepeatedly(Return());
env.preload();
env.init();
config.preload();
intelligence_comp.preload();
intelligence_comp.init();
l7_access_control.preload();
l7_access_control.init();
ctx.activate();
}
~Layer7AccessControlTest()
{
ctx.deactivate();
l7_access_control.fini();
}
string loadIntelligenceResponse(const string &file_path);
void registerTransactionData();
void verifyReport(const Report &report, const string &source_identifier, const string &security_action);
const EventVerdict drop_verdict = ngx_http_cp_verdict_e::TRAFFIC_VERDICT_DROP;
const EventVerdict accept_verdict = ngx_http_cp_verdict_e::TRAFFIC_VERDICT_ACCEPT;
const EventVerdict inspect_verdict = ngx_http_cp_verdict_e::TRAFFIC_VERDICT_INSPECT;
Layer7AccessControl l7_access_control;
::Environment env;
ConfigComponent config;
StrictMock<MockLogging> mock_logging;
StrictMock<MockTimeGet> mock_time;
StrictMock<MockMainLoop> mock_ml;
StrictMock<MockMessaging> messaging_mock;
AgentDetails agent_details;
IntelligenceComponentV2 intelligence_comp;
Context ctx;
};
string prevent_settings =
"{\n"
"\"agentSettings\": [\n"
"{\n"
"\"id\": \"aac36348-5826-17d4-de11-195dd4dfca4a\","
"\"key\": \"agent.config.useLocalIntelligence\","
"\"value\": \"true\""
"},"
"{"
"\"id\": \"f6c386fb-e221-59af-dbf5-b9bed680ec6b\","
"\"key\": \"layer7AccessControl.logOnDrop\","
"\"value\": \"true\""
"},"
"{"
"\"id\": \"5ac38ee8-8b3c-481b-b382-f1f0735c0468\","
"\"key\": \"layer7AccessControl.securityMode\","
"\"value\": \"prevent\""
"},"
"{"
"\"id\": \"54c38f89-8fe2-871e-b29a-31e088f1b1d3\","
"\"key\": \"layer7AccessControl.crowdsec.enabled\","
"\"value\": \"true\""
"}"
"],\n";
string detect_settings =
"{\n"
"\"agentSettings\": [\n"
"{\n"
"\"id\": \"aac36348-5826-17d4-de11-195dd4dfca4a\","
"\"key\": \"agent.config.useLocalIntelligence\","
"\"value\": \"true\""
"},"
"{"
"\"id\": \"f6c386fb-e221-59af-dbf5-b9bed680ec6b\","
"\"key\": \"layer7AccessControl.logOnDrop\","
"\"value\": \"true\""
"},"
"{"
"\"id\": \"5ac38ee8-8b3c-481b-b382-f1f0735c0468\","
"\"key\": \"layer7AccessControl.securityMode\","
"\"value\": \"detect\""
"},"
"{"
"\"id\": \"54c38f89-8fe2-871e-b29a-31e088f1b1d3\","
"\"key\": \"layer7AccessControl.crowdsec.enabled\","
"\"value\": \"true\""
"}"
"],\n";
string disabled_settings =
"{"
"\"agentSettings\": [\n"
"{\n"
"\"id\": \"aac36348-5826-17d4-de11-195dd4dfca4a\","
"\"key\": \"agent.config.useLocalIntelligence\","
"\"value\": \"true\""
"},"
"{"
"\"id\": \"f6c386fb-e221-59af-dbf5-b9bed680ec6b\","
"\"key\": \"layer7AccessControl.logOnDrop\","
"\"value\": \"true\""
"},"
"{"
"\"id\": \"5ac38ee8-8b3c-481b-b382-f1f0735c0468\","
"\"key\": \"layer7AccessControl.securityMode\","
"\"value\": \"detect\""
"},"
"{"
"\"id\": \"54c38f89-8fe2-871e-b29a-31e088f1b1d3\","
"\"key\": \"layer7AccessControl.crowdsec.enabled\","
"\"value\": \"false\""
"}"
"],\n";
string policy =
"\"rulebase\": {"
"\"usersIdentifiers\": ["
"{"
"\"context\": \"Any(All(Any(EqualHost(juice-shop.checkpoint.com)),EqualListeningPort(80)))\","
"\"identifierValues\": [],"
"\"sourceIdentifier\": \"\","
"\"sourceIdentifiers\": ["
"{"
"\"identifierValues\": [],"
"\"sourceIdentifier\": \"x-forwarded-for\""
"}"
"]"
"}"
"],\n"
"\"rulesConfig\": ["
"{"
"\"assetId\": \"00c37544-047b-91d4-e5e5-31d90070bcfd\","
"\"assetName\": \"juice\","
"\"context\": \"Any(All(Any(EqualHost(juice-shop.checkpoint.com)),EqualListeningPort(80)))\","
"\"isCleanup\": false,"
"\"parameters\": [],"
"\"practices\": ["
"{"
"\"practiceId\": \"36be58f5-2c99-1f16-f816-bf25118d9bc1\","
"\"practiceName\": \"WEB APPLICATION BEST PRACTICE\","
"\"practiceType\": \"WebApplication\""
"}"
"],"
"\"priority\": 1,"
"\"ruleId\": \"00c37544-047b-91d4-e5e5-31d90070bcfd\","
"\"ruleName\": \"juice\","
"\"triggers\": ["
"{"
"\"triggerId\": \"86be58f5-2b65-18ee-2bd7-b4429dab245d\","
"\"triggerName\": \"Log Trigger\","
"\"triggerType\": \"log\""
"}"
"],"
"\"zoneId\": \"\","
"\"zoneName\": \"\""
"}"
"]"
"}\n"
"}\n";
void
Layer7AccessControlTest::registerTransactionData()
{
ctx.registerValue<IPAddr>(HttpTransactionData::client_ip_ctx, IPAddr::createIPAddr("4.4.4.4").unpack());
ctx.registerValue<IPAddr>(HttpTransactionData::listening_ip_ctx, IPAddr::createIPAddr("5.6.7.8").unpack());
ctx.registerValue<string>(HttpTransactionData::http_proto_ctx, "http");
ctx.registerValue<string>(HttpTransactionData::method_ctx, "POST");
ctx.registerValue<string>(HttpTransactionData::host_name_ctx, "juice-shop.checkpoint.com");
ctx.registerValue<uint16_t>(HttpTransactionData::listening_port_ctx, 80);
ctx.registerValue<uint16_t>(HttpTransactionData::client_port_ctx, 12345);
ctx.registerValue<string>(HttpTransactionData::uri_ctx, "/");
}
static bool
operator==(const EventVerdict &first, const EventVerdict &second)
{
return first.getVerdict() == second.getVerdict();
}
string
Layer7AccessControlTest::loadIntelligenceResponse(const string &file_path)
{
stringstream ss;
ifstream f(cptestFnameInExeDir(file_path), ios::in);
dbgTrace(D_L7_ACCESS_CONTROL) << "Loading intelligence response from: " << file_path;
ss << f.rdbuf();
f.close();
return ss.str();
}
template <typename T>
string
reportToStr(const T &obj)
{
stringstream ss;
{
cereal::JSONOutputArchive ar(ss);
obj.serialize(ar);
}
return ss.str();
}
void
Layer7AccessControlTest::verifyReport(
const Report &report,
const string &source_identifier,
const string &security_action
)
{
string log = reportToStr(report);
dbgTrace(D_L7_ACCESS_CONTROL) << "Report: " << log;
if (!source_identifier.empty()) EXPECT_THAT(log, HasSubstr("\"httpSourceId\": \"" + source_identifier + "\""));
EXPECT_THAT(log, HasSubstr("\"securityAction\": \"" + security_action + "\""));
EXPECT_THAT(log, HasSubstr("\"eventName\": \"Access Control External Vendor Reputation\""));
EXPECT_THAT(log, HasSubstr("\"httpHostName\": \"juice-shop.checkpoint.com\""));
EXPECT_THAT(log, HasSubstr("\"httpUriPath\": \"/\""));
EXPECT_THAT(log, HasSubstr("\"httpMethod\": \"POST\""));
EXPECT_THAT(log, HasSubstr("\"ipProtocol\": \"http\""));
EXPECT_THAT(log, HasSubstr("\"destinationIP\": \"5.6.7.8\""));
EXPECT_THAT(log, HasSubstr("\"externalVendorName\": \"crowdsec\""));
EXPECT_THAT(log, HasSubstr("\"externalVendorRecommendationId\": 2253734"));
EXPECT_THAT(log, HasSubstr("\"externalVendorRecommendedAction\": \"ban\""));
EXPECT_THAT(log, HasSubstr("\"externalVendorRecommendationOrigin\": \"cscli\""));
EXPECT_THAT(log, HasSubstr("\"externalVendorRecommendedAffectedScope\": \"1.2.3.4\""));
EXPECT_THAT(log, HasSubstr("\"externalVendorRecommendationOriginDetails\": \"manual 'ban' from 'localhost'\""));
}
TEST_F(Layer7AccessControlTest, ReturnAcceptVerdict)
{
stringstream ss_conf(prevent_settings + policy);
Singleton::Consume<Config::I_Config>::from(config)->loadConfiguration(ss_conf);
string intelligence_response_ok = loadIntelligenceResponse("data/ok_intelligence_response.json");
EXPECT_CALL(
messaging_mock,
sendMessage(true, _, _, _, _, _, _, MessageTypeTag::INTELLIGENCE)
).WillOnce(Return(intelligence_response_ok));
registerTransactionData();
ctx.registerValue<string>(HttpTransactionData::source_identifier, "1.2.3.4");
const HttpHeader header1{ Buffer("Content-Type"), Buffer("application/json"), 0 };
const HttpHeader header2{ Buffer("date"), Buffer("Sun, 26 Mar 2023 18:45:22 GMT"), 1 };
const HttpHeader header3{ Buffer("x-forwarded-for"), Buffer("1.2.3.4"), 2, true};
EXPECT_THAT(
HttpRequestHeaderEvent(header1).performNamedQuery(),
ElementsAre(Pair("Layer-7 Access Control app", inspect_verdict))
);
EXPECT_THAT(
HttpRequestHeaderEvent(header2).performNamedQuery(),
ElementsAre(Pair("Layer-7 Access Control app", inspect_verdict))
);
EXPECT_THAT(
HttpRequestHeaderEvent(header3).performNamedQuery(),
ElementsAre(Pair("Layer-7 Access Control app", accept_verdict))
);
}
TEST_F(Layer7AccessControlTest, ReturnDropVerdictOnMaliciousReputation)
{
stringstream ss_conf(prevent_settings + policy);
Singleton::Consume<Config::I_Config>::from(config)->loadConfiguration(ss_conf);
string malicious_intelligence_response = loadIntelligenceResponse("data/malicious_intelligence_response.json");
EXPECT_CALL(
messaging_mock,
sendMessage(true, _, _, _, _, _, _, MessageTypeTag::INTELLIGENCE)
).WillOnce(Return(malicious_intelligence_response));
registerTransactionData();
ctx.registerValue<string>(HttpTransactionData::source_identifier, "1.2.3.4");
const HttpHeader header1{ Buffer("Content-Type"), Buffer("application/json"), 0 };
const HttpHeader header2{ Buffer("date"), Buffer("Sun, 26 Mar 2023 18:45:22 GMT"), 1 };
const HttpHeader header3{ Buffer("x-forwarded-for"), Buffer("1.2.3.4"), 2, true};
Report report;
EXPECT_CALL(mock_logging, sendLog(_)).WillOnce(SaveArg<0>(&report));
EXPECT_THAT(HttpRequestHeaderEvent(header1).query(), ElementsAre(inspect_verdict));
EXPECT_THAT(HttpRequestHeaderEvent(header2).query(), ElementsAre(inspect_verdict));
EXPECT_THAT(HttpRequestHeaderEvent(header3).query(), ElementsAre(drop_verdict));
verifyReport(report, "1.2.3.4", "Prevent");
}
TEST_F(Layer7AccessControlTest, ReturnDropVerdictCacheBased)
{
stringstream ss_conf(prevent_settings + policy);
Singleton::Consume<Config::I_Config>::from(config)->loadConfiguration(ss_conf);
string malicious_intelligence_response = loadIntelligenceResponse("data/malicious_intelligence_response.json");
EXPECT_CALL(
messaging_mock,
sendMessage(true, _, _, _, _, _, _, MessageTypeTag::INTELLIGENCE)
).WillOnce(Return(malicious_intelligence_response));
registerTransactionData();
ctx.registerValue<string>(HttpTransactionData::source_identifier, "1.2.3.4");
const HttpHeader header1{ Buffer("Content-Type"), Buffer("application/json"), 0 };
const HttpHeader header2{ Buffer("date"), Buffer("Sun, 26 Mar 2023 18:45:22 GMT"), 1 };
const HttpHeader header3{ Buffer("x-forwarded-for"), Buffer("1.2.3.4"), 2, true};
Report report;
EXPECT_CALL(mock_logging, sendLog(_)).Times(2).WillRepeatedly(SaveArg<0>(&report));
EXPECT_THAT(HttpRequestHeaderEvent(header1).query(), ElementsAre(inspect_verdict));
EXPECT_THAT(HttpRequestHeaderEvent(header2).query(), ElementsAre(inspect_verdict));
EXPECT_THAT(HttpRequestHeaderEvent(header3).query(), ElementsAre(drop_verdict));
verifyReport(report, "1.2.3.4", "Prevent");
EXPECT_THAT(HttpRequestHeaderEvent(header1).query(), ElementsAre(inspect_verdict));
EXPECT_THAT(HttpRequestHeaderEvent(header2).query(), ElementsAre(inspect_verdict));
EXPECT_THAT(HttpRequestHeaderEvent(header3).query(), ElementsAre(drop_verdict));
verifyReport(report, "1.2.3.4", "Prevent");
}
TEST_F(Layer7AccessControlTest, AcceptOnDetect)
{
stringstream ss_conf(detect_settings + policy);
Singleton::Consume<Config::I_Config>::from(config)->loadConfiguration(ss_conf);
string malicious_intelligence_response = loadIntelligenceResponse("data/malicious_intelligence_response.json");
EXPECT_CALL(
messaging_mock,
sendMessage(true, _, _, _, _, _, _, MessageTypeTag::INTELLIGENCE)
).WillOnce(Return(malicious_intelligence_response));
registerTransactionData();
ctx.registerValue<string>(HttpTransactionData::source_identifier, "1.2.3.4");
const HttpHeader header1{ Buffer("Content-Type"), Buffer("application/json"), 0 };
const HttpHeader header2{ Buffer("date"), Buffer("Sun, 26 Mar 2023 18:45:22 GMT"), 1 };
const HttpHeader header3{ Buffer("x-forwarded-for"), Buffer("1.2.3.4"), 2, true};
Report report;
EXPECT_CALL(mock_logging, sendLog(_)).WillOnce(SaveArg<0>(&report));
EXPECT_THAT(HttpRequestHeaderEvent(header1).query(), ElementsAre(inspect_verdict));
EXPECT_THAT(HttpRequestHeaderEvent(header2).query(), ElementsAre(inspect_verdict));
EXPECT_THAT(HttpRequestHeaderEvent(header3).query(), ElementsAre(accept_verdict));
verifyReport(report, "1.2.3.4", "Detect");
}
TEST_F(Layer7AccessControlTest, FallbackToSourceIPAndDrop)
{
stringstream ss_conf(prevent_settings + policy);
Singleton::Consume<Config::I_Config>::from(config)->loadConfiguration(ss_conf);
string malicious_intelligence_response = loadIntelligenceResponse("data/malicious_intelligence_response.json");
EXPECT_CALL(
messaging_mock,
sendMessage(true, _, _, _, _, _, _, MessageTypeTag::INTELLIGENCE)
).WillOnce(Return(malicious_intelligence_response));
registerTransactionData();
const HttpHeader header1{ Buffer("Content-Type"), Buffer("application/json"), 0 };
const HttpHeader header2{ Buffer("date"), Buffer("Sun, 26 Mar 2023 18:45:22 GMT"), 1, true };
Report report;
EXPECT_CALL(mock_logging, sendLog(_)).WillOnce(SaveArg<0>(&report));
EXPECT_THAT(HttpRequestHeaderEvent(header1).query(), ElementsAre(inspect_verdict));
EXPECT_THAT(HttpRequestHeaderEvent(header2).query(), ElementsAre(drop_verdict));
verifyReport(report, "", "Prevent");
}
TEST_F(Layer7AccessControlTest, AcceptOnDisabled)
{
stringstream ss_conf(disabled_settings + policy);
Singleton::Consume<Config::I_Config>::from(config)->loadConfiguration(ss_conf);
registerTransactionData();
ctx.registerValue<string>(HttpTransactionData::source_identifier, "1.2.3.4");
const HttpHeader header1{ Buffer("Content-Type"), Buffer("application/json"), 0 };
const HttpHeader header2{ Buffer("date"), Buffer("Sun, 26 Mar 2023 18:45:22 GMT"), 1 };
const HttpHeader header3{ Buffer("x-forwarded-for"), Buffer("1.2.3.4"), 2, true};
EXPECT_THAT(HttpRequestHeaderEvent(header1).query(), ElementsAre(accept_verdict));
}