clusev/app/Support/Ssh/VerifiesHostKey.php

45 lines
1.6 KiB
PHP

<?php
namespace App\Support\Ssh;
use App\Models\Server;
use phpseclib3\Net\SSH2;
use RuntimeException;
/**
* TOFU (trust-on-first-use) host-key pinning. phpseclib verifies the key-exchange
* signature but does NOT pin the server's host key, so a MITM presenting its own
* valid key would be accepted. We record the host key on first connect and refuse
* the connection on any later mismatch.
*/
trait VerifiesHostKey
{
private function verifyHostKey(SSH2 $conn, Server $server): void
{
$hostKey = $conn->getServerPublicHostKey();
if ($hostKey === false) {
throw new RuntimeException("Host-Key von {$server->name} ({$server->ip}) nicht lesbar — Verbindung abgebrochen.");
}
$known = (string) ($server->ssh_host_key ?? '');
if ($known !== '') {
if (! hash_equals($known, $hostKey)) {
throw new RuntimeException("Host-Key-Mismatch fuer {$server->name} ({$server->ip}) — moegliches MITM. Verbindung abgebrochen.");
}
return;
}
// Trust on first use — but ONLY pin persisted fleet servers. A transient Server (the
// "Clusev host" built on the fly from HostCredential) must never be written to the DB:
// saving it would spawn a junk fleet row on every request. The host is reached over the
// local Docker gateway (host.docker.internal), which has no network path to MITM, so
// skipping the pin there is safe.
if ($server->exists) {
$server->forceFill(['ssh_host_key' => $hostKey])->save();
}
}
}