120 lines
4.1 KiB
PHP
120 lines
4.1 KiB
PHP
<?php
|
|
|
|
use App\Models\Host;
|
|
use App\Services\Wireguard\WireguardHub;
|
|
use App\Support\HostEnrolment;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
beforeEach(function () {
|
|
fakeServices();
|
|
});
|
|
|
|
/**
|
|
* Der Code ist ein Ausweis für den Rückweg, kein Schlüssel zu einer Auskunft:
|
|
* er öffnet nichts nach außen (§5 der Spec), sondern verhindert nur, dass ein
|
|
* Gerät im Tunnel für einen fremden Host spricht.
|
|
*/
|
|
it('hands out the code exactly once and stores only its hash', function () {
|
|
$host = Host::factory()->create();
|
|
|
|
$code = HostEnrolment::issue($host);
|
|
|
|
expect($code)->toHaveLength(32);
|
|
|
|
$stored = DB::table('hosts')->where('id', $host->id)->value('enrolment_code_hash');
|
|
expect($stored)->not->toBe($code)
|
|
->and($stored)->not->toContain($code);
|
|
});
|
|
|
|
it('resolves a valid code to its host and consumes it', function () {
|
|
$host = Host::factory()->create();
|
|
$code = HostEnrolment::issue($host);
|
|
|
|
expect(HostEnrolment::claim($code)?->id)->toBe($host->id);
|
|
|
|
// Verbraucht. Ein zweiter Lauf braucht einen neuen Code aus der Konsole —
|
|
// eine halb installierte Maschine wird neu aufgesetzt, nicht nachgebessert.
|
|
expect(HostEnrolment::claim($code))->toBeNull();
|
|
});
|
|
|
|
/**
|
|
* Der Fortschritt kommt VOR der Registrierung und darf den Code deshalb nicht
|
|
* verbrauchen — sonst könnte der Host sich nach seiner ersten Meldung nie mehr
|
|
* registrieren.
|
|
*/
|
|
it('resolves a code without consuming it, for the progress reports', function () {
|
|
$host = Host::factory()->create();
|
|
$code = HostEnrolment::issue($host);
|
|
|
|
expect(HostEnrolment::resolve($code)?->id)->toBe($host->id)
|
|
->and(HostEnrolment::resolve($code)?->id)->toBe($host->id);
|
|
|
|
// Und danach ist er immer noch einlösbar.
|
|
expect(HostEnrolment::claim($code)?->id)->toBe($host->id);
|
|
});
|
|
|
|
it('refuses a code past its expiry', function () {
|
|
$host = Host::factory()->create();
|
|
$code = HostEnrolment::issue($host);
|
|
|
|
$this->travel(25)->hours();
|
|
|
|
expect(HostEnrolment::claim($code))->toBeNull();
|
|
});
|
|
|
|
it('refuses a code that was never issued', function () {
|
|
expect(HostEnrolment::claim(str_repeat('a', 32)))->toBeNull();
|
|
});
|
|
|
|
/**
|
|
* Der Hub muss den Peer kennen, BEVOR der Host im Tunnel ist — sonst käme er
|
|
* nie hinein und müsste seinen Schlüssel über einen öffentlichen Weg melden.
|
|
* Genau das vermeidet dieser Entwurf.
|
|
*/
|
|
it('allocates a tunnel address and admits the peer up front', function () {
|
|
$host = Host::factory()->create(['wg_ip' => null, 'wg_pubkey' => null]);
|
|
|
|
HostEnrolment::issue($host);
|
|
|
|
$host->refresh();
|
|
expect($host->wg_ip)->not->toBeNull()
|
|
->and($host->wg_pubkey)->not->toBeNull();
|
|
|
|
// `peers()` ist nach dem öffentlichen Schlüssel indiziert, die Werte sind
|
|
// Momentaufnahmen — geprüft wird deshalb gegen die Schlüssel.
|
|
expect(array_keys(app(WireguardHub::class)->peers()))->toContain($host->wg_pubkey);
|
|
});
|
|
|
|
/**
|
|
* Der private Schlüssel steht in der kopierten Befehlszeile und sonst nirgends.
|
|
* Ihn zu speichern hieße, ein Geheimnis aufzubewahren, das nach Task 9 des
|
|
* Skripts ohnehin wertlos ist — und bis dahin eine Stelle mehr, an der es liegt.
|
|
*/
|
|
it('returns the private key with the code and never stores it', function () {
|
|
$host = Host::factory()->create();
|
|
|
|
$enrolment = HostEnrolment::issueWithKeys($host);
|
|
|
|
expect($enrolment['private_key'])->not->toBeEmpty();
|
|
|
|
$row = (array) DB::table('hosts')->where('id', $host->id)->first();
|
|
foreach ($row as $value) {
|
|
expect((string) $value)->not->toContain($enrolment['private_key']);
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Ein zweiter Code macht den ersten wertlos. Sonst hätte eine Maschine, die im
|
|
* Rettungssystem hängengeblieben ist, weiter einen gültigen Ausweis für einen
|
|
* Host, den der Betreiber gerade neu übernimmt.
|
|
*/
|
|
it('invalidates the previous code when a new one is issued', function () {
|
|
$host = Host::factory()->create();
|
|
|
|
$first = HostEnrolment::issue($host);
|
|
$second = HostEnrolment::issue($host);
|
|
|
|
expect(HostEnrolment::claim($first))->toBeNull()
|
|
->and(HostEnrolment::claim($second)?->id)->toBe($host->id);
|
|
});
|