init openresty config
This commit is contained in:
commit
7f132979c4
55 changed files with 6767 additions and 0 deletions
17
configs/general.conf
Normal file
17
configs/general.conf
Normal file
|
@ -0,0 +1,17 @@
|
|||
# ZSTD
|
||||
# https://github.com/tokers/zstd-nginx-module
|
||||
# zstd on;
|
||||
# zstd_comp_level 1;
|
||||
# zstd_types text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml;
|
||||
|
||||
# BROTLI
|
||||
# brotli on;
|
||||
# brotli_comp_level 6;
|
||||
# brotli_types text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml;
|
||||
|
||||
# GZIP
|
||||
gzip on;
|
||||
gzip_vary on;
|
||||
gzip_proxied any;
|
||||
gzip_comp_level 6;
|
||||
gzip_types text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml;
|
9
configs/listen.conf
Normal file
9
configs/listen.conf
Normal file
|
@ -0,0 +1,9 @@
|
|||
# Netmaker internal IP
|
||||
set_real_ip_from 100.64.0.0/24;
|
||||
real_ip_header X-Forwarded-For;
|
||||
real_ip_recursive on;
|
||||
|
||||
listen 443 ssl;
|
||||
# Port that is only accessed via Netmaker and Tor, used to proxy the traffic to another server
|
||||
listen 4080;
|
||||
http2 on;
|
24
configs/proxy.conf
Normal file
24
configs/proxy.conf
Normal file
|
@ -0,0 +1,24 @@
|
|||
#Keep-alive
|
||||
# www.f5.com/company/blog/nginx/avoiding-top-10-nginx-configuration-mistakes#no-keepalives
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Connection $connection_upgrade; # Defined in /snippets/maps.conf
|
||||
|
||||
#proxy_cache_bypass $http_upgrade;
|
||||
|
||||
# Proxy SSL
|
||||
proxy_ssl_server_name on;
|
||||
|
||||
# Proxy headers
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header Forwarded $proxy_add_forwarded;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-Host $host;
|
||||
proxy_set_header X-Forwarded-Port $server_port;
|
||||
proxy_set_header Host $host;
|
||||
|
||||
# Proxy timeouts
|
||||
#proxy_connect_timeout 60s;
|
||||
#proxy_send_timeout 60s;
|
||||
#proxy_read_timeout 60s;
|
58
configs/robots.conf
Normal file
58
configs/robots.conf
Normal file
|
@ -0,0 +1,58 @@
|
|||
if ($http_user_agent ~* "(AdsBot-Google|Amazonbot|anthropic-ai|Applebot|Applebot-Extended|AwarioRssBot|AwarioSmartBot|Bytespider|CCBot|ChatGPT-User|ClaudeBot|Claude-Web|cohere-ai|DataForSeoBot|Diffbot|FacebookBot|FriendlyCrawler|Google-Extended|GoogleOther|GPTBot|img2dataset|ImagesiftBot|magpie-crawler|Meltwater|omgili|omgilibot|peer39_crawler|peer39_crawler/1.0|PerplexityBot|PiplBot|scoop.it|Seekr|YouBot|facebookexternalhit|OpenAI)"){
|
||||
return 444;
|
||||
}
|
||||
|
||||
location /robots.txt { return 200 "
|
||||
User-agent: AhrefsBot
|
||||
Disallow: /
|
||||
|
||||
User-agent: dotbot
|
||||
Disallow: /
|
||||
|
||||
User-agent: SiteAuditBot
|
||||
Disallow: /
|
||||
|
||||
User-agent: SemrushBot-BA
|
||||
Disallow: /
|
||||
|
||||
User-agent: SemrushBot-SI
|
||||
Disallow: /
|
||||
|
||||
User-agent: SemrushBot-SWA
|
||||
Disallow: /
|
||||
|
||||
User-agent: SemrushBot-CT
|
||||
Disallow: /
|
||||
|
||||
User-agent: SplitSignalBot
|
||||
Disallow: /
|
||||
|
||||
User-agent: SemrushBot-COUB
|
||||
Disallow: /
|
||||
|
||||
User-agent: AdsBot-Google
|
||||
User-agent: Amazonbot
|
||||
User-agent: anthropic-ai
|
||||
User-agent: Applebot-Extended
|
||||
User-agent: Bytespider
|
||||
User-agent: CCBot
|
||||
User-agent: ChatGPT-User
|
||||
User-agent: ClaudeBot
|
||||
User-agent: Claude-Web
|
||||
User-agent: cohere-ai
|
||||
User-agent: Diffbot
|
||||
User-agent: FacebookBot
|
||||
User-agent: FriendlyCrawler
|
||||
User-agent: Google-Extended
|
||||
User-agent: GoogleOther
|
||||
User-agent: GPTBot
|
||||
User-agent: img2dataset
|
||||
User-agent: omgili
|
||||
User-agent: omgilibot
|
||||
User-agent: peer39_crawler
|
||||
User-agent: peer39_crawler/1.0
|
||||
User-agent: PerplexityBot
|
||||
User-agent: YouBot
|
||||
User-agent: facebookexternalhit/1.1
|
||||
Disallow: /";
|
||||
}
|
1
configs/robotsNone.conf
Normal file
1
configs/robotsNone.conf
Normal file
|
@ -0,0 +1 @@
|
|||
location /robots.txt { return 200 "User-agent: *\nDisallow: /";}
|
6
configs/security.conf
Normal file
6
configs/security.conf
Normal file
|
@ -0,0 +1,6 @@
|
|||
# security headers
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header Referrer-Policy "same-origin" always;
|
||||
add_header X-Frame-Options "sameorigin" always;
|
||||
add_header Permissions-Policy "accelerometer=(), ambient-light-sensor=(), autoplay=(), battery=(), camera=(), cross-origin-isolated=(), display-capture=(), document-domain=(), encrypted-media=(), execution-while-not-rendered=(), execution-while-out-of-viewport=(), fullscreen=(self), geolocation=(), gyroscope=(), interest-cohort=(), magnetometer=(), microphone=(), midi=(), navigation-override=(), payment=(), picture-in-picture=(), publickey-credentials-get=(), screen-wake-lock=(), sync-xhr=(), usb=(), web-share=(), xr-spatial-tracking=()" always;
|
||||
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
|
5
configs/ssl.conf
Normal file
5
configs/ssl.conf
Normal file
|
@ -0,0 +1,5 @@
|
|||
# ECDSA
|
||||
ssl_certificate /etc/ssl/nadeko.net/fullchain.cer;
|
||||
ssl_certificate_key /etc/ssl/nadeko.net/nadeko.net.key;
|
||||
|
||||
include configs/sslConfig.conf;
|
13
configs/sslConfig.conf
Normal file
13
configs/sslConfig.conf
Normal file
|
@ -0,0 +1,13 @@
|
|||
# SSL
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384";
|
||||
ssl_prefer_server_ciphers off;
|
||||
ssl_session_timeout 1d;
|
||||
ssl_session_cache shared:SSL:10m;
|
||||
ssl_session_tickets off;
|
||||
ssl_early_data on;
|
||||
#ssl_conf_command Options KTLS;
|
||||
|
||||
# Custom 4096bits Diffie-Hellman parameter for DHE ciphersuites (Not the one bundled with letsencrypt
|
||||
# Changed to a custom one for trust purposes
|
||||
ssl_dhparam /etc/nginx/dhparam.pem;
|
223
configs/upstreams.conf
Normal file
223
configs/upstreams.conf
Normal file
|
@ -0,0 +1,223 @@
|
|||
upstream php-fpm-8.3 {
|
||||
server unix:/run/php-fpm/php-fpm.sock;
|
||||
}
|
||||
|
||||
lua_shared_dict servers 12k;
|
||||
|
||||
upstream inv {
|
||||
# hash $remote_addr consistent;
|
||||
# ip_hash;
|
||||
#server unix:/run/invidious-haproxy/invidious.sock max_fails=1 fail_timeout=10s;
|
||||
#server unix:/run/invidious-haproxy/invidious-vpn.sock max_fails=1 fail_timeout=10s;
|
||||
|
||||
# server 127.0.0.1:10060;
|
||||
# server 127.0.0.1:10070;
|
||||
# server 127.0.0.1:10080;
|
||||
# balancer_by_lua_file "conf/lua/invidious-sticky.lua";
|
||||
|
||||
server 127.0.0.1:11101;
|
||||
server 127.0.0.1:11102;
|
||||
server 127.0.0.1:11103;
|
||||
server 127.0.0.1:11104;
|
||||
server 127.0.0.1:11105;
|
||||
balancer_by_lua_block {
|
||||
local sticky = require "invidious-sticky"
|
||||
local servers = {
|
||||
{ "127.0.0.1", 11101, weight = 1, max_fails = 3, fail_timeout = 30, fail_count = 0, last_fail_time = 0 },
|
||||
{ "127.0.0.1", 11102, weight = 1, max_fails = 3, fail_timeout = 30, fail_count = 0, last_fail_time = 0 },
|
||||
{ "127.0.0.1", 11103, weight = 1, max_fails = 3, fail_timeout = 30, fail_count = 0, last_fail_time = 0 },
|
||||
{ "127.0.0.1", 11104, weight = 1, max_fails = 3, fail_timeout = 30, fail_count = 0, last_fail_time = 0 },
|
||||
{ "127.0.0.1", 11105, weight = 1, max_fails = 3, fail_timeout = 30, fail_count = 0, last_fail_time = 0 },
|
||||
}
|
||||
local s = ngx.shared.servers
|
||||
s:set("inv-backends", #servers)
|
||||
sticky.run(servers, "invidious", "clearnet");
|
||||
}
|
||||
}
|
||||
|
||||
upstream invidious-1 {
|
||||
keepalive 32;
|
||||
server 127.0.0.1:11101;
|
||||
}
|
||||
|
||||
upstream invidious-2 {
|
||||
keepalive 32;
|
||||
server 127.0.0.1:11102;
|
||||
}
|
||||
|
||||
upstream invidious-3 {
|
||||
keepalive 32;
|
||||
server 127.0.0.1:11103;
|
||||
}
|
||||
|
||||
upstream invidious-4 {
|
||||
keepalive 32;
|
||||
server 127.0.0.1:11104;
|
||||
}
|
||||
|
||||
upstream invidious-5 {
|
||||
keepalive 32;
|
||||
server 127.0.0.1:11105;
|
||||
}
|
||||
|
||||
# upstream invidious-5 {
|
||||
# server 127.0.0.1:11105;
|
||||
# keepalive 2;
|
||||
# }
|
||||
# upstream inv-t1 {
|
||||
# server 127.0.0.1:20201;
|
||||
# }
|
||||
|
||||
# upstream inv-tor {
|
||||
# server 127.0.0.1:10062;
|
||||
# server 127.0.0.1:10072;
|
||||
# server 127.0.0.1:10082;
|
||||
# server 127.0.0.1:20102;
|
||||
# balancer_by_lua_block {
|
||||
# local sticky = require "invidious-sticky"
|
||||
# local servers = {
|
||||
# { "127.0.0.1", 10062, weight = 1, max_fails = 3, fail_timeout = 30, fail_count = 0, last_fail_time = 0 },
|
||||
# { "127.0.0.1", 10072, weight = 1, max_fails = 3, fail_timeout = 30, fail_count = 0, last_fail_time = 0 },
|
||||
# { "127.0.0.1", 10082, weight = 1, max_fails = 3, fail_timeout = 30, fail_count = 0, last_fail_time = 0 },
|
||||
# { "127.0.0.1", 20102, weight = 1, max_fails = 3, fail_timeout = 30, fail_count = 0, last_fail_time = 0 },
|
||||
# }
|
||||
# sticky.run(servers, "invidious-tor", "clearnet");
|
||||
# }
|
||||
# }
|
||||
#
|
||||
# upstream inv-i2p {
|
||||
# server 127.0.0.1:10063;
|
||||
# server 127.0.0.1:10073;
|
||||
# server 127.0.0.1:10083;
|
||||
# server 127.0.0.1:20103;
|
||||
# balancer_by_lua_block {
|
||||
# local sticky = require "invidious-sticky"
|
||||
# local servers = {
|
||||
# { "127.0.0.1", 10063, weight = 1, max_fails = 3, fail_timeout = 30, fail_count = 0, last_fail_time = 0 },
|
||||
# { "127.0.0.1", 10073, weight = 1, max_fails = 3, fail_timeout = 30, fail_count = 0, last_fail_time = 0 },
|
||||
# { "127.0.0.1", 10083, weight = 1, max_fails = 3, fail_timeout = 30, fail_count = 0, last_fail_time = 0 },
|
||||
# { "127.0.0.1", 20103, weight = 1, max_fails = 3, fail_timeout = 30, fail_count = 0, last_fail_time = 0 },
|
||||
# }
|
||||
# sticky.run(servers, "invidious-i2p", "clearnet");
|
||||
# }
|
||||
# }
|
||||
|
||||
upstream inv-feed-receiver {
|
||||
server 127.0.0.1:20090;
|
||||
}
|
||||
|
||||
upstream http3-ytproxy {
|
||||
#hash $remote_addr consistent;
|
||||
# ip_hash;
|
||||
#server unix:/run/invidious-haproxy/http3-proxy.sock;
|
||||
#server unix:/run/invidious-haproxy/http3-proxy-vpn.sock;
|
||||
server unix:/tmp/http3-ytproxy.sock;
|
||||
keepalive 128;
|
||||
# server 127.0.0.1:10061;
|
||||
# server 127.0.0.1:10071;
|
||||
# server 127.0.0.1:10081;
|
||||
# server 127.0.0.1:20101;
|
||||
# balancer_by_lua_block {
|
||||
# local sticky = require "invidious-sticky"
|
||||
# local servers = {
|
||||
# { "unix:/tmp/http3-ytproxy.sock", 10061, weight = 1, max_fails = 3, fail_timeout = 30, fail_count = 0, last_fail_time = 0 }
|
||||
# }
|
||||
# sticky.run(servers, "http3-ytproxy", "clearnet");
|
||||
# }
|
||||
}
|
||||
|
||||
# upstream materialious {
|
||||
# server 127.0.0.1:10013;
|
||||
# }
|
||||
#
|
||||
# upstream materialious-tor {
|
||||
# server 127.0.0.1:10070;
|
||||
# }
|
||||
#
|
||||
# upstream syncious {
|
||||
# server 127.0.0.1:10014;
|
||||
# }
|
||||
#
|
||||
# upstream peerjs {
|
||||
# server 127.0.0.1:10015;
|
||||
# }
|
||||
|
||||
upstream rimgo {
|
||||
keepalive 128;
|
||||
server 127.0.0.1:10001;
|
||||
}
|
||||
|
||||
upstream redlib {
|
||||
keepalive 128;
|
||||
server 127.0.0.1:10006;
|
||||
}
|
||||
|
||||
upstream breezewiki {
|
||||
keepalive 16;
|
||||
server 127.0.0.1:10007;
|
||||
}
|
||||
|
||||
upstream privatebin {
|
||||
keepalive 4;
|
||||
server 127.0.0.1:10002;
|
||||
}
|
||||
|
||||
upstream rustlog {
|
||||
keepalive 4;
|
||||
server 127.0.0.1:10003;
|
||||
}
|
||||
|
||||
upstream matrix {
|
||||
keepalive 16;
|
||||
server 127.0.0.1:10020;
|
||||
}
|
||||
|
||||
upstream matrix-nadeko {
|
||||
keepalive 16;
|
||||
server 127.0.0.1:10022;
|
||||
}
|
||||
|
||||
upstream peertube {
|
||||
keepalive 16;
|
||||
server 127.0.0.1:10016;
|
||||
}
|
||||
|
||||
upstream umami {
|
||||
keepalive 16;
|
||||
server 127.0.0.1:10005;
|
||||
}
|
||||
|
||||
upstream grafana {
|
||||
keepalive 16;
|
||||
server 127.0.0.1:20002;
|
||||
}
|
||||
|
||||
upstream forgejo {
|
||||
keepalive 64;
|
||||
server 127.0.0.1:10004;
|
||||
server unix:/run/forgejo/forgejo.sock backup;
|
||||
}
|
||||
|
||||
# DEVELOPMENT
|
||||
upstream inv-debug {
|
||||
server 127.0.0.1:10060;
|
||||
server 127.0.0.1:10070;
|
||||
server 127.0.0.1:10080;
|
||||
server 127.0.0.1:20100;
|
||||
balancer_by_lua_block {
|
||||
local sticky = require "stickydebug"
|
||||
local servers = {
|
||||
{ "127.0.0.1", 10060, weight = 1, max_fails = 3, fail_timeout = 30, fail_count = 0, last_fail_time = 0 },
|
||||
{ "127.0.0.1", 10070, weight = 1, max_fails = 3, fail_timeout = 30, fail_count = 0, last_fail_time = 0 },
|
||||
{ "127.0.0.1", 10080, weight = 1, max_fails = 3, fail_timeout = 30, fail_count = 0, last_fail_time = 0 },
|
||||
{ "127.0.0.1", 20100, weight = 1, max_fails = 3, fail_timeout = 30, fail_count = 0, last_fail_time = 0 },
|
||||
{ "127.0.0.1", 20200, weight = 1, max_fails = 3, fail_timeout = 30, fail_count = 0, last_fail_time = 0 },
|
||||
}
|
||||
sticky.run(servers, "invidious-debug", "clearnet");
|
||||
}
|
||||
}
|
||||
|
||||
upstream http3-ytproxy-debug {
|
||||
server 127.0.0.1:10078;
|
||||
server 127.0.0.1:10080;
|
||||
}
|
76
http.d/4get.conf
Normal file
76
http.d/4get.conf
Normal file
|
@ -0,0 +1,76 @@
|
|||
# CLEARNET
|
||||
server {
|
||||
server_name
|
||||
4get.nadeko.net
|
||||
nadekonw7plitnjuawu6ytjsl7jlglk2t6pyq6eftptmiv3dvqndwvyd.onion;
|
||||
include configs/listen.conf;
|
||||
include configs/general.conf;
|
||||
include configs/robotsNone.conf;
|
||||
include configs/security.conf;
|
||||
|
||||
location @upstream {
|
||||
proxy_pass http://127.0.0.1:10031;
|
||||
proxy_intercept_errors on;
|
||||
include configs/proxy.conf;
|
||||
}
|
||||
|
||||
location / {
|
||||
try_files $uri @upstream;
|
||||
}
|
||||
|
||||
# location /web {
|
||||
# try_files $uri @upstream;
|
||||
#
|
||||
# if ($server_protocol ~* "HTTP/1.1") {
|
||||
# return 444;
|
||||
# }
|
||||
#
|
||||
# include snippets/torblacklist.conf;
|
||||
# error_page 403 =302 /torisblocked;
|
||||
# error_page 429 =302 /rl;
|
||||
# }
|
||||
#
|
||||
# location /torisblocked {
|
||||
# alias errors/$request_uri.txt;
|
||||
# }
|
||||
#
|
||||
# location /rl {
|
||||
# alias errors/$request_uri.txt;
|
||||
# }
|
||||
#
|
||||
# location /data {
|
||||
# return 444;
|
||||
# }
|
||||
|
||||
# Tor Header
|
||||
add_header Onion-Location http://4get.zzlsghu6mvvwyy75mvga6gaf4znbp3erk5xwfzedb4gg6qqh2j6rlvid.onion$request_uri;
|
||||
|
||||
# QUIC
|
||||
# include configs/http3.conf;
|
||||
|
||||
|
||||
}
|
||||
|
||||
# TOR
|
||||
# server {
|
||||
# listen 10040;
|
||||
# server_name 4get.zzlsghu6mvvwyy75mvga6gaf4znbp3erk5xwfzedb4gg6qqh2j6rlvid.onion 4get.nadekonw7plitnjuawu6ytjsl7jlglk2t6pyq6eftptmiv3dvqndwvyd.onion;
|
||||
# include configs/listen.conf;
|
||||
# root /var/www/4get-zzls;
|
||||
#
|
||||
# location @upstream {
|
||||
# try_files $uri.php $uri/index.php =404;
|
||||
# fastcgi_pass php-fpm-8.1;
|
||||
# fastcgi_index index.php;
|
||||
# include fastcgi.conf;
|
||||
# fastcgi_intercept_errors on;
|
||||
# }
|
||||
#
|
||||
# location / {
|
||||
# try_files $uri @upstream;
|
||||
# }
|
||||
#
|
||||
# location ~* ^(.*)\.php$ {
|
||||
# return 301 $1;
|
||||
# }
|
||||
# }
|
20
http.d/breezewiki.conf
Normal file
20
http.d/breezewiki.conf
Normal file
|
@ -0,0 +1,20 @@
|
|||
server {
|
||||
server_name
|
||||
breezewiki.nadeko.net
|
||||
breezewiki.nadekonw7plitnjuawu6ytjsl7jlglk2t6pyq6eftptmiv3dvqndwvyd.onion;
|
||||
include configs/listen.conf;
|
||||
include configs/general.conf;
|
||||
# Security headers are added by redlib!
|
||||
#include configs/security.conf;
|
||||
include configs/robotsNone.conf;
|
||||
|
||||
location / {
|
||||
if ($blocked_agent = 1) {
|
||||
return 200 "
|
||||
1. The Industrial Revolution and its consequences have been a disaster for the human race. They have greatly increased the life-expectancy of those of us who live in “advanced” countries, but they have destabilized society, have made life unfulfilling, have subjected human beings to indignities, have led to widespread psychological suffering (in the Third World to physical suffering as well) and have inflicted severe damage on the natural world. The continued development of technology will worsen the situation. It will certainly subject human beings to greater indignities and inflict greater damage on the natural world, it will probably lead to greater social disruption and psychological suffering, and it may lead to increased physical suffering even in “advanced” countries.
|
||||
";
|
||||
}
|
||||
proxy_pass http://breezewiki;
|
||||
include configs/proxy.conf;
|
||||
}
|
||||
}
|
14
http.d/datamining.conf
Normal file
14
http.d/datamining.conf
Normal file
|
@ -0,0 +1,14 @@
|
|||
# CLEARNET
|
||||
server {
|
||||
access_log /var/log/nginx/datamining.nadeko.net.access.log;
|
||||
server_name datamining.nadeko.net;
|
||||
include configs/listen.conf;
|
||||
include configs/general.conf;
|
||||
include configs/robotsNone.conf;
|
||||
#include configs/security.conf;
|
||||
|
||||
location / {
|
||||
proxy_pass http://umami;
|
||||
include configs/proxy.conf;
|
||||
}
|
||||
}
|
20
http.d/default.conf
Normal file
20
http.d/default.conf
Normal file
|
@ -0,0 +1,20 @@
|
|||
server {
|
||||
access_log /var/log/nginx/default.access.log;
|
||||
server_name sf.nadeko.net;
|
||||
include configs/general.conf;
|
||||
include configs/security.conf;
|
||||
include configs/robotsNone.conf;
|
||||
|
||||
location / {
|
||||
return 200 "
|
||||
Hi, you just hit the seflhosted server of nadeko.net!
|
||||
There is nothing here, this is just a landing page for services that don't exist
|
||||
or deprecated services. If you think there is something missing, contact me!
|
||||
|
||||
https://nadeko.net/contact
|
||||
";
|
||||
}
|
||||
|
||||
listen 443 ssl default_server reuseport;
|
||||
http2 on;
|
||||
}
|
20
http.d/git.conf
Normal file
20
http.d/git.conf
Normal file
|
@ -0,0 +1,20 @@
|
|||
server {
|
||||
access_log /var/log/nginx/git.access.log;
|
||||
error_log /var/log/nginx/git.error.log;
|
||||
server_name git.nadeko.net;
|
||||
include configs/listen.conf;
|
||||
include configs/general.conf;
|
||||
include configs/security.conf;
|
||||
include configs/robots.conf;
|
||||
|
||||
location / {
|
||||
if ($blocked_agent = 1) {
|
||||
return 200 "
|
||||
1. The Industrial Revolution and its consequences have been a disaster for the human race. They have greatly increased the life-expectancy of those of us who live in “advanced” countries, but they have destabilized society, have made life unfulfilling, have subjected human beings to indignities, have led to widespread psychological suffering (in the Third World to physical suffering as well) and have inflicted severe damage on the natural world. The continued development of technology will worsen the situation. It will certainly subject human beings to greater indignities and inflict greater damage on the natural world, it will probably lead to greater social disruption and psychological suffering, and it may lead to increased physical suffering even in “advanced” countries.
|
||||
";
|
||||
}
|
||||
proxy_pass http://forgejo;
|
||||
include configs/proxy.conf;
|
||||
client_max_body_size 1024M;
|
||||
}
|
||||
}
|
180
http.d/inv.conf
Normal file
180
http.d/inv.conf
Normal file
|
@ -0,0 +1,180 @@
|
|||
map $host $invidious_backend {
|
||||
default inv;
|
||||
inv1.nadeko.net invidious-1;
|
||||
inv2.nadeko.net invidious-2;
|
||||
inv3.nadeko.net invidious-3;
|
||||
inv4.nadeko.net invidious-4;
|
||||
inv5.nadeko.net invidious-5;
|
||||
}
|
||||
|
||||
map $invidious_backend $cache_case {
|
||||
default "";
|
||||
inv $cookie_INVIDIOUS_SERVER_ID;
|
||||
invidious-* $invidious_backend;
|
||||
}
|
||||
|
||||
# CLEARNET
|
||||
server {
|
||||
error_log /var/log/nginx/inv.nadeko.net.error.log;
|
||||
server_name
|
||||
inv.nadeko.net
|
||||
~^inv([1-5])\.nadeko\.net$
|
||||
inv.nadekonw7plitnjuawu6ytjsl7jlglk2t6pyq6eftptmiv3dvqndwvyd.onion ~^inv([1-5])\.nadekonw7plitnjuawu6ytjsl7jlglk2t6pyq6eftptmiv3dvqndwvyd\.onion$;
|
||||
include configs/listen.conf;
|
||||
include configs/robotsNone.conf;
|
||||
# MAINTENANCE MODE
|
||||
# include configs/maintenance-mode.conf;
|
||||
|
||||
# The messed up invidious configuration
|
||||
include http.d/locations/inv.conf;
|
||||
|
||||
if ($http_user_agent = "") {
|
||||
return 444;
|
||||
}
|
||||
|
||||
if ($request_method = OPTIONS) {
|
||||
return 204;
|
||||
}
|
||||
|
||||
#rewrite_by_lua_file conf/lua/rewrite-invidious/init.lua;
|
||||
|
||||
location = /503.html {
|
||||
content_by_lua_file conf/lua/503-invidious.lua;
|
||||
}
|
||||
|
||||
location = /502.html {
|
||||
content_by_lua_file conf/lua/502-invidious.lua;
|
||||
}
|
||||
|
||||
location = /switchbackend {
|
||||
content_by_lua_file conf/lua/switchbackend.lua;
|
||||
}
|
||||
|
||||
# header_filter_by_lua_block {
|
||||
# ngx.header["Onion-Location"] = "http://inv.nadekonw7plitnjuawu6ytjsl7jlglk2t6pyq6eftptmiv3dvqndwvyd.onion" .. ngx.var.request_uri
|
||||
# }
|
||||
listen 10040;
|
||||
}
|
||||
|
||||
# TOR
|
||||
# server {
|
||||
# listen 10040;
|
||||
#
|
||||
# error_log /var/log/nginx/inv.nadeko.net.tor.error.log;
|
||||
# #access_log /var/log/nginx/inv.nadeko.net.tor.access.log;
|
||||
# server_name inv.zzlsghu6mvvwyy75mvga6gaf4znbp3erk5xwfzedb4gg6qqh2j6rlvid.onion inv.nadekonw7plitnjuawu6ytjsl7jlglk2t6pyq6eftptmiv3dvqndwvyd.onion;
|
||||
# include configs/listen.conf;
|
||||
# #include configs/general.conf;
|
||||
# #include configs/robotsNone.conf;
|
||||
#
|
||||
# include http.d/locations/inv-tor.conf;
|
||||
#
|
||||
# rewrite_by_lua_file conf/lua/rewrite-invidious/init.lua;
|
||||
#
|
||||
# location = /503.html {
|
||||
# content_by_lua_file conf/lua/503-invidious.lua;
|
||||
# }
|
||||
#
|
||||
# location = /502.html {
|
||||
# content_by_lua_file conf/lua/502-invidious.lua;
|
||||
# }
|
||||
#
|
||||
# location = /switchbackend {
|
||||
# content_by_lua_file conf/lua/switchbackend.lua;
|
||||
# }
|
||||
# }
|
||||
|
||||
# TOR
|
||||
# server {
|
||||
# error_log /var/log/nginx/inv.nadeko.net.tor.error.log;
|
||||
# #access_log /var/log/nginx/inv.nadeko.net.tor.access.log;
|
||||
# server_name inv.nadekonw7plitnjuawu6ytjsl7jlglk2t6pyq6eftptmiv3dvqndwvyd.onion;
|
||||
# include configs/listen.conf;
|
||||
# include configs/robotsNone.conf;
|
||||
# # MAINTENANCE MODE
|
||||
# # include configs/maintenance-mode.conf;
|
||||
#
|
||||
# # The messed up invidious configuration
|
||||
# include http.d/locations/inv.conf;
|
||||
#
|
||||
# if ($http_user_agent = "") {
|
||||
# return 444;
|
||||
# }
|
||||
#
|
||||
# if ($request_method = OPTIONS) {
|
||||
# return 204;
|
||||
# }
|
||||
#
|
||||
# rewrite_by_lua_file conf/lua/rewrite-invidious/init.lua;
|
||||
#
|
||||
# location = /503.html {
|
||||
# content_by_lua_file conf/lua/503-invidious.lua;
|
||||
# }
|
||||
#
|
||||
# location = /502.html {
|
||||
# content_by_lua_file conf/lua/502-invidious.lua;
|
||||
# }
|
||||
#
|
||||
# location = /switchbackend {
|
||||
# content_by_lua_file conf/lua/switchbackend.lua;
|
||||
# }
|
||||
#
|
||||
# listen 10040;
|
||||
# }
|
||||
|
||||
# I2P
|
||||
# server {
|
||||
# error_log /var/log/nginx/inv.nadeko.net.i2p.error.log;
|
||||
# access_log /var/log/nginx/inv.nadeko.net.i2p.access.log;
|
||||
# server_name inv.zzls.i2p zzlsbhhfvwg3oh36tcvx4r7n6jrw7zibvyvfxqlodcwn3mfrvzuq.b32.i2p;
|
||||
# include configs/listen.conf;
|
||||
# include configs/robotsNone.conf;
|
||||
# # MAINTENANCE MODE
|
||||
# # include configs/maintenance-mode.conf;
|
||||
#
|
||||
# # The messed up invidious configuration
|
||||
# include http.d/locations/inv.conf;
|
||||
#
|
||||
# if ($http_user_agent = "") {
|
||||
# return 444;
|
||||
# }
|
||||
#
|
||||
# rewrite_by_lua_file conf/lua/rewrite-invidious/init.lua;
|
||||
#
|
||||
# location = /503.html {
|
||||
# content_by_lua_file conf/lua/503-invidious.lua;
|
||||
# }
|
||||
#
|
||||
# location = /502.html {
|
||||
# content_by_lua_file conf/lua/502-invidious.lua;
|
||||
# }
|
||||
#
|
||||
# location = /switchbackend {
|
||||
# content_by_lua_file conf/lua/switchbackend.lua;
|
||||
# }
|
||||
#
|
||||
# listen 10051;
|
||||
# }
|
||||
|
||||
# CLEARNET FEED
|
||||
server {
|
||||
error_log /var/log/nginx/inv.nadeko.net.feed.error.log;
|
||||
access_log /var/log/nginx/inv.nadeko.net.feed.access.log;
|
||||
server_name feed-inv.nadeko.net;
|
||||
include configs/listen.conf;
|
||||
include configs/robotsNone.conf;
|
||||
|
||||
# The messed up invidious configuration
|
||||
include http.d/locations/inv-feed.conf;
|
||||
}
|
||||
|
||||
server {
|
||||
server_name inv-cl1.nadeko.net;
|
||||
include configs/listen.conf;
|
||||
include configs/robotsNone.conf;
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:10080;
|
||||
include configs/proxy.conf;
|
||||
}
|
||||
}
|
261
http.d/locations/inv.conf
Normal file
261
http.d/locations/inv.conf
Normal file
|
@ -0,0 +1,261 @@
|
|||
location @upstream {
|
||||
proxy_pass http://$invidious_backend;
|
||||
limit_rate 1000k;
|
||||
# To reduce the load in the main storage
|
||||
proxy_buffering off;
|
||||
proxy_request_buffering off;
|
||||
#proxy_cache off;
|
||||
|
||||
# To let invidious know the Host header, needed for alternative_domains.
|
||||
# https://git.nadeko.net/Fijxu/invidious/commit/35f28b508ea049118cb6a0b3062b6c7ce2c4009f
|
||||
proxy_set_header Host $host;
|
||||
|
||||
proxy_pass_request_headers on;
|
||||
proxy_intercept_errors on;
|
||||
proxy_connect_timeout 10s;
|
||||
|
||||
# To keep-alive
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Connection "";
|
||||
|
||||
error_page 502 /502.html;
|
||||
error_page 503 /503.html;
|
||||
|
||||
if ($request_method = OPTIONS) {
|
||||
return 204;
|
||||
}
|
||||
|
||||
proxy_hide_header Access-Control-Allow-Origin;
|
||||
add_header Access-Control-Allow-Credentials true;
|
||||
add_header Access-Control-Allow-Origin "https://materialious.nadeko.net" always;
|
||||
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS, HEAD, PATCH, PUT, DELETE" always;
|
||||
add_header Access-Control-Allow-Headers "User-Agent, Authorization, Content-Type" always;
|
||||
}
|
||||
|
||||
location @upstream-api {
|
||||
proxy_pass http://inv;
|
||||
limit_rate 1000k;
|
||||
# To reduce the load in the main storage
|
||||
proxy_buffering on;
|
||||
proxy_request_buffering off;
|
||||
#proxy_cache off;
|
||||
|
||||
proxy_cache invidious-api-cache;
|
||||
proxy_cache_valid 200 240m;
|
||||
add_header X-Cache $upstream_cache_status;
|
||||
proxy_cache_key "$host$request_uri$cache_case";
|
||||
|
||||
# To let invidious know the Host header, needed for alternative_domains.
|
||||
# https://git.nadeko.net/Fijxu/invidious/commit/35f28b508ea049118cb6a0b3062b6c7ce2c4009f
|
||||
proxy_set_header Host $host;
|
||||
|
||||
proxy_pass_request_headers on;
|
||||
proxy_intercept_errors on;
|
||||
proxy_connect_timeout 10s;
|
||||
|
||||
# To keep-alive
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Connection "";
|
||||
|
||||
error_page 502 /502.html;
|
||||
error_page 503 /503.html;
|
||||
|
||||
if ($request_method = OPTIONS) {
|
||||
return 204;
|
||||
}
|
||||
|
||||
proxy_hide_header Access-Control-Allow-Origin;
|
||||
add_header Access-Control-Allow-Credentials true;
|
||||
add_header Access-Control-Allow-Origin "https://materialious.nadeko.net" always;
|
||||
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS, HEAD, PATCH, PUT, DELETE" always;
|
||||
add_header Access-Control-Allow-Headers "User-Agent, Authorization, Content-Type" always;
|
||||
}
|
||||
|
||||
location @upstream-latest_version {
|
||||
proxy_pass http://inv;
|
||||
limit_rate 1000k;
|
||||
# To reduce the load in the main storage
|
||||
proxy_buffering on;
|
||||
proxy_request_buffering off;
|
||||
#proxy_cache off;
|
||||
|
||||
proxy_cache invidious-latest_version-cache;
|
||||
proxy_cache_valid 302 240m;
|
||||
add_header X-Cache $upstream_cache_status;
|
||||
proxy_cache_key "$host$request_uri$cache_case";
|
||||
|
||||
# To let invidious know the Host header, needed for alternative_domains.
|
||||
# https://git.nadeko.net/Fijxu/invidious/commit/35f28b508ea049118cb6a0b3062b6c7ce2c4009f
|
||||
proxy_set_header Host $host;
|
||||
|
||||
proxy_pass_request_headers on;
|
||||
proxy_intercept_errors on;
|
||||
proxy_connect_timeout 10s;
|
||||
|
||||
# To keep-alive
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Connection "";
|
||||
|
||||
error_page 502 /502.html;
|
||||
error_page 503 /503.html;
|
||||
|
||||
proxy_hide_header Access-Control-Allow-Origin;
|
||||
add_header Access-Control-Allow-Credentials true;
|
||||
add_header Access-Control-Allow-Origin "https://materialious.nadeko.net" always;
|
||||
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS, HEAD, PATCH, PUT, DELETE" always;
|
||||
add_header Access-Control-Allow-Headers "User-Agent, Authorization, Content-Type" always;
|
||||
}
|
||||
|
||||
location @http3-proxy {
|
||||
proxy_pass http://http3-ytproxy;
|
||||
limit_rate 600k;
|
||||
# proxy_intercept_errors on;
|
||||
# To reduce the load in the main storage
|
||||
proxy_buffering off;
|
||||
proxy_request_buffering off;
|
||||
proxy_cache off;
|
||||
|
||||
sendfile_max_chunk 512k;
|
||||
proxy_set_header X-Forwarded-For "";
|
||||
proxy_set_header Connection "keep-alive";
|
||||
proxy_hide_header "alt-svc";
|
||||
proxy_hide_header Cache-Control;
|
||||
proxy_hide_header ETag;
|
||||
proxy_pass_request_headers on;
|
||||
proxy_http_version 1.1;
|
||||
}
|
||||
|
||||
location @xd {
|
||||
proxy_pass http://http3-ytproxy;
|
||||
limit_rate 600k;
|
||||
# Proxy buffering needs to be on in order
|
||||
# to make the cache work
|
||||
proxy_buffering on;
|
||||
proxy_request_buffering off;
|
||||
|
||||
proxy_cache invidious-image-cache;
|
||||
proxy_cache_valid 200 48h;
|
||||
add_header X-Cache $upstream_cache_status;
|
||||
|
||||
proxy_http_version 1.1;
|
||||
}
|
||||
|
||||
location ~ (^/videoplayback) {
|
||||
return 403 "";
|
||||
try_files $uri @http3-proxy;
|
||||
}
|
||||
|
||||
location ~ (^/vi/) {
|
||||
try_files $uri @xd;
|
||||
}
|
||||
|
||||
location ~ (^/ggpht/) {
|
||||
try_files $uri @xd;
|
||||
}
|
||||
|
||||
location /latest_version {
|
||||
try_files $uri @upstream-latest_version;
|
||||
limit_req zone=invidious-latestversionrl nodelay burst=12;
|
||||
}
|
||||
|
||||
location /index.html {
|
||||
return 301 $scheme://$host/;
|
||||
}
|
||||
|
||||
location / {
|
||||
try_files $uri @upstream;
|
||||
}
|
||||
|
||||
location /search {
|
||||
try_files $uri @upstream;
|
||||
limit_req zone=invidious-searchrl nodelay burst=3;
|
||||
}
|
||||
|
||||
location /watch {
|
||||
try_files $uri @upstream;
|
||||
limit_req zone=invidious-watchrl nodelay burst=3;
|
||||
}
|
||||
|
||||
location /api/v1 {
|
||||
limit_req zone=invidious-apirl nodelay burst=100;
|
||||
try_files $uri @upstream;
|
||||
}
|
||||
|
||||
location ~ ^/api/v1/(videos|channels|search|mixes|trending) {
|
||||
if ($http_user_agent !~ "Clipious/") {
|
||||
return 401 "API disabled";
|
||||
}
|
||||
try_files $uri @upstream-api;
|
||||
}
|
||||
|
||||
location /api/v1/comments {
|
||||
try_files $uri @upstream-api;
|
||||
}
|
||||
|
||||
location /api/v1/auth/notifications {
|
||||
return 403 "Endpoint disabled";
|
||||
}
|
||||
|
||||
location /api/v1/auth/subscriptions {
|
||||
return 403 "Endpoint disabled";
|
||||
}
|
||||
|
||||
location /feed/playlist {
|
||||
limit_req zone=invidious-feedplaylist nodelay burst=24;
|
||||
try_files $uri @upstream;
|
||||
}
|
||||
|
||||
# location /api/v1/storyboards {
|
||||
# try_files $uri @upstream;
|
||||
# }
|
||||
#
|
||||
# location /api/v1/comments {
|
||||
# try_files $uri @upstream;
|
||||
# }
|
||||
|
||||
# location ~ (^/api/v1/videos| {
|
||||
# return 401 "API disabled";
|
||||
# }
|
||||
# location /api/v1/channels {
|
||||
# return 401 "API disabled";
|
||||
# }
|
||||
# location /api/v1/search {
|
||||
# return 401 "API disabled";
|
||||
# }
|
||||
# location /api/v1/mixes {
|
||||
# return 401 "API disabled";
|
||||
# }
|
||||
# location /api/v1/trending {
|
||||
# return 401 "API disabled";
|
||||
# }
|
||||
#
|
||||
# location /api/v1/videos {
|
||||
# limit_req zone=invidious-apivideosrl nodelay burst=3;
|
||||
# try_files $uri @upstream-api;
|
||||
# if ($http_user_agent = "Mozilla/5.0") {
|
||||
# return 444;
|
||||
# }
|
||||
# if ($http_user_agent ~* "python") {
|
||||
# return 444;
|
||||
# }
|
||||
# }
|
||||
|
||||
# location /api/v1/channels {
|
||||
# limit_req zone=invidious-apichannelsrl nodelay burst=32;
|
||||
# try_files $uri @upstream;
|
||||
# }
|
||||
|
||||
#
|
||||
# location /api/v1/captions {
|
||||
# try_files $uri @upstream;
|
||||
# }
|
||||
#
|
||||
|
||||
#location ~ ^/api/v1/channels/(.+)/shorts {
|
||||
# try_files $uri @upstream;
|
||||
#}
|
||||
|
||||
#location @fallback {
|
||||
# root /etc/nginx/errors;
|
||||
# try_files $uri /502.html = 502;
|
||||
#}
|
38
http.d/luna.conf
Normal file
38
http.d/luna.conf
Normal file
|
@ -0,0 +1,38 @@
|
|||
server {
|
||||
access_log /var/log/nginx/luna.access.log;
|
||||
error_log /var/log/nginx/luna.error.log;
|
||||
server_name luna.nadeko.net;
|
||||
include configs/listen.conf;
|
||||
# index index.php /_h5ai/public/index.php;
|
||||
# root /mnt/960gb_ssd/luna;
|
||||
include configs/general.conf;
|
||||
include configs/security.conf;
|
||||
# default_type "application/octet-stream";
|
||||
|
||||
# location / {
|
||||
# proxy_pass http://127.0.0.1:20001;
|
||||
# }
|
||||
|
||||
# location /_h5ai/private {
|
||||
# return 403;
|
||||
# }
|
||||
#
|
||||
# location ~ [^/]\.php(/|$) {
|
||||
# fastcgi_split_path_info ^(.+?\.php)(/.*)$;
|
||||
# if (!-f $document_root$fastcgi_script_name) {
|
||||
# return 404;
|
||||
# }
|
||||
# fastcgi_param HTTP_PROXY "";
|
||||
# fastcgi_pass php-fpm-8.1;
|
||||
# fastcgi_index index.php;
|
||||
# include fastcgi_params;
|
||||
# fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||
# fastcgi_param PATH_INFO $fastcgi_path_info;
|
||||
# add_header Access-Control-Allow-Origin *;
|
||||
# }
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:10008;
|
||||
include configs/proxy.conf;
|
||||
}
|
||||
}
|
109
http.d/matrix.conf
Normal file
109
http.d/matrix.conf
Normal file
|
@ -0,0 +1,109 @@
|
|||
# server {
|
||||
# server_name matrix.zzls.xyz;
|
||||
# include configs/listen.conf;
|
||||
# include configs/general.conf;
|
||||
# include configs/robotsNone.conf;
|
||||
# include configs/security.conf;
|
||||
#
|
||||
# location /.well-known/matrix/server {
|
||||
# return 200 '{ "m.server": "matrix.zzls.xyz:8448" }';
|
||||
# }
|
||||
#
|
||||
# location /.well-known/matrix/client {
|
||||
# default_type application/json;
|
||||
# add_header Access-Control-Allow-Origin '*';
|
||||
# return 200 '{ "m.homeserver": { "base_url": "https://matrix.zzls.xyz" }, "org.matrix.msc3575.proxy": {"url": "https://matrix.zzls.xyz"}}';
|
||||
# }
|
||||
#
|
||||
# #location ~ ^/(client/|_matrix/client/unstable/org.matrix.msc3575/sync) {
|
||||
# # proxy_pass http://127.0.0.1:40022;
|
||||
# # proxy_set_header X-Forwarded-For $remote_addr;
|
||||
# # proxy_set_header X-Forwarded-Proto $scheme;
|
||||
# # proxy_set_header Host $host;
|
||||
# #}
|
||||
#
|
||||
# location ~ ^(/_matrix|/_synapse/client|/health|/_synapse/metrics) {
|
||||
# proxy_pass http://matrix;
|
||||
# include configs/proxy.conf;
|
||||
# client_max_body_size 64M;
|
||||
# }
|
||||
#
|
||||
# # QUIC
|
||||
# include configs/http3.conf;
|
||||
#
|
||||
#
|
||||
# listen 8448 ssl;
|
||||
# listen 8448 quic;
|
||||
#
|
||||
# }
|
||||
#
|
||||
server {
|
||||
server_name matrix.nadeko.net;
|
||||
include configs/listen.conf;
|
||||
include configs/general.conf;
|
||||
include configs/robotsNone.conf;
|
||||
include configs/security.conf;
|
||||
|
||||
location /.well-known/matrix/support {
|
||||
default_type application/json;
|
||||
add_header Access-Control-Allow-Origin '*';
|
||||
return 200 '{"contacts": [{"matrix_id": "@fijxu:nadeko.net","email_address": "admin@nadeko.net","role": "m.role.admin"},{"email_address": "admin@nadeko.net","role": "m.role.security"}],"support_page": "https://nadeko.net/contact" }';
|
||||
}
|
||||
|
||||
location /.well-known/matrix/server {
|
||||
default_type application/json;
|
||||
add_header Access-Control-Allow-Origin '*';
|
||||
return 200 '{ "m.server": "matrix.nadeko.net:443" }';
|
||||
}
|
||||
|
||||
location /.well-known/matrix/client {
|
||||
default_type application/json;
|
||||
add_header Access-Control-Allow-Origin '*';
|
||||
return 200 '{ "m.homeserver": { "base_url": "https://matrix.nadeko.net" }}';
|
||||
}
|
||||
|
||||
location ~ ^(/_matrix|/_synapse/client|/health|/_synapse/metrics|/_synapse/admin) {
|
||||
proxy_pass http://127.0.0.1:10022;
|
||||
include configs/proxy.conf;
|
||||
client_max_body_size 32M;
|
||||
}
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:10023;
|
||||
include configs/proxy.conf;
|
||||
client_max_body_size 32M;
|
||||
}
|
||||
|
||||
#listen 443 ssl reuseport;
|
||||
#listen 8448 ssl default_server reuseport;
|
||||
|
||||
# Port that is only accessed via netmaker, used to proxy the traffic to another
|
||||
# server
|
||||
}
|
||||
|
||||
server {
|
||||
server_name synapse-admin.nadeko.net;
|
||||
include configs/listen.conf;
|
||||
include configs/general.conf;
|
||||
include configs/security.conf;
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:10025;
|
||||
include configs/proxy.conf;
|
||||
}
|
||||
}
|
||||
|
||||
# server {
|
||||
# server_name matrix-auth.nadeko.net;
|
||||
# include configs/listen.conf;
|
||||
# include configs/general.conf;
|
||||
# include configs/security.conf;
|
||||
#
|
||||
# location / {
|
||||
# proxy_pass http://127.0.0.1:10026;
|
||||
# include configs/proxy.conf;
|
||||
# }
|
||||
#
|
||||
#
|
||||
#
|
||||
# }
|
16
http.d/pbin.conf
Normal file
16
http.d/pbin.conf
Normal file
|
@ -0,0 +1,16 @@
|
|||
# CLEARNET
|
||||
server {
|
||||
server_name
|
||||
pbin.nadeko.net
|
||||
pbin.nadekonw7plitnjuawu6ytjsl7jlglk2t6pyq6eftptmiv3dvqndwvyd.onion;
|
||||
include configs/listen.conf;
|
||||
include configs/general.conf;
|
||||
include configs/security.conf;
|
||||
include configs/robotsNone.conf;
|
||||
client_max_body_size 128M;
|
||||
|
||||
location / {
|
||||
proxy_pass http://privatebin;
|
||||
include configs/proxy.conf;
|
||||
}
|
||||
}
|
23
http.d/redlib.conf
Normal file
23
http.d/redlib.conf
Normal file
|
@ -0,0 +1,23 @@
|
|||
server {
|
||||
server_name
|
||||
redlib.nadeko.net
|
||||
redlib.nadekonw7plitnjuawu6ytjsl7jlglk2t6pyq6eftptmiv3dvqndwvyd.onion;
|
||||
include configs/listen.conf;
|
||||
include configs/general.conf;
|
||||
# Security headers are added by redlib!
|
||||
#include configs/security.conf;
|
||||
include configs/robotsNone.conf;
|
||||
|
||||
location / {
|
||||
if ($blocked_agent = 1) {
|
||||
return 200 "
|
||||
1. The Industrial Revolution and its consequences have been a disaster for the human race. They have greatly increased the life-expectancy of those of us who live in “advanced” countries, but they have destabilized society, have made life unfulfilling, have subjected human beings to indignities, have led to widespread psychological suffering (in the Third World to physical suffering as well) and have inflicted severe damage on the natural world. The continued development of technology will worsen the situation. It will certainly subject human beings to greater indignities and inflict greater damage on the natural world, it will probably lead to greater social disruption and psychological suffering, and it may lead to increased physical suffering even in “advanced” countries.
|
||||
";
|
||||
}
|
||||
proxy_pass http://redlib;
|
||||
proxy_hide_header Strict-Transport-Security;
|
||||
add_header Permissions-Policy "accelerometer=(), ambient-light-sensor=(), autoplay=(), battery=(), camera=(), cross-origin-isolated=(), display-capture=(), document-domain=(), encrypted-media=(), execution-while-not-rendered=(), execution-while-out-of-viewport=(), fullscreen=(self), geolocation=(), gyroscope=(), interest-cohort=(), magnetometer=(), microphone=(), midi=(), navigation-override=(), payment=(), picture-in-picture=(), publickey-credentials-get=(), screen-wake-lock=(), sync-xhr=(), usb=(), web-share=(), xr-spatial-tracking=()" always;
|
||||
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
|
||||
include configs/proxy.conf;
|
||||
}
|
||||
}
|
15
http.d/rimgo.conf
Normal file
15
http.d/rimgo.conf
Normal file
|
@ -0,0 +1,15 @@
|
|||
server {
|
||||
server_name
|
||||
ri.nadeko.net
|
||||
ri.nadekonw7plitnjuawu6ytjsl7jlglk2t6pyq6eftptmiv3dvqndwvyd.onion;
|
||||
include configs/listen.conf;
|
||||
include configs/general.conf;
|
||||
# Security headers added by rimgo!
|
||||
#include configs/security.conf;
|
||||
include configs/robots.conf;
|
||||
|
||||
location / {
|
||||
proxy_pass http://rimgo;
|
||||
include configs/proxy.conf;
|
||||
}
|
||||
}
|
72
lua/502-invidious.lua
Normal file
72
lua/502-invidious.lua
Normal file
|
@ -0,0 +1,72 @@
|
|||
ngx.header.content_type = 'text/html';
|
||||
local backend_num = ngx.shared.servers:get("inv-backends");
|
||||
-- local backend_num = 4;
|
||||
|
||||
local function generate_backend_list()
|
||||
local html = ""
|
||||
for i=1, backend_num do
|
||||
html = html .. "<p><a href='/switchbackend?backend_id=" .. i .. "'>Backend " .. i .. "</a></p>"
|
||||
end
|
||||
return html
|
||||
end
|
||||
|
||||
ngx.say([[
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="refresh" content="2">
|
||||
<style>
|
||||
html {
|
||||
background-color: #111111;
|
||||
color: #FFFFFF;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
a:link {
|
||||
color: rgb(255, 180, 255);
|
||||
}
|
||||
|
||||
a:visited {
|
||||
color: rgb(255, 180, 255);
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 20px 20px;
|
||||
margin: 0 auto;
|
||||
max-width: 780px;
|
||||
width: 70%;
|
||||
min-height: 60vh;
|
||||
word-wrap: break-word;
|
||||
line-height: 1.5em;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: red;
|
||||
}
|
||||
</style>
|
||||
<title>502 Bad Gateway</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="content">
|
||||
<h1>502 Bad Gateway</h1>
|
||||
<p>Looks like you got a dead backend...</p>
|
||||
<p>If you don't get an alive backend after 1 minute, it's because something is wrong...</p>
|
||||
<p><b>Refreshing automatically in 2 seconds</b></p>
|
||||
<p>Do you want to switch to another backend?</p>
|
||||
]] .. generate_backend_list() .. [[
|
||||
<div style="max-width: 60%;">
|
||||
<p>
|
||||
<i>If this page doesn't redirect you to any alive backend
|
||||
then there is no available backends alive.
|
||||
</i>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
]])
|
72
lua/503-invidious.lua
Normal file
72
lua/503-invidious.lua
Normal file
|
@ -0,0 +1,72 @@
|
|||
ngx.header.content_type = 'text/html';
|
||||
-- local backend_num = ngx.shared.servers:get("invidious-servers");
|
||||
local backend_num = 4;
|
||||
|
||||
local function generate_backend_list()
|
||||
local html = ""
|
||||
for i=1, backend_num do
|
||||
html = html .. "<p><a href='/switchbackend?backend_id=" .. i .. "'>Backend " .. i .. "</a></p>"
|
||||
end
|
||||
return html
|
||||
end
|
||||
|
||||
ngx.say([[
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="refresh" content="2">
|
||||
<style>
|
||||
html {
|
||||
background-color: #111111;
|
||||
color: #FFFFFF;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
a:link {
|
||||
color: rgb(255, 180, 255);
|
||||
}
|
||||
|
||||
a:visited {
|
||||
color: rgb(255, 180, 255);
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 20px 20px;
|
||||
margin: 0 auto;
|
||||
max-width: 780px;
|
||||
width: 70%;
|
||||
min-height: 60vh;
|
||||
word-wrap: break-word;
|
||||
line-height: 1.5em;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #ffc642;
|
||||
}
|
||||
</style>
|
||||
<title>503 Service Unavailable</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="content">
|
||||
<h1>503 Service Unavailable</h1>
|
||||
<p>Invidious is currently restarting</p>
|
||||
<p>If you don't get an alive backend after 1 minute, it's because something is wrong...</p>
|
||||
<p><b>Refreshing automatically in 2 seconds</b></p>
|
||||
<p>Do you want to switch to another backend?</p>
|
||||
]] .. generate_backend_list() .. [[
|
||||
<div style="max-width: 60%;">
|
||||
<p>
|
||||
<i>If this page doesn't redirect you to any alive backend
|
||||
then there is no available backends alive.
|
||||
</i>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
]])
|
9
lua/init.lua
Normal file
9
lua/init.lua
Normal file
|
@ -0,0 +1,9 @@
|
|||
cs = require "crowdsec"
|
||||
|
||||
local ok, err = cs.init("/etc/crowdsec/bouncers/crowdsec-nginx-bouncer.conf", "crowdsec-nginx-bouncer/v1.0.8")
|
||||
if ok == nil then
|
||||
ngx.log(ngx.ERR, "[Crowdsec] " .. err)
|
||||
error()
|
||||
end
|
||||
|
||||
ngx.log(ngx.ALERT, "[Crowdsec] Initialisation done")
|
115
lua/invidious-sticky.lua
Normal file
115
lua/invidious-sticky.lua
Normal file
|
@ -0,0 +1,115 @@
|
|||
-- Based on https://github.com/Klaessen/openresty-loadbalancers/blob/main/sticky-balancer.lua
|
||||
|
||||
local _M = {}
|
||||
|
||||
local balancer = require "ngx.balancer"
|
||||
local cookie_name = "INVIDIOUS_SERVER_ID"
|
||||
local servers
|
||||
local weighted_servers
|
||||
local domain
|
||||
|
||||
-- Generate a weighted server list based on weights
|
||||
local function generate_weighted_server_list(servers)
|
||||
local weighted_servers = {}
|
||||
for _, server in ipairs(servers) do
|
||||
for i = 1, server.weight do
|
||||
table.insert(weighted_servers, server)
|
||||
end
|
||||
end
|
||||
return weighted_servers
|
||||
end
|
||||
|
||||
-- Hash function to select server
|
||||
local function hash(key, num_buckets)
|
||||
local hash = ngx.crc32_long(key)
|
||||
return (hash % num_buckets) + 1
|
||||
end
|
||||
|
||||
-- Select server based on cookie or assign a new one
|
||||
local function select_server()
|
||||
local cookie = ngx.var["cookie_" .. cookie_name]
|
||||
local server_index
|
||||
|
||||
|
||||
math.randomseed(os.time())
|
||||
|
||||
if cookie then
|
||||
server_index = tonumber(cookie)
|
||||
ngx.header["X-Server-Id"] = server_index
|
||||
else
|
||||
server_index = math.random(#servers)
|
||||
-- https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#partitioned
|
||||
ngx.header["Set-Cookie"] = cookie_name .. "=" .. server_index .. "; domain=" .. domain .. "; Path=/; HttpOnly; SameSite=None; Secure; Partitioned"
|
||||
ngx.header["X-Server-Id"] = server_index
|
||||
end
|
||||
|
||||
local server = weighted_servers[server_index]
|
||||
return server
|
||||
end
|
||||
|
||||
function _M.run(upstreams)
|
||||
domain = ".nadeko.net"
|
||||
local host = ngx.req.get_headers()["Host"]
|
||||
|
||||
-- TOR Support
|
||||
if string.match(host, ".onion") then
|
||||
domain = host
|
||||
end
|
||||
|
||||
servers = upstreams
|
||||
weighted_servers = generate_weighted_server_list(servers)
|
||||
local ok, err
|
||||
local args = ngx.req.get_uri_args()
|
||||
|
||||
if args then
|
||||
for key, server_index in pairs(args) do
|
||||
if key == "backend" then
|
||||
server_index = tonumber(server_index)
|
||||
|
||||
-- To redirect to another backend if user inputs a backend that doesn't exists
|
||||
-- Ex: ?backend=5 will give you X-Server-Id=1 (Backend 1)
|
||||
val = val % #servers
|
||||
if val == 0 then
|
||||
val = #servers
|
||||
end
|
||||
|
||||
ok, err = balancer.set_current_peer(servers[val][1], servers[val][2])
|
||||
|
||||
if not ok then
|
||||
-- ngx.say("No peer available")
|
||||
return ngx.exit(502)
|
||||
end
|
||||
|
||||
ngx.header["Set-Cookie"] = cookie_name .. "=" .. server_index .. "; domain=" .. domain .. "; Path=/; HttpOnly; SameSite=None; Secure; Partitioned"
|
||||
ngx.header["X-Server-Id"] = server_index
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local server = select_server()
|
||||
if not server then
|
||||
-- ngx.say("No peer available")
|
||||
return ngx.exit(502)
|
||||
end
|
||||
|
||||
if string.match(server[1], 'unix:') then
|
||||
ok, err = balancer.set_current_peer(server[1])
|
||||
else
|
||||
ok, err = balancer.set_current_peer(server[1], server[2])
|
||||
end
|
||||
|
||||
if not ok then
|
||||
ngx.log(ngx.ERR, "Failed to set the current peer: ", err)
|
||||
-- ngx.say("Failed to set the current peer")
|
||||
return ngx.exit(500)
|
||||
end
|
||||
|
||||
-- https://github.com/openresty/lua-resty-core/blob/master/lib/ngx/balancer.md#enable_keepalive
|
||||
ok, err = balancer.enable_keepalive(60, 1000)
|
||||
if not ok then
|
||||
ngx.log(ngx.ERR, "failed to set keepalive: ", err)
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
return _M
|
769
lua/plugins/crowdsec/crowdsec.lua
Normal file
769
lua/plugins/crowdsec/crowdsec.lua
Normal file
|
@ -0,0 +1,769 @@
|
|||
package.path = package.path .. ";./?.lua"
|
||||
|
||||
local config = require "plugins.crowdsec.config"
|
||||
local iputils = require "plugins.crowdsec.iputils"
|
||||
local http = require "resty.http"
|
||||
local cjson = require "cjson"
|
||||
local captcha = require "plugins.crowdsec.captcha"
|
||||
local flag = require "plugins.crowdsec.flag"
|
||||
local utils = require "plugins.crowdsec.utils"
|
||||
local ban = require "plugins.crowdsec.ban"
|
||||
local url = require "plugins.crowdsec.url"
|
||||
local bit
|
||||
if _VERSION == "Lua 5.1" then bit = require "bit" else bit = require "bit32" end
|
||||
|
||||
-- contain runtime = {}
|
||||
local runtime = {}
|
||||
-- remediations are stored in cache as int (shared dict tags)
|
||||
-- we need to translate IDs to text with this.
|
||||
runtime.remediations = {}
|
||||
runtime.remediations["1"] = "ban"
|
||||
runtime.remediations["2"] = "captcha"
|
||||
|
||||
|
||||
runtime.timer_started = false
|
||||
|
||||
local csmod = {}
|
||||
|
||||
local PASSTHROUGH = "passthrough"
|
||||
local DENY = "deny"
|
||||
|
||||
local APPSEC_API_KEY_HEADER = "x-crowdsec-appsec-api-key"
|
||||
local APPSEC_IP_HEADER = "x-crowdsec-appsec-ip"
|
||||
local APPSEC_HOST_HEADER = "x-crowdsec-appsec-host"
|
||||
local APPSEC_VERB_HEADER = "x-crowdsec-appsec-verb"
|
||||
local APPSEC_URI_HEADER = "x-crowdsec-appsec-uri"
|
||||
local APPSEC_USER_AGENT_HEADER = "x-crowdsec-appsec-user-agent"
|
||||
local REMEDIATION_API_KEY_HEADER = 'x-api-key'
|
||||
|
||||
|
||||
-- init function
|
||||
function csmod.init(configFile, userAgent)
|
||||
local conf, err = config.loadConfig(configFile)
|
||||
if conf == nil then
|
||||
return nil, err
|
||||
end
|
||||
runtime.conf = conf
|
||||
runtime.userAgent = userAgent
|
||||
runtime.cache = ngx.shared.crowdsec_cache
|
||||
runtime.fallback = runtime.conf["FALLBACK_REMEDIATION"]
|
||||
|
||||
if runtime.conf["ENABLED"] == "false" then
|
||||
return "Disabled", nil
|
||||
end
|
||||
|
||||
if runtime.conf["REDIRECT_LOCATION"] == "/" then
|
||||
ngx.log(ngx.ERR, "redirect location is set to '/' this will lead into infinite redirection")
|
||||
end
|
||||
|
||||
local captcha_ok = true
|
||||
local err = captcha.New(runtime.conf["SITE_KEY"], runtime.conf["SECRET_KEY"], runtime.conf["CAPTCHA_TEMPLATE_PATH"], runtime.conf["CAPTCHA_PROVIDER"])
|
||||
if err ~= nil then
|
||||
ngx.log(ngx.ERR, "error loading captcha plugin: " .. err)
|
||||
captcha_ok = false
|
||||
end
|
||||
local succ, err, forcible = runtime.cache:set("captcha_ok", captcha_ok)
|
||||
if not succ then
|
||||
ngx.log(ngx.ERR, "failed to add captcha state key in cache: "..err)
|
||||
end
|
||||
if forcible then
|
||||
ngx.log(ngx.ERR, "Lua shared dict (crowdsec cache) is full, please increase dict size in config")
|
||||
end
|
||||
|
||||
|
||||
local err = ban.new(runtime.conf["BAN_TEMPLATE_PATH"], runtime.conf["REDIRECT_LOCATION"], runtime.conf["RET_CODE"])
|
||||
if err ~= nil then
|
||||
ngx.log(ngx.ERR, "error loading ban plugins: " .. err)
|
||||
end
|
||||
|
||||
if runtime.conf["REDIRECT_LOCATION"] ~= "" then
|
||||
table.insert(runtime.conf["EXCLUDE_LOCATION"], runtime.conf["REDIRECT_LOCATION"])
|
||||
end
|
||||
|
||||
if runtime.conf["SSL_VERIFY"] == "false" then
|
||||
runtime.conf["SSL_VERIFY"] = false
|
||||
else
|
||||
runtime.conf["SSL_VERIFY"] = true
|
||||
end
|
||||
|
||||
if runtime.conf["ALWAYS_SEND_TO_APPSEC"] == "false" then
|
||||
runtime.conf["ALWAYS_SEND_TO_APPSEC"] = false
|
||||
else
|
||||
runtime.conf["ALWAYS_SEND_TO_APPSEC"] = true
|
||||
end
|
||||
|
||||
runtime.conf["APPSEC_ENABLED"] = false
|
||||
|
||||
if runtime.conf["APPSEC_URL"] ~= "" then
|
||||
local u = url.parse(runtime.conf["APPSEC_URL"])
|
||||
runtime.conf["APPSEC_ENABLED"] = true
|
||||
runtime.conf["APPSEC_HOST"] = u.host
|
||||
if u.port ~= nil then
|
||||
runtime.conf["APPSEC_HOST"] = runtime.conf["APPSEC_HOST"] .. ":" .. u.port
|
||||
end
|
||||
ngx.log(ngx.ERR, "APPSEC is enabled on '" .. runtime.conf["APPSEC_HOST"] .. "'")
|
||||
end
|
||||
|
||||
|
||||
-- if stream mode, add callback to stream_query and start timer
|
||||
if runtime.conf["MODE"] == "stream" then
|
||||
local succ, err, forcible = runtime.cache:set("startup", true)
|
||||
if not succ then
|
||||
ngx.log(ngx.ERR, "failed to add startup key in cache: "..err)
|
||||
end
|
||||
if forcible then
|
||||
ngx.log(ngx.ERR, "Lua shared dict (crowdsec cache) is full, please increase dict size in config")
|
||||
end
|
||||
local succ, err, forcible = runtime.cache:set("first_run", true)
|
||||
if not succ then
|
||||
ngx.log(ngx.ERR, "failed to add first_run key in cache: "..err)
|
||||
end
|
||||
if forcible then
|
||||
ngx.log(ngx.ERR, "Lua shared dict (crowdsec cache) is full, please increase dict size in config")
|
||||
end
|
||||
end
|
||||
|
||||
if runtime.conf["API_URL"] == "" and runtime.conf["APPSEC_URL"] == "" then
|
||||
ngx.log(ngx.ERR, "Neither API_URL or APPSEC_URL are defined, remediation component will not do anything")
|
||||
end
|
||||
|
||||
if runtime.conf["API_URL"] == "" and runtime.conf["APPSEC_URL"] ~= "" then
|
||||
ngx.log(ngx.ERR, "Only APPSEC_URL is defined, local API decisions will be ignored")
|
||||
end
|
||||
|
||||
|
||||
|
||||
return true, nil
|
||||
end
|
||||
|
||||
|
||||
function csmod.validateCaptcha(captcha_res, remote_ip)
|
||||
return captcha.ValidateMCaptcha(captcha_res, remote_ip)
|
||||
end
|
||||
|
||||
|
||||
local function get_remediation_http_request(link)
|
||||
local httpc = http.new()
|
||||
if runtime.conf['MODE'] == 'stream' then
|
||||
httpc:set_timeout(runtime.conf['STREAM_REQUEST_TIMEOUT'])
|
||||
else
|
||||
httpc:set_timeout(runtime.conf['REQUEST_TIMEOUT'])
|
||||
end
|
||||
local res, err = httpc:request_uri(link, {
|
||||
method = "GET",
|
||||
headers = {
|
||||
['Connection'] = 'close',
|
||||
[REMEDIATION_API_KEY_HEADER] = runtime.conf["API_KEY"],
|
||||
['User-Agent'] = runtime.userAgent
|
||||
},
|
||||
ssl_verify = runtime.conf["SSL_VERIFY"]
|
||||
})
|
||||
httpc:close()
|
||||
return res, err
|
||||
end
|
||||
|
||||
local function parse_duration(duration)
|
||||
local match, err = ngx.re.match(duration, "^((?<hours>[0-9]+)h)?((?<minutes>[0-9]+)m)?(?<seconds>[0-9]+)")
|
||||
local ttl = 0
|
||||
if not match then
|
||||
if err then
|
||||
return ttl, err
|
||||
end
|
||||
end
|
||||
if match["hours"] ~= nil and match["hours"] ~= false then
|
||||
local hours = tonumber(match["hours"])
|
||||
ttl = ttl + (hours * 3600)
|
||||
end
|
||||
if match["minutes"] ~= nil and match["minutes"] ~= false then
|
||||
local minutes = tonumber(match["minutes"])
|
||||
ttl = ttl + (minutes * 60)
|
||||
end
|
||||
if match["seconds"] ~= nil and match["seconds"] ~= false then
|
||||
local seconds = tonumber(match["seconds"])
|
||||
ttl = ttl + seconds
|
||||
end
|
||||
return ttl, nil
|
||||
end
|
||||
|
||||
local function get_remediation_id(remediation)
|
||||
for key, value in pairs(runtime.remediations) do
|
||||
if value == remediation then
|
||||
return tonumber(key)
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
local function item_to_string(item, scope)
|
||||
local ip, cidr, ip_version
|
||||
if scope:lower() == "ip" then
|
||||
ip = item
|
||||
end
|
||||
if scope:lower() == "range" then
|
||||
ip, cidr = iputils.splitRange(item, scope)
|
||||
end
|
||||
|
||||
local ip_network_address, is_ipv4 = iputils.parseIPAddress(ip)
|
||||
if ip_network_address == nil then
|
||||
return nil
|
||||
end
|
||||
if is_ipv4 then
|
||||
ip_version = "ipv4"
|
||||
if cidr == nil then
|
||||
cidr = 32
|
||||
end
|
||||
else
|
||||
ip_version = "ipv6"
|
||||
ip_network_address = ip_network_address.uint32[3]..":"..ip_network_address.uint32[2]..":"..ip_network_address.uint32[1]..":"..ip_network_address.uint32[0]
|
||||
if cidr == nil then
|
||||
cidr = 128
|
||||
end
|
||||
end
|
||||
|
||||
if ip_version == nil then
|
||||
return "normal_"..item
|
||||
end
|
||||
local ip_netmask = iputils.cidrToInt(cidr, ip_version)
|
||||
return ip_version.."_"..ip_netmask.."_"..ip_network_address
|
||||
end
|
||||
|
||||
local function set_refreshing(value)
|
||||
local succ, err, forcible = runtime.cache:set("refreshing", value)
|
||||
if not succ then
|
||||
error("Failed to set refreshing key in cache: "..err)
|
||||
end
|
||||
if forcible then
|
||||
ngx.log(ngx.ERR, "Lua shared dict (crowdsec cache) is full, please increase dict size in config")
|
||||
end
|
||||
end
|
||||
|
||||
local function stream_query(premature)
|
||||
-- As this function is running inside coroutine (with ngx.timer.at),
|
||||
-- we need to raise error instead of returning them
|
||||
|
||||
if runtime.conf["API_URL"] == "" then
|
||||
return
|
||||
end
|
||||
|
||||
ngx.log(ngx.DEBUG, "running timers: " .. tostring(ngx.timer.running_count()) .. " | pending timers: " .. tostring(ngx.timer.pending_count()))
|
||||
|
||||
if premature then
|
||||
ngx.log(ngx.DEBUG, "premature run of the timer, returning")
|
||||
return
|
||||
end
|
||||
|
||||
local refreshing = runtime.cache:get("refreshing")
|
||||
|
||||
if refreshing == true then
|
||||
ngx.log(ngx.DEBUG, "another worker is refreshing the data, returning")
|
||||
local ok, err = ngx.timer.at(runtime.conf["UPDATE_FREQUENCY"], stream_query)
|
||||
if not ok then
|
||||
error("Failed to create the timer: " .. (err or "unknown"))
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
local last_refresh = runtime.cache:get("last_refresh")
|
||||
if last_refresh ~= nil then
|
||||
-- local last_refresh_time = tonumber(last_refresh)
|
||||
local now = ngx.time()
|
||||
if now - last_refresh < runtime.conf["UPDATE_FREQUENCY"] then
|
||||
ngx.log(ngx.DEBUG, "last refresh was less than " .. runtime.conf["UPDATE_FREQUENCY"] .. " seconds ago, returning")
|
||||
local ok, err = ngx.timer.at(runtime.conf["UPDATE_FREQUENCY"], stream_query)
|
||||
if not ok then
|
||||
error("Failed to create the timer: " .. (err or "unknown"))
|
||||
end
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
set_refreshing(true)
|
||||
|
||||
local is_startup = runtime.cache:get("startup")
|
||||
ngx.log(ngx.DEBUG, "Stream Query from worker : " .. tostring(ngx.worker.id()) .. " with startup "..tostring(is_startup) .. " | premature: " .. tostring(premature))
|
||||
local link = runtime.conf["API_URL"] .. "/v1/decisions/stream?startup=" .. tostring(is_startup)
|
||||
local res, err = get_remediation_http_request(link)
|
||||
if not res then
|
||||
local ok, err2 = ngx.timer.at(runtime.conf["UPDATE_FREQUENCY"], stream_query)
|
||||
if not ok then
|
||||
set_refreshing(false)
|
||||
error("Failed to create the timer: " .. (err2 or "unknown"))
|
||||
end
|
||||
set_refreshing(false)
|
||||
error("request failed: ".. err)
|
||||
end
|
||||
|
||||
local succ, err, forcible = runtime.cache:set("last_refresh", ngx.time())
|
||||
if not succ then
|
||||
error("Failed to set last_refresh key in cache: "..err)
|
||||
end
|
||||
if forcible then
|
||||
ngx.log(ngx.ERR, "Lua shared dict (crowdsec cache) is full, please increase dict size in config")
|
||||
end
|
||||
|
||||
local status = res.status
|
||||
local body = res.body
|
||||
|
||||
ngx.log(ngx.DEBUG, "Response:" .. tostring(status) .. " | " .. tostring(body))
|
||||
|
||||
if status~=200 then
|
||||
local ok, err = ngx.timer.at(runtime.conf["UPDATE_FREQUENCY"], stream_query)
|
||||
if not ok then
|
||||
set_refreshing(false)
|
||||
error("Failed to create the timer: " .. (err or "unknown"))
|
||||
end
|
||||
set_refreshing(false)
|
||||
error("HTTP error while request to Local API '" .. status .. "' with message (" .. tostring(body) .. ")")
|
||||
end
|
||||
|
||||
local decisions = cjson.decode(body)
|
||||
-- process deleted decisions
|
||||
if type(decisions.deleted) == "table" then
|
||||
for i, decision in pairs(decisions.deleted) do
|
||||
if decision.type == "captcha" then
|
||||
runtime.cache:delete("captcha_" .. decision.value)
|
||||
end
|
||||
local key = item_to_string(decision.value, decision.scope)
|
||||
runtime.cache:delete(key)
|
||||
ngx.log(ngx.DEBUG, "Deleting '" .. key .. "'")
|
||||
end
|
||||
end
|
||||
|
||||
-- process new decisions
|
||||
if type(decisions.new) == "table" then
|
||||
for i, decision in pairs(decisions.new) do
|
||||
if runtime.conf["BOUNCING_ON_TYPE"] == decision.type or runtime.conf["BOUNCING_ON_TYPE"] == "all" then
|
||||
local ttl, err = parse_duration(decision.duration)
|
||||
if err ~= nil then
|
||||
ngx.log(ngx.ERR, "[Crowdsec] failed to parse ban duration '" .. decision.duration .. "' : " .. err)
|
||||
end
|
||||
local remediation_id = get_remediation_id(decision.type)
|
||||
if remediation_id == nil then
|
||||
remediation_id = get_remediation_id(runtime.fallback)
|
||||
end
|
||||
local key = item_to_string(decision.value, decision.scope)
|
||||
local succ, err, forcible = runtime.cache:set(key, false, ttl, remediation_id)
|
||||
if not succ then
|
||||
ngx.log(ngx.ERR, "failed to add ".. decision.value .." : "..err)
|
||||
end
|
||||
if forcible then
|
||||
ngx.log(ngx.ERR, "Lua shared dict (crowdsec cache) is full, please increase dict size in config")
|
||||
end
|
||||
ngx.log(ngx.DEBUG, "Adding '" .. key .. "' in cache for '" .. ttl .. "' seconds")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- not startup anymore after first callback
|
||||
local succ, err, forcible = runtime.cache:set("startup", false)
|
||||
if not succ then
|
||||
ngx.log(ngx.ERR, "failed to set startup key in cache: "..err)
|
||||
end
|
||||
if forcible then
|
||||
ngx.log(ngx.ERR, "Lua shared dict (crowdsec cache) is full, please increase dict size in config")
|
||||
end
|
||||
|
||||
|
||||
local ok, err = ngx.timer.at(runtime.conf["UPDATE_FREQUENCY"], stream_query)
|
||||
if not ok then
|
||||
set_refreshing(false)
|
||||
error("Failed to create the timer: " .. (err or "unknown"))
|
||||
end
|
||||
|
||||
set_refreshing(false)
|
||||
ngx.log(ngx.DEBUG, "end of stream_query")
|
||||
return nil
|
||||
end
|
||||
|
||||
local function live_query(ip)
|
||||
if runtime.conf["API_URL"] == "" then
|
||||
return true, nil, nil
|
||||
end
|
||||
local link = runtime.conf["API_URL"] .. "/v1/decisions?ip=" .. ip
|
||||
local res, err = get_remediation_http_request(link)
|
||||
if not res then
|
||||
return true, nil, "request failed: ".. err
|
||||
end
|
||||
|
||||
local status = res.status
|
||||
local body = res.body
|
||||
if status~=200 then
|
||||
return true, nil, "Http error " .. status .. " while talking to LAPI (" .. link .. ")"
|
||||
end
|
||||
if body == "null" then -- no result from API, no decision for this IP
|
||||
-- set ip in cache and DON'T block it
|
||||
local key = item_to_string(ip, "ip")
|
||||
local succ, err, forcible = runtime.cache:set(key, true, runtime.conf["CACHE_EXPIRATION"], 1)
|
||||
if not succ then
|
||||
ngx.log(ngx.ERR, "failed to add ip '" .. ip .. "' in cache: "..err)
|
||||
end
|
||||
if forcible then
|
||||
ngx.log(ngx.ERR, "Lua shared dict (crowdsec cache) is full, please increase dict size in config")
|
||||
end
|
||||
return true, nil, nil
|
||||
end
|
||||
local decision = cjson.decode(body)[1]
|
||||
|
||||
if runtime.conf["BOUNCING_ON_TYPE"] == decision.type or runtime.conf["BOUNCING_ON_TYPE"] == "all" then
|
||||
local remediation_id = get_remediation_id(decision.type)
|
||||
if remediation_id == nil then
|
||||
remediation_id = get_remediation_id(runtime.fallback)
|
||||
end
|
||||
local key = item_to_string(decision.value, decision.scope)
|
||||
local succ, err, forcible = runtime.cache:set(key, false, runtime.conf["CACHE_EXPIRATION"], remediation_id)
|
||||
if not succ then
|
||||
ngx.log(ngx.ERR, "failed to add ".. decision.value .." : "..err)
|
||||
end
|
||||
if forcible then
|
||||
ngx.log(ngx.ERR, "Lua shared dict (crowdsec cache) is full, please increase dict size in config")
|
||||
end
|
||||
ngx.log(ngx.DEBUG, "Adding '" .. key .. "' in cache for '" .. runtime.conf["CACHE_EXPIRATION"] .. "' seconds")
|
||||
return false, decision.type, nil
|
||||
else
|
||||
return true, nil, nil
|
||||
end
|
||||
end
|
||||
|
||||
local function get_body()
|
||||
|
||||
-- the LUA module requires a content-length header to read a body for HTTP 2/3 requests, although it's not mandatory.
|
||||
-- This means that we will likely miss body, but AFAIK, there's no workaround for this.
|
||||
-- do not even try to read the body if there's no content-length as the LUA API will throw an error
|
||||
if ngx.req.http_version() >= 2 and ngx.var.http_content_length == nil then
|
||||
ngx.log(ngx.DEBUG, "No content-length header in request")
|
||||
return nil
|
||||
end
|
||||
ngx.req.read_body()
|
||||
local body = ngx.req.get_body_data()
|
||||
if body == nil then
|
||||
local bodyfile = ngx.req.get_body_file()
|
||||
if bodyfile then
|
||||
local fh, err = io.open(bodyfile, "r")
|
||||
if fh then
|
||||
body = fh:read("*a")
|
||||
fh:close()
|
||||
end
|
||||
end
|
||||
end
|
||||
return body
|
||||
end
|
||||
|
||||
function csmod.GetCaptchaTemplate()
|
||||
return captcha.GetTemplate()
|
||||
end
|
||||
|
||||
function csmod.GetCaptchaBackendKey()
|
||||
return captcha.GetCaptchaBackendKey()
|
||||
end
|
||||
|
||||
function csmod.SetupStream()
|
||||
-- if it stream mode and startup start timer
|
||||
if runtime.conf["API_URL"] == "" then
|
||||
return
|
||||
end
|
||||
ngx.log(ngx.DEBUG, "timer started: " .. tostring(runtime.timer_started) .. " in worker " .. tostring(ngx.worker.id()))
|
||||
if runtime.timer_started == false and runtime.conf["MODE"] == "stream" then
|
||||
local ok, err
|
||||
ok, err = ngx.timer.at(runtime.conf["UPDATE_FREQUENCY"], stream_query)
|
||||
if not ok then
|
||||
return true, nil, "Failed to create the timer: " .. (err or "unknown")
|
||||
end
|
||||
runtime.timer_started = true
|
||||
ngx.log(ngx.DEBUG, "Timer launched")
|
||||
end
|
||||
end
|
||||
|
||||
function csmod.allowIp(ip)
|
||||
if runtime.conf == nil then
|
||||
return true, nil, "Configuration is bad, cannot run properly"
|
||||
end
|
||||
|
||||
if runtime.conf["API_URL"] == "" then
|
||||
return true, nil, nil
|
||||
end
|
||||
|
||||
csmod.SetupStream()
|
||||
|
||||
local key = item_to_string(ip, "ip")
|
||||
if key == nil then
|
||||
return true, nil, "Check failed '" .. ip .. "' has no valid IP address"
|
||||
end
|
||||
local key_parts = {}
|
||||
for i in key.gmatch(key, "([^_]+)") do
|
||||
table.insert(key_parts, i)
|
||||
end
|
||||
|
||||
local key_type = key_parts[1]
|
||||
if key_type == "normal" then
|
||||
local in_cache, remediation_id = runtime.cache:get(key)
|
||||
if in_cache ~= nil then -- we have it in cache
|
||||
ngx.log(ngx.DEBUG, "'" .. key .. "' is in cache")
|
||||
return in_cache, runtime.remediations[tostring(remediation_id)], nil
|
||||
end
|
||||
end
|
||||
|
||||
local ip_network_address = key_parts[3]
|
||||
local netmasks = iputils.netmasks_by_key_type[key_type]
|
||||
for i, netmask in pairs(netmasks) do
|
||||
local item
|
||||
if key_type == "ipv4" then
|
||||
item = key_type.."_"..netmask.."_"..iputils.ipv4_band(ip_network_address, netmask)
|
||||
end
|
||||
if key_type == "ipv6" then
|
||||
item = key_type.."_"..table.concat(netmask, ":").."_"..iputils.ipv6_band(ip_network_address, netmask)
|
||||
end
|
||||
local in_cache, remediation_id = runtime.cache:get(item)
|
||||
if in_cache ~= nil then -- we have it in cache
|
||||
ngx.log(ngx.DEBUG, "'" .. key .. "' is in cache")
|
||||
return in_cache, runtime.remediations[tostring(remediation_id)], nil
|
||||
end
|
||||
end
|
||||
|
||||
-- if live mode, query lapi
|
||||
if runtime.conf["MODE"] == "live" then
|
||||
local ok, remediation, err = live_query(ip)
|
||||
return ok, remediation, err
|
||||
end
|
||||
return true, nil, nil
|
||||
end
|
||||
|
||||
|
||||
function csmod.AppSecCheck(ip)
|
||||
local httpc = http.new()
|
||||
httpc:set_timeouts(runtime.conf["APPSEC_CONNECT_TIMEOUT"], runtime.conf["APPSEC_SEND_TIMEOUT"], runtime.conf["APPSEC_PROCESS_TIMEOUT"])
|
||||
|
||||
local uri = ngx.var.request_uri
|
||||
local headers = ngx.req.get_headers()
|
||||
|
||||
-- overwrite headers with crowdsec appsec require headers
|
||||
headers[APPSEC_IP_HEADER] = ip
|
||||
headers[APPSEC_HOST_HEADER] = ngx.var.http_host
|
||||
headers[APPSEC_VERB_HEADER] = ngx.var.request_method
|
||||
headers[APPSEC_URI_HEADER] = uri
|
||||
headers[APPSEC_USER_AGENT_HEADER] = ngx.var.http_user_agent
|
||||
headers[APPSEC_API_KEY_HEADER] = runtime.conf["API_KEY"]
|
||||
|
||||
-- set CrowdSec APPSEC Host
|
||||
headers["host"] = runtime.conf["APPSEC_HOST"]
|
||||
|
||||
local ok, remediation, status_code = true, "allow", 200
|
||||
if runtime.conf["APPSEC_FAILURE_ACTION"] == DENY then
|
||||
ok = false
|
||||
remediation = runtime.conf["FALLBACK_REMEDIATION"]
|
||||
end
|
||||
|
||||
local method = "GET"
|
||||
|
||||
local body = get_body()
|
||||
if body ~= nil then
|
||||
if #body > 0 then
|
||||
method = "POST"
|
||||
if headers["content-length"] == nil then
|
||||
headers["content-length"] = tostring(#body)
|
||||
end
|
||||
end
|
||||
else
|
||||
headers["content-length"] = nil
|
||||
end
|
||||
|
||||
local res, err = httpc:request_uri(runtime.conf["APPSEC_URL"], {
|
||||
method = method,
|
||||
headers = headers,
|
||||
body = body,
|
||||
ssl_verify = runtime.conf["SSL_VERIFY"],
|
||||
})
|
||||
httpc:close()
|
||||
|
||||
if err ~= nil then
|
||||
ngx.log(ngx.ERR, "Fallback because of err: " .. err)
|
||||
return ok, remediation, status_code, err
|
||||
end
|
||||
|
||||
if res.status == 200 then
|
||||
ok = true
|
||||
remediation = "allow"
|
||||
elseif res.status == 403 then
|
||||
ok = false
|
||||
ngx.log(ngx.DEBUG, "Appsec body response: " .. res.body)
|
||||
local response = cjson.decode(res.body)
|
||||
remediation = response.action
|
||||
if response.http_status ~= nil then
|
||||
ngx.log(ngx.DEBUG, "Got status code from APPSEC: " .. response.http_status)
|
||||
status_code = response.http_status
|
||||
else
|
||||
status_code = ngx.HTTP_FORBIDDEN
|
||||
end
|
||||
elseif res.status == 401 then
|
||||
ngx.log(ngx.ERR, "Unauthenticated request to APPSEC")
|
||||
else
|
||||
ngx.log(ngx.ERR, "Bad request to APPSEC (" .. res.status .. "): " .. res.body)
|
||||
end
|
||||
|
||||
return ok, remediation, status_code, err
|
||||
|
||||
end
|
||||
|
||||
function csmod.Allow(ip)
|
||||
if runtime.conf["ENABLED"] == "false" then
|
||||
ngx.exit(ngx.DECLINED)
|
||||
end
|
||||
|
||||
if runtime.conf["ENABLE_INTERNAL"] == "false" and ngx.req.is_internal() then
|
||||
ngx.exit(ngx.DECLINED)
|
||||
end
|
||||
|
||||
local remediationSource = flag.BOUNCER_SOURCE
|
||||
local ret_code = nil
|
||||
|
||||
if utils.table_len(runtime.conf["EXCLUDE_LOCATION"]) > 0 then
|
||||
for k, v in pairs(runtime.conf["EXCLUDE_LOCATION"]) do
|
||||
if ngx.var.uri == v then
|
||||
ngx.log(ngx.ERR, "whitelisted location: " .. v)
|
||||
ngx.exit(ngx.DECLINED)
|
||||
end
|
||||
local uri_to_check = v
|
||||
if utils.ends_with(uri_to_check, "/") == false then
|
||||
uri_to_check = uri_to_check .. "/"
|
||||
end
|
||||
if utils.starts_with(ngx.var.uri, uri_to_check) then
|
||||
ngx.log(ngx.ERR, "whitelisted location: " .. uri_to_check)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local ok, remediation, err = csmod.allowIp(ip)
|
||||
if err ~= nil then
|
||||
ngx.log(ngx.ERR, "[Crowdsec] bouncer error: " .. err)
|
||||
end
|
||||
|
||||
-- if the ip is now allowed, try to delete its captcha state in cache
|
||||
if ok == true then
|
||||
ngx.shared.crowdsec_cache:delete("captcha_" .. ip)
|
||||
end
|
||||
|
||||
-- check with appSec if the remediation component doesn't have decisions for the IP
|
||||
-- OR
|
||||
-- that user configured the remediation component to always check on the appSec (even if there is a decision for the IP)
|
||||
if ok == true or runtime.conf["ALWAYS_SEND_TO_APPSEC"] == true then
|
||||
if runtime.conf["APPSEC_ENABLED"] == true and ngx.var.no_appsec ~= "1" then
|
||||
local appsecOk, appsecRemediation, status_code, err = csmod.AppSecCheck(ip)
|
||||
if err ~= nil then
|
||||
ngx.log(ngx.ERR, "AppSec check: " .. err)
|
||||
end
|
||||
if appsecOk == false then
|
||||
ok = false
|
||||
remediationSource = flag.APPSEC_SOURCE
|
||||
remediation = appsecRemediation
|
||||
ret_code = status_code
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local captcha_ok = runtime.cache:get("captcha_ok")
|
||||
|
||||
if runtime.fallback ~= "" then
|
||||
-- if we can't use captcha, fallback
|
||||
if remediation == "captcha" and captcha_ok == false then
|
||||
remediation = runtime.fallback
|
||||
end
|
||||
|
||||
-- if remediation is not supported, fallback
|
||||
if remediation ~= "captcha" and remediation ~= "ban" then
|
||||
remediation = runtime.fallback
|
||||
end
|
||||
end
|
||||
|
||||
if captcha_ok then -- if captcha can be use (configuration is valid)
|
||||
-- we check if the IP need to validate its captcha before checking it against crowdsec local API
|
||||
local previous_uri, flags = ngx.shared.crowdsec_cache:get("captcha_"..ip)
|
||||
local source, state_id, err = flag.GetFlags(flags)
|
||||
local body = get_body()
|
||||
|
||||
-- nil body means it was likely not a post, abort here because the user hasn't provided a captcha solution
|
||||
|
||||
if previous_uri ~= nil and state_id == flag.VERIFY_STATE and body ~= nil then
|
||||
local captcha_res = ngx.req.get_post_args()[csmod.GetCaptchaBackendKey()] or 0
|
||||
if captcha_res ~= 0 then
|
||||
local valid, err = csmod.validateCaptcha(captcha_res, ip)
|
||||
if err ~= nil then
|
||||
ngx.log(ngx.ERR, "Error while validating captcha: " .. err)
|
||||
end
|
||||
if valid == true then
|
||||
-- if the captcha is valid and has been applied by the application security component
|
||||
-- then we delete the state from the cache because from the bouncing part, if the user solve the captcha
|
||||
-- we will not propose a captcha until the 'CAPTCHA_EXPIRATION'.
|
||||
-- But for the Application security component, we serve the captcha each time the user trigger it.
|
||||
if source == flag.APPSEC_SOURCE then
|
||||
ngx.shared.crowdsec_cache:delete("captcha_"..ip)
|
||||
else
|
||||
local succ, err, forcible = ngx.shared.crowdsec_cache:set("captcha_"..ip, previous_uri, runtime.conf["CAPTCHA_EXPIRATION"], bit.bor(flag.VALIDATED_STATE, source) )
|
||||
if not succ then
|
||||
ngx.log(ngx.ERR, "failed to add key about captcha for ip '" .. ip .. "' in cache: "..err)
|
||||
end
|
||||
if forcible then
|
||||
ngx.log(ngx.ERR, "Lua shared dict (crowdsec cache) is full, please increase dict size in config")
|
||||
end
|
||||
end
|
||||
-- captcha is valid, we redirect the IP to its previous URI but in GET method
|
||||
ngx.req.set_method(ngx.HTTP_GET)
|
||||
if not ngx.var.query_string then
|
||||
return ngx.redirect(previous_uri)
|
||||
else
|
||||
return ngx.redirect(previous_uri .. ngx.var.query_string)
|
||||
end
|
||||
else
|
||||
ngx.log(ngx.ALERT, "Invalid captcha from " .. ip)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if not ok then
|
||||
if remediation == "ban" then
|
||||
ngx.log(ngx.ALERT, "[Crowdsec] denied '" .. ip .. "' with '"..remediation.."' (by " .. flag.Flags[remediationSource] .. ")")
|
||||
ban.apply(ret_code)
|
||||
return
|
||||
end
|
||||
-- if the remediation is a captcha and captcha is well configured
|
||||
if remediation == "captcha" and captcha_ok and ngx.var.uri ~= "/favicon.ico" then
|
||||
local previous_uri, flags = ngx.shared.crowdsec_cache:get("captcha_"..ip)
|
||||
local source, state_id, err = flag.GetFlags(flags)
|
||||
-- we check if the IP is already in cache for captcha and not yet validated
|
||||
if previous_uri == nil or state_id ~= flag.VALIDATED_STATE or remediationSource == flag.APPSEC_SOURCE then
|
||||
ngx.header.content_type = "text/html"
|
||||
ngx.header.cache_control = "no-cache"
|
||||
ngx.say(csmod.GetCaptchaTemplate())
|
||||
-- if not ngx.var.query_string then
|
||||
local uri = ngx.var.uri
|
||||
-- else
|
||||
-- local uri = ngx.var.uri .. ngx.var.query_string
|
||||
-- end
|
||||
-- in case its not a GET request, we prefer to fallback on referer
|
||||
if ngx.req.get_method() ~= "GET" then
|
||||
local headers, err = ngx.req.get_headers()
|
||||
for k, v in pairs(headers) do
|
||||
if k == "referer" then
|
||||
uri = v
|
||||
end
|
||||
end
|
||||
end
|
||||
local succ, err, forcible = ngx.shared.crowdsec_cache:set("captcha_"..ip, uri , 60, bit.bor(flag.VERIFY_STATE, remediationSource))
|
||||
if not succ then
|
||||
ngx.log(ngx.ERR, "failed to add key about captcha for ip '" .. ip .. "' in cache: "..err)
|
||||
end
|
||||
if forcible then
|
||||
ngx.log(ngx.ERR, "Lua shared dict (crowdsec cache) is full, please increase dict size in config")
|
||||
end
|
||||
ngx.log(ngx.ALERT, "[Crowdsec] denied '" .. ip .. "' with '"..remediation.."'")
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
ngx.exit(ngx.DECLINED)
|
||||
end
|
||||
|
||||
|
||||
-- Use it if you are able to close at shuttime
|
||||
function csmod.close()
|
||||
end
|
||||
|
||||
return csmod
|
77
lua/plugins/crowdsec/plugins/crowdsec/ban.lua
Normal file
77
lua/plugins/crowdsec/plugins/crowdsec/ban.lua
Normal file
|
@ -0,0 +1,77 @@
|
|||
local utils = require "plugins.crowdsec.utils"
|
||||
|
||||
|
||||
local M = {_TYPE='module', _NAME='ban.funcs', _VERSION='1.0-0'}
|
||||
|
||||
M.template_str = ""
|
||||
M.redirect_location = ""
|
||||
M.ret_code = ngx.HTTP_FORBIDDEN
|
||||
|
||||
|
||||
function M.new(template_path, redirect_location, ret_code)
|
||||
M.redirect_location = redirect_location
|
||||
|
||||
ret_code_ok = false
|
||||
if ret_code ~= nil and ret_code ~= 0 and ret_code ~= "" then
|
||||
for k, v in pairs(utils.HTTP_CODE) do
|
||||
if k == ret_code then
|
||||
M.ret_code = utils.HTTP_CODE[ret_code]
|
||||
ret_code_ok = true
|
||||
break
|
||||
end
|
||||
end
|
||||
if ret_code_ok == false then
|
||||
ngx.log(ngx.ERR, "RET_CODE '" .. ret_code .. "' is not supported, using default HTTP code " .. M.ret_code)
|
||||
end
|
||||
end
|
||||
|
||||
template_file_ok = false
|
||||
if (template_path ~= nil and template_path ~= "" and utils.file_exist(template_path) == true) then
|
||||
M.template_str = utils.read_file(template_path)
|
||||
if M.template_str ~= nil then
|
||||
template_file_ok = true
|
||||
end
|
||||
end
|
||||
|
||||
if template_file_ok == false and (M.redirect_location == nil or M.redirect_location == "") then
|
||||
ngx.log(ngx.ERR, "BAN_TEMPLATE_PATH and REDIRECT_LOCATION variable are empty, will return HTTP " .. M.ret_code .. " for ban decisions")
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
|
||||
function M.apply(...)
|
||||
local args = {...}
|
||||
local ret_code = args[1]
|
||||
|
||||
ngx.log(ngx.DEBUG, "args:" .. tostring(args[1]))
|
||||
|
||||
local status = 0
|
||||
if ret_code ~= nil then
|
||||
status = ret_code
|
||||
else
|
||||
status = M.ret_code
|
||||
end
|
||||
|
||||
ngx.log(ngx.DEBUG, "BAN: status=" .. status .. ", redirect_location=" .. M.redirect_location .. ", template_str=" .. M.template_str)
|
||||
if M.redirect_location ~= "" then
|
||||
ngx.redirect(M.redirect_location)
|
||||
return
|
||||
end
|
||||
if M.template_str ~= "" then
|
||||
ngx.header.content_type = "text/html"
|
||||
ngx.header.cache_control = "no-cache"
|
||||
ngx.status = status
|
||||
ngx.say(M.template_str)
|
||||
ngx.exit(status)
|
||||
return
|
||||
end
|
||||
|
||||
ngx.exit(status)
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
return M
|
344
lua/plugins/crowdsec/plugins/crowdsec/bitop.lua
Normal file
344
lua/plugins/crowdsec/plugins/crowdsec/bitop.lua
Normal file
|
@ -0,0 +1,344 @@
|
|||
--
|
||||
-- Library from https://github.com/AlberTajuelo/bitop-lua/
|
||||
--
|
||||
|
||||
local M = {_TYPE='module', _NAME='bitop.funcs', _VERSION='1.0-0'}
|
||||
|
||||
local floor = math.floor
|
||||
|
||||
local MOD = 2^32
|
||||
local MODM = MOD-1
|
||||
|
||||
local function memoize(f)
|
||||
|
||||
local mt = {}
|
||||
local t = setmetatable({}, mt)
|
||||
|
||||
function mt:__index(k)
|
||||
local v = f(k)
|
||||
t[k] = v
|
||||
return v
|
||||
end
|
||||
|
||||
return t
|
||||
end
|
||||
|
||||
local function make_bitop_uncached(t, m)
|
||||
local function bitop(a, b)
|
||||
local res,p = 0,1
|
||||
while a ~= 0 and b ~= 0 do
|
||||
local am, bm = a%m, b%m
|
||||
res = res + t[am][bm]*p
|
||||
a = (a - am) / m
|
||||
b = (b - bm) / m
|
||||
p = p*m
|
||||
end
|
||||
res = res + (a+b) * p
|
||||
return res
|
||||
end
|
||||
return bitop
|
||||
end
|
||||
|
||||
local function make_bitop(t)
|
||||
local op1 = make_bitop_uncached(t, 2^1)
|
||||
local op2 = memoize(function(a)
|
||||
return memoize(function(b)
|
||||
return op1(a, b)
|
||||
end)
|
||||
end)
|
||||
return make_bitop_uncached(op2, 2^(t.n or 1))
|
||||
end
|
||||
|
||||
-- ok? probably not if running on a 32-bit int Lua number type platform
|
||||
function M.tobit(x)
|
||||
return x % 2^32
|
||||
end
|
||||
|
||||
M.bxor = make_bitop {[0]={[0]=0,[1]=1},[1]={[0]=1,[1]=0}, n=4}
|
||||
local bxor = M.bxor
|
||||
|
||||
function M.bnot(a) return MODM - a end
|
||||
local bnot = M.bnot
|
||||
|
||||
function M.band(a,b) return ((a+b) - bxor(a,b))/2 end
|
||||
local band = M.band
|
||||
|
||||
function M.bor(a,b) return MODM - band(MODM - a, MODM - b) end
|
||||
local bor = M.bor
|
||||
|
||||
local lshift, rshift -- forward declare
|
||||
|
||||
function M.rshift(a,disp) -- Lua5.2 insipred
|
||||
if disp < 0 then return lshift(a,-disp) end
|
||||
return floor(a % 2^32 / 2^disp)
|
||||
end
|
||||
rshift = M.rshift
|
||||
|
||||
function M.lshift(a,disp) -- Lua5.2 inspired
|
||||
if disp < 0 then return rshift(a,-disp) end
|
||||
return (a * 2^disp) % 2^32
|
||||
end
|
||||
lshift = M.lshift
|
||||
|
||||
function M.tohex(x, n) -- BitOp style
|
||||
n = n or 8
|
||||
local up
|
||||
if n <= 0 then
|
||||
if n == 0 then return '' end
|
||||
up = true
|
||||
n = - n
|
||||
end
|
||||
x = band(x, 16^n-1)
|
||||
return ('%0'..n..(up and 'X' or 'x')):format(x)
|
||||
end
|
||||
local tohex = M.tohex
|
||||
|
||||
function M.extract(n, field, width) -- Lua5.2 inspired
|
||||
width = width or 1
|
||||
return band(rshift(n, field), 2^width-1)
|
||||
end
|
||||
local extract = M.extract
|
||||
|
||||
function M.replace(n, v, field, width) -- Lua5.2 inspired
|
||||
width = width or 1
|
||||
local mask1 = 2^width-1
|
||||
v = band(v, mask1) -- required by spec?
|
||||
local mask = bnot(lshift(mask1, field))
|
||||
return band(n, mask) + lshift(v, field)
|
||||
end
|
||||
local replace = M.replace
|
||||
|
||||
function M.bswap(x) -- BitOp style
|
||||
local a = band(x, 0xff); x = rshift(x, 8)
|
||||
local b = band(x, 0xff); x = rshift(x, 8)
|
||||
local c = band(x, 0xff); x = rshift(x, 8)
|
||||
local d = band(x, 0xff)
|
||||
return lshift(lshift(lshift(a, 8) + b, 8) + c, 8) + d
|
||||
end
|
||||
local bswap = M.bswap
|
||||
|
||||
function M.rrotate(x, disp) -- Lua5.2 inspired
|
||||
disp = disp % 32
|
||||
local low = band(x, 2^disp-1)
|
||||
return rshift(x, disp) + lshift(low, 32-disp)
|
||||
end
|
||||
local rrotate = M.rrotate
|
||||
|
||||
function M.lrotate(x, disp) -- Lua5.2 inspired
|
||||
return rrotate(x, -disp)
|
||||
end
|
||||
local lrotate = M.lrotate
|
||||
|
||||
M.rol = M.lrotate -- LuaOp inspired
|
||||
M.ror = M.rrotate -- LuaOp insipred
|
||||
|
||||
|
||||
function M.arshift(x, disp) -- Lua5.2 inspired
|
||||
local z = rshift(x, disp)
|
||||
if x >= 0x80000000 then z = z + lshift(2^disp-1, 32-disp) end
|
||||
return z
|
||||
end
|
||||
local arshift = M.arshift
|
||||
|
||||
function M.btest(x, y) -- Lua5.2 inspired
|
||||
return band(x, y) ~= 0
|
||||
end
|
||||
|
||||
--
|
||||
-- Start Lua 5.2 "bit32" compat section.
|
||||
--
|
||||
|
||||
M.bit32 = {} -- Lua 5.2 'bit32' compatibility
|
||||
|
||||
|
||||
local function bit32_bnot(x)
|
||||
return (-1 - x) % MOD
|
||||
end
|
||||
M.bit32.bnot = bit32_bnot
|
||||
|
||||
local function bit32_bxor(a, b, c, ...)
|
||||
local z
|
||||
if b then
|
||||
a = a % MOD
|
||||
b = b % MOD
|
||||
z = bxor(a, b)
|
||||
if c then
|
||||
z = bit32_bxor(z, c, ...)
|
||||
end
|
||||
return z
|
||||
elseif a then
|
||||
return a % MOD
|
||||
else
|
||||
return 0
|
||||
end
|
||||
end
|
||||
M.bit32.bxor = bit32_bxor
|
||||
|
||||
local function bit32_band(a, b, c, ...)
|
||||
local z
|
||||
if b then
|
||||
a = a % MOD
|
||||
b = b % MOD
|
||||
z = ((a+b) - bxor(a,b)) / 2
|
||||
if c then
|
||||
z = bit32_band(z, c, ...)
|
||||
end
|
||||
return z
|
||||
elseif a then
|
||||
return a % MOD
|
||||
else
|
||||
return MODM
|
||||
end
|
||||
end
|
||||
M.bit32.band = bit32_band
|
||||
|
||||
local function bit32_bor(a, b, c, ...)
|
||||
local z
|
||||
if b then
|
||||
a = a % MOD
|
||||
b = b % MOD
|
||||
z = MODM - band(MODM - a, MODM - b)
|
||||
if c then
|
||||
z = bit32_bor(z, c, ...)
|
||||
end
|
||||
return z
|
||||
elseif a then
|
||||
return a % MOD
|
||||
else
|
||||
return 0
|
||||
end
|
||||
end
|
||||
M.bit32.bor = bit32_bor
|
||||
|
||||
function M.bit32.btest(...)
|
||||
return bit32_band(...) ~= 0
|
||||
end
|
||||
|
||||
function M.bit32.lrotate(x, disp)
|
||||
return lrotate(x % MOD, disp)
|
||||
end
|
||||
|
||||
function M.bit32.rrotate(x, disp)
|
||||
return rrotate(x % MOD, disp)
|
||||
end
|
||||
|
||||
function M.bit32.lshift(x,disp)
|
||||
if disp > 31 or disp < -31 then return 0 end
|
||||
return lshift(x % MOD, disp)
|
||||
end
|
||||
|
||||
function M.bit32.rshift(x,disp)
|
||||
if disp > 31 or disp < -31 then return 0 end
|
||||
return rshift(x % MOD, disp)
|
||||
end
|
||||
|
||||
function M.bit32.arshift(x,disp)
|
||||
x = x % MOD
|
||||
if disp >= 0 then
|
||||
if disp > 31 then
|
||||
return (x >= 0x80000000) and MODM or 0
|
||||
else
|
||||
local z = rshift(x, disp)
|
||||
if x >= 0x80000000 then z = z + lshift(2^disp-1, 32-disp) end
|
||||
return z
|
||||
end
|
||||
else
|
||||
return lshift(x, -disp)
|
||||
end
|
||||
end
|
||||
|
||||
function M.bit32.extract(x, field, ...)
|
||||
local width = ... or 1
|
||||
if field < 0 or field > 31 or width < 0 or field+width > 32 then error 'out of range' end
|
||||
x = x % MOD
|
||||
return extract(x, field, ...)
|
||||
end
|
||||
|
||||
function M.bit32.replace(x, v, field, ...)
|
||||
local width = ... or 1
|
||||
if field < 0 or field > 31 or width < 0 or field+width > 32 then error 'out of range' end
|
||||
x = x % MOD
|
||||
v = v % MOD
|
||||
return replace(x, v, field, ...)
|
||||
end
|
||||
|
||||
|
||||
--
|
||||
-- Start LuaBitOp "bit" compat section.
|
||||
--
|
||||
|
||||
M.bit = {} -- LuaBitOp "bit" compatibility
|
||||
|
||||
function M.bit.tobit(x)
|
||||
x = x % MOD
|
||||
if x >= 0x80000000 then x = x - MOD end
|
||||
return x
|
||||
end
|
||||
local bit_tobit = M.bit.tobit
|
||||
|
||||
function M.bit.tohex(x, ...)
|
||||
return tohex(x % MOD, ...)
|
||||
end
|
||||
|
||||
function M.bit.bnot(x)
|
||||
return bit_tobit(bnot(x % MOD))
|
||||
end
|
||||
|
||||
local function bit_bor(a, b, c, ...)
|
||||
if c then
|
||||
return bit_bor(bit_bor(a, b), c, ...)
|
||||
elseif b then
|
||||
return bit_tobit(bor(a % MOD, b % MOD))
|
||||
else
|
||||
return bit_tobit(a)
|
||||
end
|
||||
end
|
||||
M.bit.bor = bit_bor
|
||||
|
||||
local function bit_band(a, b, c, ...)
|
||||
if c then
|
||||
return bit_band(bit_band(a, b), c, ...)
|
||||
elseif b then
|
||||
return bit_tobit(band(a % MOD, b % MOD))
|
||||
else
|
||||
return bit_tobit(a)
|
||||
end
|
||||
end
|
||||
M.bit.band = bit_band
|
||||
|
||||
local function bit_bxor(a, b, c, ...)
|
||||
if c then
|
||||
return bit_bxor(bit_bxor(a, b), c, ...)
|
||||
elseif b then
|
||||
return bit_tobit(bxor(a % MOD, b % MOD))
|
||||
else
|
||||
return bit_tobit(a)
|
||||
end
|
||||
end
|
||||
M.bit.bxor = bit_bxor
|
||||
|
||||
function M.bit.lshift(x, n)
|
||||
return bit_tobit(lshift(x % MOD, n % 32))
|
||||
end
|
||||
|
||||
function M.bit.rshift(x, n)
|
||||
return bit_tobit(rshift(x % MOD, n % 32))
|
||||
end
|
||||
|
||||
function M.bit.arshift(x, n)
|
||||
return bit_tobit(arshift(x % MOD, n % 32))
|
||||
end
|
||||
|
||||
function M.bit.rol(x, n)
|
||||
return bit_tobit(lrotate(x % MOD, n % 32))
|
||||
end
|
||||
|
||||
function M.bit.ror(x, n)
|
||||
return bit_tobit(rrotate(x % MOD, n % 32))
|
||||
end
|
||||
|
||||
function M.bit.bswap(x)
|
||||
return bit_tobit(bswap(x % MOD))
|
||||
end
|
||||
|
||||
return M
|
308
lua/plugins/crowdsec/plugins/crowdsec/captcha.lua
Normal file
308
lua/plugins/crowdsec/plugins/crowdsec/captcha.lua
Normal file
|
@ -0,0 +1,308 @@
|
|||
-- local http = require "resty.http"
|
||||
-- local cjson = require "cjson"
|
||||
-- local template = require "plugins.crowdsec.template"
|
||||
-- local utils = require "plugins.crowdsec.utils"
|
||||
--
|
||||
-- local M = {_TYPE='module', _NAME='recaptcha.funcs', _VERSION='1.0-0'}
|
||||
--
|
||||
-- local captcha_backend_url = {}
|
||||
-- captcha_backend_url["recaptcha"] = "https://www.recaptcha.net/recaptcha/api/siteverify"
|
||||
-- captcha_backend_url["hcaptcha"] = "https://hcaptcha.com/siteverify"
|
||||
-- captcha_backend_url["turnstile"] = "https://challenges.cloudflare.com/turnstile/v0/siteverify"
|
||||
-- captcha_backend_url["mcaptcha"] = "https://mcaptcha.nadeko.net/api/v1/pow/siteverify"
|
||||
--
|
||||
-- local captcha_frontend_js = {}
|
||||
-- captcha_frontend_js["recaptcha"] = "https://www.recaptcha.net/recaptcha/api.js"
|
||||
-- captcha_frontend_js["hcaptcha"] = "https://js.hcaptcha.com/1/api.js"
|
||||
-- captcha_frontend_js["turnstile"] = "https://challenges.cloudflare.com/turnstile/v0/api.js"
|
||||
-- captcha_frontend_js["mcaptcha"] = "https://unpkg.com/@mcaptcha/vanilla-glue@0.1.0-rc2/dist/index.js"
|
||||
--
|
||||
-- local captcha_frontend_key = {}
|
||||
-- captcha_frontend_key["recaptcha"] = "g-recaptcha"
|
||||
-- captcha_frontend_key["hcaptcha"] = "h-captcha"
|
||||
-- captcha_frontend_key["turnstile"] = "cf-turnstile"
|
||||
-- captcha_frontend_key["mcaptcha"] = "m-captcha"
|
||||
--
|
||||
-- M.SecretKey = ""
|
||||
-- M.SiteKey = ""
|
||||
-- M.Template = ""
|
||||
--
|
||||
-- function M.New(siteKey, secretKey, TemplateFilePath, captcha_provider)
|
||||
--
|
||||
-- if siteKey == nil or siteKey == "" then
|
||||
-- return "no recaptcha site key provided, can't use recaptcha"
|
||||
-- end
|
||||
-- M.SiteKey = siteKey
|
||||
--
|
||||
-- if secretKey == nil or secretKey == "" then
|
||||
-- return "no recaptcha secret key provided, can't use recaptcha"
|
||||
-- end
|
||||
--
|
||||
-- M.SecretKey = secretKey
|
||||
--
|
||||
-- if TemplateFilePath == nil then
|
||||
-- return "CAPTCHA_TEMPLATE_PATH variable is empty, will ban without template"
|
||||
-- end
|
||||
-- if utils.file_exist(TemplateFilePath) == false then
|
||||
-- return "captcha template file doesn't exist, can't use recaptcha"
|
||||
-- end
|
||||
--
|
||||
-- local captcha_template = utils.read_file(TemplateFilePath)
|
||||
-- if captcha_template == nil then
|
||||
-- return "Template file " .. TemplateFilePath .. "not found."
|
||||
-- end
|
||||
--
|
||||
-- M.CaptchaProvider = captcha_provider
|
||||
--
|
||||
-- local template_data = {}
|
||||
-- template_data["captcha_site_key"] = M.SiteKey
|
||||
-- template_data["captcha_frontend_js"] = captcha_frontend_js[M.CaptchaProvider]
|
||||
-- template_data["captcha_frontend_key"] = captcha_frontend_key[M.CaptchaProvider]
|
||||
-- local view = template.compile(captcha_template, template_data)
|
||||
-- M.Template = view
|
||||
--
|
||||
-- return nil
|
||||
-- end
|
||||
--
|
||||
--
|
||||
-- function M.GetTemplate()
|
||||
-- return M.Template
|
||||
-- end
|
||||
--
|
||||
-- function M.GetCaptchaBackendKey()
|
||||
-- -- return captcha_frontend_key[M.CaptchaProvider] .. "-response"
|
||||
-- return "mcaptcha__token"
|
||||
-- end
|
||||
--
|
||||
-- function table_to_encoded_url(args)
|
||||
-- local params = {}
|
||||
-- for k, v in pairs(args) do table.insert(params, k .. '=' .. v) end
|
||||
-- return table.concat(params, "&")
|
||||
-- end
|
||||
--
|
||||
-- function M.Validate(captcha_res, remote_ip)
|
||||
-- local xd = "mcaptcha"
|
||||
-- ngx.log(ngx.ERR, "xd: "..xd)
|
||||
-- ngx.log(ngx.ERR, "MCaptchaProvider: "..M.CaptchaProvider)
|
||||
-- -- if M.CaptchaProvider == xd then
|
||||
-- ngx.log(ngx.ERR, "catpcha_res: "..captcha_res)
|
||||
-- ngx.log(ngx.ERR, "M.SiteKey: "..M.SiteKey)
|
||||
-- ngx.log(ngx.ERR, "M.SecretKey: "..M.SiteKey)
|
||||
-- local body = {
|
||||
-- token = captcha_res,
|
||||
-- key = M.SiteKey,
|
||||
-- secret = M.SecretKey
|
||||
-- }
|
||||
-- -- else
|
||||
-- -- local body = {
|
||||
-- -- secret = M.SecretKey,
|
||||
-- -- response = captcha_res,
|
||||
-- -- remoteip = remote_ip
|
||||
-- -- }
|
||||
-- -- end
|
||||
--
|
||||
-- -- local data = table_to_encoded_url(body)
|
||||
-- local data = cjson.encode(body)
|
||||
-- -- ngx.log(ngx.ERR, "body: "..data)
|
||||
-- local httpc = http.new()
|
||||
-- httpc:set_timeout(2000)
|
||||
-- -- if M.CaptchaProvider == xd then
|
||||
-- local res, err = httpc:request_uri(captcha_backend_url[M.CaptchaProvider], {
|
||||
-- method = "POST",
|
||||
-- body = data,
|
||||
-- headers = {
|
||||
-- ["Content-Type"] = "application/json",
|
||||
-- },
|
||||
-- })
|
||||
-- -- ngx.log(ngx.ERR, "response: "..res)
|
||||
-- -- else
|
||||
-- -- local res, err = httpc:request_uri(captcha_backend_url[M.CaptchaProvider], {
|
||||
-- -- method = "POST",
|
||||
-- -- body = data,
|
||||
-- -- headers = {
|
||||
-- -- ["Content-Type"] = "application/x-www-form-urlencoded",
|
||||
-- -- },
|
||||
-- -- })
|
||||
-- -- end
|
||||
-- -- ngx.log(ngx.ERR, res.body)
|
||||
-- httpc:close()
|
||||
-- if err ~= nil then
|
||||
-- return true, err
|
||||
-- end
|
||||
--
|
||||
-- -- ngx.log(ngx.ALERT, M.CaptchaProvider)
|
||||
-- local result = cjson.decode(res.body)
|
||||
-- ngx.log(ngx.ALERT, result.valid)
|
||||
--
|
||||
-- -- if result.success == false then
|
||||
-- -- Disable this verification
|
||||
-- -- if result.valid == false then
|
||||
-- -- for k, v in pairs(result["error-codes"]) do
|
||||
-- -- if v == "invalid-input-secret" then
|
||||
-- -- ngx.log(ngx.ERR, "reCaptcha secret key is invalid")
|
||||
-- -- return true, nil
|
||||
-- -- end
|
||||
-- -- end
|
||||
-- -- end
|
||||
--
|
||||
-- return result.valid, nil
|
||||
-- end
|
||||
--
|
||||
--
|
||||
-- return M
|
||||
|
||||
local http = require "resty.http"
|
||||
local cjson = require "cjson"
|
||||
local template = require "plugins.crowdsec.template"
|
||||
local utils = require "plugins.crowdsec.utils"
|
||||
|
||||
local M = {_TYPE='module', _NAME='recaptcha.funcs', _VERSION='1.0-0'}
|
||||
|
||||
local captcha_backend_url = {}
|
||||
captcha_backend_url["recaptcha"] = "https://www.recaptcha.net/recaptcha/api/siteverify"
|
||||
captcha_backend_url["hcaptcha"] = "https://hcaptcha.com/siteverify"
|
||||
captcha_backend_url["turnstile"] = "https://challenges.cloudflare.com/turnstile/v0/siteverify"
|
||||
captcha_backend_url["mcaptcha"] = "https://mcaptcha.nadeko.net/api/v1/pow/siteverify"
|
||||
|
||||
local captcha_frontend_js = {}
|
||||
captcha_frontend_js["recaptcha"] = "https://www.recaptcha.net/recaptcha/api.js"
|
||||
captcha_frontend_js["hcaptcha"] = "https://js.hcaptcha.com/1/api.js"
|
||||
captcha_frontend_js["turnstile"] = "https://challenges.cloudflare.com/turnstile/v0/api.js"
|
||||
captcha_frontend_js["mcaptcha"] = "https://unpkg.com/@mcaptcha/vanilla-glue@0.1.0-rc2/dist/index.js"
|
||||
|
||||
local captcha_frontend_key = {}
|
||||
captcha_frontend_key["recaptcha"] = "g-recaptcha"
|
||||
captcha_frontend_key["hcaptcha"] = "h-captcha"
|
||||
captcha_frontend_key["turnstile"] = "cf-turnstile"
|
||||
captcha_frontend_key["mcaptcha"] = "m-captcha"
|
||||
|
||||
M.SecretKey = ""
|
||||
M.SiteKey = ""
|
||||
M.Template = ""
|
||||
|
||||
function M.New(siteKey, secretKey, TemplateFilePath, captcha_provider)
|
||||
|
||||
if siteKey == nil or siteKey == "" then
|
||||
return "no recaptcha site key provided, can't use recaptcha"
|
||||
end
|
||||
M.SiteKey = siteKey
|
||||
|
||||
if secretKey == nil or secretKey == "" then
|
||||
return "no recaptcha secret key provided, can't use recaptcha"
|
||||
end
|
||||
|
||||
M.SecretKey = secretKey
|
||||
|
||||
if TemplateFilePath == nil then
|
||||
return "CAPTCHA_TEMPLATE_PATH variable is empty, will ban without template"
|
||||
end
|
||||
if utils.file_exist(TemplateFilePath) == false then
|
||||
return "captcha template file doesn't exist, can't use recaptcha"
|
||||
end
|
||||
|
||||
local captcha_template = utils.read_file(TemplateFilePath)
|
||||
if captcha_template == nil then
|
||||
return "Template file " .. TemplateFilePath .. "not found."
|
||||
end
|
||||
|
||||
M.CaptchaProvider = captcha_provider
|
||||
|
||||
local template_data = {}
|
||||
template_data["captcha_site_key"] = M.SiteKey
|
||||
template_data["captcha_frontend_js"] = captcha_frontend_js[M.CaptchaProvider]
|
||||
template_data["captcha_frontend_key"] = captcha_frontend_key[M.CaptchaProvider]
|
||||
local view = template.compile(captcha_template, template_data)
|
||||
M.Template = view
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
function M.GetTemplate()
|
||||
return M.Template
|
||||
end
|
||||
|
||||
function M.GetCaptchaBackendKey()
|
||||
-- return captcha_frontend_key[M.CaptchaProvider] .. "-response"
|
||||
return "mcaptcha__token"
|
||||
end
|
||||
|
||||
function table_to_encoded_url(args)
|
||||
local params = {}
|
||||
for k, v in pairs(args) do table.insert(params, k .. '=' .. v) end
|
||||
return table.concat(params, "&")
|
||||
end
|
||||
|
||||
function M.Validate(captcha_res, remote_ip)
|
||||
local body = {
|
||||
secret = M.SecretKey,
|
||||
response = captcha_res,
|
||||
remoteip = remote_ip
|
||||
}
|
||||
|
||||
local data = table_to_encoded_url(body)
|
||||
local httpc = http.new()
|
||||
httpc:set_timeout(2000)
|
||||
local res, err = httpc:request_uri(captcha_backend_url[M.CaptchaProvider], {
|
||||
method = "POST",
|
||||
body = data,
|
||||
headers = {
|
||||
["Content-Type"] = "application/x-www-form-urlencoded",
|
||||
},
|
||||
})
|
||||
httpc:close()
|
||||
if err ~= nil then
|
||||
return true, err
|
||||
end
|
||||
|
||||
local result = cjson.decode(res.body)
|
||||
|
||||
if result.success == false then
|
||||
for k, v in pairs(result["error-codes"]) do
|
||||
if v == "invalid-input-secret" then
|
||||
ngx.log(ngx.ERR, "reCaptcha secret key is invalid")
|
||||
return true, nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return result.success, nil
|
||||
end
|
||||
|
||||
function M.ValidateMCaptcha(captcha_res, remote_ip)
|
||||
local body = {
|
||||
token = captcha_res,
|
||||
key = M.SiteKey,
|
||||
secret = M.SecretKey
|
||||
}
|
||||
|
||||
local data = cjson.encode(body)
|
||||
local httpc = http.new()
|
||||
httpc:set_timeout(2000)
|
||||
local res, err = httpc:request_uri(captcha_backend_url[M.CaptchaProvider], {
|
||||
method = "POST",
|
||||
body = data,
|
||||
headers = {
|
||||
["Content-Type"] = "application/json",
|
||||
},
|
||||
})
|
||||
httpc:close()
|
||||
if err ~= nil then
|
||||
return true, err
|
||||
end
|
||||
|
||||
local result = cjson.decode(res.body)
|
||||
|
||||
if result.error and result.error == "Account not found" then
|
||||
ngx.log(ngx.ERR, "siteKey is not valid")
|
||||
return true, nil
|
||||
elseif result.error and result.error == "Wrong password" then
|
||||
ngx.log(ngx.ERR, "secretKey is not valid")
|
||||
return true, nil
|
||||
end
|
||||
|
||||
return result.valid, nil
|
||||
end
|
||||
|
||||
return M
|
||||
|
136
lua/plugins/crowdsec/plugins/crowdsec/config.lua
Normal file
136
lua/plugins/crowdsec/plugins/crowdsec/config.lua
Normal file
|
@ -0,0 +1,136 @@
|
|||
local config = {}
|
||||
|
||||
function config.file_exists(file)
|
||||
local f = io.open(file, "rb")
|
||||
if f then
|
||||
f:close()
|
||||
end
|
||||
return f ~= nil
|
||||
end
|
||||
|
||||
function split(s, delimiter)
|
||||
result = {};
|
||||
for match in (s..delimiter):gmatch("(.-)"..delimiter.."(.-)") do
|
||||
table.insert(result, match);
|
||||
end
|
||||
return result;
|
||||
end
|
||||
|
||||
local function has_value (tab, val)
|
||||
for index, value in ipairs(tab) do
|
||||
if value == val then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
local function starts_with(str, start)
|
||||
return str:sub(1, #start) == start
|
||||
end
|
||||
|
||||
local function trim(s)
|
||||
return (string.gsub(s, "^%s*(.-)%s*$", "%1"))
|
||||
end
|
||||
|
||||
function config.loadConfig(file)
|
||||
if not config.file_exists(file) then
|
||||
return nil, "File ".. file .." doesn't exist"
|
||||
end
|
||||
local conf = {}
|
||||
local valid_params = {'ENABLED','API_URL', 'API_KEY', 'BOUNCING_ON_TYPE', 'MODE', 'SECRET_KEY', 'SITE_KEY', 'BAN_TEMPLATE_PATH' ,'CAPTCHA_TEMPLATE_PATH', 'REDIRECT_LOCATION', 'RET_CODE', 'EXCLUDE_LOCATION', 'FALLBACK_REMEDIATION', 'CAPTCHA_PROVIDER', 'APPSEC_URL', 'APPSEC_FAILURE_ACTION', 'ALWAYS_SEND_TO_APPSEC', 'SSL_VERIFY'}
|
||||
local valid_int_params = {'CACHE_EXPIRATION', 'CACHE_SIZE', 'REQUEST_TIMEOUT', 'UPDATE_FREQUENCY', 'CAPTCHA_EXPIRATION', 'APPSEC_CONNECT_TIMEOUT', 'APPSEC_SEND_TIMEOUT', 'APPSEC_PROCESS_TIMEOUT', 'STREAM_REQUEST_TIMEOUT'}
|
||||
local valid_bouncing_on_type_values = {'ban', 'captcha', 'all'}
|
||||
local valid_truefalse_values = {'false', 'true'}
|
||||
local default_values = {
|
||||
['ENABLED'] = "true",
|
||||
['API_URL'] = "",
|
||||
['REQUEST_TIMEOUT'] = 500,
|
||||
['STREAM_REQUEST_TIMEOUT'] = 15000,
|
||||
['BOUNCING_ON_TYPE'] = "ban",
|
||||
['MODE'] = "stream",
|
||||
['UPDATE_FREQUENCY'] = 10,
|
||||
['CAPTCHA_EXPIRATION'] = 3600,
|
||||
['REDIRECT_LOCATION'] = "",
|
||||
['EXCLUDE_LOCATION'] = {},
|
||||
['RET_CODE'] = 0,
|
||||
['CAPTCHA_PROVIDER'] = "recaptcha",
|
||||
['APPSEC_URL'] = "",
|
||||
['APPSEC_CONNECT_TIMEOUT'] = 100,
|
||||
['APPSEC_SEND_TIMEOUT'] = 100,
|
||||
['APPSEC_PROCESS_TIMEOUT'] = 500,
|
||||
['APPSEC_FAILURE_ACTION'] = "passthrough",
|
||||
['SSL_VERIFY'] = "true",
|
||||
['ALWAYS_SEND_TO_APPSEC'] = "false",
|
||||
|
||||
}
|
||||
for line in io.lines(file) do
|
||||
local isOk = false
|
||||
if starts_with(line, "#") then
|
||||
isOk = true
|
||||
end
|
||||
if trim(line) == "" then
|
||||
isOk = true
|
||||
end
|
||||
if not isOk then
|
||||
local sep_pos = line:find("=")
|
||||
if not sep_pos then
|
||||
ngx.log(ngx.ERR, "invalid configuration line: " .. line)
|
||||
break
|
||||
end
|
||||
local key = trim(line:sub(1, sep_pos - 1))
|
||||
local value = trim(line:sub(sep_pos + 1))
|
||||
if has_value(valid_params, key) then
|
||||
if key == "ENABLED" then
|
||||
if not has_value(valid_truefalse_values, value) then
|
||||
ngx.log(ngx.ERR, "unsupported value '" .. value .. "' for variable '" .. key .. "'. Using default value 'true' instead")
|
||||
value = "true"
|
||||
end
|
||||
elseif key == "BOUNCING_ON_TYPE" then
|
||||
if not has_value(valid_bouncing_on_type_values, value) then
|
||||
ngx.log(ngx.ERR, "unsupported value '" .. value .. "' for variable '" .. key .. "'. Using default value 'ban' instead")
|
||||
value = "ban"
|
||||
end
|
||||
elseif key == "SSL_VERIFY" then
|
||||
if not has_value(valid_truefalse_values, value) then
|
||||
ngx.log(ngx.ERR, "unsupported value '" .. value .. "' for variable '" .. key .. "'. Using default value 'true' instead")
|
||||
value = "true"
|
||||
end
|
||||
elseif key == "MODE" then
|
||||
if not has_value({'stream', 'live'}, value) then
|
||||
ngx.log(ngx.ERR, "unsupported value '" .. value .. "' for variable '" .. key .. "'. Using default value 'stream' instead")
|
||||
value = "stream"
|
||||
end
|
||||
elseif key == "EXCLUDE_LOCATION" then
|
||||
exclude_location = {}
|
||||
if value ~= "" then
|
||||
for match in (value..","):gmatch("(.-)"..",") do
|
||||
table.insert(exclude_location, match)
|
||||
end
|
||||
end
|
||||
value = exclude_location
|
||||
elseif key == "FALLBACK_REMEDIATION" then
|
||||
if not has_value({'captcha', 'ban'}, value) then
|
||||
ngx.log(ngx.ERR, "unsupported value '" .. value .. "' for variable '" .. key .. "'. Using default value 'ban' instead")
|
||||
value = "ban"
|
||||
end
|
||||
end
|
||||
|
||||
conf[key] = value
|
||||
|
||||
elseif has_value(valid_int_params, key) then
|
||||
conf[key] = tonumber(value)
|
||||
else
|
||||
ngx.log(ngx.ERR, "unsupported configuration '" .. key .. "'")
|
||||
end
|
||||
end
|
||||
end
|
||||
for k, v in pairs(default_values) do
|
||||
if conf[k] == nil then
|
||||
conf[k] = v
|
||||
end
|
||||
end
|
||||
return conf, nil
|
||||
end
|
||||
return config
|
43
lua/plugins/crowdsec/plugins/crowdsec/flag.lua
Normal file
43
lua/plugins/crowdsec/plugins/crowdsec/flag.lua
Normal file
|
@ -0,0 +1,43 @@
|
|||
local bit
|
||||
if _VERSION == "Lua 5.1" then bit = require "bit" else bit = require "bit32" end
|
||||
|
||||
local M = {_TYPE='module', _NAME='flag.funcs', _VERSION='1.0-0'}
|
||||
|
||||
M.BOUNCER_SOURCE = 0x1
|
||||
M.APPSEC_SOURCE = 0x2
|
||||
M.VERIFY_STATE = 0x4
|
||||
M.VALIDATED_STATE = 0x8
|
||||
|
||||
M.Flags = {}
|
||||
M.Flags[0x0] = ""
|
||||
M.Flags[0x1] = "bouncer"
|
||||
M.Flags[0x2] = "appsec"
|
||||
M.Flags[0x4] = "to_verify"
|
||||
M.Flags[0x8] = "validated"
|
||||
|
||||
|
||||
function M.GetFlags(flags)
|
||||
local source = 0x0
|
||||
local err = ""
|
||||
local state = 0x0
|
||||
|
||||
if flags == nil then
|
||||
return source, state, err
|
||||
end
|
||||
|
||||
if bit.band(flags, M.BOUNCER_SOURCE) ~= 0 then
|
||||
source = M.BOUNCER_SOURCE
|
||||
elseif bit.band(flags, M.APPSEC_SOURCE) ~= 0 then
|
||||
source = M.APPSEC_SOURCE
|
||||
end
|
||||
|
||||
if bit.band(flags, M.VERIFY_STATE) ~= 0 then
|
||||
state = M.VERIFY_STATE
|
||||
elseif bit.band(flags, M.VALIDATED_STATE) ~= 0 then
|
||||
state = M.VALIDATED_STATE
|
||||
end
|
||||
return source, state, err
|
||||
|
||||
end
|
||||
|
||||
return M
|
215
lua/plugins/crowdsec/plugins/crowdsec/iputils.lua
Normal file
215
lua/plugins/crowdsec/plugins/crowdsec/iputils.lua
Normal file
|
@ -0,0 +1,215 @@
|
|||
-- This code uses some functions from
|
||||
-- https://github.com/libmoon/libmoon/blob/master/lua/utils.lua
|
||||
|
||||
local _M = {}
|
||||
local bit = require 'plugins.crowdsec.bitop'
|
||||
local bor, band, rshift, lshift, bswap = bit.bor, bit.band, bit.rshift, bit.lshift, bit.bswap
|
||||
|
||||
--- Byte swap for 16 bit integers
|
||||
--- @param n 16 bit integer
|
||||
--- @return Byte swapped integer
|
||||
function bswap16(n)
|
||||
return bor(rshift(n, 8), lshift(band(n, 0xFF), 8))
|
||||
end
|
||||
|
||||
hton16 = bswap16
|
||||
ntoh16 = hton16
|
||||
|
||||
_G.bswap = bswap -- export bit.bswap to global namespace to be consistent with bswap16
|
||||
hton = bswap
|
||||
ntoh = hton
|
||||
|
||||
local ffi = require "ffi"
|
||||
|
||||
local ipv4_netmasks = {
|
||||
4294967295, 4294967294, 4294967292, 4294967288, 4294967280, 4294967264, 4294967232, 4294967168, 4294967040, 4294966784,
|
||||
4294966272, 4294965248, 4294963200, 4294959104, 4294950912, 4294934528, 4294901760, 4294836224, 4294705152, 4294443008,
|
||||
4293918720, 4292870144, 4290772992, 4286578688, 4278190080, 4261412864, 4227858432, 4160749568, 4026531840, 3758096384, 3221225472, 2147483648, 0
|
||||
}
|
||||
|
||||
local ipv6_netmasks = {
|
||||
{4294967295,4294967295,4294967295,4294967295}, {4294967295,4294967295,4294967295,4294967294}, {4294967295,4294967295,4294967295,4294967292},
|
||||
{4294967295,4294967295,4294967295,4294967288}, {4294967295,4294967295,4294967295,4294967280}, {4294967295,4294967295,4294967295,4294967264},
|
||||
{4294967295,4294967295,4294967295,4294967232}, {4294967295,4294967295,4294967295,4294967168}, {4294967295,4294967295,4294967295,4294967040},
|
||||
{4294967295,4294967295,4294967295,4294966784}, {4294967295,4294967295,4294967295,4294966272}, {4294967295,4294967295,4294967295,4294965248},
|
||||
{4294967295,4294967295,4294967295,4294963200}, {4294967295,4294967295,4294967295,4294959104}, {4294967295,4294967295,4294967295,4294950912},
|
||||
{4294967295,4294967295,4294967295,4294934528}, {4294967295,4294967295,4294967295,4294901760}, {4294967295,4294967295,4294967295,4294836224},
|
||||
{4294967295,4294967295,4294967295,4294705152}, {4294967295,4294967295,4294967295,4294443008}, {4294967295,4294967295,4294967295,4293918720},
|
||||
{4294967295,4294967295,4294967295,4292870144}, {4294967295,4294967295,4294967295,4290772992}, {4294967295,4294967295,4294967295,4286578688},
|
||||
{4294967295,4294967295,4294967295,4278190080}, {4294967295,4294967295,4294967295,4261412864}, {4294967295,4294967295,4294967295,4227858432},
|
||||
{4294967295,4294967295,4294967295,4160749568}, {4294967295,4294967295,4294967295,4026531840}, {4294967295,4294967295,4294967295,3758096384},
|
||||
{4294967295,4294967295,4294967295,3221225472}, {4294967295,4294967295,4294967295,2147483648}, {4294967295,4294967295,4294967295,0},
|
||||
{4294967295,4294967295,4294967294,0}, {4294967295,4294967295,4294967292,0}, {4294967295,4294967295,4294967288,0},
|
||||
{4294967295,4294967295,4294967280,0}, {4294967295,4294967295,4294967264,0}, {4294967295,4294967295,4294967232,0},
|
||||
{4294967295,4294967295,4294967168,0}, {4294967295,4294967295,4294967040,0}, {4294967295,4294967295,4294966784,0},
|
||||
{4294967295,4294967295,4294966272,0}, {4294967295,4294967295,4294965248,0}, {4294967295,4294967295,4294963200,0},
|
||||
{4294967295,4294967295,4294959104,0}, {4294967295,4294967295,4294950912,0}, {4294967295,4294967295,4294934528,0},
|
||||
{4294967295,4294967295,4294901760,0}, {4294967295,4294967295,4294836224,0}, {4294967295,4294967295,4294705152,0},
|
||||
{4294967295,4294967295,4294443008,0}, {4294967295,4294967295,4293918720,0}, {4294967295,4294967295,4292870144,0},
|
||||
{4294967295,4294967295,4290772992,0}, {4294967295,4294967295,4286578688,0}, {4294967295,4294967295,4278190080,0},
|
||||
{4294967295,4294967295,4261412864,0}, {4294967295,4294967295,4227858432,0}, {4294967295,4294967295,4160749568,0},
|
||||
{4294967295,4294967295,4026531840,0}, {4294967295,4294967295,3758096384,0}, {4294967295,4294967295,3221225472,0},
|
||||
{4294967295,4294967295,2147483648,0},
|
||||
{4294967295,4294967295,0,0}, {4294967295,4294967294,0,0}, {4294967295,4294967292,0,0}, {4294967295,4294967288,0,0},
|
||||
{4294967295,4294967280,0,0}, {4294967295,4294967264,0,0}, {4294967295,4294967232,0,0}, {4294967295,4294967168,0,0},
|
||||
{4294967295,4294967040,0,0}, {4294967295,4294966784,0,0}, {4294967295,4294966272,0,0}, {4294967295,4294965248,0,0},
|
||||
{4294967295,4294963200,0,0}, {4294967295,4294959104,0,0}, {4294967295,4294950912,0,0}, {4294967295,4294934528,0,0},
|
||||
{4294967295,4294901760,0,0}, {4294967295,4294836224,0,0}, {4294967295,4294705152,0,0}, {4294967295,4294443008,0,0},
|
||||
{4294967295,4293918720,0,0}, {4294967295,4292870144,0,0}, {4294967295,4290772992,0,0}, {4294967295,4286578688,0,0},
|
||||
{4294967295,4278190080,0,0}, {4294967295,4261412864,0,0}, {4294967295,4227858432,0,0}, {4294967295,4160749568,0,0},
|
||||
{4294967295,4026531840,0,0}, {4294967295,3758096384,0,0}, {4294967295,3221225472,0,0}, {4294967295,2147483648,0,0},
|
||||
{4294967295,0,0,0},{4294967294,0,0,0},{4294967292,0,0,0}, {4294967288,0,0,0}, {4294967280,0,0,0},{4294967264,0,0,0},
|
||||
{4294967232,0,0,0},{4294967168,0,0,0},{4294967040,0,0,0}, {4294966784,0,0,0},{4294966272,0,0,0},{4294965248,0,0,0},
|
||||
{4294963200,0,0,0},{4294959104,0,0,0},{4294950912,0,0,0}, {4294934528,0,0,0},{4294901760,0,0,0},{4294836224,0,0,0},
|
||||
{4294705152,0,0,0},{4294443008,0,0,0},{4293918720,0,0,0}, {4292870144,0,0,0},{4290772992,0,0,0},{4286578688,0,0,0},
|
||||
{4278190080,0,0,0},{4261412864,0,0,0},{4227858432,0,0,0}, {4160749568,0,0,0},{4026531840,0,0,0},{3758096384,0,0,0},
|
||||
{3221225472,0,0,0},{2147483648,0,0,0},{0,0,0,0}
|
||||
}
|
||||
|
||||
local netmasks_by_key_type = {}
|
||||
netmasks_by_key_type["ipv4"] = ipv4_netmasks
|
||||
netmasks_by_key_type["ipv6"] = ipv6_netmasks
|
||||
|
||||
_M.netmasks_by_key_type = netmasks_by_key_type
|
||||
|
||||
function _M.ipToInt( str )
|
||||
local num = 0
|
||||
if str and type(str)=="string" then
|
||||
local o1,o2,o3,o4 = str:match("(%d+)%.(%d+)%.(%d+)%.(%d+)" )
|
||||
num = 2^24*o1 + 2^16*o2 + 2^8*o3 + o4
|
||||
end
|
||||
return num
|
||||
end
|
||||
|
||||
function _M.concatIPv6(ip)
|
||||
if type(ip) == "table" and table.getn(ip) == 4 then
|
||||
return ip[1]*2^96 + ip[2]*2^64 + ip[3]*2^32 + ip[4]
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
function _M.ipv6_band(ip, netmask)
|
||||
local res_table = {}
|
||||
local nb = 1
|
||||
for item in ip.gmatch(ip, "([^:]+)") do
|
||||
table.insert(res_table, bit.band(tonumber(item), netmask[nb]))
|
||||
nb = nb + 1
|
||||
end
|
||||
return table.concat(res_table, ":")
|
||||
end
|
||||
|
||||
function _M.ipv4_band(ip, netmask)
|
||||
return bit.band(ip, netmask)
|
||||
end
|
||||
|
||||
function _M.splitRange(range)
|
||||
if range and type(range) == "string" then
|
||||
local ip_address, cidr = range:match("^([^/]+)/(%d+)")
|
||||
return ip_address, tonumber(cidr)
|
||||
end
|
||||
return nil, nil
|
||||
end
|
||||
|
||||
function _M.cidrToInt(cidr, ip_version)
|
||||
if cidr and type(cidr) ~= "number" and ip_version and type(ip_version) ~= "string" then
|
||||
return nil
|
||||
end
|
||||
if ip_version == "ipv4" then
|
||||
return tostring(ipv4_netmasks[32-cidr+1])
|
||||
end
|
||||
if ip_version == "ipv6" then
|
||||
return table.concat(ipv6_netmasks[128-cidr+1], ":")
|
||||
end
|
||||
end
|
||||
|
||||
--- Parse a string to an IP address
|
||||
--- @return address ip address in ip4_address or ip6_address format or nil if invalid address
|
||||
--- @return boolean true if IPv4 address, false otherwise
|
||||
function _M.parseIPAddress(ip)
|
||||
ip = tostring(ip)
|
||||
local address = parseIP4Address(ip)
|
||||
if address == nil then
|
||||
return parseIP6Address(ip), false
|
||||
end
|
||||
return address, true
|
||||
end
|
||||
|
||||
--- Parse a string to an IPv4 address
|
||||
--- @param ip address in string format
|
||||
--- @return address in uint32 format or nil if invalid address
|
||||
function parseIP4Address(ip)
|
||||
ip = tostring(ip)
|
||||
local bytes = {string.match(ip, '(%d+)%.(%d+)%.(%d+)%.(%d+)')}
|
||||
if bytes == nil then
|
||||
return
|
||||
end
|
||||
for i = 1, 4 do
|
||||
if bytes[i] == nil then
|
||||
return
|
||||
end
|
||||
bytes[i] = tonumber(bytes[i])
|
||||
if bytes[i] < 0 or bytes[i] > 255 then
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
-- build a uint32
|
||||
ip = bytes[1]
|
||||
for i = 2, 4 do
|
||||
ip = bor(lshift(ip, 8), bytes[i])
|
||||
end
|
||||
return ip
|
||||
end
|
||||
|
||||
ffi.cdef[[
|
||||
union ip6_address {
|
||||
uint8_t uint8[16];
|
||||
uint32_t uint32[4];
|
||||
uint64_t uint64[2];
|
||||
};
|
||||
]]
|
||||
|
||||
ffi.cdef[[
|
||||
int inet_pton(int af, const char *src, void *dst);
|
||||
]]
|
||||
|
||||
--- Parse a string to an IPv6 address
|
||||
--- @param ip address in string format
|
||||
--- @return address in ip6_address format or nil if invalid address
|
||||
function parseIP6Address(ip)
|
||||
ip = tostring(ip)
|
||||
local LINUX_AF_INET6 = 10 --preprocessor constant of Linux
|
||||
local tmp_addr = ffi.new("union ip6_address")
|
||||
local res = ffi.C.inet_pton(LINUX_AF_INET6, ip, tmp_addr)
|
||||
if res == 0 then
|
||||
return nil
|
||||
end
|
||||
|
||||
local addr = ffi.new("union ip6_address")
|
||||
addr.uint32[0] = bswap(tmp_addr.uint32[3])
|
||||
addr.uint32[1] = bswap(tmp_addr.uint32[2])
|
||||
addr.uint32[2] = bswap(tmp_addr.uint32[1])
|
||||
addr.uint32[3] = bswap(tmp_addr.uint32[0])
|
||||
|
||||
return addr
|
||||
end
|
||||
|
||||
--- Merge tables.
|
||||
--- @param args Arbitrary amount of tables to get merged.
|
||||
function mergeTables(...)
|
||||
local table = {}
|
||||
if select("#", ...) > 0 then
|
||||
table = select(1, ...)
|
||||
for i = 2, select("#", ...) do
|
||||
for k,v in pairs(select(i, ...)) do
|
||||
table[k] = v
|
||||
end
|
||||
end
|
||||
end
|
||||
return table
|
||||
end
|
||||
|
||||
local band = bit.band
|
||||
local sar = bit.arshift
|
||||
|
||||
return _M
|
13
lua/plugins/crowdsec/plugins/crowdsec/template.lua
Normal file
13
lua/plugins/crowdsec/plugins/crowdsec/template.lua
Normal file
|
@ -0,0 +1,13 @@
|
|||
local template = {}
|
||||
|
||||
function template.compile(template_str, args)
|
||||
|
||||
for k, v in pairs(args) do
|
||||
local var = "{{" .. k .. "}}"
|
||||
template_str = template_str:gsub(var, v)
|
||||
end
|
||||
|
||||
return template_str
|
||||
end
|
||||
|
||||
return template
|
523
lua/plugins/crowdsec/plugins/crowdsec/url.lua
Normal file
523
lua/plugins/crowdsec/plugins/crowdsec/url.lua
Normal file
|
@ -0,0 +1,523 @@
|
|||
-- net/url.lua - a robust url parser and builder
|
||||
--
|
||||
-- Bertrand Mansion, 2011-2021; License MIT
|
||||
-- @module net.url
|
||||
-- @alias M
|
||||
|
||||
local M = {}
|
||||
M.version = "1.1.0"
|
||||
|
||||
--- url options
|
||||
-- - `separator` is set to `&` by default but could be anything like `&amp;` or `;`
|
||||
-- - `cumulative_parameters` is false by default. If true, query parameters with the same name will be stored in a table.
|
||||
-- - `legal_in_path` is a table of characters that will not be url encoded in path components
|
||||
-- - `legal_in_query` is a table of characters that will not be url encoded in query values. Query parameters only support a small set of legal characters (-_.).
|
||||
-- - `query_plus_is_space` is true by default, so a plus sign in a query value will be converted to %20 (space), not %2B (plus)
|
||||
-- @todo Add option to limit the size of the argument table
|
||||
-- @todo Add option to limit the depth of the argument table
|
||||
-- @todo Add option to process dots in parameter names, ie. `param.filter=1`
|
||||
M.options = {
|
||||
separator = '&',
|
||||
cumulative_parameters = false,
|
||||
legal_in_path = {
|
||||
[":"] = true, ["-"] = true, ["_"] = true, ["."] = true,
|
||||
["!"] = true, ["~"] = true, ["*"] = true, ["'"] = true,
|
||||
["("] = true, [")"] = true, ["@"] = true, ["&"] = true,
|
||||
["="] = true, ["$"] = true, [","] = true,
|
||||
[";"] = true
|
||||
},
|
||||
legal_in_query = {
|
||||
[":"] = true, ["-"] = true, ["_"] = true, ["."] = true,
|
||||
[","] = true, ["!"] = true, ["~"] = true, ["*"] = true,
|
||||
["'"] = true, [";"] = true, ["("] = true, [")"] = true,
|
||||
["@"] = true, ["$"] = true,
|
||||
},
|
||||
query_plus_is_space = true
|
||||
}
|
||||
|
||||
--- list of known and common scheme ports
|
||||
-- as documented in <a href="http://www.iana.org/assignments/uri-schemes.html">IANA URI scheme list</a>
|
||||
M.services = {
|
||||
acap = 674,
|
||||
cap = 1026,
|
||||
dict = 2628,
|
||||
ftp = 21,
|
||||
gopher = 70,
|
||||
http = 80,
|
||||
https = 443,
|
||||
iax = 4569,
|
||||
icap = 1344,
|
||||
imap = 143,
|
||||
ipp = 631,
|
||||
ldap = 389,
|
||||
mtqp = 1038,
|
||||
mupdate = 3905,
|
||||
news = 2009,
|
||||
nfs = 2049,
|
||||
nntp = 119,
|
||||
rtsp = 554,
|
||||
sip = 5060,
|
||||
snmp = 161,
|
||||
telnet = 23,
|
||||
tftp = 69,
|
||||
vemmi = 575,
|
||||
afs = 1483,
|
||||
jms = 5673,
|
||||
rsync = 873,
|
||||
prospero = 191,
|
||||
videotex = 516
|
||||
}
|
||||
|
||||
local function decode(str)
|
||||
return (str:gsub("%%(%x%x)", function(c)
|
||||
return string.char(tonumber(c, 16))
|
||||
end))
|
||||
end
|
||||
|
||||
local function encode(str, legal)
|
||||
return (str:gsub("([^%w])", function(v)
|
||||
if legal[v] then
|
||||
return v
|
||||
end
|
||||
return string.upper(string.format("%%%02x", string.byte(v)))
|
||||
end))
|
||||
end
|
||||
|
||||
-- for query values, + can mean space if configured as such
|
||||
local function decodeValue(str)
|
||||
if M.options.query_plus_is_space then
|
||||
str = str:gsub('+', ' ')
|
||||
end
|
||||
return decode(str)
|
||||
end
|
||||
|
||||
local function concat(a, b)
|
||||
if type(a) == 'table' then
|
||||
return a:build() .. b
|
||||
else
|
||||
return a .. b:build()
|
||||
end
|
||||
end
|
||||
|
||||
function M:addSegment(path)
|
||||
if type(path) == 'string' then
|
||||
self.path = self.path .. '/' .. encode(path:gsub("^/+", ""), M.options.legal_in_path)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- builds the url
|
||||
-- @return a string representing the built url
|
||||
function M:build()
|
||||
local url = ''
|
||||
if self.path then
|
||||
local path = self.path
|
||||
url = url .. tostring(path)
|
||||
end
|
||||
if self.query then
|
||||
local qstring = tostring(self.query)
|
||||
if qstring ~= "" then
|
||||
url = url .. '?' .. qstring
|
||||
end
|
||||
end
|
||||
if self.host then
|
||||
local authority = self.host
|
||||
if self.port and self.scheme and M.services[self.scheme] ~= self.port then
|
||||
authority = authority .. ':' .. self.port
|
||||
end
|
||||
local userinfo
|
||||
if self.user and self.user ~= "" then
|
||||
userinfo = self.user
|
||||
if self.password then
|
||||
userinfo = userinfo .. ':' .. self.password
|
||||
end
|
||||
end
|
||||
if userinfo and userinfo ~= "" then
|
||||
authority = userinfo .. '@' .. authority
|
||||
end
|
||||
if authority then
|
||||
if url ~= "" then
|
||||
url = '//' .. authority .. '/' .. url:gsub('^/+', '')
|
||||
else
|
||||
url = '//' .. authority
|
||||
end
|
||||
end
|
||||
end
|
||||
if self.scheme then
|
||||
url = self.scheme .. ':' .. url
|
||||
end
|
||||
if self.fragment then
|
||||
url = url .. '#' .. self.fragment
|
||||
end
|
||||
return url
|
||||
end
|
||||
|
||||
--- builds the querystring
|
||||
-- @param tab The key/value parameters
|
||||
-- @param sep The separator to use (optional)
|
||||
-- @param key The parent key if the value is multi-dimensional (optional)
|
||||
-- @return a string representing the built querystring
|
||||
function M.buildQuery(tab, sep, key)
|
||||
local query = {}
|
||||
if not sep then
|
||||
sep = M.options.separator or '&'
|
||||
end
|
||||
local keys = {}
|
||||
for k in pairs(tab) do
|
||||
keys[#keys+1] = k
|
||||
end
|
||||
table.sort(keys, function (a, b)
|
||||
local function padnum(n, rest) return ("%03d"..rest):format(tonumber(n)) end
|
||||
return tostring(a):gsub("(%d+)(%.)",padnum) < tostring(b):gsub("(%d+)(%.)",padnum)
|
||||
end)
|
||||
for _,name in ipairs(keys) do
|
||||
local value = tab[name]
|
||||
name = encode(tostring(name), {["-"] = true, ["_"] = true, ["."] = true})
|
||||
if key then
|
||||
if M.options.cumulative_parameters and string.find(name, '^%d+$') then
|
||||
name = tostring(key)
|
||||
else
|
||||
name = string.format('%s[%s]', tostring(key), tostring(name))
|
||||
end
|
||||
end
|
||||
if type(value) == 'table' then
|
||||
query[#query+1] = M.buildQuery(value, sep, name)
|
||||
else
|
||||
local value = encode(tostring(value), M.options.legal_in_query)
|
||||
if value ~= "" then
|
||||
query[#query+1] = string.format('%s=%s', name, value)
|
||||
else
|
||||
query[#query+1] = name
|
||||
end
|
||||
end
|
||||
end
|
||||
return table.concat(query, sep)
|
||||
end
|
||||
|
||||
--- Parses the querystring to a table
|
||||
-- This function can parse multidimensional pairs and is mostly compatible
|
||||
-- with PHP usage of brackets in key names like ?param[key]=value
|
||||
-- @param str The querystring to parse
|
||||
-- @param sep The separator between key/value pairs, defaults to `&`
|
||||
-- @todo limit the max number of parameters with M.options.max_parameters
|
||||
-- @return a table representing the query key/value pairs
|
||||
function M.parseQuery(str, sep)
|
||||
if not sep then
|
||||
sep = M.options.separator or '&'
|
||||
end
|
||||
|
||||
local values = {}
|
||||
for key,val in str:gmatch(string.format('([^%q=]+)(=*[^%q=]*)', sep, sep)) do
|
||||
local key = decodeValue(key)
|
||||
local keys = {}
|
||||
key = key:gsub('%[([^%]]*)%]', function(v)
|
||||
-- extract keys between balanced brackets
|
||||
if string.find(v, "^-?%d+$") then
|
||||
v = tonumber(v)
|
||||
else
|
||||
v = decodeValue(v)
|
||||
end
|
||||
table.insert(keys, v)
|
||||
return "="
|
||||
end)
|
||||
key = key:gsub('=+.*$', "")
|
||||
key = key:gsub('%s', "_") -- remove spaces in parameter name
|
||||
val = val:gsub('^=+', "")
|
||||
|
||||
if not values[key] then
|
||||
values[key] = {}
|
||||
end
|
||||
if #keys > 0 and type(values[key]) ~= 'table' then
|
||||
values[key] = {}
|
||||
elseif #keys == 0 and type(values[key]) == 'table' then
|
||||
values[key] = decodeValue(val)
|
||||
elseif M.options.cumulative_parameters
|
||||
and type(values[key]) == 'string' then
|
||||
values[key] = { values[key] }
|
||||
table.insert(values[key], decodeValue(val))
|
||||
end
|
||||
|
||||
local t = values[key]
|
||||
for i,k in ipairs(keys) do
|
||||
if type(t) ~= 'table' then
|
||||
t = {}
|
||||
end
|
||||
if k == "" then
|
||||
k = #t+1
|
||||
end
|
||||
if not t[k] then
|
||||
t[k] = {}
|
||||
end
|
||||
if i == #keys then
|
||||
t[k] = val
|
||||
end
|
||||
t = t[k]
|
||||
end
|
||||
|
||||
end
|
||||
setmetatable(values, { __tostring = M.buildQuery })
|
||||
return values
|
||||
end
|
||||
|
||||
--- set the url query
|
||||
-- @param query Can be a string to parse or a table of key/value pairs
|
||||
-- @return a table representing the query key/value pairs
|
||||
function M:setQuery(query)
|
||||
local query = query
|
||||
if type(query) == 'table' then
|
||||
query = M.buildQuery(query)
|
||||
end
|
||||
self.query = M.parseQuery(query)
|
||||
return query
|
||||
end
|
||||
|
||||
--- set the authority part of the url
|
||||
-- The authority is parsed to find the user, password, port and host if available.
|
||||
-- @param authority The string representing the authority
|
||||
-- @return a string with what remains after the authority was parsed
|
||||
function M:setAuthority(authority)
|
||||
self.authority = authority
|
||||
self.port = nil
|
||||
self.host = nil
|
||||
self.userinfo = nil
|
||||
self.user = nil
|
||||
self.password = nil
|
||||
|
||||
authority = authority:gsub('^([^@]*)@', function(v)
|
||||
self.userinfo = v
|
||||
return ''
|
||||
end)
|
||||
|
||||
authority = authority:gsub(':(%d+)$', function(v)
|
||||
self.port = tonumber(v)
|
||||
return ''
|
||||
end)
|
||||
|
||||
local function getIP(str)
|
||||
-- ipv4
|
||||
local chunks = { str:match("^(%d+)%.(%d+)%.(%d+)%.(%d+)$") }
|
||||
if #chunks == 4 then
|
||||
for _, v in pairs(chunks) do
|
||||
if tonumber(v) > 255 then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return str
|
||||
end
|
||||
-- ipv6
|
||||
local chunks = { str:match("^%["..(("([a-fA-F0-9]*):"):rep(8):gsub(":$","%%]$"))) }
|
||||
if #chunks == 8 or #chunks < 8 and
|
||||
str:match('::') and not str:gsub("::", "", 1):match('::') then
|
||||
for _,v in pairs(chunks) do
|
||||
if #v > 0 and tonumber(v, 16) > 65535 then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return str
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
local ip = getIP(authority)
|
||||
if ip then
|
||||
self.host = ip
|
||||
elseif type(ip) == 'nil' then
|
||||
-- domain
|
||||
if authority ~= '' and not self.host then
|
||||
local host = authority:lower()
|
||||
if string.match(host, '^[%d%a%-%.]+$') ~= nil and
|
||||
string.sub(host, 0, 1) ~= '.' and
|
||||
string.sub(host, -1) ~= '.' and
|
||||
string.find(host, '%.%.') == nil then
|
||||
self.host = host
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if self.userinfo then
|
||||
local userinfo = self.userinfo
|
||||
userinfo = userinfo:gsub(':([^:]*)$', function(v)
|
||||
self.password = v
|
||||
return ''
|
||||
end)
|
||||
if string.find(userinfo, "^[%w%+%.]+$") then
|
||||
self.user = userinfo
|
||||
else
|
||||
-- incorrect userinfo
|
||||
self.userinfo = nil
|
||||
self.user = nil
|
||||
self.password = nil
|
||||
end
|
||||
end
|
||||
|
||||
return authority
|
||||
end
|
||||
|
||||
--- Parse the url into the designated parts.
|
||||
-- Depending on the url, the following parts can be available:
|
||||
-- scheme, userinfo, user, password, authority, host, port, path,
|
||||
-- query, fragment
|
||||
-- @param url Url string
|
||||
-- @return a table with the different parts and a few other functions
|
||||
function M.parse(url)
|
||||
local comp = {}
|
||||
M.setAuthority(comp, "")
|
||||
M.setQuery(comp, "")
|
||||
|
||||
local url = tostring(url or '')
|
||||
url = url:gsub('#(.*)$', function(v)
|
||||
comp.fragment = v
|
||||
return ''
|
||||
end)
|
||||
url =url:gsub('^([%w][%w%+%-%.]*)%:', function(v)
|
||||
comp.scheme = v:lower()
|
||||
return ''
|
||||
end)
|
||||
url = url:gsub('%?(.*)', function(v)
|
||||
M.setQuery(comp, v)
|
||||
return ''
|
||||
end)
|
||||
url = url:gsub('^//([^/]*)', function(v)
|
||||
M.setAuthority(comp, v)
|
||||
return ''
|
||||
end)
|
||||
|
||||
comp.path = url:gsub("([^/]+)", function (s) return encode(decode(s), M.options.legal_in_path) end)
|
||||
|
||||
setmetatable(comp, {
|
||||
__index = M,
|
||||
__tostring = M.build,
|
||||
__concat = concat,
|
||||
__div = M.addSegment
|
||||
})
|
||||
return comp
|
||||
end
|
||||
|
||||
--- removes dots and slashes in urls when possible
|
||||
-- This function will also remove multiple slashes
|
||||
-- @param path The string representing the path to clean
|
||||
-- @return a string of the path without unnecessary dots and segments
|
||||
function M.removeDotSegments(path)
|
||||
local fields = {}
|
||||
if string.len(path) == 0 then
|
||||
return ""
|
||||
end
|
||||
local startslash = false
|
||||
local endslash = false
|
||||
if string.sub(path, 1, 1) == "/" then
|
||||
startslash = true
|
||||
end
|
||||
if (string.len(path) > 1 or startslash == false) and string.sub(path, -1) == "/" then
|
||||
endslash = true
|
||||
end
|
||||
|
||||
path:gsub('[^/]+', function(c) table.insert(fields, c) end)
|
||||
|
||||
local new = {}
|
||||
local j = 0
|
||||
|
||||
for i,c in ipairs(fields) do
|
||||
if c == '..' then
|
||||
if j > 0 then
|
||||
j = j - 1
|
||||
end
|
||||
elseif c ~= "." then
|
||||
j = j + 1
|
||||
new[j] = c
|
||||
end
|
||||
end
|
||||
local ret = ""
|
||||
if #new > 0 and j > 0 then
|
||||
ret = table.concat(new, '/', 1, j)
|
||||
else
|
||||
ret = ""
|
||||
end
|
||||
if startslash then
|
||||
ret = '/'..ret
|
||||
end
|
||||
if endslash then
|
||||
ret = ret..'/'
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
local function reducePath(base_path, relative_path)
|
||||
if string.sub(relative_path, 1, 1) == "/" then
|
||||
return '/' .. string.gsub(relative_path, '^[%./]+', '')
|
||||
end
|
||||
local path = base_path
|
||||
local startslash = string.sub(path, 1, 1) ~= "/";
|
||||
if relative_path ~= "" then
|
||||
path = (startslash and '' or '/') .. path:gsub("[^/]*$", "")
|
||||
end
|
||||
path = path .. relative_path
|
||||
path = path:gsub("([^/]*%./)", function (s)
|
||||
if s ~= "./" then return s else return "" end
|
||||
end)
|
||||
path = string.gsub(path, "/%.$", "/")
|
||||
local reduced
|
||||
while reduced ~= path do
|
||||
reduced = path
|
||||
path = string.gsub(reduced, "([^/]*/%.%./)", function (s)
|
||||
if s ~= "../../" then return "" else return s end
|
||||
end)
|
||||
end
|
||||
path = string.gsub(path, "([^/]*/%.%.?)$", function (s)
|
||||
if s ~= "../.." then return "" else return s end
|
||||
end)
|
||||
local reduced
|
||||
while reduced ~= path do
|
||||
reduced = path
|
||||
path = string.gsub(reduced, '^/?%.%./', '')
|
||||
end
|
||||
return (startslash and '' or '/') .. path
|
||||
end
|
||||
|
||||
--- builds a new url by using the one given as parameter and resolving paths
|
||||
-- @param other A string or a table representing a url
|
||||
-- @return a new url table
|
||||
function M:resolve(other)
|
||||
if type(self) == "string" then
|
||||
self = M.parse(self)
|
||||
end
|
||||
if type(other) == "string" then
|
||||
other = M.parse(other)
|
||||
end
|
||||
if other.scheme then
|
||||
return other
|
||||
else
|
||||
other.scheme = self.scheme
|
||||
if not other.authority or other.authority == "" then
|
||||
other:setAuthority(self.authority)
|
||||
if not other.path or other.path == "" then
|
||||
other.path = self.path
|
||||
local query = other.query
|
||||
if not query or not next(query) then
|
||||
other.query = self.query
|
||||
end
|
||||
else
|
||||
other.path = reducePath(self.path, other.path)
|
||||
end
|
||||
end
|
||||
return other
|
||||
end
|
||||
end
|
||||
|
||||
--- normalize a url path following some common normalization rules
|
||||
-- described on <a href="http://en.wikipedia.org/wiki/URL_normalization">The URL normalization page of Wikipedia</a>
|
||||
-- @return the normalized path
|
||||
function M:normalize()
|
||||
if type(self) == 'string' then
|
||||
self = M.parse(self)
|
||||
end
|
||||
if self.path then
|
||||
local path = self.path
|
||||
path = reducePath(path, "")
|
||||
-- normalize multiple slashes
|
||||
path = string.gsub(path, "//+", "/")
|
||||
self.path = path
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
return M
|
57
lua/plugins/crowdsec/plugins/crowdsec/utils.lua
Normal file
57
lua/plugins/crowdsec/plugins/crowdsec/utils.lua
Normal file
|
@ -0,0 +1,57 @@
|
|||
local M = {}
|
||||
|
||||
|
||||
M.HTTP_CODE = {}
|
||||
M.HTTP_CODE["200"] = ngx.HTTP_OK
|
||||
M.HTTP_CODE["202"] = ngx.HTTP_ACCEPTED
|
||||
M.HTTP_CODE["204"] = ngx.HTTP_NO_CONTENT
|
||||
M.HTTP_CODE["301"] = ngx.HTTP_MOVED_PERMANENTLY
|
||||
M.HTTP_CODE["302"] = ngx.HTTP_MOVED_TEMPORARILY
|
||||
M.HTTP_CODE["400"] = ngx.HTTP_BAD_REQUEST
|
||||
M.HTTP_CODE["401"] = ngx.HTTP_UNAUTHORIZED
|
||||
M.HTTP_CODE["401"] = ngx.HTTP_UNAUTHORIZED
|
||||
M.HTTP_CODE["403"] = ngx.HTTP_FORBIDDEN
|
||||
M.HTTP_CODE["404"] = ngx.HTTP_NOT_FOUND
|
||||
M.HTTP_CODE["405"] = ngx.HTTP_NOT_ALLOWED
|
||||
M.HTTP_CODE["406"] = ngx.HTTP_NOT_ACCEPTABLE
|
||||
M.HTTP_CODE["500"] = ngx.HTTP_INTERNAL_SERVER_ERROR
|
||||
|
||||
function M.read_file(path)
|
||||
local file = io.open(path, "r") -- r read mode and b binary mode
|
||||
if not file then return nil end
|
||||
io.input(file)
|
||||
local content = io.read("*a")
|
||||
io.close(file)
|
||||
return content
|
||||
end
|
||||
|
||||
function M.file_exist(path)
|
||||
if path == nil then
|
||||
return nil
|
||||
end
|
||||
local f = io.open(path, "r")
|
||||
if f ~= nil then
|
||||
io.close(f)
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
function M.starts_with(str, start)
|
||||
return str:sub(1, #start) == start
|
||||
end
|
||||
|
||||
function M.ends_with(str, ending)
|
||||
return ending == "" or str:sub(-#ending) == ending
|
||||
end
|
||||
|
||||
function M.table_len(table)
|
||||
local count = 0
|
||||
for k, v in pairs(table) do
|
||||
count = count + 1
|
||||
end
|
||||
return count
|
||||
end
|
||||
|
||||
return M
|
1185
lua/plugins/crowdsec/resty/http.lua
Normal file
1185
lua/plugins/crowdsec/resty/http.lua
Normal file
File diff suppressed because it is too large
Load diff
341
lua/plugins/crowdsec/resty/http_connect.lua
Normal file
341
lua/plugins/crowdsec/resty/http_connect.lua
Normal file
|
@ -0,0 +1,341 @@
|
|||
local ffi = require "ffi"
|
||||
local ngx_re_gmatch = ngx.re.gmatch
|
||||
local ngx_re_sub = ngx.re.sub
|
||||
local ngx_re_find = ngx.re.find
|
||||
local ngx_log = ngx.log
|
||||
local ngx_WARN = ngx.WARN
|
||||
local ngx_DEBUG = ngx.DEBUG
|
||||
local to_hex = require("resty.string").to_hex
|
||||
local ffi_gc = ffi.gc
|
||||
local ffi_cast = ffi.cast
|
||||
local type = type
|
||||
|
||||
local lib_chain, lib_x509, lib_pkey
|
||||
local openssl_available, res = xpcall(function()
|
||||
lib_chain = require("resty.openssl.x509.chain")
|
||||
lib_x509 = require("resty.openssl.x509")
|
||||
lib_pkey = require("resty.openssl.pkey")
|
||||
end, debug.traceback)
|
||||
|
||||
if not openssl_available then
|
||||
ngx_log(ngx_WARN, "failed to load module `resty.openssl.*`, \z
|
||||
mTLS isn't supported without lua-resty-openssl:\n", res)
|
||||
end
|
||||
|
||||
--[[
|
||||
A connection function that incorporates:
|
||||
- tcp connect
|
||||
- ssl handshake
|
||||
- http proxy
|
||||
Due to this it will be better at setting up a socket pool where connections can
|
||||
be kept alive.
|
||||
|
||||
|
||||
Call it with a single options table as follows:
|
||||
|
||||
client:connect {
|
||||
scheme = "https" -- scheme to use, or nil for unix domain socket
|
||||
host = "myhost.com", -- target machine, or a unix domain socket
|
||||
port = nil, -- port on target machine, will default to 80/443 based on scheme
|
||||
pool = nil, -- connection pool name, leave blank! this function knows best!
|
||||
pool_size = nil, -- options as per: https://github.com/openresty/lua-nginx-module#tcpsockconnect
|
||||
backlog = nil,
|
||||
|
||||
-- ssl options as per: https://github.com/openresty/lua-nginx-module#tcpsocksslhandshake
|
||||
ssl_reused_session = nil
|
||||
ssl_server_name = nil,
|
||||
ssl_send_status_req = nil,
|
||||
ssl_verify = true, -- NOTE: defaults to true
|
||||
ctx = nil, -- NOTE: not supported
|
||||
|
||||
-- mTLS options: These require support for mTLS in cosockets, which first
|
||||
-- appeared in `ngx_http_lua_module` v0.10.23.
|
||||
ssl_client_cert = nil,
|
||||
ssl_client_priv_key = nil,
|
||||
|
||||
proxy_opts, -- proxy opts, defaults to global proxy options
|
||||
}
|
||||
]]
|
||||
local function connect(self, options)
|
||||
local sock = self.sock
|
||||
if not sock then
|
||||
return nil, "not initialized"
|
||||
end
|
||||
|
||||
local ok, err
|
||||
|
||||
local request_scheme = options.scheme
|
||||
local request_host = options.host
|
||||
local request_port = options.port
|
||||
|
||||
local poolname = options.pool
|
||||
local pool_size = options.pool_size
|
||||
local backlog = options.backlog
|
||||
|
||||
if request_scheme and not request_port then
|
||||
request_port = (request_scheme == "https" and 443 or 80)
|
||||
elseif request_port and not request_scheme then
|
||||
return nil, "'scheme' is required when providing a port"
|
||||
end
|
||||
|
||||
-- ssl settings
|
||||
local ssl, ssl_reused_session, ssl_server_name
|
||||
local ssl_verify, ssl_send_status_req, ssl_client_cert, ssl_client_priv_key
|
||||
if request_scheme == "https" then
|
||||
ssl = true
|
||||
ssl_reused_session = options.ssl_reused_session
|
||||
ssl_server_name = options.ssl_server_name
|
||||
ssl_send_status_req = options.ssl_send_status_req
|
||||
ssl_verify = true -- default
|
||||
if options.ssl_verify == false then
|
||||
ssl_verify = false
|
||||
end
|
||||
ssl_client_cert = options.ssl_client_cert
|
||||
ssl_client_priv_key = options.ssl_client_priv_key
|
||||
end
|
||||
|
||||
-- proxy related settings
|
||||
local proxy, proxy_uri, proxy_authorization, proxy_host, proxy_port, path_prefix
|
||||
proxy = options.proxy_opts or self.proxy_opts
|
||||
|
||||
if proxy then
|
||||
if request_scheme == "https" then
|
||||
proxy_uri = proxy.https_proxy
|
||||
proxy_authorization = proxy.https_proxy_authorization
|
||||
else
|
||||
proxy_uri = proxy.http_proxy
|
||||
proxy_authorization = proxy.http_proxy_authorization
|
||||
-- When a proxy is used, the target URI must be in absolute-form
|
||||
-- (RFC 7230, Section 5.3.2.). That is, it must be an absolute URI
|
||||
-- to the remote resource with the scheme, host and an optional port
|
||||
-- in place.
|
||||
--
|
||||
-- Since _format_request() constructs the request line by concatenating
|
||||
-- params.path and params.query together, we need to modify the path
|
||||
-- to also include the scheme, host and port so that the final form
|
||||
-- in conformant to RFC 7230.
|
||||
path_prefix = "http://" .. request_host .. (request_port == 80 and "" or (":" .. request_port))
|
||||
end
|
||||
if not proxy_uri then
|
||||
proxy = nil
|
||||
proxy_authorization = nil
|
||||
path_prefix = nil
|
||||
end
|
||||
end
|
||||
|
||||
if proxy and proxy.no_proxy then
|
||||
-- Check if the no_proxy option matches this host. Implementation adapted
|
||||
-- from lua-http library (https://github.com/daurnimator/lua-http)
|
||||
if proxy.no_proxy == "*" then
|
||||
-- all hosts are excluded
|
||||
proxy = nil
|
||||
|
||||
else
|
||||
local host = request_host
|
||||
local no_proxy_set = {}
|
||||
-- wget allows domains in no_proxy list to be prefixed by "."
|
||||
-- e.g. no_proxy=.mit.edu
|
||||
for host_suffix in ngx_re_gmatch(proxy.no_proxy, "\\.?([^,]+)") do
|
||||
no_proxy_set[host_suffix[1]] = true
|
||||
end
|
||||
|
||||
-- From curl docs:
|
||||
-- matched as either a domain which contains the hostname, or the
|
||||
-- hostname itself. For example local.com would match local.com,
|
||||
-- local.com:80, and www.local.com, but not www.notlocal.com.
|
||||
--
|
||||
-- Therefore, we keep stripping subdomains from the host, compare
|
||||
-- them to the ones in the no_proxy list and continue until we find
|
||||
-- a match or until there's only the TLD left
|
||||
repeat
|
||||
if no_proxy_set[host] then
|
||||
proxy = nil
|
||||
proxy_uri = nil
|
||||
proxy_authorization = nil
|
||||
break
|
||||
end
|
||||
|
||||
-- Strip the next level from the domain and check if that one
|
||||
-- is on the list
|
||||
host = ngx_re_sub(host, "^[^.]+\\.", "")
|
||||
until not ngx_re_find(host, "\\.")
|
||||
end
|
||||
end
|
||||
|
||||
if proxy then
|
||||
local proxy_uri_t
|
||||
proxy_uri_t, err = self:parse_uri(proxy_uri)
|
||||
if not proxy_uri_t then
|
||||
return nil, "uri parse error: " .. err
|
||||
end
|
||||
|
||||
local proxy_scheme = proxy_uri_t[1]
|
||||
if proxy_scheme ~= "http" then
|
||||
return nil, "protocol " .. tostring(proxy_scheme) ..
|
||||
" not supported for proxy connections"
|
||||
end
|
||||
proxy_host = proxy_uri_t[2]
|
||||
proxy_port = proxy_uri_t[3]
|
||||
end
|
||||
|
||||
local cert_hash
|
||||
if ssl and ssl_client_cert and ssl_client_priv_key then
|
||||
local cert_type = type(ssl_client_cert)
|
||||
local key_type = type(ssl_client_priv_key)
|
||||
|
||||
if cert_type ~= "cdata" then
|
||||
return nil, "bad ssl_client_cert: cdata expected, got " .. cert_type
|
||||
end
|
||||
|
||||
if key_type ~= "cdata" then
|
||||
return nil, "bad ssl_client_priv_key: cdata expected, got " .. key_type
|
||||
end
|
||||
|
||||
if not openssl_available then
|
||||
return nil, "module `resty.openssl.*` not available, mTLS isn't supported without lua-resty-openssl"
|
||||
end
|
||||
|
||||
-- convert from `void*` to `OPENSSL_STACK*`
|
||||
local cert_chain, err = lib_chain.dup(ffi_cast("OPENSSL_STACK*", ssl_client_cert))
|
||||
if not cert_chain then
|
||||
return nil, "failed to dup the ssl_client_cert: " .. err
|
||||
end
|
||||
|
||||
if #cert_chain < 1 then
|
||||
return nil, "no cert in ssl_client_cert"
|
||||
end
|
||||
|
||||
local cert, err = lib_x509.dup(cert_chain[1].ctx)
|
||||
if not cert then
|
||||
return nil, "failed to dup the x509: " .. err
|
||||
end
|
||||
|
||||
-- convert from `void*` to `EVP_PKEY*`
|
||||
local key, err = lib_pkey.new(ffi_cast("EVP_PKEY*", ssl_client_priv_key))
|
||||
if not key then
|
||||
return nil, "failed to new the pkey: " .. err
|
||||
end
|
||||
|
||||
-- should not free the cdata passed in
|
||||
ffi_gc(key.ctx, nil)
|
||||
|
||||
-- check the private key in order to make sure the caller is indeed the holder of the cert
|
||||
ok, err = cert:check_private_key(key)
|
||||
if not ok then
|
||||
return nil, "the private key doesn't match the cert: " .. err
|
||||
end
|
||||
|
||||
cert_hash, err = cert:digest("sha256")
|
||||
if not cert_hash then
|
||||
return nil, "failed to calculate the digest of the cert: " .. err
|
||||
end
|
||||
|
||||
cert_hash = to_hex(cert_hash) -- convert to hex so that it's printable
|
||||
end
|
||||
|
||||
-- construct a poolname unique within proxy and ssl info
|
||||
if not poolname then
|
||||
poolname = (request_scheme or "")
|
||||
.. ":" .. request_host
|
||||
.. ":" .. tostring(request_port)
|
||||
.. ":" .. tostring(ssl)
|
||||
.. ":" .. (ssl_server_name or "")
|
||||
.. ":" .. tostring(ssl_verify)
|
||||
.. ":" .. (proxy_uri or "")
|
||||
.. ":" .. (request_scheme == "https" and proxy_authorization or "")
|
||||
.. ":" .. (cert_hash or "")
|
||||
-- in the above we only add the 'proxy_authorization' as part of the poolname
|
||||
-- when the request is https. Because in that case the CONNECT request (which
|
||||
-- carries the authorization header) is part of the connect procedure, whereas
|
||||
-- with a plain http request the authorization is part of the actual request.
|
||||
end
|
||||
|
||||
ngx_log(ngx_DEBUG, "poolname: ", poolname)
|
||||
|
||||
-- do TCP level connection
|
||||
local tcp_opts = { pool = poolname, pool_size = pool_size, backlog = backlog }
|
||||
if proxy then
|
||||
-- proxy based connection
|
||||
ok, err = sock:connect(proxy_host, proxy_port, tcp_opts)
|
||||
if not ok then
|
||||
return nil, "failed to connect to: " .. (proxy_host or "") ..
|
||||
":" .. (proxy_port or "") ..
|
||||
": " .. err
|
||||
end
|
||||
|
||||
if ssl and sock:getreusedtimes() == 0 then
|
||||
-- Make a CONNECT request to create a tunnel to the destination through
|
||||
-- the proxy. The request-target and the Host header must be in the
|
||||
-- authority-form of RFC 7230 Section 5.3.3. See also RFC 7231 Section
|
||||
-- 4.3.6 for more details about the CONNECT request
|
||||
local destination = request_host .. ":" .. request_port
|
||||
local res
|
||||
res, err = self:request({
|
||||
method = "CONNECT",
|
||||
path = destination,
|
||||
headers = {
|
||||
["Host"] = destination,
|
||||
["Proxy-Authorization"] = proxy_authorization,
|
||||
}
|
||||
})
|
||||
|
||||
if not res then
|
||||
return nil, "failed to issue CONNECT to proxy: " .. err
|
||||
end
|
||||
|
||||
if res.status < 200 or res.status > 299 then
|
||||
return nil, "failed to establish a tunnel through a proxy: " .. res.status
|
||||
end
|
||||
end
|
||||
|
||||
elseif not request_port then
|
||||
-- non-proxy, without port -> unix domain socket
|
||||
ok, err = sock:connect(request_host, tcp_opts)
|
||||
if not ok then
|
||||
return nil, err
|
||||
end
|
||||
|
||||
else
|
||||
-- non-proxy, regular network tcp
|
||||
ok, err = sock:connect(request_host, request_port, tcp_opts)
|
||||
if not ok then
|
||||
return nil, err
|
||||
end
|
||||
end
|
||||
|
||||
local ssl_session
|
||||
-- Now do the ssl handshake
|
||||
if ssl and sock:getreusedtimes() == 0 then
|
||||
|
||||
-- Experimental mTLS support
|
||||
if ssl_client_cert and ssl_client_priv_key then
|
||||
if type(sock.setclientcert) ~= "function" then
|
||||
return nil, "cannot use SSL client cert and key without mTLS support"
|
||||
|
||||
else
|
||||
ok, err = sock:setclientcert(ssl_client_cert, ssl_client_priv_key)
|
||||
if not ok then
|
||||
return nil, "could not set client certificate: " .. err
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
ssl_session, err = sock:sslhandshake(ssl_reused_session, ssl_server_name, ssl_verify, ssl_send_status_req)
|
||||
if not ssl_session then
|
||||
self:close()
|
||||
return nil, err
|
||||
end
|
||||
end
|
||||
|
||||
self.host = request_host
|
||||
self.port = request_port
|
||||
self.keepalive = true
|
||||
self.ssl = ssl
|
||||
-- set only for http, https has already been handled
|
||||
self.http_proxy_auth = request_scheme ~= "https" and proxy_authorization or nil
|
||||
self.path_prefix = path_prefix
|
||||
|
||||
return true, nil, ssl_session
|
||||
end
|
||||
|
||||
return connect
|
44
lua/plugins/crowdsec/resty/http_headers.lua
Normal file
44
lua/plugins/crowdsec/resty/http_headers.lua
Normal file
|
@ -0,0 +1,44 @@
|
|||
local rawget, rawset, setmetatable =
|
||||
rawget, rawset, setmetatable
|
||||
|
||||
local str_lower = string.lower
|
||||
|
||||
local _M = {
|
||||
_VERSION = '0.17.2',
|
||||
}
|
||||
|
||||
|
||||
-- Returns an empty headers table with internalised case normalisation.
|
||||
function _M.new()
|
||||
local mt = {
|
||||
normalised = {},
|
||||
}
|
||||
|
||||
mt.__index = function(t, k)
|
||||
return rawget(t, mt.normalised[str_lower(k)])
|
||||
end
|
||||
|
||||
mt.__newindex = function(t, k, v)
|
||||
local k_normalised = str_lower(k)
|
||||
|
||||
-- First time seeing this header field?
|
||||
if not mt.normalised[k_normalised] then
|
||||
-- Create a lowercased entry in the metatable proxy, with the value
|
||||
-- of the given field case
|
||||
mt.normalised[k_normalised] = k
|
||||
|
||||
-- Set the header using the given field case
|
||||
rawset(t, k, v)
|
||||
else
|
||||
-- We're being updated just with a different field case. Use the
|
||||
-- normalised metatable proxy to give us the original key case, and
|
||||
-- perorm a rawset() to update the value.
|
||||
rawset(t, mt.normalised[k_normalised], v)
|
||||
end
|
||||
end
|
||||
|
||||
return setmetatable({}, mt)
|
||||
end
|
||||
|
||||
|
||||
return _M
|
99
lua/plugins/crowdsec/templates/crowdsec/ban.html
Normal file
99
lua/plugins/crowdsec/templates/crowdsec/ban.html
Normal file
|
@ -0,0 +1,99 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>nadeko.net Protection</title>
|
||||
<meta content="text/html; charset=utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<script src="{{captcha_frontend_js}}" async defer></script>
|
||||
<style>
|
||||
html {
|
||||
background-color: #111111;
|
||||
}
|
||||
|
||||
body {
|
||||
color: aliceblue;
|
||||
font-size: 1em;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 5px;
|
||||
font-family: 'Franklin Gothic Medium', sans-serif;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4 {
|
||||
margin: 5px;
|
||||
font-family: 'Franklin Gothic Medium', sans-serif;
|
||||
font-weight: lighter;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
a:link,
|
||||
a:visited {
|
||||
color: rgb(231, 196, 255);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
footer {
|
||||
text-align: center;
|
||||
padding-bottom: 1em;
|
||||
color: rgb(184, 184, 184);
|
||||
/* background-color: #f1f1f1; */
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
bottom: 0;
|
||||
font-size: 1;
|
||||
}
|
||||
|
||||
.header {
|
||||
font-size: small;
|
||||
margin: 0px;
|
||||
width: max-content;
|
||||
/* height: 200px; */
|
||||
/* outline: dashed 2px white; */
|
||||
/* Center child horizontally*/
|
||||
display: flex;
|
||||
flex: 2 1 auto;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
|
||||
.content {
|
||||
margin: 10px auto;
|
||||
max-width: 90%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#mcaptcha__widget-container {
|
||||
margin: 10px 0px;
|
||||
height: 88px;
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="content">
|
||||
<p style="color: red;">Your IP has been banned from the server.</p>
|
||||
<p style="color: red;">Tú IP ha sido baneada del servidor.</p>
|
||||
</div>
|
||||
<footer>
|
||||
<p>Beta feature to prevent bots, false positives can happen.</p>
|
||||
<p>Funcionalidad beta para evitar bots, pueden producirse falsos positivos.</p>
|
||||
</footer>
|
||||
</body>
|
||||
|
||||
</html>
|
66
lua/plugins/crowdsec/templates/crowdsec/ban2.html
Normal file
66
lua/plugins/crowdsec/templates/crowdsec/ban2.html
Normal file
|
@ -0,0 +1,66 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>nadeko.net Protection</title>
|
||||
<meta content="text/html; charset=utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<script src="{{captcha_frontend_js}}" async defer></script>
|
||||
<style>
|
||||
html {
|
||||
background-color: #111111;
|
||||
}
|
||||
|
||||
body {
|
||||
color: aliceblue;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 5px;
|
||||
font-family: 'Franklin Gothic Medium';
|
||||
}
|
||||
|
||||
a:link {
|
||||
color: rgb(231, 196, 255);
|
||||
}
|
||||
|
||||
a:visited {
|
||||
color: rgb(231, 196, 255);
|
||||
}
|
||||
|
||||
.header {
|
||||
font-size: small;
|
||||
margin: 0px;
|
||||
width: max-content;
|
||||
/* height: 200px; */
|
||||
/* outline: dashed 2px white; */
|
||||
/* Center child horizontally*/
|
||||
display: flex;
|
||||
flex: 2 1 auto;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.content {
|
||||
margin: 10px;
|
||||
/* width: 350px; */
|
||||
/* height: 200px; */
|
||||
/* outline: dashed 5px white; */
|
||||
/* Center child horizontally*/
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="content">
|
||||
<p style="color: red;">Estas baneado del servidor</p>
|
||||
<p style="color: red;">You are banned from the server</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
136
lua/plugins/crowdsec/templates/crowdsec/captcha.html
Normal file
136
lua/plugins/crowdsec/templates/crowdsec/captcha.html
Normal file
|
@ -0,0 +1,136 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>nadeko.net Protection</title>
|
||||
<meta content="text/html; charset=utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<!--<script src="{{captcha_frontend_js}}" async defer></script>-->
|
||||
<style>
|
||||
html {
|
||||
background-color: #111111;
|
||||
}
|
||||
|
||||
body {
|
||||
color: aliceblue;
|
||||
font-size: 1em;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 5px;
|
||||
font-family: 'Franklin Gothic Medium', sans-serif;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4 {
|
||||
margin: 5px;
|
||||
font-family: 'Franklin Gothic Medium', sans-serif;
|
||||
font-weight: lighter;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
a:link,
|
||||
a:visited {
|
||||
color: rgb(231, 196, 255);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
footer {
|
||||
text-align: center;
|
||||
padding-bottom: 1em;
|
||||
color: rgb(184, 184, 184);
|
||||
/* background-color: #f1f1f1; */
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
bottom: 0;
|
||||
font-size: 1;
|
||||
}
|
||||
|
||||
.header {
|
||||
font-size: small;
|
||||
margin: 0px;
|
||||
width: max-content;
|
||||
/* height: 200px; */
|
||||
/* outline: dashed 2px white; */
|
||||
/* Center child horizontally*/
|
||||
display: flex;
|
||||
flex: 2 1 auto;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
|
||||
.content {
|
||||
margin: 10px auto;
|
||||
max-width: 90%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#mcaptcha__widget-container {
|
||||
margin: 10px 0px;
|
||||
height: 88px;
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="content">
|
||||
<h3>Verifying that you are not a bot/abuser</h3>
|
||||
<h3>Verificando que no seas un bot/abusador</h3>
|
||||
<form action="" method="POST" id="captcha-form">
|
||||
<label data-mcaptcha_url="https://mcaptcha.nadeko.net/widget/?sitekey={{captcha_site_key}}"
|
||||
for="mcaptcha__token" id="mcaptcha__token-label">
|
||||
<p>Looks like you don't have javacript enabled, don't worry, you can still access this site following
|
||||
this <a href="https://mcaptcha.org/docs/user-manual/how-to-mcaptcha-without-js/">instructions</a>
|
||||
</p>
|
||||
<p>Parece que no tienes javascript habilitado, no te preocupes, aun puedes acceder al sitio siguiendo
|
||||
estas <a href="https://mcaptcha.org/docs/user-manual/how-to-mcaptcha-without-js/">instrucciones</a>
|
||||
</p>
|
||||
Token: <input type="text" name="mcaptcha__token" id="mcaptcha__token" />
|
||||
</label>
|
||||
<div id="mcaptcha__widget-container"></div>
|
||||
<script>!function (e, t) { "object" == typeof exports && "object" == typeof module ? module.exports = t() : "function" == typeof define && define.amd ? define([], t) : "object" == typeof exports ? exports.mcaptchaGlue = t() : e.mcaptchaGlue = t() }(self, (() => { return e = { 260: function (e) { var t; t = () => (() => { "use strict"; var e = {}; return { 166: function (e, t) { var n, i = this && this.__extends || (n = function (e, t) { return n = Object.setPrototypeOf || { __proto__: [] } instanceof Array && function (e, t) { e.__proto__ = t } || function (e, t) { for (var n in t) Object.prototype.hasOwnProperty.call(t, n) && (e[n] = t[n]) }, n(e, t) }, function (e, t) { if ("function" != typeof t && null !== t) throw new TypeError("Class extends value " + String(t) + " is not a constructor or null"); function i() { this.constructor = e } n(e, t), e.prototype = null === t ? Object.create(t) : (i.prototype = t.prototype, new i) }); Object.defineProperty(t, "__esModule", { value: !0 }), t.ConfigurationError = void 0; var r = function (e) { function t() { var t = null !== e && e.apply(this, arguments) || this; return t.message = "Provide either widget link or site key to display mCaptcha widget", t } return i(t, e), t }(Error); t.ConfigurationError = r; var o = function () { function e(e, t) { var n = this; if (this.handle = function (e) { console.log("message received from ".concat(e.origin, " with data: ").concat(e.data.token)), new URL(e.origin).host == n.widgetLink.host ? n.updateState(e.data.token) : console.error("expected message from ".concat(n.widgetLink.host, " but received message from ").concat(e.origin, ". Aborting.")) }, this.updateState = t, e.widgetLink && e.siteKey) throw new r; if (e.widgetLink) this.widgetLink = e.widgetLink; else { if (!e.siteKey) throw new r; e.siteKey.instanceUrl ? (this.widgetLink = e.siteKey.instanceUrl, this.widgetLink.pathname = "/widget/", this.widgetLink.search = "?sitekey=".concat(e.siteKey.key)) : this.widgetLink = new URL("https://demo.mcaptcha.org/widget/?sitekey=".concat(e.siteKey.key)) } } return e.prototype.listen = function () { window.addEventListener("message", this.handle) }, e.prototype.destroy = function () { window.removeEventListener("message", this.handle) }, e }(); t.default = o } }[166](0, e), e })(), e.exports = t() }, 942: (e, t) => { "use strict"; Object.defineProperty(t, "__esModule", { value: !0 }), t.ID = t.INPUT_LABEL_ID = t.INPUT_NAME = void 0, t.INPUT_NAME = "mcaptcha__token", t.INPUT_LABEL_ID = "mcaptcha__token-label", t.ID = "mcaptcha__widget-container" }, 166: function (e, t, n) { "use strict"; var i = this && this.__importDefault || function (e) { return e && e.__esModule ? e : { default: e } }; Object.defineProperty(t, "__esModule", { value: !0 }), t.ConfigurationError = void 0; var r = n(260); Object.defineProperty(t, "ConfigurationError", { enumerable: !0, get: function () { return r.ConfigurationError } }); var o = i(n(695)); (0, n(695).run)(), t.default = o.default }, 695: function (e, t, n) { "use strict"; var i = this && this.__importDefault || function (e) { return e && e.__esModule ? e : { default: e } }; Object.defineProperty(t, "__esModule", { value: !0 }), t.run = void 0; var r = i(n(260)), o = n(942), a = function (e) { var t = this; this.setToken = function (e) { return t.inputElement.value = e }, this.receiver = new r.default(e, this.setToken), this.receiver.listen(); var n = document.getElementById(o.ID); if (null == n) throw new Error("Element ".concat(o.ID, "'s parent element is undefined")); var i = document.getElementById(o.INPUT_LABEL_ID); null !== i && (i.style.display = "none"), this.inputElement = document.getElementById(o.INPUT_NAME), this.inputElement.id = o.INPUT_NAME, this.inputElement.name = o.INPUT_NAME, this.inputElement.hidden = !0, this.inputElement.required = !0, this.inputElement.style.display = "none", n.appendChild(this.inputElement); var a = "mcaptcha-widget__iframe", s = document.createElement("iframe"); s.title = "mCaptcha", s.src = this.receiver.widgetLink.toString(), s.ariaRoleDescription = "presentation", s.name = a, s.id = a, s.scrolling = "no"; try { s.sandbox = "allow-same-origin allow-scripts allow-popups" } catch (e) { try { s.sandbox.add("allow-same-origin"), s.sandbox.add("allow-scripts"), s.sandbox.add("allow-popups") } catch (e) { s.setAttribute("sandbox", "allow-same-origin allow-scripts allow-popups") } } s.width = "100%", s.height = "100%", s.frameBorder = "0", n.appendChild(s) }; t.run = function () { var e = document.getElementById(o.INPUT_LABEL_ID); if (null === e || !e.dataset.mcaptcha_url) throw new Error('Couldn\'t find "mcaptcha_url" dataset in elmement (ID='.concat(o.INPUT_LABEL_ID, ")")); var t = { widgetLink: new URL(e.dataset.mcaptcha_url) }; new a(t) }, t.default = a } }, t = {}, function n(i) { var r = t[i]; if (void 0 !== r) return r.exports; var o = t[i] = { exports: {} }; return e[i].call(o.exports, o, o.exports, n), o.exports }(166); var e, t }));</script>
|
||||
<!-- </div> -->
|
||||
</form>
|
||||
<span style="color: rgb(131, 255, 54);">
|
||||
<p>(This will not leave any cookies in your browser)</p>
|
||||
<p>(Esto no dejará ninguna cookie en tu navegador)</p>
|
||||
</span>
|
||||
<script>
|
||||
// Yes this is made by a LLM becuase I suck at javascript
|
||||
function captchaCallback() {
|
||||
setTimeout(() => {
|
||||
document.querySelector('#captcha-form').submit();
|
||||
}, 500);
|
||||
}
|
||||
function submitForm() {
|
||||
document.getElementById('captcha-form').submit();
|
||||
}
|
||||
function checkTokenValue() {
|
||||
var tokenInput = document.getElementById('mcaptcha__token');
|
||||
if (tokenInput.value.trim() !== "") {
|
||||
submitForm();
|
||||
}
|
||||
}
|
||||
setInterval(checkTokenValue, 500);
|
||||
</script>
|
||||
</div>
|
||||
<footer>
|
||||
<p>Beta feature to prevent bots, false positives can happen.</p>
|
||||
<p>Funcionalidad beta para evitar bots, pueden producirse falsos positivos.</p>
|
||||
</footer>
|
||||
</body>
|
||||
|
||||
</html>
|
132
lua/plugins/crowdsec/templates/crowdsec/captcha2.html
Normal file
132
lua/plugins/crowdsec/templates/crowdsec/captcha2.html
Normal file
|
@ -0,0 +1,132 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>nadeko.net Protection</title>
|
||||
<meta content="text/html; charset=utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<script src="{{captcha_frontend_js}}" async defer></script>
|
||||
<style>
|
||||
html {
|
||||
background-color: #111111;
|
||||
}
|
||||
|
||||
body {
|
||||
color: aliceblue;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 5px;
|
||||
font-family: 'Franklin Gothic Medium';
|
||||
}
|
||||
|
||||
a:link {
|
||||
color: rgb(231, 196, 255);
|
||||
}
|
||||
|
||||
a:visited {
|
||||
color: rgb(231, 196, 255);
|
||||
}
|
||||
|
||||
.header {
|
||||
font-size: small;
|
||||
margin: 0px;
|
||||
width: max-content;
|
||||
/* height: 200px; */
|
||||
/* outline: dashed 2px white; */
|
||||
/* Center child horizontally*/
|
||||
display: flex;
|
||||
flex: 2 1 auto;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.content {
|
||||
margin: 10px;
|
||||
/* width: 350px; */
|
||||
/* height: 200px; */
|
||||
/* outline: dashed 5px white; */
|
||||
/* Center child horizontally*/
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="content">
|
||||
<p>Verifying that you are not a bot/abuser</p>
|
||||
<p>Verificando que no seas un bot/abusador</p>
|
||||
<form action="" method="POST" id="captcha-form">
|
||||
<div id="captcha" style="border: #ffffff; border-style: dotted;" class="{{captcha_frontend_key}}" data-sitekey="{{captcha_site_key}}"
|
||||
data-callback="captchaCallback"></div>
|
||||
|
||||
<!--<form id="captcha-form">-->
|
||||
<!-- <label-->
|
||||
<!-- data-mcaptcha_url="https://mcaptcha.nadeko.net/widget/?sitekey=XyyQdgv8Qje0hgAhxgAkrXE8upwa2c2c"-->
|
||||
<!-- for="mcaptcha__token"-->
|
||||
<!-- id="mcaptcha__token-label">-->
|
||||
<!-- mCaptcha authorization token.-->
|
||||
<!-- <a-->
|
||||
<!-- href="https://mcaptcha.org/docs/user-manual/how-to-mcaptcha-without-js/"-->
|
||||
<!-- >Instructions</a-->
|
||||
<!-- >-->
|
||||
<!-- <input type="text" name="mcaptcha__token" id="mcaptcha__token" />-->
|
||||
<!-- </label>-->
|
||||
<!-- <div id="mcaptcha__widget-container"></div>-->
|
||||
<!-- <button type="submit">Submit Form</button>-->
|
||||
<!--</form>-->
|
||||
|
||||
<!--<label-->
|
||||
<!-- data-mcaptcha_url="https://mcaptcha.nadeko.net/widget/?sitekey=XyyQdgv8Qje0hgAhxgAkrXE8upwa2c2c"-->
|
||||
<!-- for="mcaptcha__token"-->
|
||||
<!-- id="mcaptcha__token-label">-->
|
||||
<!-- mCaptcha authorization token.-->
|
||||
<!-- <a-->
|
||||
<!-- href="https://mcaptcha.org/docs/user-manual/how-to-mcaptcha-without-js/"-->
|
||||
<!-- >Instructions</a-->
|
||||
<!-- >-->
|
||||
<!-- <input type="text" name="mcaptcha__token" id="mcaptcha__token" />-->
|
||||
<!--</label>-->
|
||||
<!--<div id="mcaptcha__widget-container"></div>-->
|
||||
|
||||
</form>
|
||||
<script>
|
||||
const moonSvgString = '<svg fill="black" class="h-6 w-6" viewBox="0 0 35 35" data-name="Layer 2" id="Layer_2" xmlns="http://www.w3.org/2000/svg"><path d="M18.44,34.68a18.22,18.22,0,0,1-2.94-.24,18.18,18.18,0,0,1-15-20.86A18.06,18.06,0,0,1,9.59.63,2.42,2.42,0,0,1,12.2.79a2.39,2.39,0,0,1,1,2.41L11.9,3.1l1.23.22A15.66,15.66,0,0,0,23.34,21h0a15.82,15.82,0,0,0,8.47.53A2.44,2.44,0,0,1,34.47,25,18.18,18.18,0,0,1,18.44,34.68ZM10.67,2.89a15.67,15.67,0,0,0-5,22.77A15.66,15.66,0,0,0,32.18,24a18.49,18.49,0,0,1-9.65-.64A18.18,18.18,0,0,1,10.67,2.89Z"/></svg>'
|
||||
const sunSvgString = '<svg class="h-6 w-6" viewBox="0 0 24 24" fill="white" xmlns="http://www.w3.org/2000/svg"><path d="M3 12H5M5.00006 19L7.00006 17M12 19V21M17 17L19 19M5 5L7 7M19 12H21M16.9999 7L18.9999 5M12 3V5M15 12C15 13.6569 13.6569 15 12 15C10.3431 15 9 13.6569 9 12C9 10.3431 10.3431 9 12 9C13.6569 9 15 10.3431 15 12Z" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>'
|
||||
const svg = Array.from(document.querySelectorAll('svg'));
|
||||
const button = document.querySelector('.dark-mode-toggle');
|
||||
window.onload = () => {
|
||||
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
||||
document.body.classList.add('dark');
|
||||
svg.forEach(element => {
|
||||
element.setAttribute('fill', 'white');
|
||||
});
|
||||
button.innerHTML = sunSvgString;
|
||||
} else {
|
||||
button.innerHTML = moonSvgString;
|
||||
}
|
||||
button.addEventListener('click', function () {
|
||||
document.body.classList.toggle('dark');
|
||||
svg.forEach(element => {
|
||||
element.getAttribute('fill') === 'black' ? element.setAttribute('fill', 'white') : element.setAttribute('fill', 'black');
|
||||
});
|
||||
if (document.body.classList.contains('dark')) {
|
||||
button.innerHTML = sunSvgString;
|
||||
} else {
|
||||
button.innerHTML = moonSvgString;
|
||||
}
|
||||
});
|
||||
};
|
||||
function captchaCallback() {
|
||||
setTimeout(() => {
|
||||
document.querySelector('#captcha-form').submit();
|
||||
}, 500);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
150
lua/plugins/crowdsec/templates/crowdsec/captcha3.html
Normal file
150
lua/plugins/crowdsec/templates/crowdsec/captcha3.html
Normal file
|
@ -0,0 +1,150 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>nadeko.net Protection</title>
|
||||
<meta content="text/html; charset=utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<!--<script src="{{captcha_frontend_js}}" async defer></script>-->
|
||||
<style>
|
||||
|
||||
html {
|
||||
background-color: #111111;
|
||||
}
|
||||
|
||||
body {
|
||||
color: aliceblue;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 5px;
|
||||
font-family: 'Franklin Gothic Medium';
|
||||
}
|
||||
|
||||
h1, h2, h3, h4 {
|
||||
margin: 5px;
|
||||
font-family: 'Franklin Gothic Medium';
|
||||
font-weight: 100;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
a:link {
|
||||
color: rgb(231, 196, 255);
|
||||
}
|
||||
|
||||
a:visited {
|
||||
color: rgb(231, 196, 255);
|
||||
}
|
||||
|
||||
.header {
|
||||
font-size: small;
|
||||
margin: 0px;
|
||||
width: max-content;
|
||||
/* height: 200px; */
|
||||
/* outline: dashed 2px white; */
|
||||
/* Center child horizontally*/
|
||||
display: flex;
|
||||
flex: 2 1 auto;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
|
||||
.content {
|
||||
margin: 10px auto;
|
||||
max-width: 60%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#mcaptcha__widget-container {
|
||||
margin: 10px 10px;
|
||||
height: 88px;
|
||||
width: 400px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="content">
|
||||
<h3>Verifying that you are not a bot/abuser</h3>
|
||||
<h3>Verificando que no seas un bot/abusador</h3>
|
||||
<!--<form action="" method="POST" id="captcha-form">-->
|
||||
<!--<div id="captcha" style="border: #ffffff; border-style: dotted;" class="{{captcha_frontend_key}}" data-sitekey="{{captcha_site_key}}" data-callback="captchaCallback"></div>-->
|
||||
|
||||
<!--<form id="captcha-form">-->
|
||||
<!-- <label-->
|
||||
<!-- data-mcaptcha_url="https://mcaptcha.nadeko.net/widget/?sitekey=XyyQdgv8Qje0hgAhxgAkrXE8upwa2c2c"-->
|
||||
<!-- for="mcaptcha__token"-->
|
||||
<!-- id="mcaptcha__token-label">-->
|
||||
<!-- mCaptcha authorization token.-->
|
||||
<!-- <a-->
|
||||
<!-- href="https://mcaptcha.org/docs/user-manual/how-to-mcaptcha-without-js/"-->
|
||||
<!-- >Instructions</a-->
|
||||
<!-- >-->
|
||||
<!-- <input type="text" name="mcaptcha__token" id="mcaptcha__token" />-->
|
||||
<!-- </label>-->
|
||||
<!-- <div id="mcaptcha__widget-container"></div>-->
|
||||
<!-- <button type="submit">Submit Form</button>-->
|
||||
<!--</form>-->
|
||||
|
||||
<!--<label-->
|
||||
<!-- data-mcaptcha_url="https://mcaptcha.nadeko.net/widget/?sitekey={{captcha_site_key}}"-->
|
||||
<!-- for="mcaptcha__token"-->
|
||||
<!-- id="mcaptcha__token-label">-->
|
||||
<!--</label>-->
|
||||
<!-- mCaptcha authorization token.-->
|
||||
<!-- <a-->
|
||||
<!-- href="https://mcaptcha.org/docs/user-manual/how-to-mcaptcha-without-js/"-->
|
||||
<!-- >Instructions</a-->
|
||||
<!-- >-->
|
||||
<!-- <input type="text" name="mcaptcha__token" id="mcaptcha__token" />-->
|
||||
<!-- <div id="mcaptcha__widget-container"></div>-->
|
||||
<!-- <script src="{{captcha_frontend_js}}"></script>-->
|
||||
<!--<div id="mcaptcha__widget-container"></div>-->
|
||||
|
||||
<form action="" method="POST" id="captcha-form">
|
||||
<!-- <div id="captcha" style="border-style: dotted;"> -->
|
||||
<label data-mcaptcha_url="https://mcaptcha.nadeko.net/widget/?sitekey={{captcha_site_key}}"
|
||||
for="mcaptcha__token" id="mcaptcha__token-label">
|
||||
<p>Looks like you don't have javacript enabled, don't worry, you can still access this site following this <a href="https://mcaptcha.org/docs/user-manual/how-to-mcaptcha-without-js/">instructions</a></p>
|
||||
<p>Parece que no tienes javascript habilitado, no te preocupes, aun puedes acceder al sitio siguiendo estas <a href="https://mcaptcha.org/docs/user-manual/how-to-mcaptcha-without-js/">instrucciones</a></p>
|
||||
<!-- <a href="https://mcaptcha.org/docs/user-manual/how-to-mcaptcha-without-js/">Instructions</a> -->
|
||||
<!--<input type="hidden" name="token" id="mcaptcha__token" />-->
|
||||
Token: <input type="text" name="mcaptcha__token" id="mcaptcha__token" />
|
||||
</label>
|
||||
<div id="mcaptcha__widget-container"></div>
|
||||
<script src="https://unpkg.com/@mcaptcha/vanilla-glue@0.1.0-rc2/dist/index.js"></script>
|
||||
<!-- </div> -->
|
||||
</form>
|
||||
<script>
|
||||
function captchaCallback() {
|
||||
setTimeout(() => {
|
||||
document.querySelector('#captcha-form').submit();
|
||||
}, 500);
|
||||
}
|
||||
|
||||
// Function to handle form submission
|
||||
function submitForm() {
|
||||
document.getElementById('captcha-form').submit();
|
||||
}
|
||||
|
||||
// Function to check the value of the hidden input field
|
||||
function checkTokenValue() {
|
||||
var tokenInput = document.getElementById('mcaptcha__token');
|
||||
if (tokenInput.value.trim() !== "") {
|
||||
submitForm();
|
||||
}
|
||||
}
|
||||
|
||||
// Set up an interval to periodically check the hidden input field
|
||||
setInterval(checkTokenValue, 500); // Check every 1 second
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
15
lua/rewrite-invidious/check-backend.lua
Normal file
15
lua/rewrite-invidious/check-backend.lua
Normal file
|
@ -0,0 +1,15 @@
|
|||
local _M = {}
|
||||
|
||||
function _M.check_backend_args()
|
||||
local args = ngx.req.get_uri_args()
|
||||
for key, val in pairs(args) do
|
||||
if key == "backend" then
|
||||
val = tonumber(val)
|
||||
if val == nil then
|
||||
ngx.say("error: Wrong backend. Backends only supports numbers!")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return _M
|
6
lua/rewrite-invidious/init.lua
Normal file
6
lua/rewrite-invidious/init.lua
Normal file
|
@ -0,0 +1,6 @@
|
|||
local checkbackend = require "rewrite-invidious/check-backend"
|
||||
|
||||
-- Functions
|
||||
if ngx.req.get_headers()["Host"] == "inv.nadeko.net" then
|
||||
checkbackend.check_backend_args()
|
||||
end
|
100
lua/sticky-http3.lua
Normal file
100
lua/sticky-http3.lua
Normal file
|
@ -0,0 +1,100 @@
|
|||
-- Based on https://github.com/Klaessen/openresty-loadbalancers/blob/main/sticky-balancer.lua
|
||||
|
||||
local _M = {}
|
||||
|
||||
local balancer = require "ngx.balancer"
|
||||
local cookie_name = "INVIDIOUS_SERVER_ID"
|
||||
|
||||
-- Define backend server based on their capabilities (IP, port, weight, number of retries before timeout, duration of timeout, current number of fails, fail timestamp)
|
||||
local servers = {
|
||||
{ "unix:/tmp/http3-ytproxy.sock", 10061, weight = 1, max_fails = 3, fail_timeout = 30, fail_count = 0, last_fail_time = 0 },
|
||||
{ "127.0.0.1", 10071, weight = 1, max_fails = 3, fail_timeout = 30, fail_count = 0, last_fail_time = 0 },
|
||||
{ "127.0.0.1", 10081, weight = 1, max_fails = 3, fail_timeout = 30, fail_count = 0, last_fail_time = 0 },
|
||||
{ "127.0.0.1", 20101, weight = 1, max_fails = 3, fail_timeout = 30, fail_count = 0, last_fail_time = 0 },
|
||||
}
|
||||
|
||||
-- Generate a weighted server list based on weights
|
||||
local function generate_weighted_server_list(servers)
|
||||
local weighted_servers = {}
|
||||
for _, server in ipairs(servers) do
|
||||
for i = 1, server.weight do
|
||||
table.insert(weighted_servers, server)
|
||||
end
|
||||
end
|
||||
return weighted_servers
|
||||
end
|
||||
|
||||
local weighted_servers = generate_weighted_server_list(servers)
|
||||
|
||||
-- Hash function to select server
|
||||
local function hash(key, num_buckets)
|
||||
local hash = ngx.crc32_long(key)
|
||||
return (hash % num_buckets) + 1
|
||||
end
|
||||
|
||||
-- Check if a server is available based on max_fails and fail_timeout
|
||||
local function is_server_available(server)
|
||||
if server.fail_count >= server.max_fails then
|
||||
if (ngx.now() - server.last_fail_time) < server.fail_timeout then
|
||||
return false
|
||||
else
|
||||
server.fail_count = 0
|
||||
server.last_fail_time = 0
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
-- Select server based on cookie or assign a new one
|
||||
local function select_server()
|
||||
local cookie = ngx.var["cookie_" .. cookie_name]
|
||||
local host = ""
|
||||
local server_index
|
||||
|
||||
math.randomseed(os.time())
|
||||
|
||||
if cookie then
|
||||
server_index = tonumber(cookie)
|
||||
ngx.header["X-Server-Id"] = server_index
|
||||
else
|
||||
-- server_index = hash(ngx.var.remote_addr, #weighted_servers)
|
||||
server_index = math.random(#servers)
|
||||
ngx.header["Set-Cookie"] = cookie_name .. "=" .. server_index .. "; domain=.nadeko.net; Path=/; HttpOnly; SameSite=None; Secure; Partitioned"
|
||||
end
|
||||
|
||||
local server = weighted_servers[server_index]
|
||||
if is_server_available(server) then
|
||||
return server
|
||||
else
|
||||
-- If the selected server is not available, find another one
|
||||
for _, s in ipairs(weighted_servers) do
|
||||
if is_server_available(s) then
|
||||
return s
|
||||
end
|
||||
end
|
||||
end
|
||||
ngx.log(ngx.ERR, "No available servers")
|
||||
return nil
|
||||
end
|
||||
|
||||
local server = select_server()
|
||||
if not server then
|
||||
ngx.exit(502)
|
||||
return
|
||||
end
|
||||
|
||||
local ok, err
|
||||
|
||||
if string.match(server[1], 'unix:') then
|
||||
ok, err = balancer.set_current_peer(server[1])
|
||||
else
|
||||
ok, err = balancer.set_current_peer(server[1], server[2])
|
||||
end
|
||||
|
||||
-- local ok, err = balancer.set_current_peer(server[1], server[2])
|
||||
if not ok then
|
||||
ngx.log(ngx.ERR, "Failed to set the current peer: ", err)
|
||||
server.fail_count = server.fail_count + 1
|
||||
server.last_fail_time = ngx.now()
|
||||
return ngx.exit(500)
|
||||
end
|
22
lua/switchbackend.lua
Normal file
22
lua/switchbackend.lua
Normal file
|
@ -0,0 +1,22 @@
|
|||
-- TODO: Support Clear, TOR and I2P
|
||||
local args = ngx.req.get_uri_args()
|
||||
local referer = ngx.req.get_headers()["Referer"]
|
||||
local host = ngx.req.get_headers()["Host"]
|
||||
local cookie_name = "INVIDIOUS_SERVER_ID"
|
||||
local domain = ".nadeko.net"
|
||||
|
||||
-- TOR Support
|
||||
if string.match(host, ".onion") then
|
||||
domain = host
|
||||
end
|
||||
|
||||
for key, server_index in pairs(args) do
|
||||
if key == "backend_id" then
|
||||
ngx.header["Set-Cookie"] = cookie_name .. "=" .. server_index .. "; domain=" .. domain .. "; Path=/; HttpOnly; SameSite=None; Secure; Partitioned"
|
||||
end
|
||||
end
|
||||
|
||||
if referer == nil then
|
||||
return ngx.redirect("/", 302)
|
||||
end
|
||||
return ngx.redirect(referer, 302)
|
20
lua/unused/image-cache.lua
Normal file
20
lua/unused/image-cache.lua
Normal file
|
@ -0,0 +1,20 @@
|
|||
local _M = {}
|
||||
|
||||
-- alternatively: local lrucache = require "resty.lrucache.pureffi"
|
||||
local lrucache = require "resty.lrucache"
|
||||
|
||||
-- we need to initialize the cache on the lua module level so that
|
||||
-- it can be shared by all the requests served by each nginx worker process:
|
||||
local c, err = lrucache.new(1024) -- allow up to 200 items in the cache
|
||||
if not c then
|
||||
error("failed to create the cache: " .. (err or "unknown"))
|
||||
end
|
||||
|
||||
function _M.go()
|
||||
ngx.say("cat: ", c:get("cat"))
|
||||
c:set("dog", { age = 10 }, 0.1) -- expire in 0.1 sec
|
||||
end
|
||||
|
||||
|
||||
|
||||
return _M
|
14
lua/unused/invidious-healthcheck.lua
Normal file
14
lua/unused/invidious-healthcheck.lua
Normal file
|
@ -0,0 +1,14 @@
|
|||
local http = require "resty.http"
|
||||
|
||||
local httpc = http.new()
|
||||
httpc:set_timeout(250)
|
||||
local res, err = httpc:request_uri("", {
|
||||
method = "HEAD",
|
||||
})
|
||||
|
||||
httpc:close()
|
||||
if err ~= nil then
|
||||
return true, err
|
||||
end
|
||||
|
||||
ngx.say(res.status)
|
94
lua/unused/invidious-sticky-i2p.lua
Normal file
94
lua/unused/invidious-sticky-i2p.lua
Normal file
|
@ -0,0 +1,94 @@
|
|||
-- Based on https://github.com/Klaessen/openresty-loadbalancers/blob/main/sticky-balancer.lua
|
||||
|
||||
local _M = {}
|
||||
|
||||
local balancer = require "ngx.balancer"
|
||||
local cookie_name = "SERVER_ID"
|
||||
|
||||
-- Define backend server based on their capabilities (IP, port, weight, number of retries before timeout, duration of timeout, current number of fails, fail timestamp)
|
||||
local servers = {
|
||||
{ "127.0.0.1", 10063, weight = 1, max_fails = 3, fail_timeout = 30, fail_count = 0, last_fail_time = 0 },
|
||||
{ "127.0.0.1", 10073, weight = 1, max_fails = 3, fail_timeout = 30, fail_count = 0, last_fail_time = 0 },
|
||||
{ "127.0.0.1", 10083, weight = 1, max_fails = 3, fail_timeout = 30, fail_count = 0, last_fail_time = 0 },
|
||||
}
|
||||
|
||||
-- Put the ammount of servers in the dictionary
|
||||
local s = ngx.shared.servers
|
||||
s:set("invidious-servers", #servers)
|
||||
|
||||
-- Generate a weighted server list based on weights
|
||||
local function generate_weighted_server_list(servers)
|
||||
local weighted_servers = {}
|
||||
for _, server in ipairs(servers) do
|
||||
for i = 1, server.weight do
|
||||
table.insert(weighted_servers, server)
|
||||
end
|
||||
end
|
||||
return weighted_servers
|
||||
end
|
||||
|
||||
local weighted_servers = generate_weighted_server_list(servers)
|
||||
|
||||
-- Hash function to select server
|
||||
local function hash(key, num_buckets)
|
||||
local hash = ngx.crc32_long(key)
|
||||
return (hash % num_buckets) + 1
|
||||
end
|
||||
|
||||
-- Check if a server is available based on max_fails and fail_timeout
|
||||
local function is_server_available(server)
|
||||
if server.fail_count >= server.max_fails then
|
||||
if (ngx.now() - server.last_fail_time) < server.fail_timeout then
|
||||
return false
|
||||
else
|
||||
server.fail_count = 0
|
||||
server.last_fail_time = 0
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
-- Select server based on cookie or assign a new one
|
||||
local function select_server()
|
||||
local cookie = ngx.var["cookie_" .. cookie_name]
|
||||
local host = ngx.req.get_headers()["Host"]
|
||||
local server_index
|
||||
|
||||
math.randomseed(os.time())
|
||||
|
||||
if cookie then
|
||||
server_index = tonumber(cookie)
|
||||
ngx.header["X-Server-Id"] = server_index
|
||||
else
|
||||
-- server_index = hash(ngx.var.remote_addr, #weighted_servers)
|
||||
server_index = math.random(#servers)
|
||||
ngx.header["Set-Cookie"] = cookie_name .. "=" .. server_index .. "; domain=" .. host .. "; Path=/; HttpOnly; SameSite=Lax"
|
||||
end
|
||||
|
||||
local server = weighted_servers[server_index]
|
||||
if is_server_available(server) then
|
||||
return server
|
||||
else
|
||||
-- If the selected server is not available, find another one
|
||||
for _, s in ipairs(weighted_servers) do
|
||||
if is_server_available(s) then
|
||||
return s
|
||||
end
|
||||
end
|
||||
end
|
||||
ngx.log(ngx.ERR, "No available servers")
|
||||
return nil
|
||||
end
|
||||
|
||||
local server = select_server()
|
||||
if not server then
|
||||
ngx.exit(502)
|
||||
return
|
||||
end
|
||||
local ok, err = balancer.set_current_peer(server[1], server[2])
|
||||
if not ok then
|
||||
ngx.log(ngx.ERR, "Failed to set the current peer: ", err)
|
||||
server.fail_count = server.fail_count + 1
|
||||
server.last_fail_time = ngx.now()
|
||||
return ngx.exit(500)
|
||||
end
|
93
lua/unused/invidious-sticky-tor.lua
Normal file
93
lua/unused/invidious-sticky-tor.lua
Normal file
|
@ -0,0 +1,93 @@
|
|||
-- Based on https://github.com/Klaessen/openresty-loadbalancers/blob/main/sticky-balancer.lua
|
||||
|
||||
local _M = {}
|
||||
|
||||
local balancer = require "ngx.balancer"
|
||||
local cookie_name = "SERVER_ID"
|
||||
|
||||
-- Define backend server based on their capabilities (IP, port, weight, number of retries before timeout, duration of timeout, current number of fails, fail timestamp)
|
||||
local servers = {
|
||||
{ "127.0.0.1", 10062, weight = 1, max_fails = 3, fail_timeout = 30, fail_count = 0, last_fail_time = 0 },
|
||||
{ "127.0.0.1", 10072, weight = 1, max_fails = 3, fail_timeout = 30, fail_count = 0, last_fail_time = 0 },
|
||||
{ "127.0.0.1", 10082, weight = 1, max_fails = 3, fail_timeout = 30, fail_count = 0, last_fail_time = 0 },
|
||||
}
|
||||
|
||||
-- Put the ammount of servers in the dictionary
|
||||
local s = ngx.shared.servers
|
||||
s:set("invidious-servers", #servers)
|
||||
|
||||
-- Generate a weighted server list based on weights
|
||||
local function generate_weighted_server_list(servers)
|
||||
local weighted_servers = {}
|
||||
for _, server in ipairs(servers) do
|
||||
for i = 1, server.weight do
|
||||
table.insert(weighted_servers, server)
|
||||
end
|
||||
end
|
||||
return weighted_servers
|
||||
end
|
||||
|
||||
local weighted_servers = generate_weighted_server_list(servers)
|
||||
|
||||
-- Hash function to select server
|
||||
local function hash(key, num_buckets)
|
||||
local hash = ngx.crc32_long(key)
|
||||
return (hash % num_buckets) + 1
|
||||
end
|
||||
|
||||
-- Check if a server is available based on max_fails and fail_timeout
|
||||
local function is_server_available(server)
|
||||
if server.fail_count >= server.max_fails then
|
||||
if (ngx.now() - server.last_fail_time) < server.fail_timeout then
|
||||
return false
|
||||
else
|
||||
server.fail_count = 0
|
||||
server.last_fail_time = 0
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
-- Select server based on cookie or assign a new one
|
||||
local function select_server()
|
||||
local cookie = ngx.var["cookie_" .. cookie_name]
|
||||
local server_index
|
||||
|
||||
math.randomseed(os.time())
|
||||
|
||||
if cookie then
|
||||
server_index = tonumber(cookie)
|
||||
ngx.header["X-Server-Id"] = server_index
|
||||
else
|
||||
-- server_index = hash(ngx.var.remote_addr, #weighted_servers)
|
||||
server_index = math.random(#servers)
|
||||
ngx.header["Set-Cookie"] = cookie_name .. "=" .. server_index .. "; domain=inv.nadekonw7plitnjuawu6ytjsl7jlglk2t6pyq6eftptmiv3dvqndwvyd.onion; Path=/; HttpOnly; SameSite=Lax"
|
||||
end
|
||||
|
||||
local server = weighted_servers[server_index]
|
||||
if is_server_available(server) then
|
||||
return server
|
||||
else
|
||||
-- If the selected server is not available, find another one
|
||||
for _, s in ipairs(weighted_servers) do
|
||||
if is_server_available(s) then
|
||||
return s
|
||||
end
|
||||
end
|
||||
end
|
||||
ngx.log(ngx.ERR, "No available servers")
|
||||
return nil
|
||||
end
|
||||
|
||||
local server = select_server()
|
||||
if not server then
|
||||
ngx.exit(502)
|
||||
return
|
||||
end
|
||||
local ok, err = balancer.set_current_peer(server[1], server[2])
|
||||
if not ok then
|
||||
ngx.log(ngx.ERR, "Failed to set the current peer: ", err)
|
||||
server.fail_count = server.fail_count + 1
|
||||
server.last_fail_time = ngx.now()
|
||||
return ngx.exit(500)
|
||||
end
|
96
lua/unused/invidious-sticky.lua
Normal file
96
lua/unused/invidious-sticky.lua
Normal file
|
@ -0,0 +1,96 @@
|
|||
-- Based on https://github.com/Klaessen/openresty-loadbalancers/blob/main/sticky-balancer.lua
|
||||
|
||||
local _M = {}
|
||||
|
||||
local balancer = require "ngx.balancer"
|
||||
local cookie_name = "SERVER_ID"
|
||||
|
||||
-- Define backend server based on their capabilities (IP, port, weight, number of retries before timeout, duration of timeout, current number of fails, fail timestamp)
|
||||
local servers = {
|
||||
{ "127.0.0.1", 10060, weight = 1, max_fails = 3, fail_timeout = 30, fail_count = 0, last_fail_time = 0 },
|
||||
{ "127.0.0.1", 10070, weight = 1, max_fails = 3, fail_timeout = 30, fail_count = 0, last_fail_time = 0 },
|
||||
{ "127.0.0.1", 10080, weight = 1, max_fails = 3, fail_timeout = 30, fail_count = 0, last_fail_time = 0 },
|
||||
{ "127.0.0.1", 20100, weight = 1, max_fails = 3, fail_timeout = 30, fail_count = 0, last_fail_time = 0 },
|
||||
}
|
||||
|
||||
-- Put the ammount of servers in the dictionary
|
||||
local s = ngx.shared.servers
|
||||
s:set("invidious-servers", #servers)
|
||||
|
||||
-- Generate a weighted server list based on weights
|
||||
local function generate_weighted_server_list(servers)
|
||||
local weighted_servers = {}
|
||||
for _, server in ipairs(servers) do
|
||||
for i = 1, server.weight do
|
||||
table.insert(weighted_servers, server)
|
||||
end
|
||||
end
|
||||
return weighted_servers
|
||||
end
|
||||
|
||||
local weighted_servers = generate_weighted_server_list(servers)
|
||||
|
||||
-- Hash function to select server
|
||||
local function hash(key, num_buckets)
|
||||
local hash = ngx.crc32_long(key)
|
||||
return (hash % num_buckets) + 1
|
||||
end
|
||||
|
||||
-- Check if a server is available based on max_fails and fail_timeout
|
||||
local function is_server_available(server)
|
||||
if server.fail_count >= server.max_fails then
|
||||
if (ngx.now() - server.last_fail_time) < server.fail_timeout then
|
||||
return false
|
||||
else
|
||||
server.fail_count = 0
|
||||
server.last_fail_time = 0
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
-- Select server based on cookie or assign a new one
|
||||
local function select_server()
|
||||
local cookie = ngx.var["cookie_" .. cookie_name]
|
||||
local server_index
|
||||
local host = ngx.req.get_headers()["Host"]
|
||||
|
||||
math.randomseed(os.time())
|
||||
|
||||
if cookie then
|
||||
server_index = tonumber(cookie)
|
||||
ngx.header["X-Server-Id"] = server_index
|
||||
else
|
||||
-- server_index = hash(ngx.var.remote_addr, #weighted_servers)
|
||||
server_index = math.random(#servers)
|
||||
-- FIX I2P. Secure doesn't work on i2p IIRC
|
||||
ngx.header["Set-Cookie"] = cookie_name .. "=" .. server_index .. "; domain=" .. host .. "; Path=/; SameSite=None; Secure"
|
||||
end
|
||||
|
||||
local server = weighted_servers[server_index]
|
||||
if is_server_available(server) then
|
||||
return server
|
||||
else
|
||||
-- If the selected server is not available, find another one
|
||||
for _, s in ipairs(weighted_servers) do
|
||||
if is_server_available(s) then
|
||||
return s
|
||||
end
|
||||
end
|
||||
end
|
||||
ngx.log(ngx.ERR, "No available servers")
|
||||
return nil
|
||||
end
|
||||
|
||||
local server = select_server()
|
||||
if not server then
|
||||
ngx.exit(502)
|
||||
return
|
||||
end
|
||||
local ok, err = balancer.set_current_peer(server[1], server[2])
|
||||
if not ok then
|
||||
ngx.log(ngx.ERR, "Failed to set the current peer: ", err)
|
||||
server.fail_count = server.fail_count + 1
|
||||
server.last_fail_time = ngx.now()
|
||||
return ngx.exit(500)
|
||||
end
|
114
lua/unused/youtube-image-proxy.lua
Normal file
114
lua/unused/youtube-image-proxy.lua
Normal file
|
@ -0,0 +1,114 @@
|
|||
local req = require("reqwest")
|
||||
local http = require "resty.http"
|
||||
-- local res, err = req.request("https://i.ytimg.com/" .. path, { headers = { ["User-Agent"] = "" }, version = 2 })
|
||||
-- if err ~= nil then
|
||||
-- ngx.status = 500
|
||||
-- ngx.say(err)
|
||||
-- end
|
||||
--
|
||||
-- ngx.print(res.body)
|
||||
-- your_script.lua
|
||||
|
||||
-- local function blocking_io_operation()
|
||||
-- local path = ngx.var.uri
|
||||
-- local res, err = req.request("https://i.ytimg.com/" .. path, { headers = { ["User-Agent"] = "" }, version = 2 })
|
||||
-- if err ~= nil then
|
||||
-- ngx.status = 500
|
||||
-- ngx.say(err)
|
||||
-- end
|
||||
-- return res
|
||||
-- end
|
||||
--
|
||||
local function blocking_io_operation()
|
||||
local httpc = http.new()
|
||||
local path = ngx.var.uri
|
||||
-- local res, err = req.request("https://i.ytimg.com/" .. path, { headers = { ["User-Agent"] = "" }, version = 2 })
|
||||
-- if err ~= nil then
|
||||
-- ngx.status = 500
|
||||
-- ngx.say(err)
|
||||
-- end
|
||||
-- return res
|
||||
|
||||
local res, err = httpc:request_uri("https://i.ytimg.com/" .. path, {
|
||||
method = "GET",
|
||||
headers = {
|
||||
["User-Agent"] = "Mozilla/5.0 (X11; Linux x86_64; rv:131.0) Gecko/20100101 Firefox/131.0",
|
||||
}
|
||||
})
|
||||
-- httpc:close()
|
||||
if err ~= nil then
|
||||
ngx.status = 500
|
||||
ngx.say(err)
|
||||
end
|
||||
ngx.print(res.body)
|
||||
-- return res.body
|
||||
|
||||
end
|
||||
--
|
||||
-- local function main()
|
||||
-- -- Spawn a new thread to handle the blocking operation
|
||||
-- local co = ngx.thread.spawn(blocking_io_operation)
|
||||
--
|
||||
-- -- Do other processing here if needed
|
||||
--
|
||||
-- -- Wait for the thread to finish and retrieve the result
|
||||
-- local ok, result = ngx.thread.wait(co)
|
||||
--
|
||||
-- if not ok then
|
||||
-- ngx.say("Error: ", result)
|
||||
-- return
|
||||
-- end
|
||||
--
|
||||
-- ngx.print(result.body)
|
||||
-- end
|
||||
|
||||
-- main()
|
||||
blocking_io_operation()
|
||||
|
||||
--
|
||||
-- local req = require("reqwest")
|
||||
--
|
||||
-- local path = ngx.var.uri
|
||||
--
|
||||
-- -- Function to perform the request
|
||||
-- local function perform_request()
|
||||
-- local res, err = req.request("https://i.ytimg.com/" .. path, { headers = { ["User-Agent"] = "reqwest" }, version = 2 })
|
||||
-- if err then
|
||||
-- ngx.log(ngx.ERR, "12313" .. err)
|
||||
-- return nil, err
|
||||
-- end
|
||||
-- return res.body
|
||||
-- end
|
||||
--
|
||||
-- -- Thread function
|
||||
-- local function thread_func(premature)
|
||||
-- if premature then
|
||||
-- return
|
||||
-- end
|
||||
--
|
||||
-- -- Perform the request in the thread
|
||||
-- local body, err = perform_request()
|
||||
--
|
||||
-- if err then
|
||||
-- ngx.log(ngx.ERR, "Request failed: " .. err)
|
||||
-- -- Here you can handle the error (e.g., log it)
|
||||
-- else
|
||||
-- -- Here you can store or process the response as needed
|
||||
-- ngx.shared.my_shared_dict:set("response_body", body)
|
||||
-- ngx.say(body)
|
||||
-- -- ngx.log(ngx.ERR, "Requesasdasd")
|
||||
-- end
|
||||
-- end
|
||||
--
|
||||
-- -- Start the thread
|
||||
-- local ok, err = ngx.thread.spawn(thread_func)
|
||||
-- if not ok then
|
||||
--
|
||||
-- ngx.log(ngx.ERR, "xd")
|
||||
-- ngx.status = 500
|
||||
-- ngx.say("Failed to spawn thread: " .. err)
|
||||
-- return
|
||||
-- end
|
||||
--
|
||||
-- -- Respond immediately, while the request is processed in the background
|
||||
-- -- ngx.say("Request is being processed. You will receive the response shortly.")
|
104
nginx.conf
Normal file
104
nginx.conf
Normal file
|
@ -0,0 +1,104 @@
|
|||
worker_processes auto;
|
||||
worker_rlimit_nofile 65535;
|
||||
pid /run/openresty.pid;
|
||||
error_log /var/log/nginx/error.log debug;
|
||||
|
||||
# include modules/*.conf;
|
||||
#load_module /usr/lib/nginx/modules/ngx_http_brotli_filter_module.so; # for compressing responses on-the-fly
|
||||
#load_module /usr/lib/nginx/modules/ngx_http_brotli_static_module.so; # for serving pre-compressed files
|
||||
#load_module /usr/lib/nginx/modules/ngx_http_headers_more_filter_module.so; # To add headers to any location without the nginx bullshit
|
||||
#load_module /usr/lib/nginx/modules/ngx_http_zstd_filter_module.so; # for compressing responses on-the-fly
|
||||
#load_module /usr/lib/nginx/modules/ngx_http_zstd_static_module.so; # for serving pre-compressed files
|
||||
#load_module /usr/lib/nginx/modules/ngx_http_lua_module.so; # ngx_lua
|
||||
#load_module /usr/lib/nginx/modules/ngx_http_lua_module.so
|
||||
|
||||
quic_bpf on;
|
||||
|
||||
events {
|
||||
worker_connections 4096;
|
||||
multi_accept on;
|
||||
use epoll;
|
||||
}
|
||||
|
||||
#resolver 127.0.0.1;
|
||||
|
||||
http {
|
||||
map $server_addr $unix {
|
||||
default 0;
|
||||
"~unix:" 1;
|
||||
}
|
||||
|
||||
include configs/cache.conf;
|
||||
|
||||
lua_package_path '/etc/openresty/lua/plugins/crowdsec/?.lua;/etc/openresty/lua/?.lua;/etc/openresty/lua/?/?.lua;;';
|
||||
lua_package_cpath '/usr/lib/lua/5.1/?.so;;';
|
||||
lua_shared_dict crowdsec_cache 128m;
|
||||
lua_socket_pool_size 1024;
|
||||
resolver 127.0.0.1 ipv6=off;
|
||||
lua_ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt;
|
||||
init_by_lua_file "conf/lua/init.lua";
|
||||
|
||||
include configs/crowdsec.conf;
|
||||
|
||||
log_format limited '$remote_addr - $remote_user [$time_local] '
|
||||
'"$request_method /bogus $server_protocol" $status $body_bytes_sent '
|
||||
'"-" "Bogus/66.6" - "$http_host"';
|
||||
|
||||
access_log off;
|
||||
error_log /dev/null;
|
||||
#error_log /var/log/nginx/error.log debug;
|
||||
|
||||
# Basic Settings
|
||||
charset utf-8;
|
||||
sendfile on;
|
||||
tcp_nopush on;
|
||||
tcp_nodelay on;
|
||||
server_tokens off;
|
||||
log_not_found off;
|
||||
types_hash_max_size 1024;
|
||||
types_hash_bucket_size 128;
|
||||
server_names_hash_bucket_size 128;
|
||||
|
||||
# MIME
|
||||
include mime.types;
|
||||
|
||||
# SSL
|
||||
include configs/ssl.conf;
|
||||
|
||||
# reset timed out connections freeing ram
|
||||
reset_timedout_connection on;
|
||||
# maximum time between packets the client can pause when sending nginx any data
|
||||
client_body_timeout 10s;
|
||||
# maximum time the client has to send the entire header to nginx
|
||||
client_header_timeout 10s;
|
||||
# timeout which a single keep-alive client connection will stay open
|
||||
keepalive_timeout 60s;
|
||||
# maximum time between packets nginx is allowed to pause when sending the client data
|
||||
send_timeout 10s;
|
||||
|
||||
client_body_buffer_size 32k;
|
||||
client_max_body_size 2m;
|
||||
|
||||
# open_file_cache max=1024 inactive=10s;
|
||||
# open_file_cache_valid 60s;
|
||||
# open_file_cache_min_uses 2;
|
||||
# open_file_cache_errors on;
|
||||
|
||||
# PERFORMANCE / ASYNC I/O
|
||||
#aio threads=default;
|
||||
#aio_write on;
|
||||
#directio 2m;
|
||||
|
||||
# QUIC settings
|
||||
# https://nginx.org/en/docs/http/ngx_http_v3_module.html
|
||||
quic_gso on;
|
||||
|
||||
# Maps
|
||||
include snippets/maps.conf;
|
||||
#include snippets/poop.conf;
|
||||
|
||||
include configs/general.conf;
|
||||
include configs/upstreams.conf;
|
||||
include configs/limits.conf;
|
||||
include http.d/*.conf;
|
||||
}
|
35
snippets/maps.conf
Normal file
35
snippets/maps.conf
Normal file
|
@ -0,0 +1,35 @@
|
|||
# Connection header for WebSocket reverse proxy
|
||||
|
||||
map $http_upgrade $connection_upgrade {
|
||||
default upgrade;
|
||||
"" close;
|
||||
}
|
||||
|
||||
map $remote_addr $proxy_forwarded_elem {
|
||||
|
||||
# IPv4 addresses can be sent as-is
|
||||
~^[0-9.]+$ "for=$remote_addr";
|
||||
|
||||
# IPv6 addresses need to be bracketed and quoted
|
||||
~^[0-9A-Fa-f:.]+$ "for=\"[$remote_addr]\"";
|
||||
|
||||
# Unix domain socket names cannot be represented in RFC 7239 syntax
|
||||
default "for=unknown";
|
||||
}
|
||||
|
||||
map $http_forwarded $proxy_add_forwarded {
|
||||
|
||||
# If the incoming Forwarded header is syntactically valid, append to it
|
||||
"~^(,[ \\t]*)*([!#$%&'*+.^_`|~0-9A-Za-z-]+=([!#$%&'*+.^_`|~0-9A-Za-z-]+|\"([\\t \\x21\\x23-\\x5B\\x5D-\\x7E\\x80-\\xFF]|\\\\[\\t \\x21-\\x7E\\x80-\\xFF])*\"))?(;([!#$%&'*+.^_`|~0-9A-Za-z-]+=([!#$%&'*+.^_`|~0-9A-Za-z-]+|\"([\\t \\x21\\x23-\\x5B\\x5D-\\x7E\\x80-\\xFF]|\\\\[\\t \\x21-\\x7E\\x80-\\xFF])*\"))?)*([ \\t]*,([ \\t]*([!#$%&'*+.^_`|~0-9A-Za-z-]+=([!#$%&'*+.^_`|~0-9A-Za-z-]+|\"([\\t \\x21\\x23-\\x5B\\x5D-\\x7E\\x80-\\xFF]|\\\\[\\t \\x21-\\x7E\\x80-\\xFF])*\"))?(;([!#$%&'*+.^_`|~0-9A-Za-z-]+=([!#$%&'*+.^_`|~0-9A-Za-z-]+|\"([\\t \\x21\\x23-\\x5B\\x5D-\\x7E\\x80-\\xFF]|\\\\[\\t \\x21-\\x7E\\x80-\\xFF])*\"))?)*)?)*$" "$http_forwarded, $proxy_forwarded_elem";
|
||||
|
||||
# Otherwise, replace it
|
||||
default "$proxy_forwarded_elem";
|
||||
}
|
||||
|
||||
map $http_user_agent $blocked_agent {
|
||||
default 0;
|
||||
~*meta-externalagent 1;
|
||||
~*SemrushBot 1;
|
||||
~*MJ12bot 1;
|
||||
~*amazonbot 1;
|
||||
}
|
Loading…
Add table
Reference in a new issue