88 lines
3.2 KiB
PHP
88 lines
3.2 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Terminal;
|
|
|
|
use Illuminate\Support\Facades\File;
|
|
use Illuminate\Support\Facades\Log;
|
|
use Throwable;
|
|
|
|
/**
|
|
* Was der Wirt über sich selbst hinterlassen hat, als er eingerichtet wurde.
|
|
*
|
|
* Zwei Werte, und keiner von beiden darf aus dem Code kommen:
|
|
*
|
|
* - **Die Adresse**, unter der der Container den Wirt erreicht — das Gateway
|
|
* des compose-Netzes. Auf dieser Installation 172.18.0.1, auf der nächsten
|
|
* eine andere. Ein fester Wert im Code wäre auf jeder zweiten Maschine falsch.
|
|
* - **Der Fingerabdruck** des Hostschlüssels, in CluPilots Form gebildet
|
|
* (`SHA256:` base64 über `"<algorithmus> <base64-blob>"`, NICHT der
|
|
* OpenSSH-Fingerabdruck — siehe docker/terminal/bridge.py). Er ist der Grund,
|
|
* warum eine Sitzung überhaupt sicher ist: ohne ihn meldet sich die Brücke
|
|
* mit einem Root-Schlüssel bei irgendwem an, der gerade auf Port 22 antwortet.
|
|
*
|
|
* Geschrieben wird die Datei von `/usr/local/sbin/clupilot-host-step
|
|
* install-server-terminal`, also von der root-eigenen Hälfte, die das
|
|
* Dienstkonto nicht anfassen kann. Gelesen wird sie hier.
|
|
*/
|
|
final readonly class ServerTerminalSetup
|
|
{
|
|
public function __construct(
|
|
public string $address,
|
|
public string $fingerprint,
|
|
public ?string $installedAt,
|
|
) {}
|
|
|
|
public static function path(): string
|
|
{
|
|
return storage_path('app/deploy/server-terminal.json');
|
|
}
|
|
|
|
/**
|
|
* Die Einrichtung, oder null, wenn es keine brauchbare gibt.
|
|
*
|
|
* Fehlt die Datei, ist das der Normalfall vor dem ersten
|
|
* `sudo bash deploy/install-agent.sh` — dazu gibt es nichts zu sagen. Ist
|
|
* sie aber DA und trotzdem unbrauchbar, wird das protokolliert: sonst sehen
|
|
* „nie eingerichtet" und „Einrichtung kaputt" von der Seite aus gleich aus,
|
|
* und die Anleitung, die dann dort steht, hilft im zweiten Fall nicht.
|
|
*/
|
|
public static function read(): ?self
|
|
{
|
|
$path = self::path();
|
|
|
|
if (! File::exists($path)) {
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
$data = json_decode(File::get($path), true, flags: JSON_THROW_ON_ERROR);
|
|
} catch (Throwable $e) {
|
|
Log::warning('Die Einrichtung des Server-Terminals ist nicht lesbar.', [
|
|
'path' => $path,
|
|
'error' => $e->getMessage(),
|
|
]);
|
|
|
|
return null;
|
|
}
|
|
|
|
$address = is_array($data) ? (string) ($data['address'] ?? '') : '';
|
|
$fingerprint = is_array($data) ? (string) ($data['fingerprint'] ?? '') : '';
|
|
|
|
// Geprüft, nicht geglaubt. Eine leere Adresse ergäbe ein Fenster, das
|
|
// nie verbindet; ein leerer Fingerabdruck ist das Gegenteil von
|
|
// harmlos — die naheliegendste Lesart davon („nichts zu prüfen") wäre
|
|
// eine ungepinnte Root-Sitzung.
|
|
if (filter_var($address, FILTER_VALIDATE_IP) === false || ! str_starts_with($fingerprint, 'SHA256:')) {
|
|
Log::warning('Die Einrichtung des Server-Terminals ist unvollständig.', ['path' => $path]);
|
|
|
|
return null;
|
|
}
|
|
|
|
return new self(
|
|
address: $address,
|
|
fingerprint: $fingerprint,
|
|
installedAt: ($data['installed_at'] ?? null) ? (string) $data['installed_at'] : null,
|
|
);
|
|
}
|
|
}
|