diff --git a/README.md b/README.md index 6ae66b35..b2a3711b 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,7 @@ These options can be configured by setting environment variables using `-e KEY=" | `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_POST_UP` | `...` | `iptables ...` | See [config.js](https://github.com/WeeJeWel/wg-easy/blob/master/src/config.js#L19) for the default value. | | `WG_POST_DOWN` | `...` | `iptables ...` | See [config.js](https://github.com/WeeJeWel/wg-easy/blob/master/src/config.js#L26) for the default value. | +| `WG_HARDEN_CLIENTS` | - | `1` | When clients are hardened their PrivateKeys will not be stored. All requests to obtain their config, eg. download or QR code, will trigger a key regen. | > If you change `WG_PORT`, make sure to also change the exposed port. diff --git a/docker-compose.yml b/docker-compose.yml index f9db1ec1..96d92ab7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -13,7 +13,8 @@ services: # - WG_DEFAULT_DNS=1.1.1.1 # - WG_MTU=1420 # - WG_ALLOWED_IPS=192.168.15.0/24, 10.0.1.0/24 - + # - WG_HARDEN_CLIENTS=1 + image: weejewel/wg-easy container_name: wg-easy volumes: diff --git a/src/config.js b/src/config.js index 4dd4a1ff..833294b3 100644 --- a/src/config.js +++ b/src/config.js @@ -19,3 +19,6 @@ module.exports.WG_ALLOWED_IPS = process.env.WG_ALLOWED_IPS || '0.0.0.0/0, ::/0'; module.exports.WG_POST_UP = process.env.WG_POST_UP || ''; module.exports.WG_POST_DOWN = process.env.WG_POST_DOWN || ''; module.exports.WG_DEFAULT_DNS = process.env.WG_DEFAULT_DNS; +module.exports.WG_HARDEN_CLIENTS = typeof process.env.WG_HARDEN_CLIENTS === 'string' + ? process.env.WG_HARDEN_CLIENTS === '1' + : false; diff --git a/src/lib/Server.js b/src/lib/Server.js index bcb5f0a5..7a54759b 100644 --- a/src/lib/Server.js +++ b/src/lib/Server.js @@ -86,6 +86,9 @@ module.exports = class Server { debug(`Deleted Session: ${sessionId}`); })) + .get('/api/wireguard/hardened', Util.promisify(async req => { + return WireGuard.areClientsHardened(); + })) .get('/api/wireguard/dns', Util.promisify(async req => { return WireGuard.getDns(); })) diff --git a/src/lib/Util.js b/src/lib/Util.js index ee40c5a3..d6dcfd86 100644 --- a/src/lib/Util.js +++ b/src/lib/Util.js @@ -52,14 +52,14 @@ module.exports = class Util { }; } - static async exec(cmd, hide=null) { - // eslint-disable-next-line no-console - + static async exec(cmd, hide = null) { if (hide == null) { + // eslint-disable-next-line no-console console.log(`$ ${cmd}`); } else { // Don't log sensitive information - console.log(`$ ${cmd.replace(hide, "*HIDDEN*")}`); + // eslint-disable-next-line no-console + console.log(`$ ${cmd.replace(hide, '*HIDDEN*')}`); } if (process.platform !== 'linux') { diff --git a/src/lib/WireGuard.js b/src/lib/WireGuard.js index 4c96450d..a0c2b217 100644 --- a/src/lib/WireGuard.js +++ b/src/lib/WireGuard.js @@ -21,6 +21,7 @@ const { WG_ALLOWED_IPS, WG_POST_UP, WG_POST_DOWN, + WG_HARDEN_CLIENTS, } = require('../config'); module.exports = class WireGuard { @@ -126,7 +127,11 @@ AllowedIPs = ${client.address}/32`; } async getDns() { - return WG_DEFAULT_DNS ? WG_DEFAULT_DNS : null; + return WG_DEFAULT_DNS; + } + + async areClientsHardened() { + return WG_HARDEN_CLIENTS; } async getClients() { @@ -201,12 +206,18 @@ AllowedIPs = ${client.address}/32`; const config = await this.getConfig(); const client = await this.getClient({ clientId }); - const privateKey = await Util.exec('wg genkey'); - client.publicKey = await Util.exec(`echo ${privateKey} | wg pubkey`, privateKey); - client.preSharedKey = await Util.exec('wg genpsk'); + let { privateKey } = client; - await this.saveConfig(); - await this.restartGateway(); + if (WG_HARDEN_CLIENTS) { + // Generate new client keys + privateKey = await Util.exec('wg genkey'); + client.privateKey = null; + client.publicKey = await Util.exec(`echo ${privateKey} | wg pubkey`, privateKey); + client.preSharedKey = await Util.exec('wg genpsk'); + + // Restart gateway to complete key regen + await this.saveConfig(); + } return ` [Interface] @@ -240,9 +251,8 @@ Endpoint = ${WG_HOST}:${WG_PORT}`; } const config = await this.getConfig(); - - // Public key is placeholder, new one is generated on getClientConfig - const publicKey = await Util.exec('wg genpsk'); + const privateKey = await Util.exec('wg genkey'); + const publicKey = await Util.exec(`echo ${privateKey} | wg pubkey`); const preSharedKey = await Util.exec('wg genpsk'); // Calculate next IP @@ -262,14 +272,18 @@ Endpoint = ${WG_HOST}:${WG_PORT}`; throw new Error('Maximum number of clients reached.'); } + // Avoid lint problem + const realPrivateKey = WG_HARDEN_CLIENTS ? null : privateKey; + // Create Client const clientId = uuid.v4(); const client = { name, address, + realPrivateKey, publicKey, preSharedKey, - allowedIPs: allowedIPs, + allowedIPs, createdAt: new Date(), updatedAt: new Date(), diff --git a/src/www/index.html b/src/www/index.html index 52684214..adf1a74c 100644 --- a/src/www/index.html +++ b/src/www/index.html @@ -130,7 +130,7 @@ + + + @@ -367,6 +377,147 @@ + +
+
+ + + + + + + +
+
+ + +
+
+ + + + + + + +
+
+
diff --git a/src/www/js/api.js b/src/www/js/api.js index 3231b3d8..17d0fa7e 100644 --- a/src/www/js/api.js +++ b/src/www/js/api.js @@ -58,6 +58,13 @@ class API { }); } + async areClientsHardened() { + return this.call({ + method: 'get', + path: '/wireguard/hardened', + }); + } + async getDns() { return this.call({ method: 'get', diff --git a/src/www/js/app.js b/src/www/js/app.js index 48343861..b7133f26 100644 --- a/src/www/js/app.js +++ b/src/www/js/app.js @@ -38,19 +38,21 @@ new Vue({ clientsPersist: {}, clientDelete: null, clientCreate: null, + clientQRShow: null, + clientConfigDownload: null, clientCreateName: '', clientCreateAllowedIPs: '', clientCreateAllowedIPsDefault: '0.0.0.0/0, ::0/0', clientCreateAllowedIPsExclude: ( - "::/0, 1.0.0.0/8, 2.0.0.0/8, 3.0.0.0/8, " - + "4.0.0.0/6, 8.0.0.0/7, 11.0.0.0/8, 12.0.0.0/6, " - + "16.0.0.0/4, 32.0.0.0/3, 64.0.0.0/2, 128.0.0.0/3, " - + "160.0.0.0/5, 168.0.0.0/6, 172.0.0.0/12, 172.32.0.0/11, " - + "172.64.0.0/10, 172.128.0.0/9, 173.0.0.0/8, 174.0.0.0/7, " - + "176.0.0.0/4, 192.0.0.0/9, 192.128.0.0/11, 192.160.0.0/13, " - + "192.169.0.0/16, 192.170.0.0/15, 192.172.0.0/14, 192.176.0.0/12, " - + "192.192.0.0/10, 193.0.0.0/8, 194.0.0.0/7, 196.0.0.0/6, " - + "200.0.0.0/5, 208.0.0.0/4" + '::/0, 1.0.0.0/8, 2.0.0.0/8, 3.0.0.0/8, ' + + '4.0.0.0/6, 8.0.0.0/7, 11.0.0.0/8, 12.0.0.0/6, ' + + '16.0.0.0/4, 32.0.0.0/3, 64.0.0.0/2, 128.0.0.0/3, ' + + '160.0.0.0/5, 168.0.0.0/6, 172.0.0.0/12, 172.32.0.0/11, ' + + '172.64.0.0/10, 172.128.0.0/9, 173.0.0.0/8, 174.0.0.0/7, ' + + '176.0.0.0/4, 192.0.0.0/9, 192.128.0.0/11, 192.160.0.0/13, ' + + '192.169.0.0/16, 192.170.0.0/15, 192.172.0.0/14, 192.176.0.0/12, ' + + '192.192.0.0/10, 193.0.0.0/8, 194.0.0.0/7, 196.0.0.0/6, ' + + '200.0.0.0/5, 208.0.0.0/4' ), clientEditName: null, clientEditNameId: null, @@ -223,8 +225,11 @@ new Vue({ alert(err.message || err.toString()); }); }, + areClientsHardened() { + return this.api.areClientsHardened(); + }, getDns() { - return this.api.getDns() + return this.api.getDns(); }, createClient() { const name = this.clientCreateName;