feat(wireguard): bootstrap server keypair and default site exit node, fix UI confirmation actionMessage and query token auth (#43)

This commit is contained in:
2026-08-09 00:08:55 -04:00
committed by GitHub
parent 43349579e1
commit 03dd903e07
7 changed files with 488 additions and 604 deletions
+3 -1
View File
@@ -15,8 +15,10 @@ const { Server } = require('socket.io');
// createOidcClient), so the fetch MUST resolve before require('../models').
// Fail-soft: if OpenBao is unreachable, init() leaves conf as the file-loaded
// fallback and boot continues from ./config/jump-secrets.js.
require('@simpleworkjs/bao-conf').init({ path: 'jump-host', conf }).then(() => {
require('@simpleworkjs/bao-conf').init({ path: 'jump-host', conf }).then(async () => {
require('../models');
const { bootstrapWireguard } = require('../services/wg_bootstrap');
await bootstrapWireguard();
const app = require('../app');
const middleware = require('../middleware/auth');
+2 -1
View File
@@ -27,7 +27,8 @@ async function auth(req, res, next){
return next();
}
req.token = await Auth.checkToken(req.header('auth-token'));
const tokStr = req.header('auth-token') || req.query.token;
req.token = await Auth.checkToken(tokStr);
req.user = req.token.user;
req.groups = typeof req.token.groupsArray === 'function' ? req.token.groupsArray() : [];
return next();
+1 -1
View File
@@ -14,7 +14,7 @@
"scripts": {
"start": "node ./bin/www",
"dev": "npx nodemon --ignore public/ ./bin/www",
"test": "NODE_ENV=test node --test --test-force-exit test/unit/access.test.js test/unit/host_keys.test.js test/unit/no_native_dialogs.test.js test/unit/target_match.test.js test/unit/username_grammar.test.js",
"test": "NODE_ENV=test node --test --test-force-exit test/unit/access.test.js test/unit/host_keys.test.js test/unit/no_native_dialogs.test.js test/unit/target_match.test.js test/unit/username_grammar.test.js test/unit/wireguard.test.js",
"test:unit": "NODE_ENV=test node --test --test-force-exit test/unit/*.test.js",
"test:integration": "NODE_ENV=test node --test --test-force-exit test/integration/*.test.js"
},
+6
View File
@@ -35,6 +35,12 @@ function gwConf() {
};
}
// ── Gateway Info ─────────────────────────────────────────────────────────────
router.get('/gateway-info', async (req, res) => {
res.json(gwConf());
});
// ── Sites ───────────────────────────────────────────────────────────────────
router.get('/sites', async (req, res, next) => {
+56
View File
@@ -0,0 +1,56 @@
'use strict';
const conf = require('@simpleworkjs/conf');
const { getRedis } = require('../models');
const { generateKeypair } = require('../utils/wg_keys');
const wgSite = require('../models/wg_site');
async function bootstrapWireguard() {
try {
const redis = await getRedis();
const P = conf.redis.prefix || '';
const keypairKey = `${P}wg_gateway_keypair`;
// 1. Ensure Gateway WireGuard Keypair
let keypairData = await redis.hGetAll(keypairKey);
if (!keypairData || !keypairData.publicKey) {
const kp = generateKeypair();
keypairData = {
privateKey: kp.privateKey,
publicKey: kp.publicKey,
createdAt: String(Date.now()),
};
await redis.hSet(keypairKey, keypairData);
console.log('[bootstrap] Generated fresh WireGuard Gateway keypair.');
}
if (!conf.wireguard) conf.wireguard = {};
conf.wireguard.serverPublicKey = keypairData.publicKey;
conf.wireguard.serverPrivateKey = keypairData.privateKey;
if (!conf.wireguard.serverEndpoint) {
const domain = conf.domain || 'suite.vm42.us';
conf.wireguard.serverEndpoint = `${domain}:51820`;
}
// 2. Ensure Default Site Exit Node ("This Site")
const existingSites = await wgSite.list().catch(() => []);
if (!existingSites || existingSites.length === 0) {
const siteName = conf.siteName || process.env.CFG_SITE_NAME || '718it';
const defaultSite = await wgSite.create({
name: `${siteName} (This Site)`,
endpoint: conf.wireguard.serverEndpoint,
publicKey: keypairData.publicKey,
subnet: '0.0.0.0/0',
exitAll: true,
siteId: siteName,
note: 'Default local site exit node initialized during bootstrap',
}, 'bootstrap');
console.log(`[bootstrap] Initialized default WireGuard exit node '${defaultSite.name}' (${defaultSite.id}).`);
}
} catch (err) {
console.error('[bootstrap] WireGuard bootstrap error:', err.message);
}
}
module.exports = { bootstrapWireguard };
+42
View File
@@ -0,0 +1,42 @@
'use strict';
const test = require('node:test');
const assert = require('node:assert/strict');
const { generateKeypair } = require('../../utils/wg_keys');
const { renderClientConf } = require('../../utils/wg_conf');
test('generateKeypair returns valid base64 WireGuard keypair', () => {
const kp = generateKeypair();
assert.ok(kp.privateKey, 'privateKey should exist');
assert.ok(kp.publicKey, 'publicKey should exist');
assert.notEqual(kp.privateKey, kp.publicKey);
// WireGuard raw X25519 base64 keys are 44 characters ending with '='
assert.equal(kp.privateKey.length, 44);
assert.equal(kp.publicKey.length, 44);
});
test('renderClientConf generates valid wg0 client configuration', () => {
const peer = {
name: 'test-phone',
assignedIP: '10.100.0.5',
privateKey: 'c3VwZXJzZWNyZXRwcml2YXRla2V5MTIzNDU2Nzg5MDE=',
};
const site = {
subnet: '192.168.1.0/24',
exitAll: false,
};
const confStr = renderClientConf({
peer,
site,
serverPub: 'c2VydmVycHVibGlja2V5MTIzNDU2Nzg5MDEyMzQ1Njc=',
serverEndpoint: 'gw.theta42.com:51820',
dns: '1.1.1.1',
});
assert.ok(confStr.includes('[Interface]'));
assert.ok(confStr.includes('PrivateKey = c3VwZXJzZWNyZXRwcml2YXRla2V5MTIzNDU2Nzg5MDE='));
assert.ok(confStr.includes('Address = 10.100.0.5/32'));
assert.ok(confStr.includes('[Peer]'));
assert.ok(confStr.includes('PublicKey = c2VydmVycHVibGlja2V5MTIzNDU2Nzg5MDEyMzQ1Njc='));
assert.ok(confStr.includes('Endpoint = gw.theta42.com:51820'));
});
+378 -601
View File
File diff suppressed because it is too large Load Diff