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; }'); } }