76 lines
2.7 KiB
PHP
76 lines
2.7 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Terminal;
|
|
|
|
use App\Models\Operator;
|
|
use App\Services\Secrets\SecretVault;
|
|
use App\Services\Ssh\Keypair;
|
|
use phpseclib3\Crypt\PublicKeyLoader;
|
|
use Throwable;
|
|
|
|
/**
|
|
* Die SSH-Kennung, mit der das Terminal auf den CluPilot-Server selbst kommt.
|
|
*
|
|
* Bewusst eine eigene, nicht der Flottenschlüssel: dessen öffentliche Hälfte
|
|
* liegt auf JEDEM übernommenen Host in `/root/.ssh/authorized_keys`. Ein
|
|
* Schlüssel für beides hieße, dass ein Host, der einmal in falsche Hände gerät,
|
|
* dieselbe Kennung trägt wie die Tür zur Steuerungsebene.
|
|
*
|
|
* Nur die private Hälfte wird aufbewahrt. Die öffentliche wird daraus
|
|
* abgeleitet, wann immer sie gebraucht wird — zwei gespeicherte Hälften können
|
|
* auseinanderlaufen, eine abgeleitete nie.
|
|
*/
|
|
final class ServerIdentity
|
|
{
|
|
/** Der Tresor-Eintrag. Steht hier, nicht verstreut bei den Aufrufern. */
|
|
public const VAULT_KEY = 'server.private_key';
|
|
|
|
public static function exists(): bool
|
|
{
|
|
return filled(app(SecretVault::class)->get(self::VAULT_KEY));
|
|
}
|
|
|
|
/**
|
|
* Erzeugt eine neue Kennung und ersetzt eine bestehende.
|
|
*
|
|
* Ersetzen sperrt aus: die alte öffentliche Hälfte steht auf dem Wirt in
|
|
* `authorized_keys` und wird von hier aus nicht angefasst. Zwischen dem
|
|
* Erzeugen und dem nächsten `install-agent.sh` erreicht das Terminal den
|
|
* Server nicht. Deshalb hängt an dieser Methode eine Rückfrage (R23), und
|
|
* deshalb sagt die Einstellungsseite danach, was noch zu tun ist.
|
|
*/
|
|
public static function generate(Operator $by): void
|
|
{
|
|
app(SecretVault::class)->put(self::VAULT_KEY, Keypair::generate()->privateKey, $by);
|
|
}
|
|
|
|
/**
|
|
* Die öffentliche Hälfte als eine Zeile `ssh-ed25519 AAAA… clupilot`, oder
|
|
* null, wenn es keine Kennung gibt.
|
|
*
|
|
* Genau diese Zeile holt sich der root-eigene Schritt auf dem Wirt über
|
|
* `php artisan clupilot:server-terminal-key`. Sie ist öffentlich — das
|
|
* Kommando gibt nichts preis, was nicht ohnehin in einer authorized_keys
|
|
* stünde.
|
|
*/
|
|
public static function publicKey(): ?string
|
|
{
|
|
$private = (string) app(SecretVault::class)->get(self::VAULT_KEY);
|
|
|
|
if (blank($private)) {
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
return PublicKeyLoader::load($private)
|
|
->getPublicKey()
|
|
->toString('OpenSSH', ['comment' => Keypair::COMMENT]);
|
|
} catch (Throwable) {
|
|
// Unlesbar gespeichert heißt „keine Kennung". Nicht werfen: die
|
|
// Aufrufer sind ein Kommando, das eine Zeile ausgeben soll, und
|
|
// eine Seite, die nie mit einer Fehlerseite aufgehen darf.
|
|
return null;
|
|
}
|
|
}
|
|
}
|