feat(wireguard): bootstrap server keypair and default site exit node, fix UI confirmation actionMessage and query token auth (#43)
This commit is contained in:
+3
-1
@@ -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');
|
||||
|
||||
@@ -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
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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 };
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user