core: add IP-range based rules for permissions and fifficulty
This commit is contained in:
11
README.md
11
README.md
@@ -12,13 +12,22 @@ AI scrapers are everywhere. This will stop them. `robots.txt` won't.
|
|||||||
- Protect your endpoint from AI bots with a cryptographic challenge
|
- Protect your endpoint from AI bots with a cryptographic challenge
|
||||||
- Easy configuration in jsonc
|
- Easy configuration in jsonc
|
||||||
- Support for cloudflare
|
- Support for cloudflare
|
||||||
|
- Support for IP-Range based rules (both ipv4 and ipv6)
|
||||||
- Minimal. The waiting page is tiny and light on network usage.
|
- Minimal. The waiting page is tiny and light on network usage.
|
||||||
|
|
||||||
### Planned features
|
### Planned features
|
||||||
- Dynamic challenge amount (aka difficulty)
|
- Dynamic challenge amount (aka difficulty) based on traffic
|
||||||
- Detection of token overuse
|
- Detection of token overuse
|
||||||
- Better wait screen
|
- Better wait screen
|
||||||
- Better git integration (it's quite rudimentary right now)
|
- Better git integration (it's quite rudimentary right now)
|
||||||
|
|
||||||
## Caveats
|
## Caveats
|
||||||
If you are using this, it's almost certain search engines will stop indexing your site. Keep this in mind.
|
If you are using this, it's almost certain search engines will stop indexing your site. Keep this in mind.
|
||||||
|
|
||||||
|
## Setup guide
|
||||||
|
|
||||||
|
1. Clone and build this repo. You will need `openssl`, `g++>=12`, and deps for `pistache`, `fmt` and `tinylates`.
|
||||||
|
2. Create a `config.jsonc`. An example one is in `example/`.
|
||||||
|
3. Adjust the config to your needs. Options are documented with comments in the example config.
|
||||||
|
4. Set up your IP rules if you want. These allow you to set up IPs that are automatically blocked, or allowed to access without a challenge. This is useful for e.g. search engine scrapers. Some IP ranges can be found in `example/index_bots.jsonc`.
|
||||||
|
5. Run checkpoint with your config: `./build/checkpoint -c config.jsonc`. How you run it long-term as a service is up to you.
|
||||||
|
|||||||
@@ -2,13 +2,14 @@
|
|||||||
// where the html files are located
|
// where the html files are located
|
||||||
"html_dir": "./html",
|
"html_dir": "./html",
|
||||||
|
|
||||||
// where the proxy should store its db (directory)
|
// where the proxy should store its private signing key (directory)
|
||||||
"data_dir": "./data",
|
"data_dir": "./data",
|
||||||
|
|
||||||
// what port the proxy should listen on
|
// what port the proxy should listen on
|
||||||
"port": 3001,
|
"port": 3001,
|
||||||
|
|
||||||
// what address should the proxy pass after successful verification
|
// what address should the proxy pass after successful verification. DO NOT add a / at the end or http(s)://.
|
||||||
|
// this address should be local (127.0.0.1). Other configurations are not supported.
|
||||||
"forward_address": "127.0.0.1:3000",
|
"forward_address": "127.0.0.1:3000",
|
||||||
|
|
||||||
// max request size of 10MB
|
// max request size of 10MB
|
||||||
@@ -18,5 +19,21 @@
|
|||||||
"proxy_timeout_sec": 120,
|
"proxy_timeout_sec": 120,
|
||||||
|
|
||||||
// enables (a lot) more logging
|
// enables (a lot) more logging
|
||||||
"trace_logging": false
|
"trace_logging": false,
|
||||||
|
|
||||||
|
// the default difficulty for the challenge. 4 takes less than a second on a powerful desktop, and up to 15s on a low-powered phone.
|
||||||
|
// 5 is 16x slower.
|
||||||
|
// NOT recommended to set to anything below 4 or above 5.
|
||||||
|
"default_challenge_difficulty": 4,
|
||||||
|
|
||||||
|
// specific ip range configs.
|
||||||
|
"ip_configs": [
|
||||||
|
{
|
||||||
|
"action": "ALLOW",
|
||||||
|
"ip_ranges": [
|
||||||
|
"127.0.0.1/24",
|
||||||
|
"::1/128"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
311
example/index_bots.jsonc
Normal file
311
example/index_bots.jsonc
Normal file
@@ -0,0 +1,311 @@
|
|||||||
|
// This jsonc file contains some known web crawlers used by search engines.
|
||||||
|
// You may pick and choose whichever you like.
|
||||||
|
[
|
||||||
|
// Google, gathered 15 IV 2025
|
||||||
|
{
|
||||||
|
"action": "Allow",
|
||||||
|
"ip_ranges": [
|
||||||
|
"2001:4860:4801:10::/64",
|
||||||
|
"2001:4860:4801:11::/64",
|
||||||
|
"2001:4860:4801:12::/64",
|
||||||
|
"2001:4860:4801:13::/64",
|
||||||
|
"2001:4860:4801:14::/64",
|
||||||
|
"2001:4860:4801:15::/64",
|
||||||
|
"2001:4860:4801:16::/64",
|
||||||
|
"2001:4860:4801:17::/64",
|
||||||
|
"2001:4860:4801:18::/64",
|
||||||
|
"2001:4860:4801:19::/64",
|
||||||
|
"2001:4860:4801:1a::/64",
|
||||||
|
"2001:4860:4801:1b::/64",
|
||||||
|
"2001:4860:4801:1c::/64",
|
||||||
|
"2001:4860:4801:1d::/64",
|
||||||
|
"2001:4860:4801:1e::/64",
|
||||||
|
"2001:4860:4801:1f::/64",
|
||||||
|
"2001:4860:4801:20::/64",
|
||||||
|
"2001:4860:4801:21::/64",
|
||||||
|
"2001:4860:4801:22::/64",
|
||||||
|
"2001:4860:4801:23::/64",
|
||||||
|
"2001:4860:4801:24::/64",
|
||||||
|
"2001:4860:4801:25::/64",
|
||||||
|
"2001:4860:4801:26::/64",
|
||||||
|
"2001:4860:4801:27::/64",
|
||||||
|
"2001:4860:4801:28::/64",
|
||||||
|
"2001:4860:4801:29::/64",
|
||||||
|
"2001:4860:4801:2::/64",
|
||||||
|
"2001:4860:4801:2a::/64",
|
||||||
|
"2001:4860:4801:2b::/64",
|
||||||
|
"2001:4860:4801:2c::/64",
|
||||||
|
"2001:4860:4801:2d::/64",
|
||||||
|
"2001:4860:4801:2e::/64",
|
||||||
|
"2001:4860:4801:2f::/64",
|
||||||
|
"2001:4860:4801:31::/64",
|
||||||
|
"2001:4860:4801:32::/64",
|
||||||
|
"2001:4860:4801:33::/64",
|
||||||
|
"2001:4860:4801:34::/64",
|
||||||
|
"2001:4860:4801:35::/64",
|
||||||
|
"2001:4860:4801:36::/64",
|
||||||
|
"2001:4860:4801:37::/64",
|
||||||
|
"2001:4860:4801:38::/64",
|
||||||
|
"2001:4860:4801:39::/64",
|
||||||
|
"2001:4860:4801:3a::/64",
|
||||||
|
"2001:4860:4801:3b::/64",
|
||||||
|
"2001:4860:4801:3c::/64",
|
||||||
|
"2001:4860:4801:3d::/64",
|
||||||
|
"2001:4860:4801:3e::/64",
|
||||||
|
"2001:4860:4801:40::/64",
|
||||||
|
"2001:4860:4801:41::/64",
|
||||||
|
"2001:4860:4801:42::/64",
|
||||||
|
"2001:4860:4801:43::/64",
|
||||||
|
"2001:4860:4801:44::/64",
|
||||||
|
"2001:4860:4801:45::/64",
|
||||||
|
"2001:4860:4801:46::/64",
|
||||||
|
"2001:4860:4801:47::/64",
|
||||||
|
"2001:4860:4801:48::/64",
|
||||||
|
"2001:4860:4801:49::/64",
|
||||||
|
"2001:4860:4801:4a::/64",
|
||||||
|
"2001:4860:4801:4b::/64",
|
||||||
|
"2001:4860:4801:4c::/64",
|
||||||
|
"2001:4860:4801:50::/64",
|
||||||
|
"2001:4860:4801:51::/64",
|
||||||
|
"2001:4860:4801:52::/64",
|
||||||
|
"2001:4860:4801:53::/64",
|
||||||
|
"2001:4860:4801:54::/64",
|
||||||
|
"2001:4860:4801:55::/64",
|
||||||
|
"2001:4860:4801:56::/64",
|
||||||
|
"2001:4860:4801:60::/64",
|
||||||
|
"2001:4860:4801:61::/64",
|
||||||
|
"2001:4860:4801:62::/64",
|
||||||
|
"2001:4860:4801:63::/64",
|
||||||
|
"2001:4860:4801:64::/64",
|
||||||
|
"2001:4860:4801:65::/64",
|
||||||
|
"2001:4860:4801:66::/64",
|
||||||
|
"2001:4860:4801:67::/64",
|
||||||
|
"2001:4860:4801:68::/64",
|
||||||
|
"2001:4860:4801:69::/64",
|
||||||
|
"2001:4860:4801:6a::/64",
|
||||||
|
"2001:4860:4801:6b::/64",
|
||||||
|
"2001:4860:4801:6c::/64",
|
||||||
|
"2001:4860:4801:6d::/64",
|
||||||
|
"2001:4860:4801:6e::/64",
|
||||||
|
"2001:4860:4801:6f::/64",
|
||||||
|
"2001:4860:4801:70::/64",
|
||||||
|
"2001:4860:4801:71::/64",
|
||||||
|
"2001:4860:4801:72::/64",
|
||||||
|
"2001:4860:4801:73::/64",
|
||||||
|
"2001:4860:4801:74::/64",
|
||||||
|
"2001:4860:4801:75::/64",
|
||||||
|
"2001:4860:4801:76::/64",
|
||||||
|
"2001:4860:4801:77::/64",
|
||||||
|
"2001:4860:4801:78::/64",
|
||||||
|
"2001:4860:4801:79::/64",
|
||||||
|
"2001:4860:4801:80::/64",
|
||||||
|
"2001:4860:4801:81::/64",
|
||||||
|
"2001:4860:4801:82::/64",
|
||||||
|
"2001:4860:4801:83::/64",
|
||||||
|
"2001:4860:4801:84::/64",
|
||||||
|
"2001:4860:4801:85::/64",
|
||||||
|
"2001:4860:4801:86::/64",
|
||||||
|
"2001:4860:4801:87::/64",
|
||||||
|
"2001:4860:4801:88::/64",
|
||||||
|
"2001:4860:4801:90::/64",
|
||||||
|
"2001:4860:4801:91::/64",
|
||||||
|
"2001:4860:4801:92::/64",
|
||||||
|
"2001:4860:4801:93::/64",
|
||||||
|
"2001:4860:4801:94::/64",
|
||||||
|
"2001:4860:4801:95::/64",
|
||||||
|
"2001:4860:4801:96::/64",
|
||||||
|
"2001:4860:4801:a0::/64",
|
||||||
|
"2001:4860:4801:a1::/64",
|
||||||
|
"2001:4860:4801:a2::/64",
|
||||||
|
"2001:4860:4801:a3::/64",
|
||||||
|
"2001:4860:4801:a4::/64",
|
||||||
|
"2001:4860:4801:a5::/64",
|
||||||
|
"2001:4860:4801:c::/64",
|
||||||
|
"2001:4860:4801:f::/64",
|
||||||
|
"192.178.5.0/27",
|
||||||
|
"192.178.6.0/27",
|
||||||
|
"192.178.6.128/27",
|
||||||
|
"192.178.6.160/27",
|
||||||
|
"192.178.6.192/27",
|
||||||
|
"192.178.6.32/27",
|
||||||
|
"192.178.6.64/27",
|
||||||
|
"192.178.6.96/27",
|
||||||
|
"34.100.182.96/28",
|
||||||
|
"34.101.50.144/28",
|
||||||
|
"34.118.254.0/28",
|
||||||
|
"34.118.66.0/28",
|
||||||
|
"34.126.178.96/28",
|
||||||
|
"34.146.150.144/28",
|
||||||
|
"34.147.110.144/28",
|
||||||
|
"34.151.74.144/28",
|
||||||
|
"34.152.50.64/28",
|
||||||
|
"34.154.114.144/28",
|
||||||
|
"34.155.98.32/28",
|
||||||
|
"34.165.18.176/28",
|
||||||
|
"34.175.160.64/28",
|
||||||
|
"34.176.130.16/28",
|
||||||
|
"34.22.85.0/27",
|
||||||
|
"34.64.82.64/28",
|
||||||
|
"34.65.242.112/28",
|
||||||
|
"34.80.50.80/28",
|
||||||
|
"34.88.194.0/28",
|
||||||
|
"34.89.10.80/28",
|
||||||
|
"34.89.198.80/28",
|
||||||
|
"34.96.162.48/28",
|
||||||
|
"35.247.243.240/28",
|
||||||
|
"66.249.64.0/27",
|
||||||
|
"66.249.64.128/27",
|
||||||
|
"66.249.64.160/27",
|
||||||
|
"66.249.64.224/27",
|
||||||
|
"66.249.64.32/27",
|
||||||
|
"66.249.64.64/27",
|
||||||
|
"66.249.64.96/27",
|
||||||
|
"66.249.65.0/27",
|
||||||
|
"66.249.65.128/27",
|
||||||
|
"66.249.65.160/27",
|
||||||
|
"66.249.65.192/27",
|
||||||
|
"66.249.65.224/27",
|
||||||
|
"66.249.65.32/27",
|
||||||
|
"66.249.65.64/27",
|
||||||
|
"66.249.65.96/27",
|
||||||
|
"66.249.66.0/27",
|
||||||
|
"66.249.66.128/27",
|
||||||
|
"66.249.66.160/27",
|
||||||
|
"66.249.66.192/27",
|
||||||
|
"66.249.66.224/27",
|
||||||
|
"66.249.66.32/27",
|
||||||
|
"66.249.66.64/27",
|
||||||
|
"66.249.66.96/27",
|
||||||
|
"66.249.68.0/27",
|
||||||
|
"66.249.68.128/27",
|
||||||
|
"66.249.68.32/27",
|
||||||
|
"66.249.68.64/27",
|
||||||
|
"66.249.68.96/27",
|
||||||
|
"66.249.69.0/27",
|
||||||
|
"66.249.69.128/27",
|
||||||
|
"66.249.69.160/27",
|
||||||
|
"66.249.69.192/27",
|
||||||
|
"66.249.69.224/27",
|
||||||
|
"66.249.69.32/27",
|
||||||
|
"66.249.69.64/27",
|
||||||
|
"66.249.69.96/27",
|
||||||
|
"66.249.70.0/27",
|
||||||
|
"66.249.70.128/27",
|
||||||
|
"66.249.70.160/27",
|
||||||
|
"66.249.70.192/27",
|
||||||
|
"66.249.70.224/27",
|
||||||
|
"66.249.70.32/27",
|
||||||
|
"66.249.70.64/27",
|
||||||
|
"66.249.70.96/27",
|
||||||
|
"66.249.71.0/27",
|
||||||
|
"66.249.71.128/27",
|
||||||
|
"66.249.71.160/27",
|
||||||
|
"66.249.71.192/27",
|
||||||
|
"66.249.71.224/27",
|
||||||
|
"66.249.71.32/27",
|
||||||
|
"66.249.71.64/27",
|
||||||
|
"66.249.71.96/27",
|
||||||
|
"66.249.72.0/27",
|
||||||
|
"66.249.72.128/27",
|
||||||
|
"66.249.72.160/27",
|
||||||
|
"66.249.72.192/27",
|
||||||
|
"66.249.72.224/27",
|
||||||
|
"66.249.72.32/27",
|
||||||
|
"66.249.72.64/27",
|
||||||
|
"66.249.72.96/27",
|
||||||
|
"66.249.73.0/27",
|
||||||
|
"66.249.73.128/27",
|
||||||
|
"66.249.73.160/27",
|
||||||
|
"66.249.73.192/27",
|
||||||
|
"66.249.73.224/27",
|
||||||
|
"66.249.73.32/27",
|
||||||
|
"66.249.73.64/27",
|
||||||
|
"66.249.73.96/27",
|
||||||
|
"66.249.74.0/27",
|
||||||
|
"66.249.74.128/27",
|
||||||
|
"66.249.74.160/27",
|
||||||
|
"66.249.74.192/27",
|
||||||
|
"66.249.74.32/27",
|
||||||
|
"66.249.74.64/27",
|
||||||
|
"66.249.74.96/27",
|
||||||
|
"66.249.75.0/27",
|
||||||
|
"66.249.75.128/27",
|
||||||
|
"66.249.75.160/27",
|
||||||
|
"66.249.75.192/27",
|
||||||
|
"66.249.75.224/27",
|
||||||
|
"66.249.75.32/27",
|
||||||
|
"66.249.75.64/27",
|
||||||
|
"66.249.75.96/27",
|
||||||
|
"66.249.76.0/27",
|
||||||
|
"66.249.76.128/27",
|
||||||
|
"66.249.76.160/27",
|
||||||
|
"66.249.76.192/27",
|
||||||
|
"66.249.76.224/27",
|
||||||
|
"66.249.76.32/27",
|
||||||
|
"66.249.76.64/27",
|
||||||
|
"66.249.76.96/27",
|
||||||
|
"66.249.77.0/27",
|
||||||
|
"66.249.77.128/27",
|
||||||
|
"66.249.77.160/27",
|
||||||
|
"66.249.77.192/27",
|
||||||
|
"66.249.77.224/27",
|
||||||
|
"66.249.77.32/27",
|
||||||
|
"66.249.77.64/27",
|
||||||
|
"66.249.77.96/27",
|
||||||
|
"66.249.78.0/27",
|
||||||
|
"66.249.78.32/27",
|
||||||
|
"66.249.79.0/27",
|
||||||
|
"66.249.79.128/27",
|
||||||
|
"66.249.79.160/27",
|
||||||
|
"66.249.79.192/27",
|
||||||
|
"66.249.79.224/27",
|
||||||
|
"66.249.79.32/27",
|
||||||
|
"66.249.79.64/27",
|
||||||
|
"66.249.79.96/27"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
// Bing, retrieved 15 IV 2025
|
||||||
|
{
|
||||||
|
"action": "Allow",
|
||||||
|
"ip_ranges": [
|
||||||
|
"157.55.39.0/24",
|
||||||
|
"207.46.13.0/24",
|
||||||
|
"40.77.167.0/24",
|
||||||
|
"13.66.139.0/24",
|
||||||
|
"13.66.144.0/24",
|
||||||
|
"52.167.144.0/24",
|
||||||
|
"13.67.10.16/28",
|
||||||
|
"13.69.66.240/28",
|
||||||
|
"13.71.172.224/28",
|
||||||
|
"139.217.52.0/28",
|
||||||
|
"191.233.204.224/28",
|
||||||
|
"20.36.108.32/28",
|
||||||
|
"20.43.120.16/28",
|
||||||
|
"40.79.131.208/28",
|
||||||
|
"40.79.186.176/28",
|
||||||
|
"52.231.148.0/28",
|
||||||
|
"20.79.107.240/28",
|
||||||
|
"51.105.67.0/28",
|
||||||
|
"20.125.163.80/28",
|
||||||
|
"40.77.188.0/22",
|
||||||
|
"65.55.210.0/24",
|
||||||
|
"199.30.24.0/23",
|
||||||
|
"40.77.202.0/24",
|
||||||
|
"40.77.139.0/25",
|
||||||
|
"20.74.197.0/28",
|
||||||
|
"20.15.133.160/27",
|
||||||
|
"40.77.177.0/24",
|
||||||
|
"40.77.178.0/23"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Kagi, retrieved 15 IV 2025
|
||||||
|
"action": "Allow",
|
||||||
|
"ip_ranges": [
|
||||||
|
"216.18.205.234/32",
|
||||||
|
"35.212.27.76/32",
|
||||||
|
"104.254.65.50/32",
|
||||||
|
"209.151.156.194/32"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -5,6 +5,18 @@
|
|||||||
#include "../helpers/FsUtils.hpp"
|
#include "../helpers/FsUtils.hpp"
|
||||||
#include "../GlobalState.hpp"
|
#include "../GlobalState.hpp"
|
||||||
|
|
||||||
|
static CConfig::eConfigIPAction strToAction(const std::string& s) {
|
||||||
|
// TODO: allow any case I'm lazy it's 1am
|
||||||
|
if (s == "ALLOW" || s == "allow" || s == "Allow")
|
||||||
|
return CConfig::IP_ACTION_ALLOW;
|
||||||
|
if (s == "Deny" || s == "deny" || s == "Deny")
|
||||||
|
return CConfig::IP_ACTION_DENY;
|
||||||
|
if (s == "CHALLENGE" || s == "challenge" || s == "Challenge")
|
||||||
|
return CConfig::IP_ACTION_CHALLENGE;
|
||||||
|
|
||||||
|
throw std::runtime_error("Invalid ip config action");
|
||||||
|
}
|
||||||
|
|
||||||
CConfig::CConfig() {
|
CConfig::CConfig() {
|
||||||
auto json = glz::read_jsonc<SConfig>(
|
auto json = glz::read_jsonc<SConfig>(
|
||||||
NFsUtils::readFileAsString(NFsUtils::isAbsolute(g_pGlobalState->configPath) ? g_pGlobalState->configPath : g_pGlobalState->cwd + "/" + g_pGlobalState->configPath).value());
|
NFsUtils::readFileAsString(NFsUtils::isAbsolute(g_pGlobalState->configPath) ? g_pGlobalState->configPath : g_pGlobalState->cwd + "/" + g_pGlobalState->configPath).value());
|
||||||
@@ -13,4 +25,17 @@ CConfig::CConfig() {
|
|||||||
throw std::runtime_error("No config / bad config format");
|
throw std::runtime_error("No config / bad config format");
|
||||||
|
|
||||||
m_config = json.value();
|
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;
|
||||||
|
|
||||||
|
for (const auto& ir : ic.ip_ranges) {
|
||||||
|
parsed.ip_ranges.emplace_back(CIPRange(ir));
|
||||||
|
}
|
||||||
|
|
||||||
|
m_parsedConfigDatas.ip_configs.emplace_back(std::move(parsed));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -3,20 +3,46 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
|
#include "IPRange.hpp"
|
||||||
|
|
||||||
class CConfig {
|
class CConfig {
|
||||||
public:
|
public:
|
||||||
CConfig();
|
CConfig();
|
||||||
|
|
||||||
|
enum eConfigIPAction : uint8_t {
|
||||||
|
IP_ACTION_DENY = 0,
|
||||||
|
IP_ACTION_ALLOW,
|
||||||
|
IP_ACTION_CHALLENGE
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SIPRangeConfig {
|
||||||
|
std::string action = "";
|
||||||
|
std::vector<std::string> ip_ranges;
|
||||||
|
int difficulty = -1;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SIPRangeConfigParsed {
|
||||||
|
eConfigIPAction action = IP_ACTION_DENY;
|
||||||
|
std::vector<CIPRange> ip_ranges;
|
||||||
|
int difficulty = -1;
|
||||||
|
};
|
||||||
|
|
||||||
struct SConfig {
|
struct SConfig {
|
||||||
int port = 3001;
|
int port = 3001;
|
||||||
std::string forward_address = "127.0.0.1:3000";
|
std::string forward_address = "127.0.0.1:3000";
|
||||||
std::string data_dir = "";
|
std::string data_dir = "";
|
||||||
std::string html_dir = "";
|
std::string html_dir = "";
|
||||||
unsigned long int max_request_size = 10000000; // 10MB
|
unsigned long int max_request_size = 10000000; // 10MB
|
||||||
bool git_host = false;
|
bool git_host = false;
|
||||||
unsigned long int proxy_timeout_sec = 120; // 2 minutes
|
unsigned long int proxy_timeout_sec = 120; // 2 minutes
|
||||||
bool trace_logging = false;
|
bool trace_logging = false;
|
||||||
|
std::vector<SIPRangeConfig> ip_configs;
|
||||||
|
int default_challenge_difficulty = 4;
|
||||||
} m_config;
|
} m_config;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
std::vector<SIPRangeConfigParsed> ip_configs;
|
||||||
|
} m_parsedConfigDatas;
|
||||||
};
|
};
|
||||||
|
|
||||||
inline std::unique_ptr<CConfig> g_pConfig;
|
inline std::unique_ptr<CConfig> g_pConfig;
|
||||||
129
src/config/IPRange.cpp
Normal file
129
src/config/IPRange.cpp
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
#include "IPRange.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
CIP::CIP(const std::string& ip) {
|
||||||
|
if (std::count(ip.begin(), ip.end(), '.') == 3)
|
||||||
|
parseV4(ip);
|
||||||
|
else if (std::count(ip.begin(), ip.end(), ':') >= 2)
|
||||||
|
parseV6(ip);
|
||||||
|
else
|
||||||
|
throw std::runtime_error("IP not valid");
|
||||||
|
}
|
||||||
|
|
||||||
|
void CIP::parseV4(const std::string& ip) {
|
||||||
|
m_v6 = false;
|
||||||
|
|
||||||
|
std::string_view curr;
|
||||||
|
size_t lastPos = 0;
|
||||||
|
auto advance = [&]() {
|
||||||
|
size_t prev = lastPos ? lastPos + 1 : lastPos;
|
||||||
|
lastPos = ip.find('.', prev);
|
||||||
|
|
||||||
|
if (lastPos == std::string::npos)
|
||||||
|
curr = std::string_view{ip}.substr(prev);
|
||||||
|
else
|
||||||
|
curr = std::string_view{ip}.substr(prev, lastPos - prev);
|
||||||
|
};
|
||||||
|
|
||||||
|
for (size_t i = 0; i < 4; ++i) {
|
||||||
|
advance();
|
||||||
|
m_blocks.push_back(std::stoul(std::string{curr}));
|
||||||
|
|
||||||
|
if (m_blocks.back() > 0xFF)
|
||||||
|
throw std::runtime_error("Invalid IPv4 byte");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CIP::parseV6(const std::string& ip) {
|
||||||
|
const auto COLONS = std::count(ip.begin(), ip.end(), ':');
|
||||||
|
|
||||||
|
m_v6 = true;
|
||||||
|
|
||||||
|
std::string_view curr;
|
||||||
|
size_t lastPos = 0;
|
||||||
|
bool first = true;
|
||||||
|
auto advance = [&]() {
|
||||||
|
size_t prev = !first ? lastPos + 1 : lastPos;
|
||||||
|
lastPos = ip.find(':', prev);
|
||||||
|
|
||||||
|
if (lastPos == std::string::npos)
|
||||||
|
curr = std::string_view{ip}.substr(prev);
|
||||||
|
else
|
||||||
|
curr = std::string_view{ip}.substr(prev, lastPos - prev);
|
||||||
|
|
||||||
|
first = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
for (size_t i = 0; i < 8; ++i) {
|
||||||
|
advance();
|
||||||
|
if (curr.empty()) {
|
||||||
|
for (size_t j = 0; j < 8 - COLONS; ++j) {
|
||||||
|
i++;
|
||||||
|
m_blocks.push_back(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ip.starts_with("::") || ip.ends_with("::")) {
|
||||||
|
m_blocks.push_back(0);
|
||||||
|
advance();
|
||||||
|
} else
|
||||||
|
i--;
|
||||||
|
continue;
|
||||||
|
} else
|
||||||
|
m_blocks.push_back(std::stoul(std::string{curr}, nullptr, 16));
|
||||||
|
|
||||||
|
if (m_blocks.back() > 0xFFFF)
|
||||||
|
throw std::runtime_error("Invalid IPv6 byte");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CIPRange::CIPRange(const std::string& range) {
|
||||||
|
if (!range.contains('/'))
|
||||||
|
throw std::runtime_error("Range has no subnet");
|
||||||
|
|
||||||
|
m_subnet = std::stoul(range.substr(range.find('/') + 1));
|
||||||
|
|
||||||
|
m_ip = CIP(range.substr(0, range.find('/')));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CIPRange::ipMatches(const CIP& ip) const {
|
||||||
|
if (m_ip.m_v6 != ip.m_v6)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (m_ip.m_v6)
|
||||||
|
return ipMatchesV6(ip);
|
||||||
|
return ipMatchesV4(ip);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CIPRange::ipMatchesV4(const CIP& ip) const {
|
||||||
|
uint32_t rangeMask = 0xFFFFFFFF << (32 - m_subnet);
|
||||||
|
uint32_t rangeIP =
|
||||||
|
(((uint32_t)m_ip.m_blocks.at(0)) << 24) | (((uint32_t)m_ip.m_blocks.at(1)) << 16) | (((uint32_t)m_ip.m_blocks.at(2)) << 8) | (((uint32_t)m_ip.m_blocks.at(3)) << 0);
|
||||||
|
uint32_t incomingIP =
|
||||||
|
(((uint32_t)ip.m_blocks.at(0)) << 24) | (((uint32_t)ip.m_blocks.at(1)) << 16) | (((uint32_t)ip.m_blocks.at(2)) << 8) | (((uint32_t)ip.m_blocks.at(3)) << 0);
|
||||||
|
|
||||||
|
return (rangeMask & rangeIP) == (rangeMask & incomingIP);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CIPRange::ipMatchesV6(const CIP& ip) const {
|
||||||
|
uint64_t rangeMaskLeft = 0xFFFFFFFFFFFFFFFF << (m_subnet > 64 ? 0 : 64 - m_subnet);
|
||||||
|
uint64_t rangeIPLeft =
|
||||||
|
(((uint64_t)m_ip.m_blocks.at(0)) << 48) | (((uint64_t)m_ip.m_blocks.at(1)) << 32) | (((uint64_t)m_ip.m_blocks.at(2)) << 16) | (((uint64_t)m_ip.m_blocks.at(3)) << 0);
|
||||||
|
uint64_t incomingIPLeft =
|
||||||
|
(((uint64_t)ip.m_blocks.at(0)) << 48) | (((uint64_t)ip.m_blocks.at(1)) << 32) | (((uint64_t)ip.m_blocks.at(2)) << 16) | (((uint64_t)ip.m_blocks.at(3)) << 0);
|
||||||
|
|
||||||
|
if ((rangeMaskLeft & rangeIPLeft) != (rangeMaskLeft & incomingIPLeft))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (m_subnet <= 64)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
uint64_t rangeMaskRight = 0xFFFFFFFFFFFFFFFF << (/* m_subnet > 64 */ 128 - m_subnet);
|
||||||
|
uint64_t rangeIPRight =
|
||||||
|
(((uint64_t)m_ip.m_blocks.at(4)) << 48) | (((uint64_t)m_ip.m_blocks.at(5)) << 32) | (((uint64_t)m_ip.m_blocks.at(6)) << 16) | (((uint64_t)m_ip.m_blocks.at(7)) << 0);
|
||||||
|
uint64_t incomingIPRight =
|
||||||
|
(((uint64_t)ip.m_blocks.at(4)) << 48) | (((uint64_t)ip.m_blocks.at(5)) << 32) | (((uint64_t)ip.m_blocks.at(6)) << 16) | (((uint64_t)ip.m_blocks.at(7)) << 0);
|
||||||
|
|
||||||
|
return (rangeMaskRight & rangeIPRight) == (rangeMaskRight & incomingIPRight);
|
||||||
|
}
|
||||||
33
src/config/IPRange.hpp
Normal file
33
src/config/IPRange.hpp
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
class CIP {
|
||||||
|
public:
|
||||||
|
CIP() = default;
|
||||||
|
CIP(const std::string& ip);
|
||||||
|
|
||||||
|
bool m_v6 = false;
|
||||||
|
std::vector<uint16_t> m_blocks;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void parseV4(const std::string& ip);
|
||||||
|
void parseV6(const std::string& ip);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Accepts both ipv4 and ipv6
|
||||||
|
class CIPRange {
|
||||||
|
public:
|
||||||
|
CIPRange(const std::string& range);
|
||||||
|
|
||||||
|
bool ipMatches(const CIP& ip) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
CIP m_ip;
|
||||||
|
size_t m_subnet = 0;
|
||||||
|
|
||||||
|
bool ipMatchesV6(const CIP& ip) const;
|
||||||
|
bool ipMatchesV4(const CIP& ip) const;
|
||||||
|
};
|
||||||
@@ -213,6 +213,40 @@ 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()) {
|
||||||
|
const auto IP = CIP(req.address().host());
|
||||||
|
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.action == CConfig::IP_ACTION_ALLOW) {
|
||||||
|
Debug::log(LOG, " | Action: PASS (ip rule matched for {})", req.address().host());
|
||||||
|
proxyPass(req, response);
|
||||||
|
return;
|
||||||
|
} else if (ic.action == CConfig::IP_ACTION_DENY) {
|
||||||
|
Debug::log(LOG, " | Action: DENY (ip rule matched for {})", req.address().host());
|
||||||
|
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
|
||||||
|
if (ic.difficulty != -1)
|
||||||
|
challengeDifficulty = ic.difficulty;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (req.cookies().has(TOKEN_COOKIE_NAME)) {
|
if (req.cookies().has(TOKEN_COOKIE_NAME)) {
|
||||||
// check the token
|
// check the token
|
||||||
const auto TOKEN = CToken(req.cookies().get(TOKEN_COOKIE_NAME).value);
|
const auto TOKEN = CToken(req.cookies().get(TOKEN_COOKIE_NAME).value);
|
||||||
@@ -234,7 +268,7 @@ void CServerHandler::onRequest(const Pistache::Http::Request& req, Pistache::Htt
|
|||||||
} else
|
} else
|
||||||
Debug::log(LOG, " | Action: CHALLENGE (no token)");
|
Debug::log(LOG, " | Action: CHALLENGE (no token)");
|
||||||
|
|
||||||
serveStop(req, response);
|
serveStop(req, response, challengeDifficulty);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CServerHandler::onTimeout(const Pistache::Http::Request& request, Pistache::Http::ResponseWriter response) {
|
void CServerHandler::onTimeout(const Pistache::Http::Request& request, Pistache::Http::ResponseWriter response) {
|
||||||
@@ -274,18 +308,16 @@ void CServerHandler::challengeSubmitted(const Pistache::Http::Request& req, Pist
|
|||||||
response.send(Pistache::Http::Code::Ok, "Ok");
|
response.send(Pistache::Http::Code::Ok, "Ok");
|
||||||
}
|
}
|
||||||
|
|
||||||
void CServerHandler::serveStop(const Pistache::Http::Request& req, Pistache::Http::ResponseWriter& response) {
|
void CServerHandler::serveStop(const Pistache::Http::Request& req, Pistache::Http::ResponseWriter& response, int difficulty) {
|
||||||
static const auto PAGE_INDEX = NFsUtils::readFileAsString(NFsUtils::htmlPath("/index.min.html")).value();
|
static const auto PAGE_INDEX = NFsUtils::readFileAsString(NFsUtils::htmlPath("/index.min.html")).value();
|
||||||
static const auto PAGE_ROOT = PAGE_INDEX.substr(0, PAGE_INDEX.find_last_of("/") + 1);
|
static const auto PAGE_ROOT = PAGE_INDEX.substr(0, PAGE_INDEX.find_last_of("/") + 1);
|
||||||
CTinylates page(PAGE_INDEX);
|
CTinylates page(PAGE_INDEX);
|
||||||
page.setTemplateRoot(PAGE_ROOT);
|
page.setTemplateRoot(PAGE_ROOT);
|
||||||
|
|
||||||
const auto NONCE = generateNonce();
|
const auto NONCE = generateNonce();
|
||||||
const auto DIFFICULTY = 4;
|
const auto CHALLENGE = CChallenge(fingerprintForRequest(req), NONCE, difficulty);
|
||||||
|
|
||||||
const auto CHALLENGE = CChallenge(fingerprintForRequest(req), NONCE, DIFFICULTY);
|
page.add("challengeDifficulty", CTinylatesProp(std::to_string(difficulty)));
|
||||||
|
|
||||||
page.add("challengeDifficulty", CTinylatesProp(std::to_string(DIFFICULTY)));
|
|
||||||
page.add("challengeNonce", CTinylatesProp(NONCE));
|
page.add("challengeNonce", CTinylatesProp(NONCE));
|
||||||
page.add("challengeSignature", CTinylatesProp(CHALLENGE.signature()));
|
page.add("challengeSignature", CTinylatesProp(CHALLENGE.signature()));
|
||||||
page.add("challengeFingerprint", CTinylatesProp(CHALLENGE.fingerprint()));
|
page.add("challengeFingerprint", CTinylatesProp(CHALLENGE.fingerprint()));
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ class CServerHandler : public Pistache::Http::Handler {
|
|||||||
void onTimeout(const Pistache::Http::Request& request, Pistache::Http::ResponseWriter response);
|
void onTimeout(const Pistache::Http::Request& request, Pistache::Http::ResponseWriter response);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void serveStop(const Pistache::Http::Request& req, Pistache::Http::ResponseWriter& response);
|
void serveStop(const Pistache::Http::Request& req, Pistache::Http::ResponseWriter& response, int difficulty);
|
||||||
void proxyPass(const Pistache::Http::Request& req, Pistache::Http::ResponseWriter& response);
|
void proxyPass(const Pistache::Http::Request& req, Pistache::Http::ResponseWriter& response);
|
||||||
void challengeSubmitted(const Pistache::Http::Request& req, Pistache::Http::ResponseWriter& response);
|
void challengeSubmitted(const Pistache::Http::Request& req, Pistache::Http::ResponseWriter& response);
|
||||||
std::string fingerprintForRequest(const Pistache::Http::Request& req);
|
std::string fingerprintForRequest(const Pistache::Http::Request& req);
|
||||||
|
|||||||
Reference in New Issue
Block a user