diff --git a/src/nuxt.config.ts b/src/nuxt.config.ts index f1980d72..4de0ea6f 100644 --- a/src/nuxt.config.ts +++ b/src/nuxt.config.ts @@ -147,6 +147,7 @@ export default defineNuxtConfig({ }, alias: { '#db': fileURLToPath(new URL('./server/database/', import.meta.url)), + '#utils': fileURLToPath(new URL('./server/utils/', import.meta.url)), }, externals: { traceInclude: [fileURLToPath(new URL('./cli/index.ts', import.meta.url))], @@ -155,5 +156,6 @@ export default defineNuxtConfig({ alias: { // for typecheck reasons (https://github.com/nuxt/cli/issues/323) '#db': fileURLToPath(new URL('./server/database/', import.meta.url)), + '#utils': fileURLToPath(new URL('./server/utils/', import.meta.url)), }, }); diff --git a/src/server/api/admin/interface/index.post.ts b/src/server/api/admin/interface/index.post.ts index d6beedbe..ca8145f9 100644 --- a/src/server/api/admin/interface/index.post.ts +++ b/src/server/api/admin/interface/index.post.ts @@ -1,4 +1,5 @@ import { InterfaceUpdateSchema } from '#db/repositories/interface/types'; +import { firewall } from '#utils/firewall'; export default definePermissionEventHandler( 'admin', @@ -8,6 +9,24 @@ export default definePermissionEventHandler( event, validateZod(InterfaceUpdateSchema, event) ); + + // If enabling firewall, check if iptables is available + if (data.firewallEnabled) { + // Clear cache to force fresh check + firewall.clearAvailabilityCache(); + + const iptablesAvailable = await firewall.isAvailable(!WG_ENV.DISABLE_IPV6); + if (!iptablesAvailable) { + const requiredTools = WG_ENV.DISABLE_IPV6 + ? 'iptables' + : 'iptables and ip6tables'; + throw createError({ + statusCode: 400, + statusMessage: `Per-Client Firewall requires ${requiredTools} to be installed on the host system. Please install ${requiredTools} before enabling this feature.`, + }); + } + } + await Database.interfaces.update(data); await WireGuard.saveConfig(); return { success: true }; diff --git a/src/server/utils/WireGuard.ts b/src/server/utils/WireGuard.ts index 7008a3d5..eff01cab 100644 --- a/src/server/utils/WireGuard.ts +++ b/src/server/utils/WireGuard.ts @@ -266,6 +266,22 @@ class WireGuard { await this.#syncWireguardConfig(wgInterface); WG_DEBUG(`Wireguard Interface ${wgInterface.name} started successfully.`); + // Check if firewall was enabled but iptables isn't available + if (wgInterface.firewallEnabled) { + const enableIpv6 = !WG_ENV.DISABLE_IPV6; + const iptablesAvailable = await firewall.isAvailable(enableIpv6); + if (!iptablesAvailable) { + const requiredTools = enableIpv6 + ? 'iptables/ip6tables' + : 'iptables'; + console.warn( + `WARNING: Per-Client Firewall is enabled but ${requiredTools} is not available. Disabling firewall feature. Please install ${requiredTools} to use this feature.` + ); + await Database.interfaces.update({ firewallEnabled: false }); + wgInterface.firewallEnabled = false; // Update local copy + } + } + WG_DEBUG('Applying firewall rules...'); await this.#applyFirewallRules(wgInterface); WG_DEBUG('Firewall rules applied successfully.'); diff --git a/src/server/utils/firewall.ts b/src/server/utils/firewall.ts index 707260dd..3afb93f8 100644 --- a/src/server/utils/firewall.ts +++ b/src/server/utils/firewall.ts @@ -13,6 +13,9 @@ const CHAIN_NAME = 'WG_CLIENTS'; let rebuildInProgress = false; let rebuildQueued = false; +// Cache iptables availability check result +let iptablesAvailable: boolean | null = null; + type ParsedEntry = { ip: string; port?: number; @@ -249,4 +252,43 @@ export const firewall = { await exec(`iptables -X ${CHAIN_NAME} 2>/dev/null || true`); await exec(`ip6tables -X ${CHAIN_NAME} 2>/dev/null || true`); }, + + /** + * Check if iptables (and optionally ip6tables) are available on the system. + * @param enableIpv6 - If true, also check for ip6tables. Defaults to true. + */ + async isAvailable(enableIpv6: boolean = true): Promise { + // Return cached result if we've already checked + if (iptablesAvailable !== null) { + return iptablesAvailable; + } + + try { + // Check for iptables (always required) + await exec('iptables --version'); + FW_DEBUG('iptables is available'); + + // Check for ip6tables (only if IPv6 is enabled) + if (enableIpv6) { + await exec('ip6tables --version'); + FW_DEBUG('ip6tables is available'); + } else { + FW_DEBUG('IPv6 disabled, skipping ip6tables check'); + } + + iptablesAvailable = true; + return true; + } catch (error) { + iptablesAvailable = false; + FW_DEBUG('iptables/ip6tables is not available:', error); + return false; + } + }, + + /** + * Clear the availability cache to force a re-check + */ + clearAvailabilityCache(): void { + iptablesAvailable = null; + }, };