get('ssh.private_key'))) { return 'no_key'; } // Derselbe Gedanke, für die beiden anderen Felder, ohne die keine // sichere Sitzung entstehen kann. Eine fehlende Tunneladresse ergibt // bloß ein Fenster, das nie verbindet — ärgerlich, aber harmlos. Ein // fehlender Fingerabdruck ist das Gegenteil: er übergäbe die Prüfung an // Code, der noch gar nicht existiert, und dessen naheliegendste Fassung // ("kein Fingerabdruck im Ticket → nicht prüfen") eine ungepinnte // Root-SSH-Sitzung im Tunnel wäre — genau das, was die Spec mit // „Fingerabdruck geprüft" ausdrücklich ausschließt. Die Prüfung gehört // hierher, wo der Host ohnehin schon feststeht, nicht in einen // Container, der dem Ticket nur noch glauben kann. if (blank($host->wg_ip) || blank($host->ssh_host_key)) { return 'not_ready'; } return null; } public static function issue(Host $host, Operator $for): string { // Die Prüfung steht in `blocker()`; hier bleibt nur, sie ernst zu // nehmen. Der Wortlaut ist der von früher — er steht in Logs und in // den Tests aus Aufgabe 1. match (self::blocker($host)) { 'no_key' => throw new RuntimeException('Kein SSH-Schlüssel hinterlegt — ohne ihn kann keine Terminalsitzung entstehen.'), 'not_ready' => throw new RuntimeException("Host {$host->name} hat keine Tunneladresse oder keinen geprüften Fingerabdruck hinterlegt — ohne beides kein Ticket."), default => null, }; $key = (string) app(SecretVault::class)->get('ssh.private_key'); return TicketStore::issue([ 'operator_id' => $for->id, 'host_uuid' => $host->uuid, // Die Tunneladresse. Die öffentliche IP wäre der Weg, den // SecureHostFirewall ausdrücklich zumacht. 'ip' => $host->wg_ip, 'user' => 'root', 'private_key' => $key, 'fingerprint' => $host->ssh_host_key, ]); } /** * `ip` und `fingerprint` sind hier nicht optional: issue() weist beide * zurück, bevor überhaupt etwas in Redis landet. * * @return array{operator_id: int, host_uuid: string, ip: string, user: string, private_key: string, fingerprint: string}|null */ public static function redeem(string $ticket): ?array { /** @var array{operator_id: int, host_uuid: string, ip: string, user: string, private_key: string, fingerprint: string}|null */ return TicketStore::redeem($ticket); } }