wg-easy/src/lib/WireGuard.js

311 lines
7.9 KiB
JavaScript
Raw Normal View History

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,
WG_ALLOWED_IPS,
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;
});
2021-05-23 21:27:46 +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
ListenPort = 51820`;
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
}
2021-05-23 20:28:22 +08:00
debug('Saving config...');
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() {
debug('Syncing config...');
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
${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}
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
};