95 lines
3.5 KiB
PHP
95 lines
3.5 KiB
PHP
<?php
|
|
|
|
namespace App\Provisioning\Jobs;
|
|
|
|
use App\Models\Host;
|
|
use App\Services\Wireguard\WireguardHub;
|
|
use Illuminate\Bus\Queueable;
|
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
|
use Illuminate\Foundation\Bus\Dispatchable;
|
|
use Illuminate\Queue\InteractsWithQueue;
|
|
use Illuminate\Queue\SerializesModels;
|
|
use Illuminate\Support\Facades\Cache;
|
|
use Illuminate\Support\Facades\Log;
|
|
use Throwable;
|
|
|
|
/**
|
|
* Trägt den Tunnel-Peer eines Hosts auf dem Hub ein.
|
|
*
|
|
* Auf der Provisioning-Queue, weil nur deren Container `wg0` besitzt — genau
|
|
* wie ApplyVpnPeer, aus genau demselben Grund.
|
|
*
|
|
* HostEnrolment tat das bis hierher SELBST, im Web-Request. Der läuft im
|
|
* `app`-Container, und der hat weder NET_ADMIN noch `/dev/net/tun`: `wg set`
|
|
* antwortete mit „Unable to modify interface: Operation not permitted", und das
|
|
* Anlegen eines Hosts endete für den Betreiber in einem 500 — auf der einen
|
|
* Seite, die ihm den Einmal-Code zeigen sollte. Aufgefallen ist es nie, weil
|
|
* die Testsuite den Hub gegen FakeWireguardHub tauscht; die Klasse trug den
|
|
* Hinweis darauf sogar im Kopf („verified on the real VM, not in the mocked
|
|
* test-suite") und meinte damit einen anderen Aufrufer.
|
|
*/
|
|
class ApplyHostVpnPeer implements ShouldQueue
|
|
{
|
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
|
|
|
public int $tries = 3;
|
|
|
|
public function __construct(
|
|
public int $hostId,
|
|
/**
|
|
* Der abgelöste Schlüssel, falls die Übernahme ein zweites Mal läuft.
|
|
*
|
|
* Als Wert und nicht aus der Zeile gelesen: die trägt zum Zeitpunkt des
|
|
* Einreihens bereits den NEUEN Schlüssel, der alte stünde dann nirgends
|
|
* mehr und bliebe für immer auf dem Hub.
|
|
*/
|
|
public ?string $previousPublicKey = null,
|
|
) {
|
|
$this->onQueue('provisioning');
|
|
}
|
|
|
|
public function handle(WireguardHub $hub): void
|
|
{
|
|
// Derselbe Sperrname wie in ApplyVpnPeer und SyncVpnPeers: ein Abgleich
|
|
// darf den Hub nicht mitten in dieser Änderung lesen und danach auf das
|
|
// handeln, was er gesehen hat.
|
|
Cache::lock('wireguard:hub', 30)->block(10, fn () => $this->apply($hub));
|
|
}
|
|
|
|
private function apply(WireguardHub $hub): void
|
|
{
|
|
// Der Sollzustand kommt aus der Zeile, nicht aus dem beim Einreihen
|
|
// festgehaltenen Wert. Ein Wiederholungsversuch nach einem zweiten
|
|
// Übernahmeversuch trüge sonst den überholten Schlüssel wieder ein.
|
|
$host = Host::find($this->hostId);
|
|
|
|
if ($host === null || blank($host->wg_pubkey) || blank($host->wg_ip)) {
|
|
return;
|
|
}
|
|
|
|
// Erst den neuen Peer aufnehmen, dann den alten entfernen — dazwischen
|
|
// darf es keinen Moment geben, in dem gar kein Peer eingetragen ist.
|
|
$hub->addPeer($host->wg_pubkey, $host->wg_ip);
|
|
|
|
if (filled($this->previousPublicKey) && $this->previousPublicKey !== $host->wg_pubkey) {
|
|
$hub->removePeer($this->previousPublicKey);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Laut scheitern.
|
|
*
|
|
* Der Betreiber hat seine Befehlszeile längst in der Hand; bleibt der Peer
|
|
* aus, kommt der Tunnel auf der übernommenen Maschine nie hoch, und das
|
|
* sieht dort aus wie ein Netzproblem. Diese Zeile ist die einzige Stelle,
|
|
* an der steht, dass es keines war.
|
|
*/
|
|
public function failed(?Throwable $e): void
|
|
{
|
|
Log::error('Der Tunnel-Peer eines Hosts liess sich nicht eintragen', [
|
|
'host' => $this->hostId,
|
|
'exception' => $e?->getMessage(),
|
|
]);
|
|
}
|
|
}
|