103 lines
4.2 KiB
PHP
103 lines
4.2 KiB
PHP
<?php
|
|
|
|
namespace App\Provisioning\Steps\Host;
|
|
|
|
use App\Models\ProvisioningRun;
|
|
use App\Provisioning\StepResult;
|
|
use App\Services\Ssh\RemoteShell;
|
|
use App\Support\ProvisioningSettings;
|
|
use Illuminate\Support\Facades\Crypt;
|
|
|
|
/**
|
|
* First login with the one-time root password, deploy CluPilot's SSH key, pin
|
|
* the host key, verify key login, then scrub the password from the run context.
|
|
*/
|
|
class EstablishSshTrust extends HostStep
|
|
{
|
|
public function __construct(private RemoteShell $shell) {}
|
|
|
|
public function key(): string
|
|
{
|
|
return 'establish_ssh_trust';
|
|
}
|
|
|
|
public function execute(ProvisioningRun $run): StepResult
|
|
{
|
|
$host = $this->host($run);
|
|
|
|
// Idempotent: already trusted (host key pinned) and password already scrubbed.
|
|
if (filled($host->ssh_host_key) && blank($run->context('root_password'))) {
|
|
return StepResult::advance();
|
|
}
|
|
|
|
$publicKey = trim(ProvisioningSettings::sshPublicKey());
|
|
$password = Crypt::decryptString((string) $run->context('root_password'));
|
|
|
|
$this->shell->connectWithPassword($host->public_ip, 'root', $password);
|
|
$this->shell->run('mkdir -p /root/.ssh && chmod 700 /root/.ssh');
|
|
$this->shell->run(
|
|
'grep -qxF '.escapeshellarg($publicKey).' /root/.ssh/authorized_keys 2>/dev/null '.
|
|
'|| echo '.escapeshellarg($publicKey).' >> /root/.ssh/authorized_keys'
|
|
);
|
|
|
|
$host->update(['ssh_host_key' => $this->shell->hostKeyFingerprint()]);
|
|
|
|
// Verify the key works, then remove the password from state.
|
|
$this->keyLogin($this->shell, $host);
|
|
$run->forgetContext('root_password');
|
|
|
|
$this->refusePasswordLogins();
|
|
|
|
return StepResult::advance();
|
|
}
|
|
|
|
/**
|
|
* Ab hier kommt man auf diese Maschine nur noch mit dem Schlüssel.
|
|
*
|
|
* Bis hierher blieb die Anmeldung als root MIT PASSWORT erlaubt — und Port
|
|
* 22 steht während der ganzen Übernahme offen im Internet, denn die
|
|
* Host-Firewall macht ihn erst als vorletzter Schritt zu. Zwischen diesem
|
|
* Schritt und jenem lag also ein Fenster von Stunden, in dem jede Maschine
|
|
* der Welt Root-Passwörter durchprobieren durfte. Und wer später einmal das
|
|
* Notfallskript benutzt, reisst dieses Fenster wieder auf.
|
|
*
|
|
* Der richtige Zeitpunkt ist GENAU HIER: eine Zeile weiter oben hat sich
|
|
* `keyLogin()` erfolgreich mit dem Schlüssel angemeldet. Wir wissen also,
|
|
* dass der Weg hinein steht, bevor wir den anderen zumachen.
|
|
*
|
|
* `reload`, nicht `restart`: bestehende Sitzungen bleiben, auch die, über
|
|
* die wir hier gerade reden. Und `sshd -t` davor — eine Konfiguration, die
|
|
* sshd nicht laden kann, dürfte er nie zu sehen bekommen.
|
|
*
|
|
* Fehlschläge sind ausdrücklich KEIN Grund, die Übernahme abzubrechen: der
|
|
* Schlüssel liegt dann trotzdem, die Maschine ist benutzbar, und die
|
|
* Firewall macht Port 22 weiter unten ohnehin zu. Eine Härtung, die einen
|
|
* ganzen Aufbau scheitern lässt, wird beim nächsten Mal weggelassen.
|
|
*/
|
|
private function refusePasswordLogins(): void
|
|
{
|
|
// Eigene Datei unter sshd_config.d, nicht die Hauptdatei bearbeiten:
|
|
// Debians sshd_config zieht dieses Verzeichnis in der ERSTEN Zeile
|
|
// herein, und bei sshd gewinnt der erste Treffer. Was hier steht,
|
|
// schlägt damit alles, was weiter unten in der Hauptdatei steht — ohne
|
|
// dass wir eine fremde Datei umschreiben, die das nächste Systemupdate
|
|
// wieder ersetzt.
|
|
$this->shell->run('install -d -m 755 /etc/ssh/sshd_config.d');
|
|
$this->shell->putFile('/etc/ssh/sshd_config.d/99-clupilot.conf', <<<'SSHD'
|
|
# Geschrieben von CluPilot (EstablishSshTrust). Nicht von Hand ändern —
|
|
# der nächste Lauf dieses Schrittes überschreibt die Datei.
|
|
#
|
|
# Auf diese Maschine kommt man mit dem Flottenschlüssel. Ein Passwort ist ab
|
|
# hier keine Eintrittskarte mehr, auch nicht für root. Der Weg zurück, wenn der
|
|
# Schlüssel je verlorengeht, ist die Konsole des Anbieters — nicht ein
|
|
# Passwort, das jemand raten kann.
|
|
PasswordAuthentication no
|
|
KbdInteractiveAuthentication no
|
|
PermitRootLogin prohibit-password
|
|
|
|
SSHD);
|
|
|
|
$this->shell->run('sshd -t && { systemctl reload ssh || systemctl reload sshd; }');
|
|
}
|
|
}
|