mirror of
https://gitgud.io/fatchan/haproxy-protection.git
synced 2025-05-09 02:05:37 +00:00
Merge branch 'master' into kikeflare
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2021 Eugene Prodan
|
Copyright (c) 2021 Eugene Prodan
|
||||||
Copyright (c) 2022-2023 Thomas Lynch (fatchan) <thomas@69420.me>
|
Copyright (c) 2022-2023 Thomas Lynch (fatchan) <tom@69420.me>
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
@@ -28,8 +28,8 @@ See [INSTALLATION.md](INSTALLATION.md)
|
|||||||
|
|
||||||
#### Screenshots
|
#### Screenshots
|
||||||
|
|
||||||

|

|
||||||
")
|
")
|
||||||
|
|
||||||
## For generous people
|
## For generous people
|
||||||
|
|
||||||
|
@@ -5,11 +5,14 @@ services:
|
|||||||
network_mode: host
|
network_mode: host
|
||||||
ports:
|
ports:
|
||||||
- 80:80
|
- 80:80
|
||||||
|
# - 2000:2000 #runtime api
|
||||||
|
# - 2001:2001 #dataplaneapi
|
||||||
build:
|
build:
|
||||||
context: ./
|
context: ./
|
||||||
dockerfile: haproxy/Dockerfile
|
dockerfile: haproxy/Dockerfile
|
||||||
volumes:
|
volumes:
|
||||||
- ./haproxy/haproxy.cfg:/etc/haproxy/haproxy.cfg
|
- ./haproxy/haproxy.cfg:/etc/haproxy/haproxy.cfg
|
||||||
|
- ./haproxy/dataplaneapi.hcl:/etc/haproxy/dataplaneapi.hcl
|
||||||
- ./haproxy/map/:/etc/haproxy/map/
|
- ./haproxy/map/:/etc/haproxy/map/
|
||||||
- ./haproxy/template/:/etc/haproxy/template/
|
- ./haproxy/template/:/etc/haproxy/template/
|
||||||
- ./src/lua/scripts/:/etc/haproxy/scripts/
|
- ./src/lua/scripts/:/etc/haproxy/scripts/
|
||||||
|
@@ -17,7 +17,8 @@ RUN set -eux; \
|
|||||||
--uid 99 \
|
--uid 99 \
|
||||||
haproxy
|
haproxy
|
||||||
|
|
||||||
ENV HAPROXY_URL http://www.haproxy.org/download/2.6/src/snapshot/haproxy-ss-LATEST.tar.gz
|
ENV HAPROXY_URL http://www.haproxy.org/download/2.7/src/snapshot/haproxy-ss-LATEST.tar.gz
|
||||||
|
ENV DATAPLANEAPI_URL https://github.com/haproxytech/dataplaneapi/releases/download/v2.7.2/dataplaneapi_2.7.2_Linux_x86_64.tar.gz
|
||||||
|
|
||||||
# see https://sources.debian.net/src/haproxy/jessie/debian/rules/ for some helpful navigation of the possible "make" arguments
|
# see https://sources.debian.net/src/haproxy/jessie/debian/rules/ for some helpful navigation of the possible "make" arguments
|
||||||
RUN set -eux; \
|
RUN set -eux; \
|
||||||
@@ -37,6 +38,11 @@ RUN set -eux; \
|
|||||||
; \
|
; \
|
||||||
rm -rf /var/lib/apt/lists/*; \
|
rm -rf /var/lib/apt/lists/*; \
|
||||||
\
|
\
|
||||||
|
wget -O dataplaneapi_Linux_x86_64.tar.gz "$DATAPLANEAPI_URL"; \
|
||||||
|
tar -zxvf dataplaneapi_Linux_x86_64.tar.gz; \
|
||||||
|
chmod +x build/dataplaneapi; \
|
||||||
|
cp build/dataplaneapi /usr/local/bin/; \
|
||||||
|
\
|
||||||
wget -O haproxy.tar.gz "$HAPROXY_URL"; \
|
wget -O haproxy.tar.gz "$HAPROXY_URL"; \
|
||||||
# echo "$HAPROXY_SHA256 *haproxy.tar.gz" | sha256sum -c; \
|
# echo "$HAPROXY_SHA256 *haproxy.tar.gz" | sha256sum -c; \
|
||||||
mkdir -p /usr/src/haproxy; \
|
mkdir -p /usr/src/haproxy; \
|
||||||
|
27
haproxy/dataplaneapi.hcl
Normal file
27
haproxy/dataplaneapi.hcl
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
config_version = 2
|
||||||
|
name = "basedflare"
|
||||||
|
mode = "single"
|
||||||
|
|
||||||
|
dataplaneapi {
|
||||||
|
host = "127.0.0.1"
|
||||||
|
port = 2001
|
||||||
|
user "admin" {
|
||||||
|
insecure = true
|
||||||
|
password = "admin"
|
||||||
|
}
|
||||||
|
transaction {
|
||||||
|
transaction_dir = "/tmp/haproxy"
|
||||||
|
}
|
||||||
|
advertised {}
|
||||||
|
}
|
||||||
|
|
||||||
|
haproxy {
|
||||||
|
config_file = "/etc/haproxy/haproxy.cfg"
|
||||||
|
haproxy_bin = "/usr/local/sbin/haproxy"
|
||||||
|
reload {
|
||||||
|
reload_delay = 5
|
||||||
|
reload_cmd = "service haproxy reload"
|
||||||
|
restart_cmd = "service haproxy restart"
|
||||||
|
reload_strategy = "custom"
|
||||||
|
}
|
||||||
|
}
|
@@ -18,17 +18,21 @@ defaults
|
|||||||
timeout server 50000ms
|
timeout server 50000ms
|
||||||
timeout tarpit 5000ms
|
timeout tarpit 5000ms
|
||||||
|
|
||||||
#frontend stats-frontend
|
# program api
|
||||||
# bind *:2000
|
# command dataplaneapi -f /etc/haproxy/dataplaneapi.hcl --update-map-files
|
||||||
# option tcplog
|
# no option start-on-reload
|
||||||
# mode tcp
|
#
|
||||||
# acl white_list src xxx.xxx.xxx.xxx
|
# frontend stats-frontend
|
||||||
# tcp-request connection reject unless white_list
|
# bind *:2000
|
||||||
# default_backend stats-backend
|
# option tcplog
|
||||||
|
# mode tcp
|
||||||
#backend stats-backend
|
# acl white_list src 127.0.0.1
|
||||||
# mode tcp
|
# tcp-request connection reject unless white_list
|
||||||
# server stats-localhost 127.0.0.1:1999
|
# default_backend stats-backend
|
||||||
|
#
|
||||||
|
# backend stats-backend
|
||||||
|
# mode tcp
|
||||||
|
# server stats-localhost 127.0.0.1:1999
|
||||||
|
|
||||||
frontend http-in
|
frontend http-in
|
||||||
|
|
||||||
|
@@ -1,2 +1,2 @@
|
|||||||
localhost 1
|
localhost 1
|
||||||
localhost/captcha 2
|
localhost/test 2
|
||||||
|
BIN
img/captcha.gif
Normal file
BIN
img/captcha.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 235 KiB |
BIN
img/captcha.png
BIN
img/captcha.png
Binary file not shown.
Before Width: | Height: | Size: 22 KiB |
BIN
img/nocaptcha.gif
Normal file
BIN
img/nocaptcha.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 165 KiB |
Binary file not shown.
Before Width: | Height: | Size: 21 KiB |
@@ -4,10 +4,11 @@ function updateElem(selector, text) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function insertError(str) {
|
function insertError(str) {
|
||||||
const ring = document.querySelector('.lds-ring');
|
const loader = document.querySelector('#loader');
|
||||||
const captcha = document.querySelector('#captcha');
|
const captcha = document.querySelector('#captcha');
|
||||||
(ring || captcha).insertAdjacentHTML('afterend', `<p class="red">Error: ${str}</p>`);
|
console.log(loader, captcha);
|
||||||
ring && ring.remove();
|
(captcha || loader).insertAdjacentHTML('afterend', `<p class="red">Error: ${str}</p>`);
|
||||||
|
loader && loader.remove();
|
||||||
captcha && captcha.remove();
|
captcha && captcha.remove();
|
||||||
updateElem('.powstatus', '');
|
updateElem('.powstatus', '');
|
||||||
}
|
}
|
||||||
@@ -153,7 +154,8 @@ const powFinished = new Promise(resolve => {
|
|||||||
|
|
||||||
function onCaptchaSubmit(captchaResponse) {
|
function onCaptchaSubmit(captchaResponse) {
|
||||||
const captchaElem = document.querySelector('[data-sitekey]');
|
const captchaElem = document.querySelector('[data-sitekey]');
|
||||||
captchaElem.insertAdjacentHTML('afterend', `<div class="lds-ring"><div></div><div></div><div></div><div></div></div>`);
|
// captchaElem.insertAdjacentHTML('afterend', `<div id="loader" class="loader"><div></div><div></div><div></div><div></div></div>`);
|
||||||
|
captchaElem.insertAdjacentHTML('afterend', `<div id="loader"><div class="b"></div><div class="b"></div><div class="b"></div></div>`);
|
||||||
captchaElem.remove();
|
captchaElem.remove();
|
||||||
powFinished.then(powResponse => {
|
powFinished.then(powResponse => {
|
||||||
postResponse(powResponse, captchaResponse);
|
postResponse(powResponse, captchaResponse);
|
||||||
|
@@ -228,6 +228,7 @@ function _M.view(applet)
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
valid_submission = true
|
valid_submission = true
|
||||||
|
matched_expiry = number_expiry
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -238,18 +239,24 @@ function _M.view(applet)
|
|||||||
|
|
||||||
-- handle setting the captcha cookie
|
-- handle setting the captcha cookie
|
||||||
local user_captcha_response = parsed_body["h-captcha-response"] or parsed_body["g-recaptcha-response"]
|
local user_captcha_response = parsed_body["h-captcha-response"] or parsed_body["g-recaptcha-response"]
|
||||||
|
|
||||||
if valid_submission and user_captcha_response then -- only check captcha if POW is already correct
|
if valid_submission and user_captcha_response then -- only check captcha if POW is already correct
|
||||||
|
|
||||||
-- format the url for verifying the captcha response
|
-- format the url for verifying the captcha response
|
||||||
local captcha_url = string.format(
|
local captcha_url = string.format(
|
||||||
"https://%s%s",
|
"https://%s%s",
|
||||||
core.backends[captcha_backend_name].servers[captcha_backend_name]:get_addr(),
|
--Seems this is no longer needed, captcha_provider_domain works since 2.7
|
||||||
|
--core.backends[captcha_backend_name].servers[captcha_backend_name]:get_addr(),
|
||||||
|
captcha_provider_domain,
|
||||||
captcha_siteverify_path
|
captcha_siteverify_path
|
||||||
)
|
)
|
||||||
|
|
||||||
-- construct the captcha body to send to the captcha url
|
-- construct the captcha body to send to the captcha url
|
||||||
local captcha_body = url.buildQuery({
|
local captcha_body = url.buildQuery({
|
||||||
secret=captcha_secret,
|
secret=captcha_secret,
|
||||||
response=user_captcha_response
|
response=user_captcha_response
|
||||||
})
|
})
|
||||||
|
|
||||||
-- instantiate an http client and make the request
|
-- instantiate an http client and make the request
|
||||||
local httpclient = core.httpclient()
|
local httpclient = core.httpclient()
|
||||||
local res = httpclient:post{
|
local res = httpclient:post{
|
||||||
@@ -257,17 +264,19 @@ function _M.view(applet)
|
|||||||
body=captcha_body,
|
body=captcha_body,
|
||||||
headers={
|
headers={
|
||||||
[ "host" ] = { captcha_provider_domain },
|
[ "host" ] = { captcha_provider_domain },
|
||||||
[ "content-type" ] = { "application/x-www-form-urlencoded" }
|
[ "content-type" ] = { "application/x-www-form-urlencoded" },
|
||||||
|
[ "user-agent" ] = { "haproxy-protection (haproxy-protection/0.1; +https://gitgud.io/fatchan/haproxy-protection)" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
-- try parsing the response as json
|
-- try parsing the response as json
|
||||||
local status, api_response = pcall(json.decode, res.body)
|
local status, api_response = pcall(json.decode, res.body)
|
||||||
if not status then
|
if not status then
|
||||||
api_response = {}
|
api_response = {}
|
||||||
end
|
end
|
||||||
|
|
||||||
-- the response was good i.e the captcha provider says they passed, give them a cookie
|
-- the response was good i.e the captcha provider says they passed, give them a cookie
|
||||||
if api_response.success == true then
|
if api_response.success == true then
|
||||||
|
|
||||||
local user_key = sha.bin_to_hex(randbytes(16))
|
local user_key = sha.bin_to_hex(randbytes(16))
|
||||||
local user_hash = utils.generate_challenge(applet, captcha_cookie_secret, user_key, true)
|
local user_hash = utils.generate_challenge(applet, captcha_cookie_secret, user_key, true)
|
||||||
local signature = sha.hmac(sha.sha3_256, hmac_cookie_secret, user_key .. user_hash .. matched_expiry)
|
local signature = sha.hmac(sha.sha3_256, hmac_cookie_secret, user_key .. user_hash .. matched_expiry)
|
||||||
@@ -282,8 +291,8 @@ function _M.view(applet)
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
valid_submission = valid_submission and true
|
valid_submission = valid_submission and true
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
if not valid_submission then
|
if not valid_submission then
|
||||||
|
@@ -12,19 +12,26 @@ _M.body = [[
|
|||||||
@media (prefers-color-scheme:light){:root{--text-color:#333;--bg-color:#EEE}}
|
@media (prefers-color-scheme:light){:root{--text-color:#333;--bg-color:#EEE}}
|
||||||
.h-captcha,.g-recaptcha{min-height:85px;display:block}
|
.h-captcha,.g-recaptcha{min-height:85px;display:block}
|
||||||
.red{color:red;font-weight:bold}
|
.red{color:red;font-weight:bold}
|
||||||
|
.left{text-align:left}
|
||||||
.powstatus{color:green;font-weight:bold}
|
.powstatus{color:green;font-weight:bold}
|
||||||
a,a:visited{color:var(--text-color)}
|
a,a:visited{color:var(--text-color)}
|
||||||
body,html{height:100%%}
|
body,html{height:100%%;text-align:center;}
|
||||||
body{display:flex;flex-direction:column;background-color:var(--bg-color);color:var(--text-color);font-family:Helvetica,Arial,sans-serif;max-width:1200px;margin:0 auto;padding: 0 20px}
|
body{display:flex;flex-direction:column;background-color:var(--bg-color);color:var(--text-color);font-family:Helvetica,Arial,sans-serif;max-width:60em;margin:0 auto;padding: 0 20px}
|
||||||
details{transition: border-left-color 0.5s;max-width:1200px;text-align:left;border-left: 2px solid var(--text-color);padding:10px}
|
details{transition: border-left-color 0.5s;max-width:1200px;text-align:left;border-left: 2px solid var(--text-color);padding:10px}
|
||||||
code{background-color:#dfdfdf30;border-radius:3px;padding:0 3px;}
|
code{background-color:#dfdfdf30;border-radius:4px;padding:0 3px;color:#ff6590}
|
||||||
img,h3,p{margin:0 0 5px 0}
|
img,h3{margin:0 0 5px 0}
|
||||||
footer{font-size:x-small;margin-top:auto;margin-bottom:20px;text-align:center}
|
li{margin-bottom: 1em}
|
||||||
|
footer{font-size:x-small;margin-top:auto;padding:10px;text-align:center;border-top:1px solid #80808040;padding:10px}
|
||||||
img{display:inline}
|
img{display:inline}
|
||||||
.pt{padding-top:15vh;display:flex;align-items:center;word-break:break-all}
|
textarea,input{background:var(--bg-color);color:var(--text-color);border:1px solid var(--text-color);width:100%%;box-sizing: border-box;resize:none;padding:0.5em;font-family:inherit}
|
||||||
|
.pt{padding-top:30vh;display:flex;align-items:center;word-break:break-all;justify-content: center;}
|
||||||
.pt img{margin-right:10px}
|
.pt img{margin-right:10px}
|
||||||
details[open]{border-left-color: #1400ff}
|
details[open]{border-left-color: #1400ff}
|
||||||
.lds-ring{display:inline-block;position:relative;width:80px;height:80px}.lds-ring div{box-sizing:border-box;display:block;position:absolute;width:32px;height:32px;margin:10px;border:5px solid var(--text-color);border-radius:50%%;animation:lds-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;border-color:var(--text-color) transparent transparent transparent}.lds-ring div:nth-child(1){animation-delay:-0.45s}.lds-ring div:nth-child(2){animation-delay:-0.3s}.lds-ring div:nth-child(3){animation-delay:-0.15s}@keyframes lds-ring{0%%{transform:rotate(0deg)}100%%{transform:rotate(360deg)}}
|
.b{display:inline-block;background:#6b93f7;border-radius:50%%;margin:10px;height:16px;width:16px;box-shadow:0 0 0 0 #6b93f720;transform:scale(1)}
|
||||||
|
.b:nth-of-type(1){animation:p 3s infinite}
|
||||||
|
.b:nth-of-type(2){animation:p 3s .5s infinite}
|
||||||
|
.b:nth-of-type(3){animation:p 3s 1s infinite}
|
||||||
|
@keyframes p{0%%{transform:scale(.95);box-shadow:0 0 0 0 #6b93f790}70%%{transform:scale(1);box-shadow:0 0 0 10px #6b93f700}100%%{transform:scale(.95);box-shadow:0 0 0 0 #6b93f700}}
|
||||||
</style>
|
</style>
|
||||||
<noscript>
|
<noscript>
|
||||||
<style>.jsonly{display:none}</style>
|
<style>.jsonly{display:none}</style>
|
||||||
@@ -38,14 +45,13 @@ _M.body = [[
|
|||||||
%s
|
%s
|
||||||
<noscript>
|
<noscript>
|
||||||
<br>
|
<br>
|
||||||
<p class="red">JavaScript is required on this page.</p>
|
<p class="red left">JavaScript is required on this page.</p>
|
||||||
%s
|
%s
|
||||||
</noscript>
|
</noscript>
|
||||||
<div class="powstatus"></div>
|
<div class="powstatus"></div>
|
||||||
<footer>
|
<footer>
|
||||||
<img src="/.basedflare/img/footerlogo.png" />
|
|
||||||
<p>Security and Performance by <a href="https://basedflare.com">BasedFlare</a></p>
|
|
||||||
<p>Node: <code>%s</code></p>
|
<p>Node: <code>%s</code></p>
|
||||||
|
<p>Performance & security by <a href="https://gitgud.io/fatchan/haproxy-protection/">haproxy-protection</a></p>
|
||||||
</footer>
|
</footer>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -90,20 +96,30 @@ _M.noscript_extra_sha256 = [[
|
|||||||
-- title with favicon and hostname
|
-- title with favicon and hostname
|
||||||
_M.site_name_section = [[
|
_M.site_name_section = [[
|
||||||
<h3 class="pt">
|
<h3 class="pt">
|
||||||
<img src="/favicon.ico" width="32" height="32" alt="icon">
|
<img src="/favicon.ico" width="32" height="32" alt=" ">
|
||||||
%s
|
%s
|
||||||
</h3>
|
</h3>
|
||||||
]]
|
]]
|
||||||
|
|
||||||
-- spinner animation for proof of work
|
-- animation while waiting
|
||||||
_M.pow_section = [[
|
_M.pow_section = [[
|
||||||
<h3>
|
<h3>
|
||||||
Checking your browser for robots 🤖
|
Checking your browser for robots 🤖
|
||||||
</h3>
|
</h3>
|
||||||
<div class="jsonly">
|
<div class="jsonly">
|
||||||
<div class="lds-ring"><div></div><div></div><div></div><div></div></div>
|
<div id="loader"><div class="b"></div><div class="b"></div><div class="b"></div></div>
|
||||||
</div>
|
</div>
|
||||||
]]
|
]]
|
||||||
|
-- alternative, spinner animation
|
||||||
|
-- .loader{display:inline-block;position:relative;width:80px;height:80px}
|
||||||
|
-- .loader div{box-sizing:border-box;display:block;position:absolute;width:32px;height:32px;margin:10px;border:5px solid var(--text-color);border-radius:50%%;animation:loader 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;border-color:var(--text-color) transparent transparent transparent}
|
||||||
|
-- .loader div:nth-child(1){animation-delay:-0.45s}
|
||||||
|
-- .loader div:nth-child(2){animation-delay:-0.3s}
|
||||||
|
-- .loader div:nth-child(3){animation-delay:-0.15s}
|
||||||
|
-- @keyframes loader{0%%{transform:rotate(0deg)}100%%{transform:rotate(360deg)}}
|
||||||
|
-- <div class="jsonly">
|
||||||
|
-- <div class="loader"><div></div><div></div><div></div><div></div></div>
|
||||||
|
-- </div>
|
||||||
|
|
||||||
-- message, captcha form and submit button
|
-- message, captcha form and submit button
|
||||||
_M.captcha_section = [[
|
_M.captcha_section = [[
|
||||||
|
Reference in New Issue
Block a user