set('admin_access.secrets_key', 'base64:'.base64_encode(random_bytes(32))); }); it('mints a key the SSH client itself can load', function () { // Die eigentliche Zusicherung, und der Grund für phpseclib statt eines // selbstgeschriebenen OpenSSH-Rahmens: PhpseclibRemoteShell::connectWithKey() // reicht den gespeicherten Text an genau dieses PublicKeyLoader::load() // weiter. Was diese Prüfung lädt, ist damit dasselbe, was sich später an // einem Host anmeldet — ein Format, das nur „nach OpenSSH aussieht", fiele // erst bei der ersten Host-Übernahme auf. $pair = Keypair::generate(); $loaded = PublicKeyLoader::load($pair->privateKey); expect($loaded)->toBeInstanceOf(EC\PrivateKey::class) ->and($loaded->getCurve())->toBe('Ed25519'); }); it('publishes a public half that is one authorized_keys line and belongs to the private half', function () { // EstablishSshTrust hängt genau diesen String mit `echo … >> authorized_keys` // an. Ein Zeilenumbruch darin schriebe zwei kaputte Zeilen; ein öffentlicher // Teil, der nicht zum privaten gehört, ergäbe eine Übernahme, die beim // Schlüssel-Login scheitert, nachdem das Passwort schon verworfen wurde. $pair = Keypair::generate(); expect($pair->publicKey)->toStartWith('ssh-ed25519 ') ->and($pair->publicKey)->not->toContain("\n") ->and(PublicKeyLoader::load($pair->privateKey)->getPublicKey()->toString('OpenSSH', ['comment' => 'clupilot'])) ->toBe($pair->publicKey); }); it('never mints the same key twice', function () { expect(Keypair::generate()->privateKey)->not->toBe(Keypair::generate()->privateKey); }); it('stores the private half in the vault and publishes only the public half', function () { $owner = operator('Owner'); $page = Livewire::actingAs($owner, 'operator') ->test(Integrations::class) ->set('confirmablePassword', 'password') ->call('confirmPassword') ->call('generateSshKey') ->assertHasNoErrors(); $private = (string) app(SecretVault::class)->get('ssh.private_key'); $public = ProvisioningSettings::sshPublicKey(); expect($private)->toContain('BEGIN OPENSSH PRIVATE KEY') ->and($public)->toStartWith('ssh-ed25519 ') // Das Feld auf der Seite zeigt den öffentlichen Teil, damit der // Betreiber ihn ohne Umweg auf einen Host legen kann. ->and($page->get('sshPublicKey'))->toBe($public) ->and(PublicKeyLoader::load($private)->getPublicKey()->toString('OpenSSH', ['comment' => 'clupilot'])) ->toBe($public); }); it('never lets the generated private half reach the page', function () { // Derselbe Maßstab wie für jeden anderen Tresorwert: gespeichert wird er, // angezeigt nie. Ein erzeugter Schlüssel ist dabei der heikelste Fall — er // entsteht in derselben Anfrage, die die Antwort schreibt. $owner = operator('Owner'); $page = Livewire::actingAs($owner, 'operator') ->test(Integrations::class) ->set('confirmablePassword', 'password') ->call('confirmPassword') ->call('generateSshKey'); $private = (string) app(SecretVault::class)->get('ssh.private_key'); $body = json_encode($page->snapshot).$page->html(); expect($body)->not->toContain('BEGIN OPENSSH PRIVATE KEY'); foreach (array_slice(explode("\n", trim($private)), 1, -1) as $zeile) { expect($body)->not->toContain(trim($zeile)); } }); it('refuses to generate without a confirmed password', function () { // Dieselbe Sperre wie Speichern und Vergessen: das Erzeugen ÜBERSCHREIBT // den Schlüssel, mit dem diese Anwendung jeden Host erreicht. Livewire::actingAs(operator('Owner'), 'operator') ->test(Integrations::class) ->call('generateSshKey') ->assertForbidden(); }); it('refuses to generate without the capability', function () { Livewire::actingAs(operator('Admin'), 'operator') ->test(Integrations::class) ->call('generateSshKey') ->assertForbidden(); }); it('confirms in a modal before replacing the identity every host trusts', function () { // R23, und hier mit echten Folgen: der bisherige öffentliche Teil bleibt in // den authorized_keys jedes übernommenen Hosts stehen, der neue steht dort // noch nicht. Zwischen Erzeugen und Ausrollen erreicht die Konsole keine // einzige Maschine mehr. Livewire::actingAs(operator('Owner'), 'operator') ->test(ConfirmGenerateSshKey::class) ->call('confirm') ->assertDispatched('ssh-key-generate-confirmed'); });