
This PR allows the use of Address Ranges using the CIDR notation. To make it backward compatible, i introduced a new env variable WG_DEFAULT_ADDRESS_RANGE (defaults to the previous default of 24). This allows the usage of smaller subnets (or possibly larger; but i didn't test that due to restrictions on my network). Client IPs will be calculated with correct IP addresses instead of making assumptions of the address space.
334 lines
8.5 KiB
JavaScript
334 lines
8.5 KiB
JavaScript
'use strict';
|
|
|
|
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');
|
|
|
|
const Util = require('./Util');
|
|
const ServerError = require('./ServerError');
|
|
|
|
const {
|
|
WG_PATH,
|
|
WG_HOST,
|
|
WG_PORT,
|
|
WG_MTU,
|
|
WG_DEFAULT_DNS,
|
|
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,
|
|
WG_POST_DOWN,
|
|
} = require('../config');
|
|
|
|
module.exports = class WireGuard {
|
|
|
|
async getConfig() {
|
|
if (!this.__configPromise) {
|
|
this.__configPromise = Promise.resolve().then(async () => {
|
|
if (!WG_HOST) {
|
|
throw new Error('WG_HOST Environment Variable Not Set!');
|
|
}
|
|
|
|
debug('Loading configuration...');
|
|
let config;
|
|
try {
|
|
config = await fs.readFile(path.join(WG_PATH, 'wg0.json'), 'utf8');
|
|
config = JSON.parse(config);
|
|
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_SERVER_ADDRESS;
|
|
const cidrBlock = WG_DEFAULT_ADDRESS_RANGE;
|
|
|
|
config = {
|
|
server: {
|
|
privateKey,
|
|
publicKey,
|
|
address,
|
|
cidrBlock,
|
|
},
|
|
clients: {},
|
|
};
|
|
debug('Configuration generated.');
|
|
}
|
|
|
|
await this.__saveConfig(config);
|
|
await Util.exec('wg-quick down wg0').catch(() => { });
|
|
await Util.exec('wg-quick up wg0').catch((err) => {
|
|
if (err && err.message && err.message.includes('Cannot find device "wg0"')) {
|
|
throw new Error('WireGuard exited with the error: Cannot find device "wg0"\nThis usually means that your host\'s kernel does not support 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 -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');
|
|
await this.__syncConfig();
|
|
|
|
return config;
|
|
});
|
|
}
|
|
|
|
return this.__configPromise;
|
|
}
|
|
|
|
async saveConfig() {
|
|
const config = await this.getConfig();
|
|
await this.__saveConfig(config);
|
|
await this.__syncConfig();
|
|
}
|
|
|
|
async __saveConfig(config) {
|
|
let result = `
|
|
# Note: Do not edit this file directly.
|
|
# Your changes will be overwritten!
|
|
|
|
# Server
|
|
[Interface]
|
|
PrivateKey = ${config.server.privateKey}
|
|
Address = ${config.server.address}/${config.server.cidrBlock}
|
|
ListenPort = 51820
|
|
PreUp = ${WG_PRE_UP}
|
|
PostUp = ${WG_POST_UP}
|
|
PreDown = ${WG_PRE_DOWN}
|
|
PostDown = ${WG_POST_DOWN}
|
|
`;
|
|
|
|
for (const [clientId, client] of Object.entries(config.clients)) {
|
|
if (!client.enabled) continue;
|
|
|
|
result += `
|
|
|
|
# Client: ${client.name} (${clientId})
|
|
[Peer]
|
|
PublicKey = ${client.publicKey}
|
|
${client.preSharedKey ? `PresharedKey = ${client.preSharedKey}\n` : ''
|
|
}AllowedIPs = ${client.address}/32`;
|
|
}
|
|
|
|
debug('Config saving...');
|
|
await fs.writeFile(path.join(WG_PATH, 'wg0.json'), JSON.stringify(config, false, 2), {
|
|
mode: 0o660,
|
|
});
|
|
await fs.writeFile(path.join(WG_PATH, 'wg0.conf'), result, {
|
|
mode: 0o600,
|
|
});
|
|
debug('Config saved.');
|
|
}
|
|
|
|
async __syncConfig() {
|
|
debug('Config syncing...');
|
|
await Util.exec('wg syncconf wg0 <(wg-quick strip wg0)');
|
|
debug('Config synced.');
|
|
}
|
|
|
|
async getClients() {
|
|
const config = await this.getConfig();
|
|
const clients = Object.entries(config.clients).map(([clientId, client]) => ({
|
|
id: clientId,
|
|
name: client.name,
|
|
enabled: client.enabled,
|
|
address: client.address,
|
|
publicKey: client.publicKey,
|
|
createdAt: new Date(client.createdAt),
|
|
updatedAt: new Date(client.updatedAt),
|
|
allowedIPs: client.allowedIPs,
|
|
|
|
persistentKeepalive: null,
|
|
latestHandshakeAt: null,
|
|
transferRx: null,
|
|
transferTx: null,
|
|
}));
|
|
|
|
// Loop WireGuard status
|
|
const dump = await Util.exec('wg show wg0 dump', {
|
|
log: false,
|
|
});
|
|
dump
|
|
.trim()
|
|
.split('\n')
|
|
.slice(1)
|
|
.forEach((line) => {
|
|
const [
|
|
publicKey,
|
|
preSharedKey, // eslint-disable-line no-unused-vars
|
|
endpoint, // eslint-disable-line no-unused-vars
|
|
allowedIps, // eslint-disable-line no-unused-vars
|
|
latestHandshakeAt,
|
|
transferRx,
|
|
transferTx,
|
|
persistentKeepalive,
|
|
] = line.split('\t');
|
|
|
|
const client = clients.find((client) => client.publicKey === publicKey);
|
|
if (!client) return;
|
|
|
|
client.latestHandshakeAt = latestHandshakeAt === '0'
|
|
? null
|
|
: new Date(Number(`${latestHandshakeAt}000`));
|
|
client.transferRx = Number(transferRx);
|
|
client.transferTx = Number(transferTx);
|
|
client.persistentKeepalive = persistentKeepalive;
|
|
});
|
|
|
|
return clients;
|
|
}
|
|
|
|
async getClient({ clientId }) {
|
|
const config = await this.getConfig();
|
|
const client = config.clients[clientId];
|
|
if (!client) {
|
|
throw new ServerError(`Client Not Found: ${clientId}`, 404);
|
|
}
|
|
|
|
return client;
|
|
}
|
|
|
|
async getClientConfiguration({ clientId }) {
|
|
const config = await this.getConfig();
|
|
const client = await this.getClient({ clientId });
|
|
|
|
return `[Interface]
|
|
PrivateKey = ${client.privateKey}
|
|
Address = ${client.address}/24
|
|
${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}
|
|
PersistentKeepalive = ${WG_PERSISTENT_KEEPALIVE}
|
|
Endpoint = ${WG_HOST}:${WG_PORT}`;
|
|
}
|
|
|
|
async getClientQRCodeSVG({ clientId }) {
|
|
const config = await this.getClientConfiguration({ clientId });
|
|
return QRCode.toString(config, {
|
|
type: 'svg',
|
|
width: 512,
|
|
});
|
|
}
|
|
|
|
async createClient({ name }) {
|
|
if (!name) {
|
|
throw new Error('Missing: Name');
|
|
}
|
|
|
|
const config = await this.getConfig();
|
|
|
|
const privateKey = await Util.exec('wg genkey');
|
|
const publicKey = await Util.exec(`echo ${privateKey} | wg pubkey`);
|
|
const preSharedKey = await Util.exec('wg genpsk');
|
|
|
|
// find next IP
|
|
let address;
|
|
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 === currentIp;
|
|
});
|
|
|
|
if (!client) {
|
|
address = currentIp;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!address) {
|
|
throw new Error('Maximum number of clients reached.');
|
|
}
|
|
|
|
// Create Client
|
|
const id = uuid.v4();
|
|
const client = {
|
|
id,
|
|
name,
|
|
address,
|
|
privateKey,
|
|
publicKey,
|
|
preSharedKey,
|
|
|
|
createdAt: new Date(),
|
|
updatedAt: new Date(),
|
|
|
|
enabled: true,
|
|
};
|
|
|
|
config.clients[id] = client;
|
|
|
|
await this.saveConfig();
|
|
|
|
return client;
|
|
}
|
|
|
|
async deleteClient({ clientId }) {
|
|
const config = await this.getConfig();
|
|
|
|
if (config.clients[clientId]) {
|
|
delete config.clients[clientId];
|
|
await this.saveConfig();
|
|
}
|
|
}
|
|
|
|
async enableClient({ clientId }) {
|
|
const client = await this.getClient({ clientId });
|
|
|
|
client.enabled = true;
|
|
client.updatedAt = new Date();
|
|
|
|
await this.saveConfig();
|
|
}
|
|
|
|
async disableClient({ clientId }) {
|
|
const client = await this.getClient({ clientId });
|
|
|
|
client.enabled = false;
|
|
client.updatedAt = new Date();
|
|
|
|
await this.saveConfig();
|
|
}
|
|
|
|
async updateClientName({ clientId, name }) {
|
|
const client = await this.getClient({ clientId });
|
|
|
|
client.name = name;
|
|
client.updatedAt = new Date();
|
|
|
|
await this.saveConfig();
|
|
}
|
|
|
|
async updateClientAddress({ clientId, address }) {
|
|
const client = await this.getClient({ clientId });
|
|
|
|
if (!Util.isValidIPv4(address)) {
|
|
throw new ServerError(`Invalid Address: ${address}`, 400);
|
|
}
|
|
|
|
client.address = address;
|
|
client.updatedAt = new Date();
|
|
|
|
await this.saveConfig();
|
|
}
|
|
|
|
// Shutdown wireguard
|
|
async Shutdown() {
|
|
await Util.exec('wg-quick down wg0').catch(() => { });
|
|
}
|
|
|
|
};
|