2021-05-23 04:21:09 +08:00
|
|
|
'use strict';
|
|
|
|
|
|
|
|
const fs = require('fs').promises;
|
|
|
|
const path = require('path');
|
|
|
|
|
2021-05-23 19:36:26 +08:00
|
|
|
const debug = require('debug')('WireGuard');
|
2021-05-23 05:29:01 +08:00
|
|
|
const uuid = require('uuid');
|
2021-05-23 04:21:09 +08:00
|
|
|
const QRCode = require('qrcode');
|
|
|
|
|
2021-05-23 04:40:11 +08:00
|
|
|
const Util = require('./Util');
|
2021-05-23 04:21:09 +08:00
|
|
|
const ServerError = require('./ServerError');
|
|
|
|
|
|
|
|
const {
|
|
|
|
WG_PATH,
|
|
|
|
WG_HOST,
|
|
|
|
WG_PORT,
|
2021-12-26 21:08:41 +08:00
|
|
|
WG_MTU,
|
2021-05-23 04:40:11 +08:00
|
|
|
WG_DEFAULT_DNS,
|
2021-05-23 04:21:09 +08:00
|
|
|
WG_DEFAULT_ADDRESS,
|
2021-07-18 22:48:33 +08:00
|
|
|
WG_PERSISTENT_KEEPALIVE,
|
2021-06-15 23:58:58 +08:00
|
|
|
WG_ALLOWED_IPS,
|
2022-01-11 15:11:34 +08:00
|
|
|
WG_POST_UP,
|
|
|
|
WG_POST_DOWN,
|
2021-05-23 04:21:09 +08:00
|
|
|
} = require('../config');
|
|
|
|
|
|
|
|
module.exports = class WireGuard {
|
|
|
|
|
2021-05-23 04:40:11 +08:00
|
|
|
async getConfig() {
|
|
|
|
if (!this.__configPromise) {
|
|
|
|
this.__configPromise = Promise.resolve().then(async () => {
|
2021-05-23 18:25:14 +08:00
|
|
|
if (!WG_HOST) {
|
|
|
|
throw new Error('WG_HOST Environment Variable Not Set!');
|
|
|
|
}
|
|
|
|
|
2021-05-23 19:36:26 +08:00
|
|
|
debug('Loading configuration...');
|
2021-05-23 04:40:11 +08:00
|
|
|
let config;
|
|
|
|
try {
|
|
|
|
config = await fs.readFile(path.join(WG_PATH, 'wg0.json'), 'utf8');
|
|
|
|
config = JSON.parse(config);
|
2021-05-23 20:28:22 +08:00
|
|
|
debug('Configuration loaded.');
|
2021-05-23 04:40:11 +08:00
|
|
|
} catch (err) {
|
2021-05-23 20:28:22 +08:00
|
|
|
const privateKey = await Util.exec('wg genkey');
|
2021-11-13 05:20:28 +08:00
|
|
|
const publicKey = await Util.exec(`echo ${privateKey} | wg pubkey`, {
|
2021-11-13 05:27:58 +08:00
|
|
|
log: 'echo ***hidden*** | wg pubkey',
|
2021-11-13 05:20:28 +08:00
|
|
|
});
|
2021-05-23 20:28:22 +08:00
|
|
|
const address = WG_DEFAULT_ADDRESS.replace('x', '1');
|
|
|
|
|
2021-05-23 04:40:11 +08:00
|
|
|
config = {
|
|
|
|
server: {
|
2021-05-23 20:28:22 +08:00
|
|
|
privateKey,
|
|
|
|
publicKey,
|
|
|
|
address,
|
2021-05-23 04:40:11 +08:00
|
|
|
},
|
|
|
|
clients: {},
|
|
|
|
};
|
2021-05-23 20:38:33 +08:00
|
|
|
debug('Configuration generated.');
|
2021-05-23 04:40:11 +08:00
|
|
|
}
|
|
|
|
|
2021-05-23 19:36:26 +08:00
|
|
|
await this.__saveConfig(config);
|
2021-11-13 05:20:28 +08:00
|
|
|
await Util.exec('wg-quick down wg0').catch(() => { });
|
2022-01-11 14:52:24 +08:00
|
|
|
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;
|
|
|
|
});
|
2022-01-11 15:11:34 +08:00
|
|
|
// await Util.exec(`iptables -t nat -A POSTROUTING -s ${WG_DEFAULT_ADDRESS.replace('x', '0')}/24 -o eth0 -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');
|
2021-05-23 20:28:22 +08:00
|
|
|
await this.__syncConfig();
|
2021-05-23 18:02:56 +08:00
|
|
|
|
2021-05-23 04:40:11 +08:00
|
|
|
return config;
|
|
|
|
});
|
|
|
|
}
|
2021-05-23 04:21:09 +08:00
|
|
|
|
2021-05-23 04:40:11 +08:00
|
|
|
return this.__configPromise;
|
2021-05-23 04:21:09 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
async saveConfig() {
|
2021-05-23 04:40:11 +08:00
|
|
|
const config = await this.getConfig();
|
2021-05-23 19:36:26 +08:00
|
|
|
await this.__saveConfig(config);
|
2021-05-23 20:28:22 +08:00
|
|
|
await this.__syncConfig();
|
2021-05-23 19:36:26 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
async __saveConfig(config) {
|
2021-05-23 04:21:09 +08:00
|
|
|
let result = `
|
|
|
|
# Note: Do not edit this file directly.
|
|
|
|
# Your changes will be overwritten!
|
|
|
|
|
|
|
|
# Server
|
|
|
|
[Interface]
|
2021-05-23 04:40:11 +08:00
|
|
|
PrivateKey = ${config.server.privateKey}
|
2021-05-23 05:29:01 +08:00
|
|
|
Address = ${config.server.address}/24
|
2022-01-11 15:11:34 +08:00
|
|
|
ListenPort = 51820
|
|
|
|
PostUp = ${WG_POST_UP}
|
|
|
|
PostDown = ${WG_POST_DOWN}
|
|
|
|
`;
|
2021-05-23 04:21:09 +08:00
|
|
|
|
2021-05-23 04:40:11 +08:00
|
|
|
for (const [clientId, client] of Object.entries(config.clients)) {
|
2021-05-23 04:21:09 +08:00
|
|
|
if (!client.enabled) continue;
|
|
|
|
|
|
|
|
result += `
|
|
|
|
|
|
|
|
# Client: ${client.name} (${clientId})
|
|
|
|
[Peer]
|
|
|
|
PublicKey = ${client.publicKey}
|
|
|
|
PresharedKey = ${client.preSharedKey}
|
2021-05-23 05:29:01 +08:00
|
|
|
AllowedIPs = ${client.address}/32`;
|
2021-05-23 04:21:09 +08:00
|
|
|
}
|
|
|
|
|
2022-01-11 15:11:34 +08:00
|
|
|
debug('Config saving...');
|
2022-01-11 14:47:45 +08:00
|
|
|
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,
|
|
|
|
});
|
2021-05-23 20:28:22 +08:00
|
|
|
debug('Config saved.');
|
|
|
|
}
|
|
|
|
|
|
|
|
async __syncConfig() {
|
2022-01-11 15:11:34 +08:00
|
|
|
debug('Config syncing...');
|
2021-05-23 20:28:22 +08:00
|
|
|
await Util.exec('wg syncconf wg0 <(wg-quick strip wg0)');
|
|
|
|
debug('Config synced.');
|
2021-05-23 04:21:09 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
async getClients() {
|
2021-05-23 04:40:11 +08:00
|
|
|
const config = await this.getConfig();
|
|
|
|
const clients = Object.entries(config.clients).map(([clientId, client]) => ({
|
2021-05-23 04:21:09 +08:00
|
|
|
id: clientId,
|
|
|
|
name: client.name,
|
|
|
|
enabled: client.enabled,
|
2021-05-23 05:29:01 +08:00
|
|
|
address: client.address,
|
2021-05-23 04:21:09 +08:00
|
|
|
publicKey: client.publicKey,
|
2021-05-23 04:40:11 +08:00
|
|
|
createdAt: new Date(client.createdAt),
|
|
|
|
updatedAt: new Date(client.updatedAt),
|
2021-05-23 04:21:09 +08:00
|
|
|
allowedIPs: client.allowedIPs,
|
|
|
|
|
2021-05-23 04:40:11 +08:00
|
|
|
persistentKeepalive: null,
|
|
|
|
latestHandshakeAt: null,
|
|
|
|
transferRx: null,
|
|
|
|
transferTx: null,
|
2021-05-23 04:21:09 +08:00
|
|
|
}));
|
2021-05-23 04:40:11 +08:00
|
|
|
|
|
|
|
// Loop WireGuard status
|
2021-11-13 05:20:28 +08:00
|
|
|
const dump = await Util.exec('wg show wg0 dump', {
|
|
|
|
log: false,
|
|
|
|
});
|
2021-05-23 05:29:01 +08:00
|
|
|
dump
|
2021-05-23 04:40:11 +08:00
|
|
|
.trim()
|
|
|
|
.split('\n')
|
|
|
|
.slice(1)
|
|
|
|
.forEach(line => {
|
|
|
|
const [
|
|
|
|
publicKey,
|
2021-05-23 04:45:47 +08:00
|
|
|
preSharedKey, // eslint-disable-line no-unused-vars
|
2021-05-23 18:02:56 +08:00
|
|
|
endpoint, // eslint-disable-line no-unused-vars
|
2021-05-23 04:45:47 +08:00
|
|
|
allowedIps, // eslint-disable-line no-unused-vars
|
2021-05-23 04:40:11 +08:00
|
|
|
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;
|
2021-05-23 04:21:09 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
async getClient({ clientId }) {
|
2021-05-23 04:40:11 +08:00
|
|
|
const config = await this.getConfig();
|
|
|
|
const client = config.clients[clientId];
|
2021-05-23 04:21:09 +08:00
|
|
|
if (!client) {
|
|
|
|
throw new ServerError(`Client Not Found: ${clientId}`, 404);
|
|
|
|
}
|
|
|
|
|
|
|
|
return client;
|
|
|
|
}
|
|
|
|
|
|
|
|
async getClientConfiguration({ clientId }) {
|
2021-05-23 20:28:22 +08:00
|
|
|
const config = await this.getConfig();
|
2021-05-23 04:21:09 +08:00
|
|
|
const client = await this.getClient({ clientId });
|
|
|
|
|
|
|
|
return `
|
|
|
|
[Interface]
|
|
|
|
PrivateKey = ${client.privateKey}
|
2021-05-23 05:29:01 +08:00
|
|
|
Address = ${client.address}/24
|
2021-07-14 03:00:40 +08:00
|
|
|
${WG_DEFAULT_DNS ? `DNS = ${WG_DEFAULT_DNS}` : ''}
|
2022-01-11 14:57:32 +08:00
|
|
|
${WG_MTU ? `MTU = ${WG_MTU}` : ''}
|
2021-05-23 04:21:09 +08:00
|
|
|
|
|
|
|
[Peer]
|
2021-05-23 20:28:22 +08:00
|
|
|
PublicKey = ${config.server.publicKey}
|
2021-05-23 04:21:09 +08:00
|
|
|
PresharedKey = ${client.preSharedKey}
|
2021-06-15 23:58:58 +08:00
|
|
|
AllowedIPs = ${WG_ALLOWED_IPS}
|
2021-07-17 22:19:17 +08:00
|
|
|
PersistentKeepalive = ${WG_PERSISTENT_KEEPALIVE}
|
2021-05-23 04:21:09 +08:00
|
|
|
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');
|
|
|
|
}
|
|
|
|
|
2021-05-23 05:29:01 +08:00
|
|
|
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');
|
2021-05-23 04:21:09 +08:00
|
|
|
|
2021-05-23 05:29:01 +08:00
|
|
|
// Calculate next IP
|
|
|
|
let address;
|
|
|
|
for (let i = 2; i < 255; i++) {
|
|
|
|
const client = Object.values(config.clients).find(client => {
|
|
|
|
return client.address === WG_DEFAULT_ADDRESS.replace('x', i);
|
|
|
|
});
|
2021-05-23 04:21:09 +08:00
|
|
|
|
2021-05-23 05:29:01 +08:00
|
|
|
if (!client) {
|
|
|
|
address = WG_DEFAULT_ADDRESS.replace('x', i);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!address) {
|
|
|
|
throw new Error('Maximum number of clients reached.');
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create Client
|
|
|
|
const clientId = uuid.v4();
|
|
|
|
const client = {
|
|
|
|
name,
|
|
|
|
address,
|
|
|
|
privateKey,
|
|
|
|
publicKey,
|
|
|
|
preSharedKey,
|
|
|
|
|
|
|
|
createdAt: new Date(),
|
|
|
|
updatedAt: new Date(),
|
|
|
|
|
|
|
|
enabled: true,
|
|
|
|
};
|
|
|
|
|
|
|
|
config.clients[clientId] = client;
|
2021-05-23 04:21:09 +08:00
|
|
|
|
|
|
|
await this.saveConfig();
|
|
|
|
}
|
|
|
|
|
|
|
|
async deleteClient({ clientId }) {
|
2021-05-23 04:40:11 +08:00
|
|
|
const config = await this.getConfig();
|
|
|
|
|
|
|
|
if (config.clients[clientId]) {
|
|
|
|
delete config.clients[clientId];
|
2021-05-23 04:21:09 +08:00
|
|
|
await this.saveConfig();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async enableClient({ clientId }) {
|
|
|
|
const client = await this.getClient({ clientId });
|
2021-05-23 05:29:01 +08:00
|
|
|
|
2021-05-23 04:21:09 +08:00
|
|
|
client.enabled = true;
|
2021-05-23 05:29:01 +08:00
|
|
|
client.updatedAt = new Date();
|
2021-05-23 04:21:09 +08:00
|
|
|
|
|
|
|
await this.saveConfig();
|
|
|
|
}
|
|
|
|
|
|
|
|
async disableClient({ clientId }) {
|
|
|
|
const client = await this.getClient({ clientId });
|
2021-05-23 05:29:01 +08:00
|
|
|
|
2021-05-23 04:21:09 +08:00
|
|
|
client.enabled = false;
|
2021-05-23 05:29:01 +08:00
|
|
|
client.updatedAt = new Date();
|
2021-05-23 04:21:09 +08:00
|
|
|
|
|
|
|
await this.saveConfig();
|
|
|
|
}
|
|
|
|
|
2021-07-14 02:51:23 +08:00
|
|
|
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();
|
|
|
|
}
|
|
|
|
|
2021-05-23 04:21:09 +08:00
|
|
|
};
|