fix(proxmox): harden console sessions and firewall rollout safety

Reduce security and reliability risk across the Proxmox extension by removing password exposure from Livewire state, hardening console auth/cookie handling, and improving sanitized error paths. Improve firewall and router performance by adding safer lazy loading, bounded pagination, cross-server compatibility guards, and lower-overhead API workflows.

Made-with: Cursor
This commit is contained in:
2026-03-28 19:34:46 -04:00
parent 9f996035db
commit 68043f0416
39 changed files with 1106 additions and 5013 deletions

View File

@@ -3,6 +3,7 @@
namespace Paymenter\Extensions\Servers\Proxmox\Http\Controllers;
use Carbon\Carbon;
use Illuminate\Http\Client\RequestException;
use Illuminate\Http\JsonResponse;
use Illuminate\Routing\Controller;
use Paymenter\Extensions\Servers\Proxmox\Models\Server;
@@ -25,16 +26,17 @@ class LiveTrafficController extends Controller
);
$config = $configResponse->json()['data'] ?? [];
// Extract network interfaces from config - only show net0
// Extract network interfaces from config
$interfaces = [];
// Only include net0 interface
$key = "net0";
if (isset($config[$key])) {
// Parse the network config string
$netConfig = $config[$key];
foreach ($config as $key => $value) {
if (!preg_match('/^net\d+$/', (string) $key)) {
continue;
}
$netConfig = (string) $value;
preg_match('/bridge=([^,]+)/', $netConfig, $bridgeMatch);
preg_match('/^([^=]+)/', $netConfig, $modelMatch);
$interfaces[] = [
'id' => $key,
'name' => $key,
@@ -43,30 +45,6 @@ class LiveTrafficController extends Controller
];
}
// Try to get guest agent network data for per-interface statistics
// This provides actual per-interface data when RRD doesn't have it
$guestAgentInterfaces = [];
try {
$guestResponse = $proxmox->request(
'/nodes/' . $server->node->name . '/' . $server->node->vmType() . '/' . $server->vm_id . '/agent/network-get-interfaces'
);
$guestJson = $guestResponse->json();
$guestAgentInterfaces = $guestJson['data']['result'] ?? [];
} catch (\Throwable $e) {
// Guest agent not available - continue without it
$guestAgentInterfaces = [];
}
// Map guest agent interfaces by name for easier lookup
// Guest agent interfaces are typically named eth0, eth1, ens3, etc.
$guestInterfaceMap = [];
foreach ($guestAgentInterfaces as $guestIf) {
$name = $guestIf['name'] ?? '';
if ($name) {
$guestInterfaceMap[$name] = $guestIf;
}
}
// Get RRD data once for all interfaces (more efficient)
$rrdResponse = null;
$rrdRows = null;
@@ -90,6 +68,9 @@ class LiveTrafficController extends Controller
}
}
usort($interfaces, static fn (array $a, array $b) => strcmp($a['id'], $b['id']));
$primaryInterfaceId = $interfaces[0]['id'] ?? 'net0';
// Get RRD data for each interface
$interfaceData = [];
$totalInRate = 0;
@@ -139,7 +120,7 @@ class LiveTrafficController extends Controller
// If interface-specific data not found, use aggregate data as fallback for net0 only
// For other interfaces, use zeros to ensure each interface has unique data
if (!$currentRow) {
if ($ifNum === '0') {
if ($interface['id'] === $primaryInterfaceId) {
// For net0, use aggregate data as fallback (since aggregate typically = net0)
foreach (array_reverse($rows) as $row) {
if (isset($row['netin'], $row['netout'])) {
@@ -192,7 +173,7 @@ class LiveTrafficController extends Controller
} else {
// For net0, use aggregate data as fallback (since aggregate typically = net0)
// For other interfaces, use zeros to ensure unique graphs per interface
if ($ifNum === '0') {
if ($interface['id'] === $primaryInterfaceId) {
$history['download'][] = (float) ($sample['netin'] ?? 0);
$history['upload'][] = (float) ($sample['netout'] ?? 0);
} else {
@@ -211,7 +192,7 @@ class LiveTrafficController extends Controller
// For net0, we'll use aggregate totals (set later)
// For other interfaces, use zeros to ensure uniqueness
if ($ifNum !== '0') {
if ($interface['id'] !== $primaryInterfaceId) {
$totalIn = 0;
$totalOut = 0;
}
@@ -238,17 +219,7 @@ class LiveTrafficController extends Controller
// If no interface-specific data, fall back to general RRD data
if (empty($interfaceData)) {
$response = $proxmox->request(
'/nodes/' . $server->node->name . '/' . $server->node->vmType() . '/' . $server->vm_id . '/rrddata',
'get',
[
'timeframe' => 'hour',
'cf' => 'AVERAGE',
]
);
$json = $response->json();
$rows = $json['data'] ?? [];
$rows = is_array($rrdRows) ? $rrdRows : [];
if (is_array($rows) && count($rows) > 0) {
$currentRow = null;
@@ -324,10 +295,10 @@ class LiveTrafficController extends Controller
$totalIn = (float) ($status['netin'] ?? 0);
$totalOut = (float) ($status['netout'] ?? 0);
// Update net0 totals with aggregate data (since aggregate = net0 typically)
// Update the first configured interface totals with aggregate data
// Other interfaces keep zeros to ensure uniqueness
foreach ($interfaceData as &$interface) {
if ($interface['id'] === 'net0') {
if ($interface['id'] === $primaryInterfaceId) {
$interface['totalIn'] = $totalIn;
$interface['totalOut'] = $totalOut;
}
@@ -349,8 +320,24 @@ class LiveTrafficController extends Controller
'timestamp' => $timestamp->timestamp,
]);
} catch (RequestException $e) {
return response()->json([
'interfaces' => [],
'total' => [
'downloadRate' => 0,
'uploadRate' => 0,
'combinedRate' => 0,
'downloadTotal' => 0,
'uploadTotal' => 0,
'combinedTotal' => 0,
],
'scale' => self::RATE_SCALE,
'updatedAt' => now()->toIso8601String(),
'timestamp' => now()->timestamp,
'error' => 'Unable to load live traffic from hypervisor.',
'code' => 'proxmox_request_failed',
], 502);
} catch (\Throwable $e) {
// Fallback: return empty data
return response()->json([
'interfaces' => [],
'total' => [
@@ -365,6 +352,7 @@ class LiveTrafficController extends Controller
'updatedAt' => now()->toIso8601String(),
'timestamp' => now()->timestamp,
'error' => 'An internal error occurred.',
'code' => 'internal_error',
], 500);
}
}