diff --git a/README.md b/README.md index a892180..5103112 100644 --- a/README.md +++ b/README.md @@ -288,10 +288,11 @@ Four power control buttons: - **Force Off** - Immediate power cut (use with caution) ### Firewall Management -- View firewall status (enabled/disabled) -- Enable or disable the server firewall -- Apply/synchronize firewall rules -- For advanced rule management, use the VirtFusion control panel +- View firewall status (enabled/disabled) with status badge +- Enable or disable the server firewall on primary/secondary interfaces +- Apply firewall rulesets by ID (rulesets are predefined in VirtFusion admin) +- Re-apply/synchronize currently assigned rulesets +- **Note**: VirtFusion uses a ruleset-based firewall system. Individual rules cannot be created or deleted via the API. Create rulesets in the VirtFusion admin panel, then apply them to servers through this module or the control panel ### Network Management - View all IPv4 addresses and IPv6 subnets assigned to the server @@ -404,10 +405,12 @@ WHMCS automatically loads theme-specific templates when they exist. Copy the ori | Method | Endpoint | Purpose | |---|---|---| -| `GET` | `/servers/{id}/firewall` | Firewall status | -| `POST` | `/servers/{id}/firewall/enable` | Enable firewall | -| `POST` | `/servers/{id}/firewall/disable` | Disable firewall | -| `POST` | `/servers/{id}/firewall/rules/apply` | Apply firewall rules | +| `GET` | `/servers/{id}/firewall/{interface}` | Firewall status and rules | +| `POST` | `/servers/{id}/firewall/{interface}/enable` | Enable firewall | +| `POST` | `/servers/{id}/firewall/{interface}/disable` | Disable firewall | +| `POST` | `/servers/{id}/firewall/{interface}/rules` | Apply rulesets (body: `{"rulesets": [1,2]}`) | + +`{interface}` is `primary` or `secondary`. Individual firewall rules cannot be managed via the API - use rulesets created in the VirtFusion admin panel. ### Network diff --git a/modules/servers/VirtFusionDirect/client.php b/modules/servers/VirtFusionDirect/client.php index ae6e665..ccd0018 100644 --- a/modules/servers/VirtFusionDirect/client.php +++ b/modules/servers/VirtFusionDirect/client.php @@ -178,10 +178,14 @@ switch ($action) { // ================================================================= // Firewall Management + // + // VirtFusion uses a ruleset-based system. Individual rules cannot + // be added/deleted via the API. Rulesets are created in admin panel + // and applied to servers by ID. // ================================================================= /** - * Get firewall status and rules. + * Get firewall status, rules, and assigned rulesets. */ case 'firewallStatus': @@ -191,7 +195,8 @@ switch ($action) { $vf->output(['success' => false, 'errors' => 'service <> owner mismatch'], true, true, 403); } - $result = $vf->getFirewallStatus($serviceID); + $interface = isset($_GET['interface']) ? preg_replace('/[^a-z]/', '', $_GET['interface']) : 'primary'; + $result = $vf->getFirewallStatus($serviceID, $interface); if ($result !== false) { $vf->output(['success' => true, 'data' => $result], true, true, 200); @@ -211,7 +216,8 @@ switch ($action) { $vf->output(['success' => false, 'errors' => 'service <> owner mismatch'], true, true, 403); } - $result = $vf->enableFirewall($serviceID); + $interface = isset($_GET['interface']) ? preg_replace('/[^a-z]/', '', $_GET['interface']) : 'primary'; + $result = $vf->enableFirewall($serviceID, $interface); if ($result) { $vf->output(['success' => true, 'data' => ['message' => 'Firewall enabled successfully']], true, true, 200); @@ -231,7 +237,8 @@ switch ($action) { $vf->output(['success' => false, 'errors' => 'service <> owner mismatch'], true, true, 403); } - $result = $vf->disableFirewall($serviceID); + $interface = isset($_GET['interface']) ? preg_replace('/[^a-z]/', '', $_GET['interface']) : 'primary'; + $result = $vf->disableFirewall($serviceID, $interface); if ($result) { $vf->output(['success' => true, 'data' => ['message' => 'Firewall disabled successfully']], true, true, 200); @@ -241,7 +248,7 @@ switch ($action) { break; /** - * Apply/sync firewall rules. + * Apply/sync firewall rules (re-applies currently assigned rulesets). */ case 'firewallApplyRules': @@ -251,7 +258,8 @@ switch ($action) { $vf->output(['success' => false, 'errors' => 'service <> owner mismatch'], true, true, 403); } - $result = $vf->applyFirewallRules($serviceID); + $interface = isset($_GET['interface']) ? preg_replace('/[^a-z]/', '', $_GET['interface']) : 'primary'; + $result = $vf->applyFirewallRules($serviceID, $interface); if ($result) { $vf->output(['success' => true, 'data' => ['message' => 'Firewall rules applied successfully']], true, true, 200); @@ -260,6 +268,41 @@ switch ($action) { $vf->output(['success' => false, 'errors' => 'Failed to apply firewall rules'], true, true, 500); break; + /** + * Apply specific firewall rulesets by ID. + * Expects comma-separated ruleset IDs in the 'rulesets' parameter. + */ + case 'firewallApplyRulesets': + + $serviceID = $vf->validateServiceID(true); + + if (!$vf->validateUserOwnsService($serviceID)) { + $vf->output(['success' => false, 'errors' => 'service <> owner mismatch'], true, true, 403); + } + + $rulesetsParam = isset($_GET['rulesets']) ? trim($_GET['rulesets']) : ''; + if (empty($rulesetsParam)) { + $vf->output(['success' => false, 'errors' => 'No ruleset IDs provided'], true, true, 400); + } + + $rulesetIds = array_values(array_filter(array_map('intval', explode(',', $rulesetsParam)), function ($id) { + return $id > 0; + })); + + if (empty($rulesetIds)) { + $vf->output(['success' => false, 'errors' => 'Invalid ruleset IDs'], true, true, 400); + } + + $interface = isset($_GET['interface']) ? preg_replace('/[^a-z]/', '', $_GET['interface']) : 'primary'; + $result = $vf->applyFirewallRulesets($serviceID, $rulesetIds, $interface); + + if ($result) { + $vf->output(['success' => true, 'data' => ['message' => 'Firewall rulesets applied successfully']], true, true, 200); + } + + $vf->output(['success' => false, 'errors' => 'Failed to apply firewall rulesets'], true, true, 500); + break; + // ================================================================= // IP Address Management // ================================================================= diff --git a/modules/servers/VirtFusionDirect/lib/Module.php b/modules/servers/VirtFusionDirect/lib/Module.php index 361db77..66f5e21 100644 --- a/modules/servers/VirtFusionDirect/lib/Module.php +++ b/modules/servers/VirtFusionDirect/lib/Module.php @@ -299,24 +299,32 @@ class Module // ========================================================================= // Firewall Management + // + // VirtFusion uses a ruleset-based firewall system. Individual rules cannot + // be created or deleted via the API. Instead, predefined rulesets (created + // in the VirtFusion admin panel) are applied to servers by ID. + // + // The {interface} parameter is "primary" or "secondary". // ========================================================================= /** * Get firewall status and rules for a server. * * @param int $serviceID + * @param string $interface Network interface: "primary" or "secondary" * @return array|false */ - public function getFirewallStatus($serviceID) + public function getFirewallStatus($serviceID, $interface = 'primary') { $serviceID = (int) $serviceID; + $interface = $this->sanitizeFirewallInterface($interface); $service = Database::getSystemService($serviceID); if ($service) { $whmcsService = Database::getWhmcsService($serviceID); $cp = $this->getCP($whmcsService->server); $request = $this->initCurl($cp['token']); - $data = $request->get($cp['url'] . '/servers/' . (int) $service->server_id . '/firewall'); + $data = $request->get($cp['url'] . '/servers/' . (int) $service->server_id . '/firewall/' . $interface); Log::insert(__FUNCTION__, $request->getRequestInfo(), $data); @@ -331,18 +339,20 @@ class Module * Enable firewall on a server. * * @param int $serviceID + * @param string $interface Network interface: "primary" or "secondary" * @return object|false */ - public function enableFirewall($serviceID) + public function enableFirewall($serviceID, $interface = 'primary') { $serviceID = (int) $serviceID; + $interface = $this->sanitizeFirewallInterface($interface); $service = Database::getSystemService($serviceID); if ($service) { $whmcsService = Database::getWhmcsService($serviceID); $cp = $this->getCP($whmcsService->server); $request = $this->initCurl($cp['token']); - $data = $request->post($cp['url'] . '/servers/' . (int) $service->server_id . '/firewall/enable'); + $data = $request->post($cp['url'] . '/servers/' . (int) $service->server_id . '/firewall/' . $interface . '/enable'); Log::insert(__FUNCTION__, $request->getRequestInfo(), $data); @@ -358,18 +368,20 @@ class Module * Disable firewall on a server. * * @param int $serviceID + * @param string $interface Network interface: "primary" or "secondary" * @return object|false */ - public function disableFirewall($serviceID) + public function disableFirewall($serviceID, $interface = 'primary') { $serviceID = (int) $serviceID; + $interface = $this->sanitizeFirewallInterface($interface); $service = Database::getSystemService($serviceID); if ($service) { $whmcsService = Database::getWhmcsService($serviceID); $cp = $this->getCP($whmcsService->server); $request = $this->initCurl($cp['token']); - $data = $request->post($cp['url'] . '/servers/' . (int) $service->server_id . '/firewall/disable'); + $data = $request->post($cp['url'] . '/servers/' . (int) $service->server_id . '/firewall/' . $interface . '/disable'); Log::insert(__FUNCTION__, $request->getRequestInfo(), $data); @@ -382,32 +394,101 @@ class Module } /** - * Apply/synchronize firewall rules on a server. + * Apply firewall rulesets to a server. + * + * VirtFusion uses predefined rulesets (created in admin panel). + * Individual rules cannot be added/deleted via the API. * * @param int $serviceID + * @param array $rulesetIds Array of ruleset IDs to apply + * @param string $interface Network interface: "primary" or "secondary" * @return object|false */ - public function applyFirewallRules($serviceID) + public function applyFirewallRulesets($serviceID, array $rulesetIds, $interface = 'primary') { $serviceID = (int) $serviceID; + $interface = $this->sanitizeFirewallInterface($interface); + + // Validate and sanitize ruleset IDs + $rulesetIds = array_values(array_filter(array_map('intval', $rulesetIds), function ($id) { + return $id > 0; + })); + + if (empty($rulesetIds)) { + return false; + } + $service = Database::getSystemService($serviceID); if ($service) { $whmcsService = Database::getWhmcsService($serviceID); $cp = $this->getCP($whmcsService->server); $request = $this->initCurl($cp['token']); - $data = $request->post($cp['url'] . '/servers/' . (int) $service->server_id . '/firewall/rules/apply'); + $request->addOption(CURLOPT_POSTFIELDS, json_encode(['rulesets' => $rulesetIds])); + $data = $request->post($cp['url'] . '/servers/' . (int) $service->server_id . '/firewall/' . $interface . '/rules'); Log::insert(__FUNCTION__, $request->getRequestInfo(), $data); $httpCode = $request->getRequestInfo('http_code'); - if ($httpCode == 200 || $httpCode == 204) { + if ($httpCode == 200 || $httpCode == 201 || $httpCode == 204) { return json_decode($data) ?: (object) ['success' => true]; } } return false; } + /** + * Backward-compatible wrapper for applying firewall rules. + * Syncs/applies existing ruleset assignments on the server. + * + * @param int $serviceID + * @param string $interface Network interface: "primary" or "secondary" + * @return object|false + */ + public function applyFirewallRules($serviceID, $interface = 'primary') + { + // Fetch current firewall status to get assigned rulesets + $status = $this->getFirewallStatus($serviceID, $interface); + if ($status && isset($status['data']['rulesets'])) { + $rulesetIds = array_column($status['data']['rulesets'], 'id'); + if (!empty($rulesetIds)) { + return $this->applyFirewallRulesets($serviceID, $rulesetIds, $interface); + } + } + + // If no rulesets found, try a direct re-apply via the enable cycle + $serviceID = (int) $serviceID; + $interface = $this->sanitizeFirewallInterface($interface); + $service = Database::getSystemService($serviceID); + + if ($service) { + $whmcsService = Database::getWhmcsService($serviceID); + $cp = $this->getCP($whmcsService->server); + $request = $this->initCurl($cp['token']); + $request->addOption(CURLOPT_POSTFIELDS, json_encode(['rulesets' => []])); + $data = $request->post($cp['url'] . '/servers/' . (int) $service->server_id . '/firewall/' . $interface . '/rules'); + + Log::insert(__FUNCTION__, $request->getRequestInfo(), $data); + + $httpCode = $request->getRequestInfo('http_code'); + if ($httpCode == 200 || $httpCode == 201 || $httpCode == 204) { + return json_decode($data) ?: (object) ['success' => true]; + } + } + return false; + } + + /** + * Sanitize firewall interface parameter. + * + * @param string $interface + * @return string "primary" or "secondary" + */ + private function sanitizeFirewallInterface($interface) + { + return in_array($interface, ['primary', 'secondary'], true) ? $interface : 'primary'; + } + // ========================================================================= // IP Address Management // =========================================================================