mirror of
https://github.com/ManfredAabye/opensimMULTITOOLS-II.git
synced 2026-08-14 08:52:24 +00:00
020420261226
This commit is contained in:
+22
-2
@@ -56,20 +56,40 @@ if (($_POST['form'] ?? '') === 'login') {
|
||||
$postedLang = detect_lang(supported_languages());
|
||||
$_SESSION['lang'] = $postedLang;
|
||||
$lang = $postedLang;
|
||||
$loginIp = client_ip_address();
|
||||
|
||||
if (!verify_csrf($_POST['csrf'] ?? null)) {
|
||||
$remainingLock = remaining_lock_seconds($config, $loginIp);
|
||||
if ($remainingLock > 0) {
|
||||
$minutes = (int)max(1, ceil($remainingLock / 60));
|
||||
$message = str_replace('{minutes}', (string)$minutes, t($lang, 'login_locked'));
|
||||
$messageType = 'error';
|
||||
append_auth_audit_log($config, $loginIp, 'login_blocked', 'remaining_seconds=' . $remainingLock);
|
||||
} elseif (!verify_csrf($_POST['csrf'] ?? null)) {
|
||||
$message = t($lang, 'csrf_error');
|
||||
$messageType = 'error';
|
||||
append_auth_audit_log($config, $loginIp, 'csrf_invalid', 'login-form');
|
||||
} else {
|
||||
$password = (string)($_POST['password'] ?? '');
|
||||
$expected = (string)($config['web_password'] ?? '');
|
||||
if ($expected !== '' && hash_equals($expected, $password)) {
|
||||
register_login_attempt($config, $loginIp, true);
|
||||
append_auth_audit_log($config, $loginIp, 'login_success');
|
||||
session_regenerate_id(true);
|
||||
$_SESSION['auth'] = true;
|
||||
header('Location: index.php?lang=' . urlencode($lang));
|
||||
exit;
|
||||
}
|
||||
$message = t($lang, 'invalid_login');
|
||||
|
||||
register_login_attempt($config, $loginIp, false);
|
||||
$remainingAfterFail = remaining_lock_seconds($config, $loginIp);
|
||||
if ($remainingAfterFail > 0) {
|
||||
$minutes = (int)max(1, ceil($remainingAfterFail / 60));
|
||||
$message = str_replace('{minutes}', (string)$minutes, t($lang, 'login_locked'));
|
||||
append_auth_audit_log($config, $loginIp, 'login_failed_locked', 'remaining_seconds=' . $remainingAfterFail);
|
||||
} else {
|
||||
$message = t($lang, 'invalid_login');
|
||||
append_auth_audit_log($config, $loginIp, 'login_failed_password');
|
||||
}
|
||||
$messageType = 'error';
|
||||
}
|
||||
}
|
||||
|
||||
+15
-1
@@ -8,6 +8,20 @@ return [
|
||||
'runner_path' => realpath(__DIR__ . '/../bin/osmtool_web_runner.sh') ?: (__DIR__ . '/../bin/osmtool_web_runner.sh'),
|
||||
'command_timeout_seconds' => 120,
|
||||
'default_workdir' => '/opt',
|
||||
|
||||
// Optional dashboard overrides for live status cards.
|
||||
'status_ports' => [
|
||||
'grid-sim' => ['8002', '9010', '9020', '9030'],
|
||||
'robust' => ['8002', '9010', '9020', '9030'],
|
||||
'standalone' => ['9000'],
|
||||
],
|
||||
|
||||
'status_screens' => [
|
||||
'grid-sim' => ['robustserver', 'sim1', 'sim2', 'sim3'],
|
||||
'robust' => ['robustserver'],
|
||||
'standalone' => ['standalone'],
|
||||
],
|
||||
|
||||
'use_sudo' => true,
|
||||
'sudo_user' => 'manni',
|
||||
'sudo_user' => 'sudoUserHier',
|
||||
];
|
||||
|
||||
@@ -8,6 +8,14 @@ return [
|
||||
// Default login password. Replace immediately in production.
|
||||
'web_password' => 'ChangeMeNow-2026',
|
||||
|
||||
// Login brute-force protection.
|
||||
'auth_lockout_max_attempts' => 3,
|
||||
'auth_lockout_seconds' => 1800,
|
||||
|
||||
// Optional custom storage paths for lockout state and audit logs.
|
||||
// 'auth_store_path' => '/var/www/html/osmtool-web/var/security/login_attempts.json',
|
||||
// 'auth_audit_log_path' => '/var/log/osmtool-web/auth_audit.log',
|
||||
|
||||
// Default assumes deployment under /var/www/html/osmtool-web.
|
||||
// You can keep this dynamic path or set an absolute path manually.
|
||||
'runner_path' => realpath(__DIR__ . '/../bin/osmtool_web_runner.sh') ?: (__DIR__ . '/../bin/osmtool_web_runner.sh'),
|
||||
@@ -20,8 +28,8 @@ return [
|
||||
|
||||
// Optional dashboard overrides for live status cards.
|
||||
'status_ports' => [
|
||||
'grid-sim' => ['8002', '9000', '9001', '8088'],
|
||||
'robust' => ['8002', '9000', '9001'],
|
||||
'grid-sim' => ['8002', '9010', '9020', '9030'],
|
||||
'robust' => ['8002', '9010', '9020', '9030'],
|
||||
'standalone' => ['9000'],
|
||||
],
|
||||
|
||||
@@ -36,5 +44,5 @@ return [
|
||||
|
||||
// If use_sudo=true, command becomes: sudo -n <sudo_user> <runner>
|
||||
// Example: www-data (Linux) or your service account.
|
||||
'sudo_user' => 'manni',
|
||||
'sudo_user' => 'sudoUserHier',
|
||||
];
|
||||
|
||||
@@ -28,6 +28,7 @@ function translations(): array
|
||||
'status_ok' => 'Erfolgreich',
|
||||
'status_error' => 'Fehler',
|
||||
'invalid_login' => 'Anmeldung fehlgeschlagen.',
|
||||
'login_locked' => 'Zu viele Fehlversuche von dieser IP. Bitte {minutes} Minute(n) warten.',
|
||||
'csrf_error' => 'Ungueltiges Formular-Token.',
|
||||
'hint_full' => 'Nur whitelisted OSMTool-Befehle werden ueber den Runner gestartet. Erweiterte Parameter sind modulabhaengig sichtbar.',
|
||||
'action_start' => 'Start',
|
||||
@@ -231,6 +232,7 @@ function translations(): array
|
||||
'status_ok' => 'Success',
|
||||
'status_error' => 'Error',
|
||||
'invalid_login' => 'Login failed.',
|
||||
'login_locked' => 'Too many failed attempts from this IP. Please wait {minutes} minute(s).',
|
||||
'csrf_error' => 'Invalid form token.',
|
||||
'hint_full' => 'Only whitelisted OSMTool commands are started through the runner. Advanced parameters appear only when the selected module uses them.',
|
||||
'action_start' => 'Start',
|
||||
|
||||
@@ -84,3 +84,178 @@ function push_execution_history(array $entry): void
|
||||
array_unshift($history, $entry);
|
||||
$_SESSION['execution_history'] = array_slice($history, 0, 8);
|
||||
}
|
||||
|
||||
function client_ip_address(): string
|
||||
{
|
||||
$remote = trim((string)($_SERVER['REMOTE_ADDR'] ?? 'unknown'));
|
||||
if ($remote === '') {
|
||||
$remote = 'unknown';
|
||||
}
|
||||
|
||||
$forwarded = (string)($_SERVER['HTTP_X_FORWARDED_FOR'] ?? '');
|
||||
if ($forwarded !== '') {
|
||||
$first = trim(explode(',', $forwarded)[0] ?? '');
|
||||
if ($first !== '' && filter_var($first, FILTER_VALIDATE_IP)) {
|
||||
return $first;
|
||||
}
|
||||
}
|
||||
|
||||
return $remote;
|
||||
}
|
||||
|
||||
function security_store_path(array $config): string
|
||||
{
|
||||
$configured = trim((string)($config['auth_store_path'] ?? ''));
|
||||
if ($configured !== '') {
|
||||
return $configured;
|
||||
}
|
||||
return __DIR__ . '/../var/security/login_attempts.json';
|
||||
}
|
||||
|
||||
function security_audit_log_path(array $config): string
|
||||
{
|
||||
$configured = trim((string)($config['auth_audit_log_path'] ?? ''));
|
||||
if ($configured !== '') {
|
||||
return $configured;
|
||||
}
|
||||
return __DIR__ . '/../var/security/auth_audit.log';
|
||||
}
|
||||
|
||||
function ensure_parent_directory(string $filePath): void
|
||||
{
|
||||
$dir = dirname($filePath);
|
||||
if ($dir !== '' && !is_dir($dir)) {
|
||||
@mkdir($dir, 0770, true);
|
||||
}
|
||||
}
|
||||
|
||||
function auth_lockout_max_attempts(array $config): int
|
||||
{
|
||||
$value = (int)($config['auth_lockout_max_attempts'] ?? 3);
|
||||
return $value > 0 ? $value : 3;
|
||||
}
|
||||
|
||||
function auth_lockout_seconds(array $config): int
|
||||
{
|
||||
$value = (int)($config['auth_lockout_seconds'] ?? 1800);
|
||||
return $value > 0 ? $value : 1800;
|
||||
}
|
||||
|
||||
function read_auth_state(array $config): array
|
||||
{
|
||||
$path = security_store_path($config);
|
||||
ensure_parent_directory($path);
|
||||
|
||||
$handle = @fopen($path, 'c+');
|
||||
if (!is_resource($handle)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!flock($handle, LOCK_EX)) {
|
||||
fclose($handle);
|
||||
return [];
|
||||
}
|
||||
|
||||
$raw = stream_get_contents($handle);
|
||||
$state = [];
|
||||
if (is_string($raw) && trim($raw) !== '') {
|
||||
$decoded = json_decode($raw, true);
|
||||
if (is_array($decoded)) {
|
||||
$state = $decoded;
|
||||
}
|
||||
}
|
||||
|
||||
$now = time();
|
||||
foreach ($state as $ip => $entry) {
|
||||
$last = (int)($entry['last_attempt'] ?? 0);
|
||||
$lockedUntil = (int)($entry['locked_until'] ?? 0);
|
||||
if ($last > 0 && ($now - $last) > (7 * 24 * 3600) && $lockedUntil < $now) {
|
||||
unset($state[$ip]);
|
||||
}
|
||||
}
|
||||
|
||||
ftruncate($handle, 0);
|
||||
rewind($handle);
|
||||
fwrite($handle, json_encode($state, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
|
||||
fflush($handle);
|
||||
flock($handle, LOCK_UN);
|
||||
fclose($handle);
|
||||
|
||||
return $state;
|
||||
}
|
||||
|
||||
function write_auth_state(array $config, array $state): void
|
||||
{
|
||||
$path = security_store_path($config);
|
||||
ensure_parent_directory($path);
|
||||
|
||||
$handle = @fopen($path, 'c+');
|
||||
if (!is_resource($handle)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!flock($handle, LOCK_EX)) {
|
||||
fclose($handle);
|
||||
return;
|
||||
}
|
||||
|
||||
ftruncate($handle, 0);
|
||||
rewind($handle);
|
||||
fwrite($handle, json_encode($state, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
|
||||
fflush($handle);
|
||||
flock($handle, LOCK_UN);
|
||||
fclose($handle);
|
||||
}
|
||||
|
||||
function remaining_lock_seconds(array $config, string $ip): int
|
||||
{
|
||||
$state = read_auth_state($config);
|
||||
$entry = $state[$ip] ?? null;
|
||||
if (!is_array($entry)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$lockedUntil = (int)($entry['locked_until'] ?? 0);
|
||||
$remaining = $lockedUntil - time();
|
||||
return $remaining > 0 ? $remaining : 0;
|
||||
}
|
||||
|
||||
function register_login_attempt(array $config, string $ip, bool $success): void
|
||||
{
|
||||
$state = read_auth_state($config);
|
||||
$entry = $state[$ip] ?? ['failed_attempts' => 0, 'locked_until' => 0, 'last_attempt' => 0];
|
||||
$now = time();
|
||||
|
||||
if ($success) {
|
||||
$entry['failed_attempts'] = 0;
|
||||
$entry['locked_until'] = 0;
|
||||
} else {
|
||||
if ((int)($entry['locked_until'] ?? 0) <= $now) {
|
||||
$entry['failed_attempts'] = (int)($entry['failed_attempts'] ?? 0) + 1;
|
||||
if ($entry['failed_attempts'] >= auth_lockout_max_attempts($config)) {
|
||||
$entry['locked_until'] = $now + auth_lockout_seconds($config);
|
||||
$entry['failed_attempts'] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$entry['last_attempt'] = $now;
|
||||
$state[$ip] = $entry;
|
||||
write_auth_state($config, $state);
|
||||
}
|
||||
|
||||
function append_auth_audit_log(array $config, string $ip, string $event, string $detail = ''): void
|
||||
{
|
||||
$path = security_audit_log_path($config);
|
||||
ensure_parent_directory($path);
|
||||
$ua = trim((string)($_SERVER['HTTP_USER_AGENT'] ?? 'unknown-agent'));
|
||||
$line = sprintf(
|
||||
"%s\tip=%s\tevent=%s\tdetail=%s\tua=%s\n",
|
||||
date('Y-m-d H:i:s'),
|
||||
$ip,
|
||||
$event,
|
||||
$detail,
|
||||
str_replace(["\t", "\n", "\r"], ' ', $ua)
|
||||
);
|
||||
@file_put_contents($path, $line, FILE_APPEND | LOCK_EX);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user