CluPilotCloud/app/Services/Terminal/ServerTerminalSetup.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,
);
}
}