CluPilotCloud/app/Support/Readiness/SecurityChecks.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;
}
}