99 lines
3.6 KiB
PHP
99 lines
3.6 KiB
PHP
<?php
|
|
|
|
namespace App\Support\Readiness;
|
|
|
|
use App\Http\Middleware\RestrictConsoleNetwork;
|
|
|
|
/**
|
|
* Was von aussen offensteht — und was offenstehen soll.
|
|
*
|
|
* Die anderen Gruppen in dieser Datei fragen, ob etwas EINGERICHTET ist. Diese
|
|
* fragt, ob etwas ZU ist. Der Unterschied ist wichtig: eine fehlende
|
|
* Einrichtung faellt beim ersten Versuch auf, eine offene Tuer nie — bis sie
|
|
* jemand benutzt.
|
|
*
|
|
* Alle drei Punkte hier sind aus einer Durchsicht am 2026-08-03 entstanden, und
|
|
* zwei davon standen auf der echten Anlage wirklich so.
|
|
*/
|
|
final class SecurityChecks
|
|
{
|
|
public const GROUP = 'security';
|
|
|
|
/**
|
|
* @return list<Check>
|
|
*/
|
|
public static function all(): array
|
|
{
|
|
return [
|
|
new Check(
|
|
key: 'security.console_restricted',
|
|
group: self::GROUP,
|
|
severity: Check::SEVERITY_BLOCKING,
|
|
label: __('readiness.security.console_restricted'),
|
|
breaks: __('readiness.security.console_restricted_breaks'),
|
|
tab: 'security',
|
|
satisfied: RestrictConsoleNetwork::isRestricted(),
|
|
),
|
|
|
|
new Check(
|
|
key: 'security.trusted_ranges_clean',
|
|
group: self::GROUP,
|
|
severity: Check::SEVERITY_WARNING,
|
|
label: __('readiness.security.trusted_ranges'),
|
|
breaks: __('readiness.security.trusted_ranges_breaks'),
|
|
tab: 'security',
|
|
// Alles, was ueber das Management-Netz und die eigene Maschine
|
|
// hinausgeht, ist von Hand in die .env geschrieben worden — und
|
|
// steht damit an einer Stelle, die der Betreiber in der Konsole
|
|
// nicht aendern kann. Genau so stand eine Bueroadresse dort:
|
|
// beim naechsten Anschlusswechsel waere das ein Aussperren
|
|
// gewesen. Sie gehoert in die Liste, die man pflegen kann.
|
|
satisfied: array_diff(
|
|
(array) config('admin_access.trusted_ranges', []),
|
|
(array) config('admin_access.structural_ranges', []),
|
|
) === [],
|
|
),
|
|
|
|
new Check(
|
|
key: 'security.ports_loopback',
|
|
group: self::GROUP,
|
|
severity: Check::SEVERITY_WARNING,
|
|
label: __('readiness.security.ports_loopback'),
|
|
breaks: __('readiness.security.ports_loopback_breaks'),
|
|
tab: 'env',
|
|
satisfied: self::onlyLoopback(),
|
|
),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Sind die Anwendungs-Ports nur auf der eigenen Maschine erreichbar?
|
|
*
|
|
* Docker traegt veroeffentlichte Ports VOR der Firewall ein. Ein Dienst auf
|
|
* `0.0.0.0` ist damit aus dem Internet erreichbar, auch wenn ufw geschlossen
|
|
* aussieht — und wer ihn direkt anspricht, geht am Reverse Proxy vorbei und
|
|
* damit an dessen Zugangsliste und an TLS.
|
|
*
|
|
* Ohne Angabe gilt in docker-compose.yml die Schleife (`127.0.0.1:8080`),
|
|
* dann ist alles in Ordnung. Ein blosser Port ohne Adresse — `APP_PORT=80` —
|
|
* bedeutet bei Docker ausdruecklich ALLE Adressen; das ist der Fall, den
|
|
* diese Pruefung sucht.
|
|
*/
|
|
private static function onlyLoopback(): bool
|
|
{
|
|
foreach (['APP_PORT', 'REVERB_HOST_PORT'] as $name) {
|
|
$wert = trim((string) env($name, ''));
|
|
|
|
if ($wert === '') {
|
|
continue; // Vorgabe aus docker-compose.yml: Schleife.
|
|
}
|
|
|
|
if (! str_starts_with($wert, '127.0.0.1:') && ! str_starts_with($wert, 'localhost:')) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|