onQueue('provisioning'); } public function handle(WireguardHub $hub): void { // Reading the hub and reconciling must not straddle a peer removal: a // snapshot taken before ApplyVpnPeer removes a peer and purges its // tombstone would otherwise be adopted afterwards as a fresh, enabled // access — restoring exactly what was revoked. ApplyVpnPeer holds the // same lock, so read and mutation are mutually exclusive. $retryRemoval = []; Cache::lock('wireguard:hub', 30)->block(10, function () use ($hub, &$retryRemoval) { $this->reconcile($hub, $retryRemoval); }); // Dispatched outside the lock: with the sync queue driver this executes // inline, and ApplyVpnPeer takes the very same lock. foreach ($retryRemoval as $publicKey) { ApplyVpnPeer::dispatch($publicKey, null, false); } } /** @param list $retryRemoval collected, dispatched by the caller */ private function reconcile(WireguardHub $hub, array &$retryRemoval): void { $snapshots = $hub->peers(); $now = now(); // hosts.wg_pubkey => host id, so pipeline-created peers show up // under their host name instead of as an unknown key. $hostsByKey = Host::query() ->whereNotNull('wg_pubkey') ->pluck('id', 'wg_pubkey'); foreach ($snapshots as $publicKey => $snapshot) { $peer = VpnPeer::withTrashed()->where('public_key', $publicKey)->first(); // Revoked, yet still on the hub: the removal never landed. Retry it // rather than adopting the peer back into the console — otherwise a // failed removal quietly becomes a live access again. if ($peer?->trashed()) { $retryRemoval[] = $publicKey; continue; } $hostId = $hostsByKey[$publicKey] ?? null; $observed = [ 'present' => true, 'endpoint' => $snapshot->endpoint, 'last_handshake_at' => $snapshot->latestHandshake, 'rx_bytes' => $snapshot->rxBytes, 'tx_bytes' => $snapshot->txBytes, 'observed_at' => $now, ]; if ($peer === null) { VpnPeer::create($observed + [ 'public_key' => $publicKey, // Fall back to the dump's allowed-ips for peers that were // never created through the console. 'allowed_ip' => strtok($snapshot->allowedIps, '/') ?: $snapshot->allowedIps, 'host_id' => $hostId, 'name' => $hostId !== null ? (Host::find($hostId)?->name ?? __('vpn.unknown_peer')) : __('vpn.unknown_peer'), 'enabled' => true, ]); continue; } // Never overwrite `enabled`: that is the operator's intent, and a // peer being present only says the hub has not caught up yet. $peer->update($observed + ['host_id' => $peer->host_id ?? $hostId]); } $seen = array_keys($snapshots); VpnPeer::query() ->whereNotIn('public_key', $seen) ->update(['present' => false, 'observed_at' => $now]); // A tombstone whose peer is gone from the hub has done its job. VpnPeer::onlyTrashed()->whereNotIn('public_key', $seen)->forceDelete(); } }