finished adding rate limiting
This commit is contained in:
+2
-2
@@ -13,10 +13,10 @@ RUN mkdir -p /usr/local/etc/php/conf.d
|
||||
COPY preauth-php.ini /usr/local/etc/php/conf.d/preauth-php.ini
|
||||
|
||||
# login sessions are stored here
|
||||
RUN mkdir -p /tmp/sessions
|
||||
RUN mkdir -p /tmp/data/sessions
|
||||
|
||||
# rate limit monitoring information is stored here
|
||||
RUN mkdir -p /tmp/monitor
|
||||
RUN mkdir -p /tmp/data/monitor
|
||||
|
||||
# fcgi command for the healthcheck
|
||||
RUN apk add fcgi
|
||||
|
||||
+2
-2
@@ -12,8 +12,8 @@ services:
|
||||
restart: unless-stopped
|
||||
user: ${USER_ID}:${GROUP_ID}
|
||||
volumes:
|
||||
- preauth-sessions:/tmp/sessions
|
||||
- preauth:/tmp/data
|
||||
|
||||
volumes:
|
||||
preauth-sessions:
|
||||
preauth:
|
||||
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
#!/bin/sh
|
||||
|
||||
# ensure we have required folders
|
||||
`mkdir -p /tmp/data/sessions`
|
||||
`mkdir -p /tmp/data/monitor`
|
||||
|
||||
# ensure we have required settings
|
||||
if [ -z "$PREAUTH_KEY" ]; then
|
||||
export PREAUTH_KEY=$(cd /preauth && php init-key.php)
|
||||
@@ -9,5 +13,6 @@ if [ -z "$PREAUTH_TOKEN" ]; then
|
||||
export PREAUTH_TOKEN=$(cd /preauth && php init-token.php)
|
||||
fi
|
||||
|
||||
# start the process, now that setup is done
|
||||
exec php-fpm
|
||||
|
||||
|
||||
+12
-11
@@ -1,20 +1,21 @@
|
||||
# preauth uses "sessions" to store usage statistics by IP address and is used for rate limiting
|
||||
# login sessions are stored as file in /tmp/sessions, and are unrelated to this configuration
|
||||
; preauth uses php sessions to store usage statistics by IP address and is used for rate limiting
|
||||
; login sessions are stored as file in /tmp/data/sessions, and are unrelated to this configuration
|
||||
|
||||
# sessions are IP based, cookie not needed nor wanted
|
||||
; sessions are IP based, cookie not needed nor wanted
|
||||
session.use_cookies = off
|
||||
session.use_only_cookies = off
|
||||
; deprecated in php 8.4
|
||||
;session.use_only_cookies = off
|
||||
session.cache_limiter = ''
|
||||
session.name = preauth
|
||||
session.save_path = /tmp/monitor
|
||||
session.save_path = /tmp/data/monitor
|
||||
|
||||
# seconds until a session may be pruned
|
||||
# IE: maximum time range to review for rate limiting and
|
||||
# maximum time an IP address can be blocked
|
||||
# 86400 seconds (24 hours) hardcoded upper limit
|
||||
; seconds until a session may be pruned
|
||||
; IE: maximum time range to review for rate limiting and
|
||||
; maximum time an IP address can be blocked
|
||||
; 86400 seconds aka 24 hours hardcoded upper limit
|
||||
session.gc_maxlifetime = 86400
|
||||
|
||||
# disable random random garbage collection
|
||||
# our healthcheck runs session_gc()
|
||||
; disable random random garbage collection
|
||||
; our healthcheck runs session_gc function
|
||||
session.gc_probability = 0
|
||||
|
||||
|
||||
+22
-5
@@ -21,7 +21,7 @@ class Auth {
|
||||
/* cookie name */
|
||||
private const NAME = '_auth_uuid';
|
||||
/* directory to store sessions in */
|
||||
private const BASE = '/tmp/sessions/';
|
||||
private const BASE = '/tmp/data/sessions/';
|
||||
/* top-level-domains which are known to have multiple parts */
|
||||
private const TLD = [
|
||||
'ai' => ['com','net','off','org'],
|
||||
@@ -137,12 +137,12 @@ class Auth {
|
||||
} else {
|
||||
/* not already logged in, not trying to login, and not on auth page */
|
||||
/* so send to login screen */
|
||||
$rt = rawurlencode(
|
||||
$query = self::RETURN_FIELD . '=' . rawurlencode(
|
||||
($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? 'https') . '://' .
|
||||
($_SERVER['HTTP_X_FORWARDED_HOST'] ?? $this->domain) .
|
||||
($_SERVER['HTTP_X_FORWARDED_URI'] ?? '/')
|
||||
);
|
||||
header("Location: https://$this->preauth.$this->domain/?rt=$rt");
|
||||
header("Location: https://$this->preauth.$this->domain/?$query");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,13 +211,15 @@ class Auth {
|
||||
private function rateLimit(): bool {
|
||||
/* our identifier for this remote-host, replace all special characters with dashes */
|
||||
/* session_id() only allows ",", "-", and alphanumeric characters */
|
||||
$limiterId = preg_replace('[^a-zA-Z0-9]', '-', $this->getRemoteHost());
|
||||
$limiterId = preg_replace('/[^a-zA-Z0-9]/', '-', $this->getRemoteHost());
|
||||
//error_log("limiterId: {$limiterId}");
|
||||
session_id($limiterId);
|
||||
session_start();
|
||||
|
||||
/* new remote-ip, start logging */
|
||||
if ( ! isset($_SESSION['count'], $_SESSION['time'])) {
|
||||
$this->resetRateLimit(false);
|
||||
//error_log('limiter new, allow');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -230,14 +232,17 @@ class Auth {
|
||||
|
||||
if ($sessionCount >= $rateLimit && time() - $rateBlocked <= $sessionTime) {
|
||||
/* if over limit, and block current, block them */
|
||||
//error_log("limiter over limit, block ($sessionCount)");
|
||||
return true;
|
||||
} else if ($sessionCount < $rateLimit && time() - $rateTimeout <= $sessionTime) {
|
||||
/* if under limit, and timeout current, allow them */
|
||||
//error_log("limiter under limit, allow ($sessionCount)");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* either over limit and block has expired or */
|
||||
/* under limit and timeout has expired, so reset them */
|
||||
//error_log("limiter expired, reset");
|
||||
$this->resetRateLimit(false);
|
||||
return false;
|
||||
}
|
||||
@@ -265,6 +270,7 @@ class Auth {
|
||||
$id = substr(preg_replace(self::URL64, '', $this->get[self::ID_FIELD]), 0, 100);
|
||||
$otp = TOTP::createFromSecret($this->token);
|
||||
$date = date('Y-m-d H:i:s');
|
||||
$remoteHost = $this->getRemoteHost();
|
||||
|
||||
/* if given token is valid, login, store session file and set the cookie */
|
||||
if ($otp->now() === $this->get[self::TOKEN_FIELD]) {
|
||||
@@ -276,7 +282,7 @@ class Auth {
|
||||
file_put_contents(
|
||||
self::BASE . $encUUID,
|
||||
base64_encode($rawIV) .
|
||||
"\$$id\$$this->expire\$$date\${$this->getRemoteHost()}\$\n"
|
||||
"\$$id\$$this->expire\$$date\$$remoteHost\$\n"
|
||||
);
|
||||
setcookie(
|
||||
self::NAME,
|
||||
@@ -293,8 +299,19 @@ class Auth {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* login attempted and failed, update monitoring for rate limiting */
|
||||
$sessionCount = (int)$_SESSION['count'];
|
||||
$rateLimit = (int)getenv('PREAUTH_RATE_LIMIT') ?: 4;
|
||||
$this->logFailedAttempt();
|
||||
|
||||
if (($sessionCount + 1) >= $rateLimit) {
|
||||
/* +1 to count this failure */
|
||||
error_log("[$date] rate-limiting trigger for: $remoteHost");
|
||||
/* just reached the rate limit, block them */
|
||||
include __DIR__ . '/../400.php';
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
+4
-3
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
use Preauth\Auth;
|
||||
use Preauth\Env;
|
||||
global $auth;
|
||||
$env = new Env();
|
||||
@@ -63,11 +64,11 @@ $env = new Env();
|
||||
<body>
|
||||
<h1><?php echo $env->getTitle(); ?></h1>
|
||||
<form action="/" method="get">
|
||||
<input type="hidden" name="<?php echo Preauth::RETURN_FIELD; ?>" value="<?php echo $auth->getReturnTo(); ?>">
|
||||
<input type="hidden" name="<?php echo Auth::RETURN_FIELD; ?>" value="<?php echo $auth->getReturnTo(); ?>">
|
||||
<div class="right"><label for="id"><?php echo $env->getIdName(); ?>:</label></div>
|
||||
<div><input type="text" name="<?php echo Preauth::ID_FIELD; ?>" id="id" autocomplete="on" required="required" autofocus="autofocus"></div>
|
||||
<div><input type="text" name="<?php echo Auth::ID_FIELD; ?>" id="id" autocomplete="on" required="required" autofocus="autofocus"></div>
|
||||
<div class="right"><label for="token"><?php echo $env->getTokenName(); ?>:</label></div>
|
||||
<div><input type="text" name="<?php echo Preauth::TOKEN_FIELD; ?>" id="token" autocomplete="off" required="required"></div>
|
||||
<div><input type="text" name="<?php echo Auth::TOKEN_FIELD; ?>" id="token" autocomplete="off" required="required"></div>
|
||||
<div class="center"><button type="submit"><?php echo $env->getSubmitName(); ?></button></div>
|
||||
</form>
|
||||
</body>
|
||||
|
||||
Reference in New Issue
Block a user