@@ -26,17 +26,24 @@
|
||||
// NOT recommended to set to anything below 4 or above 5.
|
||||
"default_challenge_difficulty": 4,
|
||||
|
||||
// specific ip range configs.
|
||||
"ip_configs": [
|
||||
// specific rules. They are checked top to bottom, and the first one to match will determine the request's fate
|
||||
"rules": [
|
||||
{
|
||||
"action": "ALLOW",
|
||||
"ip_ranges": [
|
||||
"127.0.0.1/24",
|
||||
"::1/128"
|
||||
],
|
||||
// if this regex matches the resource requested, a different rule will be applied
|
||||
"exclude_regex": ".*/commit/.*",
|
||||
"action_on_exclude": "DENY"
|
||||
]
|
||||
}, {
|
||||
"action": "DENY",
|
||||
"user_agent": ".*(bot).*"
|
||||
}, {
|
||||
"action": "DENY",
|
||||
"resource": "(/secret/).*"
|
||||
}, {
|
||||
"action": "CHALLENGE",
|
||||
"difficulty": 5, // quite damn hard!
|
||||
"resource": "(/hard/).*"
|
||||
}
|
||||
],
|
||||
|
||||
|
||||
@@ -7,23 +7,22 @@
|
||||
|
||||
#include "../debug/log.hpp"
|
||||
|
||||
static CConfig::eConfigIPAction strToAction(const std::string& s) {
|
||||
// TODO: allow any case I'm lazy it's 1am
|
||||
static eConfigIPAction strToAction(const std::string& s) {
|
||||
if (s.empty())
|
||||
return CConfig::IP_ACTION_NONE;
|
||||
return IP_ACTION_NONE;
|
||||
|
||||
std::string LC = s;
|
||||
std::transform(LC.begin(), LC.end(), LC.begin(), ::tolower);
|
||||
|
||||
if (LC == "allow")
|
||||
return CConfig::IP_ACTION_ALLOW;
|
||||
return IP_ACTION_ALLOW;
|
||||
if (LC == "deny")
|
||||
return CConfig::IP_ACTION_DENY;
|
||||
return IP_ACTION_DENY;
|
||||
if (LC == "challenge")
|
||||
return CConfig::IP_ACTION_CHALLENGE;
|
||||
return IP_ACTION_CHALLENGE;
|
||||
|
||||
Debug::log(ERR, "Invalid action: {}, assuming NONE", s);
|
||||
return CConfig::IP_ACTION_NONE;
|
||||
return IP_ACTION_NONE;
|
||||
}
|
||||
|
||||
CConfig::CConfig() {
|
||||
@@ -36,24 +35,33 @@ CConfig::CConfig() {
|
||||
m_config = json.value();
|
||||
|
||||
// parse some datas
|
||||
for (const auto& ic : m_config.ip_configs) {
|
||||
SIPRangeConfigParsed parsed;
|
||||
parsed.action = strToAction(ic.action);
|
||||
parsed.difficulty = ic.difficulty;
|
||||
parsed.action_on_exclude = strToAction(ic.action_on_exclude);
|
||||
for (const auto& ic : m_config.rules) {
|
||||
CConfigRule rule;
|
||||
rule.action = strToAction(ic.action);
|
||||
|
||||
if (ic.difficulty != -1)
|
||||
rule.difficulty = ic.difficulty;
|
||||
|
||||
if (!ic.exclude_regex.empty()) {
|
||||
parsed.exclude_regex = std::make_unique<re2::RE2>(ic.exclude_regex);
|
||||
if (parsed.exclude_regex->error_code() != RE2::NoError) {
|
||||
Debug::log(CRIT, "Regex \"{}\" failed to parse", ic.exclude_regex);
|
||||
if (!ic.user_agent.empty()) {
|
||||
rule.user_agent = std::make_unique<re2::RE2>(ic.user_agent);
|
||||
if ((*rule.user_agent)->error_code() != RE2::NoError) {
|
||||
Debug::log(CRIT, "Regex \"{}\" failed to parse", ic.user_agent);
|
||||
Debug::die("Failed to parse regex");
|
||||
}
|
||||
}
|
||||
|
||||
if (!ic.resource.empty()) {
|
||||
rule.resource = std::make_unique<re2::RE2>(ic.resource);
|
||||
if ((*rule.resource)->error_code() != RE2::NoError) {
|
||||
Debug::log(CRIT, "Regex \"{}\" failed to parse", ic.resource);
|
||||
Debug::die("Failed to parse regex");
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& ir : ic.ip_ranges) {
|
||||
parsed.ip_ranges.emplace_back(CIPRange(ir));
|
||||
rule.ip_ranges.emplace_back(CIPRange(ir));
|
||||
}
|
||||
|
||||
m_parsedConfigDatas.ip_configs.emplace_back(std::move(parsed));
|
||||
m_parsedConfigDatas.configs.emplace_back(std::move(rule));
|
||||
}
|
||||
}
|
||||
@@ -5,51 +5,36 @@
|
||||
|
||||
#include <re2/re2.h>
|
||||
|
||||
#include "IPRange.hpp"
|
||||
#include "ConfigRule.hpp"
|
||||
|
||||
class CConfig {
|
||||
public:
|
||||
CConfig();
|
||||
|
||||
enum eConfigIPAction : uint8_t {
|
||||
IP_ACTION_NONE = 0,
|
||||
IP_ACTION_DENY,
|
||||
IP_ACTION_ALLOW,
|
||||
IP_ACTION_CHALLENGE
|
||||
};
|
||||
|
||||
struct SIPRangeConfig {
|
||||
std::string action = "";
|
||||
std::vector<std::string> ip_ranges;
|
||||
int difficulty = -1;
|
||||
std::string exclude_regex = "";
|
||||
std::string action_on_exclude = "";
|
||||
};
|
||||
|
||||
struct SIPRangeConfigParsed {
|
||||
eConfigIPAction action = IP_ACTION_DENY;
|
||||
std::vector<CIPRange> ip_ranges;
|
||||
int difficulty = -1;
|
||||
std::unique_ptr<re2::RE2> exclude_regex;
|
||||
eConfigIPAction action_on_exclude = IP_ACTION_NONE;
|
||||
struct SConfigRule {
|
||||
std::string action = "";
|
||||
int difficulty = -1;
|
||||
std::vector<std::string> ip_ranges = {};
|
||||
std::string user_agent = "";
|
||||
std::string resource = "";
|
||||
};
|
||||
|
||||
struct SConfig {
|
||||
int port = 3001;
|
||||
std::string forward_address = "127.0.0.1:3000";
|
||||
std::string data_dir = "";
|
||||
std::string html_dir = "";
|
||||
unsigned long int max_request_size = 10000000; // 10MB
|
||||
bool git_host = false;
|
||||
unsigned long int proxy_timeout_sec = 120; // 2 minutes
|
||||
bool trace_logging = false;
|
||||
std::vector<SIPRangeConfig> ip_configs = {};
|
||||
int default_challenge_difficulty = 4;
|
||||
bool async_proxy = true;
|
||||
int port = 3001;
|
||||
std::string forward_address = "127.0.0.1:3000";
|
||||
std::string data_dir = "";
|
||||
std::string html_dir = "";
|
||||
unsigned long int max_request_size = 10000000; // 10MB
|
||||
bool git_host = false;
|
||||
unsigned long int proxy_timeout_sec = 120; // 2 minutes
|
||||
bool trace_logging = false;
|
||||
std::vector<SConfigRule> rules = {};
|
||||
int default_challenge_difficulty = 4;
|
||||
bool async_proxy = true;
|
||||
} m_config;
|
||||
|
||||
struct {
|
||||
std::vector<SIPRangeConfigParsed> ip_configs;
|
||||
std::vector<CConfigRule> configs;
|
||||
} m_parsedConfigDatas;
|
||||
};
|
||||
|
||||
|
||||
28
src/config/ConfigRule.cpp
Normal file
28
src/config/ConfigRule.cpp
Normal file
@@ -0,0 +1,28 @@
|
||||
#include "ConfigRule.hpp"
|
||||
|
||||
bool CConfigRule::passes(const CIP& ip, const std::string& ua, const std::string& res) const {
|
||||
if (!ip_ranges.empty()) {
|
||||
bool passed = false;
|
||||
for (const auto& r : ip_ranges) {
|
||||
if (r.ipMatches(ip)) {
|
||||
passed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!passed)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (user_agent.has_value()) {
|
||||
if (!RE2::FullMatch(ua, **user_agent))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (resource.has_value()) {
|
||||
if (!RE2::FullMatch(res, **resource))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
26
src/config/ConfigRule.hpp
Normal file
26
src/config/ConfigRule.hpp
Normal file
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include <re2/re2.h>
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
#include "ConfigTypes.hpp"
|
||||
#include "IPRange.hpp"
|
||||
|
||||
class CConfigRule {
|
||||
public:
|
||||
eConfigIPAction action = IP_ACTION_DENY;
|
||||
|
||||
std::optional<int> difficulty;
|
||||
|
||||
bool passes(const CIP& ip, const std::string& ua, const std::string& res) const;
|
||||
|
||||
private:
|
||||
std::vector<CIPRange> ip_ranges;
|
||||
std::optional<std::unique_ptr<re2::RE2>> user_agent;
|
||||
std::optional<std::unique_ptr<re2::RE2>> resource;
|
||||
|
||||
friend class CConfig;
|
||||
};
|
||||
10
src/config/ConfigTypes.hpp
Normal file
10
src/config/ConfigTypes.hpp
Normal file
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
enum eConfigIPAction : uint8_t {
|
||||
IP_ACTION_NONE = 0,
|
||||
IP_ACTION_DENY,
|
||||
IP_ACTION_ALLOW,
|
||||
IP_ACTION_CHALLENGE
|
||||
};
|
||||
@@ -241,53 +241,29 @@ void CServerHandler::onRequest(const Pistache::Http::Request& req, Pistache::Htt
|
||||
|
||||
int challengeDifficulty = g_pConfig->m_config.default_challenge_difficulty;
|
||||
|
||||
if (!g_pConfig->m_parsedConfigDatas.ip_configs.empty()) {
|
||||
if (!g_pConfig->m_parsedConfigDatas.configs.empty()) {
|
||||
const auto IP = CIP(REQUEST_IP);
|
||||
for (const auto& ic : g_pConfig->m_parsedConfigDatas.ip_configs) {
|
||||
bool matched = false;
|
||||
|
||||
for (const auto& ipr : ic.ip_ranges) {
|
||||
if (!ipr.ipMatches(IP))
|
||||
continue;
|
||||
|
||||
matched = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (matched) {
|
||||
if (ic.difficulty != -1)
|
||||
challengeDifficulty = ic.difficulty;
|
||||
|
||||
// if we have an exclude regex and it matches the resource, skip this rule
|
||||
if (ic.exclude_regex && RE2::FullMatch(req.resource(), *ic.exclude_regex)) {
|
||||
if (ic.action_on_exclude == CConfig::IP_ACTION_ALLOW) {
|
||||
Debug::log(LOG, " | Action: PASS (ip rule matched for {}, excluded resource, exclude action is PASS)", REQUEST_IP);
|
||||
for (const auto& ic : g_pConfig->m_parsedConfigDatas.configs) {
|
||||
if (ic.passes(IP, userAgentHeader ? userAgentHeader->agent() : "", req.resource())) {
|
||||
switch (ic.action) {
|
||||
case IP_ACTION_DENY:
|
||||
Debug::log(LOG, " | Action: DENY (rule)");
|
||||
response.send(Pistache::Http::Code::Forbidden, "Blocked by checkpoint");
|
||||
return;
|
||||
case IP_ACTION_ALLOW:
|
||||
Debug::log(LOG, " | Action: PASS (rule)");
|
||||
proxyPass(req, response);
|
||||
return;
|
||||
} else if (ic.action_on_exclude == CConfig::IP_ACTION_DENY) {
|
||||
Debug::log(LOG, " | Action: DENY (ip rule matched for {}, excluded resource, exclude action is DENY)", REQUEST_IP);
|
||||
response.send(Pistache::Http::Code::Forbidden, "Forbidden");
|
||||
return;
|
||||
} else if (ic.action_on_exclude == CConfig::IP_ACTION_CHALLENGE) {
|
||||
Debug::log(LOG, " | ip rule matched for {}, excluded resource, exclude action is CHALLENGE", REQUEST_IP);
|
||||
case IP_ACTION_CHALLENGE:
|
||||
Debug::log(LOG, " | Action: CHALLENGE (rule)");
|
||||
challengeDifficulty = ic.difficulty.value_or(g_pConfig->m_config.default_challenge_difficulty);
|
||||
break;
|
||||
}
|
||||
Debug::log(LOG, " | ip rule matched for {}, excluded resource, exclude action is NONE", REQUEST_IP);
|
||||
continue;
|
||||
default:
|
||||
Debug::log(LOG, " | Invalid rule found (no action) skipping");
|
||||
}
|
||||
|
||||
if (ic.action == CConfig::IP_ACTION_ALLOW) {
|
||||
Debug::log(LOG, " | Action: PASS (ip rule matched for {})", REQUEST_IP);
|
||||
proxyPass(req, response);
|
||||
return;
|
||||
} else if (ic.action == CConfig::IP_ACTION_DENY) {
|
||||
Debug::log(LOG, " | Action: DENY (ip rule matched for {})", REQUEST_IP);
|
||||
response.send(Pistache::Http::Code::Forbidden, "Forbidden");
|
||||
return;
|
||||
}
|
||||
|
||||
// if it's challenge then it's default so just set the difficulty if applicable and proceed
|
||||
break;
|
||||
if (ic.action == IP_ACTION_CHALLENGE)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user