init openresty config

This commit is contained in:
Fijxu 2025-02-11 20:13:49 -03:00
commit 7f132979c4
55 changed files with 6767 additions and 0 deletions

17
configs/general.conf Normal file
View 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
View 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
View 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
View 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
View file

@ -0,0 +1 @@
location /robots.txt { return 200 "User-agent: *\nDisallow: /";}

6
configs/security.conf Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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;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

View 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

File diff suppressed because it is too large Load diff

View 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

View 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

View 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>

View 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>

View 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>

View 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>

View 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>

View 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

View 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
View 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
View 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)

View 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

View 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)

View 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

View 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

View 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

View 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
View 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
View 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;
}