diff --git a/README.md b/README.md index a90a2b310..b956c87d4 100644 --- a/README.md +++ b/README.md @@ -100,7 +100,8 @@ These options can be configured by setting environment variables using `-e KEY=" | `WG_PORT` | `51820` | `12345` | The public UDP port of your VPN server. WireGuard will always listen on 51820 inside the Docker container. | | `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 a22b24836..89528264d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -15,7 +15,8 @@ services: # Optional: # - PASSWORD=foobar123 # - WG_PORT=51820 - # - 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 diff --git a/src/config.js b/src/config.js index 1fff4c275..c50b7cf63 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; @@ -12,15 +14,21 @@ module.exports.WG_HOST = process.env.WG_HOST; module.exports.WG_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 -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; @@ -28,7 +36,7 @@ iptables -A FORWARD -o wg0 -j ACCEPT; 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 -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; diff --git a/src/lib/Util.js b/src/lib/Util.js index cc6e89c2d..829425996 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 739676fb9..de36e1e1e 100644 --- a/src/lib/WireGuard.js +++ b/src/lib/WireGuard.js @@ -4,6 +4,7 @@ const fs = require('fs').promises; const path = require('path'); const debug = require('debug')('WireGuard'); +const ip = require('ip'); const uuid = require('uuid'); const QRCode = require('qrcode'); @@ -16,9 +17,12 @@ const { WG_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, @@ -45,13 +49,15 @@ module.exports = class WireGuard { const publicKey = await Util.exec(`echo ${privateKey} | wg pubkey`, { log: 'echo ***hidden*** | wg pubkey', }); - const address = WG_DEFAULT_ADDRESS.replace('x', '1'); + const address = WG_SERVER_ADDRESS; + const cidrBlock = WG_DEFAULT_ADDRESS_RANGE; config = { server: { privateKey, publicKey, address, + cidrBlock, }, clients: {}, }; @@ -67,7 +73,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 +100,7 @@ module.exports = class WireGuard { # Server [Interface] PrivateKey = ${config.server.privateKey} -Address = ${config.server.address}/24 +Address = ${config.server.address}/${config.server.cidrBlock} ListenPort = 51820 PreUp = ${WG_PRE_UP} PostUp = ${WG_POST_UP} @@ -137,6 +143,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,7 +206,7 @@ ${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` : ''}\ @@ -230,15 +237,16 @@ Endpoint = ${WG_HOST}:${WG_PORT}`; const publicKey = await Util.exec(`echo ${privateKey} | wg pubkey`); const preSharedKey = await Util.exec('wg genpsk'); - // Calculate next IP + // find 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 +257,12 @@ Endpoint = ${WG_HOST}:${WG_PORT}`; // Create Client const id = uuid.v4(); + const cidrBlock = WG_DEFAULT_ADDRESS_RANGE; const client = { id, name, address, + cidrBlock, privateKey, publicKey, preSharedKey, @@ -309,7 +319,7 @@ Endpoint = ${WG_HOST}:${WG_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 50bbf5557..72e20cf8c 100644 --- a/src/package-lock.json +++ b/src/package-lock.json @@ -13,12 +13,13 @@ "debug": "^4.3.4", "express-session": "^1.18.0", "h3": "^1.11.1", + "ip": "^2.0.1", "qrcode": "^1.5.3", "uuid": "^9.0.1" }, "devDependencies": { "eslint-config-athom": "^3.1.3", - "tailwindcss": "^3.4.1" + "tailwindcss": "^3.4.3" }, "engines": { "node": "18" @@ -1144,9 +1145,9 @@ } }, "node_modules/cookie-es": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-1.0.0.tgz", - "integrity": "sha512-mWYvfOLrfEc996hlKcdABeIiPHUPC6DM2QYZdGGOvhOTbA3tjm2eBwqlJpoFdjC89NI4Qt6h0Pu06Mp+1Pj5OQ==" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-1.1.0.tgz", + "integrity": "sha512-L2rLOcK0wzWSfSDA33YR+PUHDG10a8px7rUHKWbGLP4YfbsMed2KFUw5fczvDPbT98DDe3LEzviswl810apTEw==" }, "node_modules/cookie-signature": { "version": "1.0.7", @@ -1399,9 +1400,9 @@ } }, "node_modules/es-abstract": { - "version": "1.23.2", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.2.tgz", - "integrity": "sha512-60s3Xv2T2p1ICykc7c+DNDPLDMm9t4QxCOUU0K9JxiLjM3C1zB9YVdN7tjxrFd4+AkZ8CdX1ovUga4P2+1e+/w==", + "version": "1.23.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", + "integrity": "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==", "dev": true, "dependencies": { "array-buffer-byte-length": "^1.0.1", @@ -1443,11 +1444,11 @@ "safe-regex-test": "^1.0.3", "string.prototype.trim": "^1.2.9", "string.prototype.trimend": "^1.0.8", - "string.prototype.trimstart": "^1.0.7", + "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.2", "typed-array-byte-length": "^1.0.1", "typed-array-byte-offset": "^1.0.2", - "typed-array-length": "^1.0.5", + "typed-array-length": "^1.0.6", "unbox-primitive": "^1.0.2", "which-typed-array": "^1.1.15" }, @@ -2654,6 +2655,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.1.0", "resolved": "https://registry.npmjs.org/iron-webcrypto/-/iron-webcrypto-1.1.0.tgz", @@ -3460,12 +3466,12 @@ "dev": true }, "node_modules/path-scurry": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", - "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.2.tgz", + "integrity": "sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==", "dev": true, "dependencies": { - "lru-cache": "^9.1.1 || ^10.0.0", + "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" }, "engines": { @@ -3768,9 +3774,9 @@ ] }, "node_modules/radix3": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/radix3/-/radix3-1.1.1.tgz", - "integrity": "sha512-yUUd5VTiFtcMEx0qFUxGAv5gbMc1un4RvEO1JZdP7ZUl/RHygZK6PknIKntmQRZxnMY3ZXD2ISaw1ij8GYW1yg==" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/radix3/-/radix3-1.1.2.tgz", + "integrity": "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==" }, "node_modules/ramda": { "version": "0.27.2", @@ -4282,16 +4288,16 @@ } }, "node_modules/sucrase/node_modules/glob": { - "version": "10.3.10", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", - "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", + "version": "10.3.12", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz", + "integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==", "dev": true, "dependencies": { "foreground-child": "^3.1.0", - "jackspeak": "^2.3.5", + "jackspeak": "^2.3.6", "minimatch": "^9.0.1", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", - "path-scurry": "^1.10.1" + "minipass": "^7.0.4", + "path-scurry": "^1.10.2" }, "bin": { "glob": "dist/esm/bin.mjs" @@ -4329,9 +4335,9 @@ } }, "node_modules/table": { - "version": "6.8.1", - "resolved": "https://registry.npmjs.org/table/-/table-6.8.1.tgz", - "integrity": "sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA==", + "version": "6.8.2", + "resolved": "https://registry.npmjs.org/table/-/table-6.8.2.tgz", + "integrity": "sha512-w2sfv80nrAh2VCbqR5AK27wswXhqcck2AhfnNW76beQXskGZ1V12GwS//yYVa3d3fcvAip2OUnbDAjW2k3v9fA==", "dev": true, "peer": true, "dependencies": { @@ -4370,9 +4376,9 @@ "peer": true }, "node_modules/tailwindcss": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.1.tgz", - "integrity": "sha512-qAYmXRfk3ENzuPBakNK0SRrUDipP8NQnEY6772uDhflcQz5EhRdD7JNZxyrFHVQNCwULPBn6FNPp9brpO7ctcA==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.3.tgz", + "integrity": "sha512-U7sxQk/n397Bmx4JHbJx/iSOOv5G+II3f1kpLpY2QeUv5DcPdcTsYLlusZfq1NthHS1c1cZoyFmmkex1rzke0A==", "dev": true, "dependencies": { "@alloc/quick-lru": "^5.2.0", @@ -4383,7 +4389,7 @@ "fast-glob": "^3.3.0", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", - "jiti": "^1.19.1", + "jiti": "^1.21.0", "lilconfig": "^2.1.0", "micromatch": "^4.0.5", "normalize-path": "^3.0.0", diff --git a/src/package.json b/src/package.json index 545218bf9..ae8172e18 100644 --- a/src/package.json +++ b/src/package.json @@ -16,13 +16,14 @@ "bcryptjs": "^2.4.3", "debug": "^4.3.4", "express-session": "^1.18.0", + "ip": "^2.0.1", "h3": "^1.11.1", "qrcode": "^1.5.3", "uuid": "^9.0.1" }, "devDependencies": { "eslint-config-athom": "^3.1.3", - "tailwindcss": "^3.4.1" + "tailwindcss": "^3.4.3" }, "nodemonConfig": { "ignore": [ diff --git a/src/www/css/app.css b/src/www/css/app.css index 1db1aa068..84270471d 100644 --- a/src/www/css/app.css +++ b/src/www/css/app.css @@ -1,5 +1,5 @@ /* -! tailwindcss v3.4.1 | MIT License | https://tailwindcss.com +! tailwindcss v3.4.3 | MIT License | https://tailwindcss.com */ /* @@ -211,6 +211,8 @@ textarea { /* 1 */ line-height: inherit; /* 1 */ + letter-spacing: inherit; + /* 1 */ color: inherit; /* 1 */ margin: 0; @@ -234,9 +236,9 @@ select { */ button, -[type='button'], -[type='reset'], -[type='submit'] { +input:where([type='button']), +input:where([type='reset']), +input:where([type='submit']) { -webkit-appearance: button; /* 1 */ background-color: transparent; @@ -492,6 +494,10 @@ video { --tw-backdrop-opacity: ; --tw-backdrop-saturate: ; --tw-backdrop-sepia: ; + --tw-contain-size: ; + --tw-contain-layout: ; + --tw-contain-paint: ; + --tw-contain-style: ; } ::backdrop { @@ -542,6 +548,10 @@ video { --tw-backdrop-opacity: ; --tw-backdrop-saturate: ; --tw-backdrop-sepia: ; + --tw-contain-size: ; + --tw-contain-layout: ; + --tw-contain-paint: ; + --tw-contain-style: ; } .container { diff --git a/wg-easy.service b/wg-easy.service index bcdf72fdf..e1ea96ea4 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.