mirror of https://github.com/wg-easy/wg-easy
2 changed files with 192 additions and 211 deletions
@ -1,209 +0,0 @@ |
|||
import { exec } from '../utils/cmd'; |
|||
import { WG_ENV } from '../utils/config'; |
|||
import { parseIpAndPort } from '../utils/ip'; |
|||
import type { ClientType } from '#db/repositories/client/types'; |
|||
import { isIPv6 } from 'is-ip'; |
|||
|
|||
declare module 'nitropack/types' { |
|||
interface NitroRuntimeHooks { |
|||
'wireguard:config': () => void; |
|||
'wireguard:start': () => void; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Client Traffic Restriction Plugin |
|||
* |
|||
* This plugin manages iptables/ip6tables rules to restrict client traffic |
|||
* based on configured allowed IPs. It integrates with wireguard lifecycle hooks. |
|||
*/ |
|||
export default defineNitroPlugin(async (nitroApp) => { |
|||
console.log('Client Traffic Restriction Plugin loaded'); |
|||
|
|||
/** |
|||
* Generates iptables rules for a specific client based on their allowed IPs. |
|||
* |
|||
* @param client - The client configuration |
|||
* @returns Array of iptables command strings |
|||
*/ |
|||
const generateClientIptablesRules = ( |
|||
client: Omit<ClientType, 'createdAt' | 'updatedAt'> |
|||
): string[] => { |
|||
const rules: string[] = []; |
|||
|
|||
// Extract IP addresses from client configuration (remove CIDR prefix)
|
|||
const clientIp = client.ipv4Address.split('/')[0]; |
|||
const clientIpv6 = client.ipv6Address?.split('/')[0]; |
|||
|
|||
// Generate rules for each allowed IP if configured
|
|||
if (client.allowedIps && client.allowedIps.length > 0) { |
|||
client.allowedIps.forEach((allowedIpWithPort) => { |
|||
const { ip: allowedIp, port } = parseIpAndPort(allowedIpWithPort); |
|||
|
|||
if (isIPv6(allowedIp)) { |
|||
// IPv6: generate ip6tables rules
|
|||
if (!WG_ENV.DISABLE_IPV6 && clientIpv6) { |
|||
if (port) { |
|||
// TCP port rules
|
|||
rules.push( |
|||
`ip6tables -A FORWARD -s ${clientIpv6} -d ${allowedIp} -p tcp --dport ${port} -j ACCEPT` |
|||
); |
|||
rules.push( |
|||
`ip6tables -A FORWARD -d ${clientIpv6} -s ${allowedIp} -p tcp --sport ${port} -j ACCEPT` |
|||
); |
|||
// UDP port rules
|
|||
rules.push( |
|||
`ip6tables -A FORWARD -s ${clientIpv6} -d ${allowedIp} -p udp --dport ${port} -j ACCEPT` |
|||
); |
|||
rules.push( |
|||
`ip6tables -A FORWARD -d ${clientIpv6} -s ${allowedIp} -p udp --sport ${port} -j ACCEPT` |
|||
); |
|||
} else { |
|||
// No port restriction - allow all traffic
|
|||
rules.push( |
|||
`ip6tables -A FORWARD -s ${clientIpv6} -d ${allowedIp} -j ACCEPT` |
|||
); |
|||
rules.push( |
|||
`ip6tables -A FORWARD -d ${clientIpv6} -s ${allowedIp} -j ACCEPT` |
|||
); |
|||
} |
|||
} |
|||
} else { |
|||
// IPv4: generate iptables rules
|
|||
if (port) { |
|||
// TCP port rules
|
|||
rules.push( |
|||
`iptables -A FORWARD -s ${clientIp} -d ${allowedIp} -p tcp --dport ${port} -j ACCEPT` |
|||
); |
|||
rules.push( |
|||
`iptables -A FORWARD -d ${clientIp} -s ${allowedIp} -p tcp --sport ${port} -j ACCEPT` |
|||
); |
|||
// UDP port rules
|
|||
rules.push( |
|||
`iptables -A FORWARD -s ${clientIp} -d ${allowedIp} -p udp --dport ${port} -j ACCEPT` |
|||
); |
|||
rules.push( |
|||
`iptables -A FORWARD -d ${clientIp} -s ${allowedIp} -p udp --sport ${port} -j ACCEPT` |
|||
); |
|||
} else { |
|||
// No port restriction - allow all traffic
|
|||
rules.push( |
|||
`iptables -A FORWARD -s ${clientIp} -d ${allowedIp} -j ACCEPT` |
|||
); |
|||
rules.push( |
|||
`iptables -A FORWARD -d ${clientIp} -s ${allowedIp} -j ACCEPT` |
|||
); |
|||
} |
|||
} |
|||
}); |
|||
} else { |
|||
// Default: allow access to server only when no allowed IPs configured
|
|||
const serverIps = ['10.8.0.1/32']; |
|||
serverIps.forEach((serverIp) => { |
|||
rules.push( |
|||
`iptables -A FORWARD -s ${clientIp} -d ${serverIp} -j ACCEPT` |
|||
); |
|||
rules.push( |
|||
`iptables -A FORWARD -d ${clientIp} -s ${serverIp} -j ACCEPT` |
|||
); |
|||
}); |
|||
} |
|||
|
|||
return rules; |
|||
}; |
|||
|
|||
/** |
|||
* Applies all client-specific iptables rules. |
|||
* Clears existing rules and sets up new ones based on current client configuration. |
|||
*/ |
|||
const applyClientIptablesRules = async () => { |
|||
try { |
|||
console.log('Applying client-specific iptables rules...'); |
|||
|
|||
// Fetch all clients from database
|
|||
const clients = await Database.clients.getAll(); |
|||
|
|||
// Flush existing FORWARD chain rules
|
|||
await exec('iptables -F FORWARD'); |
|||
if (!WG_ENV.DISABLE_IPV6) { |
|||
await exec('ip6tables -F FORWARD'); |
|||
} |
|||
|
|||
// Set default FORWARD policy to DROP (block all traffic by default)
|
|||
await exec('iptables -P FORWARD DROP'); |
|||
if (!WG_ENV.DISABLE_IPV6) { |
|||
await exec('ip6tables -P FORWARD DROP'); |
|||
} |
|||
|
|||
// Generate and apply rules for each enabled client
|
|||
const includedClients: string[] = []; |
|||
for (const client of clients) { |
|||
if (!client.enabled) { |
|||
console.log(`Skipping disabled client: ${client.name}`); |
|||
continue; |
|||
} |
|||
const rules = generateClientIptablesRules(client); |
|||
for (const rule of rules) { |
|||
await exec(rule); |
|||
} |
|||
|
|||
includedClients.push(client.name); |
|||
console.log(`Applied rules for client: ${client.name}`); |
|||
} |
|||
|
|||
console.log( |
|||
`Applied rules for ${includedClients.length} enabled clients: ${includedClients.join(', ')}` |
|||
); |
|||
} catch (error) { |
|||
console.error('Error applying client iptables rules:', error); |
|||
} |
|||
}; |
|||
|
|||
/** |
|||
* Removes FORWARD chain rules from hook commands. |
|||
* |
|||
* @param rule - The hook rule string |
|||
* @returns Filtered rule string without FORWARD rules |
|||
*/ |
|||
const remove_forward_rule = async (rule: string) => { |
|||
try { |
|||
const filteredRules = rule.split(';').filter((line) => { |
|||
return !/FORWARD/i.test(line); |
|||
}); |
|||
return filteredRules.join(';'); |
|||
} catch (error) { |
|||
console.error('Error removing default FORWARD rule:', error); |
|||
} |
|||
return rule; |
|||
}; |
|||
|
|||
/** |
|||
* Initializes traffic restriction by removing default FORWARD allow rules |
|||
* from hook commands. |
|||
*/ |
|||
const initializeTrafficRestriction = async () => { |
|||
try { |
|||
const hooks = await Database.hooks.get(); |
|||
|
|||
// Remove default FORWARD allow rules from hooks
|
|||
const updatedHooks = { |
|||
...hooks, |
|||
postUp: await remove_forward_rule(hooks.postUp), |
|||
postDown: await remove_forward_rule(hooks.postDown), |
|||
}; |
|||
|
|||
// Update hooks in database
|
|||
await Database.hooks.update(updatedHooks); |
|||
console.log('Removed default FORWARD rules from hooks'); |
|||
} catch (error) { |
|||
console.error('Error initializing traffic restriction:', error); |
|||
} |
|||
}; |
|||
|
|||
// Register event listeners for wireguard lifecycle hooks
|
|||
nitroApp.hooks.hook('wireguard:config', applyClientIptablesRules); |
|||
nitroApp.hooks.hook('wireguard:start', async () => { |
|||
await initializeTrafficRestriction(); |
|||
await applyClientIptablesRules(); |
|||
}); |
|||
}); |
|||
Loading…
Reference in new issue