summaryrefslogtreecommitdiffstats
path: root/modules-available/webinterface
diff options
context:
space:
mode:
authorSimon Rettberg2026-04-28 14:42:07 +0200
committerSimon Rettberg2026-04-28 14:42:07 +0200
commitf1e35d43695f914677fcf8b2b4550a3c58cdcf10 (patch)
tree99b9ca7ba24cbaed033963c20ff41e9303d28a21 /modules-available/webinterface
parentAdd README.md (diff)
downloadslx-admin-f1e35d43695f914677fcf8b2b4550a3c58cdcf10.tar.gz
slx-admin-f1e35d43695f914677fcf8b2b4550a3c58cdcf10.tar.xz
slx-admin-f1e35d43695f914677fcf8b2b4550a3c58cdcf10.zip
Add IP address normalization, add support for X-Forwarded-For
Tests written by Junie AI
Diffstat (limited to 'modules-available/webinterface')
-rw-r--r--modules-available/webinterface/api.inc.php2
-rw-r--r--modules-available/webinterface/inc/webinterface.inc.php18
-rw-r--r--modules-available/webinterface/lang/de/messages.json1
-rw-r--r--modules-available/webinterface/lang/de/permissions.json3
-rw-r--r--modules-available/webinterface/lang/de/template-tags.json4
-rw-r--r--modules-available/webinterface/lang/en/messages.json1
-rw-r--r--modules-available/webinterface/lang/en/permissions.json3
-rw-r--r--modules-available/webinterface/lang/en/template-tags.json4
-rw-r--r--modules-available/webinterface/page.inc.php36
-rw-r--r--modules-available/webinterface/permissions/permissions.json3
-rw-r--r--modules-available/webinterface/templates/trusted-proxies.html25
11 files changed, 97 insertions, 3 deletions
diff --git a/modules-available/webinterface/api.inc.php b/modules-available/webinterface/api.inc.php
index 271ccc60..3be7d882 100644
--- a/modules-available/webinterface/api.inc.php
+++ b/modules-available/webinterface/api.inc.php
@@ -21,7 +21,7 @@ if (empty($newCert)) {
// Import will try to validate the certificate too
$task = WebInterface::tmImportCustomCert($newKey, $newCert, 'api',
- 'Applying new HTTPS certificate uploaded via API from ' . $_SERVER['REMOTE_ADDR']);
+ 'Applying new HTTPS certificate uploaded via API from ' . Util::getClientIp());
$task = Taskmanager::waitComplete($task, 10000);
if (!Taskmanager::isTask($task)) {
http_send_status(500);
diff --git a/modules-available/webinterface/inc/webinterface.inc.php b/modules-available/webinterface/inc/webinterface.inc.php
index d50acd50..2541ea24 100644
--- a/modules-available/webinterface/inc/webinterface.inc.php
+++ b/modules-available/webinterface/inc/webinterface.inc.php
@@ -11,6 +11,8 @@ class WebInterface
public const PROP_API_KEY = 'webinterface.api-key';
+ public const PROP_PROXIES_TRUSTED = 'webinterface.proxies-trusted';
+
/**
* Read data all handled domains from current certificate.
* SAN takes precedence, if empty, we fall back to CN.
@@ -139,4 +141,20 @@ class WebInterface
Property::set(self::PROP_API_KEY, empty($key) ? null : $key);
}
+ /**
+ * List of trusted proxies. Key is the IP address, value is optional comment.
+ */
+ public static function getProxiesTrusted(): array
+ {
+ return Property::get(self::PROP_PROXIES_TRUSTED, []);
+ }
+
+ /**
+ * Set list of trusted proxies. Key is the IP address, value is optional comment.
+ */
+ public static function setProxiesTrusted(array $proxies): void
+ {
+ Property::set(self::PROP_PROXIES_TRUSTED, $proxies);
+ }
+
} \ No newline at end of file
diff --git a/modules-available/webinterface/lang/de/messages.json b/modules-available/webinterface/lang/de/messages.json
index f87f3d3a..32cac43d 100644
--- a/modules-available/webinterface/lang/de/messages.json
+++ b/modules-available/webinterface/lang/de/messages.json
@@ -7,5 +7,6 @@
"https-on-cert-missing": "HTTPS ist aktiviert, das Zertifikat ist jedoch nicht vorhanden. Bitte nehmen Sie die HTTPS-Konfiguration erneut vor.",
"https-want-redirect-is-plain": "Weiterleitung von HTTP auf HTTPS ist aktiviert, trotzdem scheint die Verbindung Ihres Browsers mit dem Server unverschl\u00fcsselt zu sein. Nehmen Sie die Konfiguration erneut vor und wenden Sie sich an den Support, wenn das Problem weiterhin besteht.",
"invalid-domain": "Ung\u00fcltige Domain: {{0}}",
+ "invalid-proxy-ip": "Ung\u00fcltige Proxy-IP: {{0}}",
"mw-acme-errors": "Fehler beim erneuern\/abrufen des Zertifikats via ACME"
} \ No newline at end of file
diff --git a/modules-available/webinterface/lang/de/permissions.json b/modules-available/webinterface/lang/de/permissions.json
index 213ebd8f..5694f37b 100644
--- a/modules-available/webinterface/lang/de/permissions.json
+++ b/modules-available/webinterface/lang/de/permissions.json
@@ -2,5 +2,6 @@
"access-page": "Seite sehen.",
"edit.design": "Seitentitel und Hintergrundfarbe des Logos bearbeiten.",
"edit.https": "HTTPS Einstellungen bearbeiten.",
- "edit.password": "\u00c4ndern, ob Passwortfelder in der Web-Schnittstelle maskiert werden sollen."
+ "edit.password": "\u00c4ndern, ob Passwortfelder in der Web-Schnittstelle maskiert werden sollen.",
+ "edit.trusted-proxies": "Vertrauensw\u00fcrdige Proxies bearbeiten."
} \ No newline at end of file
diff --git a/modules-available/webinterface/lang/de/template-tags.json b/modules-available/webinterface/lang/de/template-tags.json
index dfe7bac5..28adcdb3 100644
--- a/modules-available/webinterface/lang/de/template-tags.json
+++ b/modules-available/webinterface/lang/de/template-tags.json
@@ -51,6 +51,10 @@
"lang_regenerate": "(Re)generieren",
"lang_showPasswords": "Passw\u00f6rter anzeigen",
"lang_suppliedSelected": "Der Server verwendet zur Zeit ein \u00fcber die Option \"Eigenes Zertifikat\" hochgeladenes Zertifikat.",
+ "lang_trustedProxiesDescription": "Wenn dieser Server hinter einem Reverse-Proxy betrieben wird (z.B. nginx oder HAProxy), sollten Sie hier die IP-Adressen dieser Proxies hinterlegen. Dies stellt sicher, dass die korrekte Client-IP-Adresse f\u00fcr Protokollierung und Zugriffskontrolle erkannt wird.",
+ "lang_trustedProxiesList": "Vertrauensw\u00fcrdige Proxy-Server",
+ "lang_trustedProxiesListHelp": "Geben Sie eine IP-Adresse pro Zeile ein.",
+ "lang_trustedProxiesSettings": "Vertrauensw\u00fcrdige Proxies",
"lang_unknownSelected": "Unbekanntes oder ung\u00fcltiges Zertifikat vorhanden. Wahrscheinlich wurde der Server von einer alten Version aktualisiert. Um diese Meldung zu entfernen, die HTTPS-Konfiguration erneut vornehmen.",
"lang_useHsts": "HSTS aktivieren (dies erh\u00f6ht die Sicherheit, kann aber bei sp\u00e4terem Deaktivieren von HTTPS zu Zugriffsproblemen f\u00fchren)",
"lang_youreNotUsingHttps": "Sie besuchen diese Seite nicht per HTTPS (oder die HTTPS-Terminierung wird von einem vorgeschalteten Proxy \u00fcbernommen).",
diff --git a/modules-available/webinterface/lang/en/messages.json b/modules-available/webinterface/lang/en/messages.json
index b4083ec3..c55b670e 100644
--- a/modules-available/webinterface/lang/en/messages.json
+++ b/modules-available/webinterface/lang/en/messages.json
@@ -7,5 +7,6 @@
"https-on-cert-missing": "HTTPS is enabled, but the certificate is missing. Please redo the configuration steps.",
"https-want-redirect-is-plain": "HTTP to HTTPS redirects are enabled, but the connection from your browser appears to be unencrypted. Please redo the HTTPS configuration and contact support if the problem persists.",
"invalid-domain": "Invalid domain: {{0}}",
+ "invalid-proxy-ip": "Invalid proxy IP: {{0}}",
"mw-acme-errors": "Error renewing\/requesting certificate via ACME"
} \ No newline at end of file
diff --git a/modules-available/webinterface/lang/en/permissions.json b/modules-available/webinterface/lang/en/permissions.json
index 8ebb2830..1d56f06b 100644
--- a/modules-available/webinterface/lang/en/permissions.json
+++ b/modules-available/webinterface/lang/en/permissions.json
@@ -2,5 +2,6 @@
"access-page": "View page.",
"edit.design": "Edit page title and logo background color.",
"edit.https": "Edit HTTPS settings.",
- "edit.password": "Change whether password fields should be masked or not."
+ "edit.password": "Change whether password fields should be masked or not.",
+ "edit.trusted-proxies": "Edit trusted proxies."
} \ No newline at end of file
diff --git a/modules-available/webinterface/lang/en/template-tags.json b/modules-available/webinterface/lang/en/template-tags.json
index cc6e45a5..9c367377 100644
--- a/modules-available/webinterface/lang/en/template-tags.json
+++ b/modules-available/webinterface/lang/en/template-tags.json
@@ -51,6 +51,10 @@
"lang_regenerate": "(Re)generate",
"lang_showPasswords": "Show passwords",
"lang_suppliedSelected": "The server is currently using a certificate supplied using the \"Supply own certificate\" option.",
+ "lang_trustedProxiesDescription": "If this server is behind a reverse proxy (like nginx or HAProxy), you should list the IP addresses of those proxies here. This ensures that the correct client IP address is identified for logging and access control.",
+ "lang_trustedProxiesList": "Trusted proxy servers",
+ "lang_trustedProxiesListHelp": "Enter one IP address per line.",
+ "lang_trustedProxiesSettings": "Trusted Proxies",
"lang_unknownSelected": "Unknown or invalid certificate in use. The server was probably updated from an old version while HTTPS was already enabled. Redo the HTTPS configuration steps to get rid of this message.",
"lang_useHsts": "Use HSTS (increases security but might lead to problems accessing the site if you disable HTTPS later)",
"lang_youreNotUsingHttps": "You're not using HTTPS to visit this website (or the HTTPS termination is done by a reverse proxy).",
diff --git a/modules-available/webinterface/page.inc.php b/modules-available/webinterface/page.inc.php
index b721da27..1b082000 100644
--- a/modules-available/webinterface/page.inc.php
+++ b/modules-available/webinterface/page.inc.php
@@ -29,6 +29,10 @@ class Page_WebInterface extends Page
User::assertPermission("edit.https");
$this->handleApiKey(substr($action, 14));
break;
+ case 'trusted-proxies':
+ User::assertPermission("edit.proxies");
+ $this->actionConfigureProxies();
+ break;
default:
if ($action !== null) {
Message::addWarning('main.invalid-action', $action);
@@ -87,6 +91,24 @@ class Page_WebInterface extends Page
Util::redirect('?do=WebInterface');
}
+ private function actionConfigureProxies(): void
+ {
+ $trustedProxies = Request::post('trusted-proxies-list', '', 'string');
+ $trustedProxies = preg_split('/[\r\n]+/', $trustedProxies, 0, PREG_SPLIT_NO_EMPTY);
+ $cleaned = [];
+ foreach ($trustedProxies as $line) {
+ $line = preg_split("~(#|//|'|;)~", $line, 2, PREG_SPLIT_NO_EMPTY);
+ $ip = trim($line[0]);
+ $ipNormal = IpUtil::normalizeIp($ip);
+ if ($ipNormal !== null) {
+ $cleaned[$ip] = $line[1] ?? '';
+ } else {
+ Message::addWarning('invalid-proxy-ip', $ip);
+ }
+ }
+ WebInterface::setProxiesTrusted($cleaned);
+ }
+
protected function doRender()
{
Render::addTemplate("heading");
@@ -192,6 +214,20 @@ class Page_WebInterface extends Page
Permission::addGlobalTags($data['perms'], null, ['edit.password']);
Render::addTemplate('passwords', $data);
//
+ // Trusted Proxies
+ //
+ $list = '';
+ foreach (WebInterface::getProxiesTrusted() as $ip => $comment) {
+ $list .= $ip;
+ if (!empty($comment)) {
+ $list .= " # $comment";
+ }
+ $list .= "\r\n";
+ }
+ $data = ['trustedProxiesList' => $list];
+ Permission::addGlobalTags($data['perms'], null, ['edit.trusted-proxies']);
+ Render::addTemplate('trusted-proxies', $data);
+ //
// Colors/Prefix
//
$data = array('prefix' => Property::get('page-title-prefix'));
diff --git a/modules-available/webinterface/permissions/permissions.json b/modules-available/webinterface/permissions/permissions.json
index ed81602a..cbdc4738 100644
--- a/modules-available/webinterface/permissions/permissions.json
+++ b/modules-available/webinterface/permissions/permissions.json
@@ -10,5 +10,8 @@
},
"edit.password": {
"location-aware": false
+ },
+ "edit.trusted-proxies": {
+ "location-aware": false
}
} \ No newline at end of file
diff --git a/modules-available/webinterface/templates/trusted-proxies.html b/modules-available/webinterface/templates/trusted-proxies.html
new file mode 100644
index 00000000..a2461edc
--- /dev/null
+++ b/modules-available/webinterface/templates/trusted-proxies.html
@@ -0,0 +1,25 @@
+<div class="panel panel-default">
+ <div class="panel-heading">{{lang_trustedProxiesSettings}}</div>
+ <div class="panel-body">
+ <p>{{lang_trustedProxiesDescription}}</p>
+
+ <form action="?do=WebInterface" method="post">
+ <input type="hidden" name="token" value="{{token}}">
+ <input type="hidden" name="action" value="trusted-proxies">
+
+ <div class="form-group">
+ <label for="trusted-proxies-list">{{lang_trustedProxiesList}}</label>
+ <textarea class="form-control" name="trusted-proxies-list" id="trusted-proxies-list" rows="10"
+ placeholder="10.0.0.1 # public proxy&#10;192.168.1.0 # VPN proxy">{{trustedProxiesList}}</textarea>
+ <p class="help-block">{{lang_trustedProxiesListHelp}}</p>
+ </div>
+
+ <div class="pull-right">
+ <button type="submit" class="btn btn-primary" {{perms.edit.trusted-proxies.disabled}}>
+ <span class="glyphicon glyphicon-floppy-disk"></span>
+ {{lang_save}}
+ </button>
+ </div>
+ </form>
+ </div>
+</div>