CluPilotCloud/tests/Feature/ServerTerminalInstallTest.php

80 lines
3.6 KiB
PHP

<?php
use Illuminate\Support\Facades\File;
/**
* Die root-eigene Hälfte, als Text gelesen.
*
* Ausführen lässt sie sich hier nicht — sie will einen Wirt mit sshd, einer
* laufenden Anlage und einem Auschecken unter /opt/clupilot. Was ein Test
* trotzdem festhalten kann, sind die Entscheidungen, die in ihr stecken und
* die man beim nächsten Anfassen versehentlich zurücknimmt.
*
* Nachbar, nicht Ersatz: tests/Feature/HostStepTest.php prüft den Helfer als
* Ganzes — dass er root gehört, dass sudoers je eine feste Kommandozeile nennt,
* dass der Vertrag zu update.sh passt. Hier steht nur, was DIESEN Schritt
* ausmacht.
*/
function installAgent(): string
{
return File::get(base_path('deploy/install-agent.sh'));
}
it('erhoeht den Vertrag, damit Bestandsserver ueberhaupt erfahren, dass es den Schritt gibt', function () {
// Ein neuer Schritt nuetzt auf einem Server nichts, der noch den alten
// Helfer traegt — und still nutzlos ist die Fehlerart, fuer die update.sh
// seine Vertragspruefung hat.
expect(installAgent())->toContain('CONTRACT=3')
->and(installAgent())->toContain('install-server-terminal)');
});
it('gibt dem Dienstkonto KEINEN sudo-Weg, sich selbst einen Root-Schluessel einzutragen', function () {
$script = installAgent();
// Der sudoers-Block nennt genau die Schritte, die das Dienstkonto anstossen
// darf. `install-server-terminal` schreibt nach /root/.ssh/authorized_keys;
// stuende er dort, koennte eine uebernommene Anwendung sich jederzeit
// selbst root geben — Kennung neu erzeugen, Schritt anstossen, fertig.
$sudoers = str($script)->after('/etc/sudoers.d/clupilot-host-step <<EOF')->before('EOF')->toString();
expect($sudoers)->toContain('ensure-rsync')
->and($sudoers)->toContain('apply-proxy-hosts')
->and($sudoers)->not->toContain('install-server-terminal');
});
it('nimmt genau eine ssh-ed25519-Zeile an und nichts sonst', function () {
$script = installAgent();
// Die beiden Haelften der Pruefung: „genau eine Zeile" und „sieht aus wie
// ein Schluessel". Ohne die erste waere ein Zeilenumbruch im Wert ein
// zweiter Schluessel; ohne die zweite genuegte irgendein Text.
expect($script)->toContain('-eq 1 ]] \\')
->and($script)->toContain('^ssh-ed25519\ [A-Za-z0-9+/]+=*');
});
it('schreibt die Optionsliste selbst, statt sie sich reichen zu lassen', function () {
// `from` bindet den Schluessel an das Container-Netz, `restrict` nimmt ihm
// alles ausser einer Sitzung, `pty` gibt das eine zurueck, ohne das ein
// Terminal keines ist. Kaeme diese Liste aus der Anwendung, waere aus „die
// Anwendung nennt ihren Schluessel" ein „die Anwendung schreibt in
// authorized_keys" geworden.
expect(installAgent())->toContain('from="%s",restrict,pty %s');
});
it('bildet den Fingerabdruck in CluPilots Form, nicht in der von OpenSSH', function () {
$script = installAgent();
// Gehasht wird die Zeichenkette "<algorithmus> <base64-blob>" — siehe
// fingerprints() in docker/terminal/bridge.py. `ssh-keygen -lf` gaebe den
// bekannten OpenSSH-Fingerabdruck und damit bei JEDEM Verbindungsversuch
// eine Abweichung.
expect($script)->toContain("printf '%s %s'")
->and($script)->toContain('openssl dgst -sha256 -binary')
->and($script)->not->toContain('ssh-keygen -lf');
});
it('haelt Adresse und Fingerabdruck dort fest, wo die Anwendung sie liest', function () {
expect(installAgent())->toContain('storage/app/deploy/server-terminal.json')
->and(installAgent())->toContain('"installed_at"');
});