diff --git a/README.md b/README.md index 47a7b3d6..6cf98ce1 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ You have found the easiest way to install & manage WireGuard on any Linux host!

## Features + * All-in-one: WireGuard + Web UI. * Easy installation, simple to use. * List, create, edit, delete, enable & disable clients. @@ -110,7 +111,8 @@ These options can be configured by setting environment variables using `-e KEY=" | `WG_CONFIG_PORT`| `51820` | `12345` | The UDP port used on [Home Assistent Plugin](https://github.com/adriy-be/homeassistant-addons-jdeath/tree/main/wgeasy) | `WG_MTU` | `null` | `1420` | The MTU the clients will use. Server uses default WG MTU. | | `WG_PERSISTENT_KEEPALIVE` | `0` | `25` | Value in seconds to keep the "connection" open. If this value is 0, then connections won't be kept alive. | -| `WG_DEFAULT_ADDRESS` | `10.8.0.x` | `10.6.0.x` | Clients IP address range. | +| `WG_DEFAULT_ADDRESS` | `10.8.0.0` | `10.6.0.0` | Clients IP address range. | +| `WG_DEFAULT_ADDRESS_RANGE` | `24` | `32` | Value to define CIDR Range. If not defined fallback to `24` | `WG_DEFAULT_DNS` | `1.1.1.1` | `8.8.8.8, 8.8.4.4` | DNS server clients will use. If set to blank value, clients will not use any DNS. | | `WG_ALLOWED_IPS` | `0.0.0.0/0, ::/0` | `192.168.15.0/24, 10.0.1.0/24` | Allowed IPs clients will use. | | `WG_PRE_UP` | `...` | - | See [config.js](https://github.com/wg-easy/wg-easy/blob/master/src/config.js#L19) for the default value. | diff --git a/docker-compose.yml b/docker-compose.yml index a9e8d0bf..444f4c33 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -17,7 +17,8 @@ services: # - PORT=51821 # - WG_PORT=51820 # - WG_CONFIG_PORT=92820 - # - WG_DEFAULT_ADDRESS=10.8.0.x + # - WG_DEFAULT_ADDRESS=10.8.0.0 + # - WG_DEFAULT_ADDRESS_RANGE=24 # - WG_DEFAULT_DNS=1.1.1.1 # - WG_MTU=1420 # - WG_ALLOWED_IPS=192.168.15.0/24, 10.0.1.0/24 @@ -40,7 +41,7 @@ services: cap_add: - NET_ADMIN - SYS_MODULE - # - NET_RAW # ⚠️ Uncomment if using Podman + # - NET_RAW # ⚠️ Uncomment if using Podman sysctls: - net.ipv4.ip_forward=1 - net.ipv4.conf.all.src_valid_mark=1 diff --git a/src/config.js b/src/config.js index 40b70ddf..30ba5c4d 100644 --- a/src/config.js +++ b/src/config.js @@ -1,5 +1,7 @@ 'use strict'; +const ip = require('ip'); + const { release } = require('./package.json'); module.exports.RELEASE = release; @@ -14,24 +16,30 @@ module.exports.WG_PORT = process.env.WG_PORT || '51820'; module.exports.WG_CONFIG_PORT = process.env.WG_CONFIG_PORT || process.env.WG_PORT || '51820'; module.exports.WG_MTU = process.env.WG_MTU || null; module.exports.WG_PERSISTENT_KEEPALIVE = process.env.WG_PERSISTENT_KEEPALIVE || '0'; -module.exports.WG_DEFAULT_ADDRESS = process.env.WG_DEFAULT_ADDRESS || '10.8.0.x'; +module.exports.WG_DEFAULT_ADDRESS = (process.env.WG_DEFAULT_ADDRESS && process.env.WG_DEFAULT_ADDRESS.replace('x', '0')) || '10.8.0.0'; +module.exports.WG_DEFAULT_ADDRESS_RANGE = process.env.WG_DEFAULT_ADDRESS_RANGE || '24'; module.exports.WG_DEFAULT_DNS = typeof process.env.WG_DEFAULT_DNS === 'string' ? process.env.WG_DEFAULT_DNS : '1.1.1.1'; module.exports.WG_ALLOWED_IPS = process.env.WG_ALLOWED_IPS || '0.0.0.0/0, ::/0'; +module.exports.WG_SUBNET = ip.cidrSubnet(`${module.exports.WG_DEFAULT_ADDRESS}/${module.exports.WG_DEFAULT_ADDRESS_RANGE}`); +module.exports.WG_SERVER_ADDRESS = module.exports.WG_SUBNET.firstAddress; +module.exports.WG_CLIENT_FIRST_ADDRESS = ip.toLong(module.exports.WG_SERVER_ADDRESS) + 1; +module.exports.WG_CLIENT_LAST_ADDRESS = ip.toLong(module.exports.WG_SUBNET.lastAddress) - 1; // Exclude the broadcast address + module.exports.WG_PRE_UP = process.env.WG_PRE_UP || ''; module.exports.WG_POST_UP = process.env.WG_POST_UP || ` -iptables -t nat -A POSTROUTING -s ${module.exports.WG_DEFAULT_ADDRESS.replace('x', '0')}/24 -o ${module.exports.WG_DEVICE} -j MASQUERADE; -iptables -A INPUT -p udp -m udp --dport ${module.exports.WG_PORT} -j ACCEPT; +iptables -t nat -A POSTROUTING -s ${module.exports.WG_SERVER_ADDRESS}/${module.exports.WG_DEFAULT_ADDRESS_RANGE} -o ${module.exports.WG_DEVICE} -j MASQUERADE; +iptables -A INPUT -p udp -m udp --dport 51820 -j ACCEPT; iptables -A FORWARD -i wg0 -j ACCEPT; iptables -A FORWARD -o wg0 -j ACCEPT; `.split('\n').join(' '); module.exports.WG_PRE_DOWN = process.env.WG_PRE_DOWN || ''; module.exports.WG_POST_DOWN = process.env.WG_POST_DOWN || ` -iptables -t nat -D POSTROUTING -s ${module.exports.WG_DEFAULT_ADDRESS.replace('x', '0')}/24 -o ${module.exports.WG_DEVICE} -j MASQUERADE; -iptables -D INPUT -p udp -m udp --dport ${module.exports.WG_PORT} -j ACCEPT; +iptables -t nat -D POSTROUTING -s ${module.exports.WG_SERVER_ADDRESS}/${module.exports.WG_DEFAULT_ADDRESS_RANGE} -o ${module.exports.WG_DEVICE} -j MASQUERADE; +iptables -D INPUT -p udp -m udp --dport 51820 -j ACCEPT; iptables -D FORWARD -i wg0 -j ACCEPT; iptables -D FORWARD -o wg0 -j ACCEPT; `.split('\n').join(' '); diff --git a/src/lib/Util.js b/src/lib/Util.js index cc6e89c2..82942599 100644 --- a/src/lib/Util.js +++ b/src/lib/Util.js @@ -4,19 +4,6 @@ const childProcess = require('child_process'); module.exports = class Util { - static isValidIPv4(str) { - const blocks = str.split('.'); - if (blocks.length !== 4) return false; - - for (let value of blocks) { - value = parseInt(value, 10); - if (Number.isNaN(value)) return false; - if (value < 0 || value > 255) return false; - } - - return true; - } - static promisify(fn) { // eslint-disable-next-line func-names return function(req, res) { diff --git a/src/lib/WireGuard.js b/src/lib/WireGuard.js index d4f32c5f..c99499ce 100644 --- a/src/lib/WireGuard.js +++ b/src/lib/WireGuard.js @@ -4,6 +4,7 @@ const fs = require('node:fs/promises'); const path = require('path'); const debug = require('debug')('WireGuard'); const crypto = require('node:crypto'); +const ip = require('ip'); const QRCode = require('qrcode'); const Util = require('./Util'); @@ -16,9 +17,12 @@ const { WG_CONFIG_PORT, WG_MTU, WG_DEFAULT_DNS, - WG_DEFAULT_ADDRESS, + WG_DEFAULT_ADDRESS_RANGE, WG_PERSISTENT_KEEPALIVE, WG_ALLOWED_IPS, + WG_SERVER_ADDRESS, + WG_CLIENT_FIRST_ADDRESS, + WG_CLIENT_LAST_ADDRESS, WG_PRE_UP, WG_POST_UP, WG_PRE_DOWN, @@ -36,22 +40,32 @@ module.exports = class WireGuard { debug('Loading configuration...'); let config; + const address = WG_SERVER_ADDRESS; + const cidrBlock = WG_DEFAULT_ADDRESS_RANGE; try { config = await fs.readFile(path.join(WG_PATH, 'wg0.json'), 'utf8'); config = JSON.parse(config); + config = { + ...config, + server: { + ...config.server, + address, + cidrBlock, + }, + }; debug('Configuration loaded.'); } catch (err) { const privateKey = await Util.exec('wg genkey'); const publicKey = await Util.exec(`echo ${privateKey} | wg pubkey`, { log: 'echo ***hidden*** | wg pubkey', }); - const address = WG_DEFAULT_ADDRESS.replace('x', '1'); config = { server: { privateKey, publicKey, address, + cidrBlock, }, clients: {}, }; @@ -67,7 +81,7 @@ module.exports = class WireGuard { throw err; }); - // await Util.exec(`iptables -t nat -A POSTROUTING -s ${WG_DEFAULT_ADDRESS.replace('x', '0')}/24 -o ' + WG_DEVICE + ' -j MASQUERADE`); + // await Util.exec(`iptables -t nat -A POSTROUTING -s ${WG_SERVER_ADDRESS}/${WG_DEFAULT_ADDRESS_RANGE} -o ' + WG_DEVICE + ' -j MASQUERADE`); // await Util.exec('iptables -A INPUT -p udp -m udp --dport 51820 -j ACCEPT'); // await Util.exec('iptables -A FORWARD -i wg0 -j ACCEPT'); // await Util.exec('iptables -A FORWARD -o wg0 -j ACCEPT'); @@ -94,7 +108,7 @@ module.exports = class WireGuard { # Server [Interface] PrivateKey = ${config.server.privateKey} -Address = ${config.server.address}/24 +Address = ${config.server.address}/${config.server.cidrBlock} ListenPort = ${WG_PORT} PreUp = ${WG_PRE_UP} PostUp = ${WG_POST_UP} @@ -106,12 +120,12 @@ PostDown = ${WG_POST_DOWN} if (!client.enabled) continue; result += ` - # Client: ${client.name} (${clientId}) [Peer] PublicKey = ${client.publicKey} -${client.preSharedKey ? `PresharedKey = ${client.preSharedKey}\n` : '' -}AllowedIPs = ${client.address}/32`; +${client.preSharedKey ? `PresharedKey = ${client.preSharedKey}` : ''} +AllowedIPs = ${client.address}/32 +`; } debug('Config saving...'); @@ -137,6 +151,7 @@ ${client.preSharedKey ? `PresharedKey = ${client.preSharedKey}\n` : '' name: client.name, enabled: client.enabled, address: client.address, + cidrBlock: client.cidrBlock, publicKey: client.publicKey, createdAt: new Date(client.createdAt), updatedAt: new Date(client.updatedAt), @@ -199,14 +214,14 @@ ${client.preSharedKey ? `PresharedKey = ${client.preSharedKey}\n` : '' return ` [Interface] PrivateKey = ${client.privateKey ? `${client.privateKey}` : 'REPLACE_ME'} -Address = ${client.address}/24 +Address = ${client.address}/${client.cidrBlock} ${WG_DEFAULT_DNS ? `DNS = ${WG_DEFAULT_DNS}\n` : ''}\ ${WG_MTU ? `MTU = ${WG_MTU}\n` : ''}\ [Peer] PublicKey = ${config.server.publicKey} -${client.preSharedKey ? `PresharedKey = ${client.preSharedKey}\n` : '' -}AllowedIPs = ${WG_ALLOWED_IPS} +${client.preSharedKey ? `PresharedKey = ${client.preSharedKey}` : ''} +AllowedIPs = ${WG_ALLOWED_IPS} PersistentKeepalive = ${WG_PERSISTENT_KEEPALIVE} Endpoint = ${WG_HOST}:${WG_CONFIG_PORT}`; } @@ -232,13 +247,14 @@ Endpoint = ${WG_HOST}:${WG_CONFIG_PORT}`; // Calculate next IP let address; - for (let i = 2; i < 255; i++) { + for (let i = WG_CLIENT_FIRST_ADDRESS; i <= WG_CLIENT_LAST_ADDRESS; i++) { + const currentIp = ip.fromLong(i); const client = Object.values(config.clients).find((client) => { - return client.address === WG_DEFAULT_ADDRESS.replace('x', i); + return client.address === currentIp; }); if (!client) { - address = WG_DEFAULT_ADDRESS.replace('x', i); + address = currentIp; break; } } @@ -249,10 +265,12 @@ Endpoint = ${WG_HOST}:${WG_CONFIG_PORT}`; // Create Client const id = crypto.randomUUID(); + const cidrBlock = WG_DEFAULT_ADDRESS_RANGE; const client = { id, name, address, + cidrBlock, privateKey, publicKey, preSharedKey, @@ -309,7 +327,7 @@ Endpoint = ${WG_HOST}:${WG_CONFIG_PORT}`; async updateClientAddress({ clientId, address }) { const client = await this.getClient({ clientId }); - if (!Util.isValidIPv4(address)) { + if (!ip.isV4Format(address)) { throw new ServerError(`Invalid Address: ${address}`, 400); } diff --git a/src/package-lock.json b/src/package-lock.json index bd59b290..76ad1517 100644 --- a/src/package-lock.json +++ b/src/package-lock.json @@ -13,7 +13,9 @@ "debug": "^4.3.5", "express-session": "^1.18.0", "h3": "^1.12.0", - "qrcode": "^1.5.3" + "ip": "^2.0.1", + "qrcode": "^1.5.3", + "uuid": "^9.0.1" }, "devDependencies": { "eslint-config-athom": "^3.1.3", @@ -2540,6 +2542,11 @@ "node": ">= 0.4" } }, + "node_modules/ip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.1.tgz", + "integrity": "sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ==" + }, "node_modules/iron-webcrypto": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/iron-webcrypto/-/iron-webcrypto-1.2.1.tgz", diff --git a/src/package.json b/src/package.json index 8202f38f..f1ff2a89 100644 --- a/src/package.json +++ b/src/package.json @@ -17,7 +17,9 @@ "debug": "^4.3.5", "express-session": "^1.18.0", "h3": "^1.12.0", - "qrcode": "^1.5.3" + "ip": "^2.0.1", + "qrcode": "^1.5.3", + "uuid": "^9.0.1" }, "devDependencies": { "eslint-config-athom": "^3.1.3", diff --git a/wg-easy.service b/wg-easy.service index bcdf72fd..e1ea96ea 100644 --- a/wg-easy.service +++ b/wg-easy.service @@ -5,7 +5,8 @@ After=network-online.target nss-lookup.target [Service] Environment="WG_HOST=raspberrypi.local" # Change this to your host's public address or static public ip. Environment="PASSWORD=REPLACEME" # When set, requires a password when logging in to the Web UI, to disable add a hashtag -#Environment="WG_DEFAULT_ADDRESS=10.0.8.x" #Clients IP address range. +#Environment="WG_DEFAULT_ADDRESS=10.0.8.0" # Clients IP addresses. +#Environment="WG_DEFAULT_ADDRESS_RANGE=32" # Client CIDR Range (if not set fallback to 24) #Environment="WG_DEFAULT_DNS=10.0.8.1, 1.1.1.1" #DNS server clients will use. If set to blank value, clients will not use any DNS. #Environment="WG_ALLOWED_IPS=0.0.0.0/0,::/0" #Allowed IPs clients will use. #Environment="WG_DEVICE=ens1" #Ethernet device the wireguard traffic should be forwarded through.