diff --git a/add.js b/add.js new file mode 100644 index 0000000..53dbf31 --- /dev/null +++ b/add.js @@ -0,0 +1,130 @@ +'use strict' +const fs = require('fs').promises +const wg = require('./wireguard') +const env = require('./env.json') + +module.exports = async (req, res) => { + const hostname = req.query['name'] + if (!hostname) { + console.log(`New peer request from ${req.requester} didn't provide a hostname`) + res.sendStatus(500); return + } + console.log(`New peer request from ${req.requester} for ${hostname}`) + + // Determine user subnet + let subnet + if (req.requester.includes(env.IPV4_NET)) subnet = req.requester.split('.').slice(-2,-1)[0] + else if (req.requester.includes(env.IPV6_NET)) subnet = req.requester.split(':').slice(-2,-1)[0] + else console.log(`Received add request from ${req.requester}, which does not appear to be from the network.`) + + // Read wg.conf file for this user's other devices + const userpeers = (await fs.readFile(env.WG_CONFIG_FILE)).toString() + .split('\n\n').filter( (paragraph) => { + return paragraph.includes('[Peer]') + }).filter( (peer) => { + return peer.includes(`${env.IPV4_NET}.${subnet}`) || peer.includes(`${env.IPV6_NET}:${subnet}`) + }) + let found_usernames = [] + let found_ipv6s = [] + let found_ipv4s = [] + for (const userpeer of userpeers) { + for (const line of userpeer.split('\n')) { + if (line.includes('[Peer]')) { + // Check if host exists + if (line.includes(`# ${hostname}.`)) { + console.log(`Host already exists for ${hostname}`) + res.sendStatus(500); return + } + found_usernames.push(line.split('.').slice(-2,-1)[0]) + } + else if (line.includes('AllowedIPs')) { + const ips = line.split('=')[1].split(',') + found_ipv4s.push(ips.filter( (ip) => ip.includes(env.IPV4_NET) )[0].trim()) + found_ipv6s.push(ips.filter( (ip) => ip.includes(env.IPV6_NET) )[0].trim()) + } + } + } + // Check that all IP addresses are in correct subnet or error out + if (!found_ipv4s.every((found_ipv4) => + found_ipv4.toString().includes(`${env.IPV4_NET}.${subnet}.`))) { + console.log(`Found unmatching IPv4 address subnets for ${req.requester}: ${found_ipv4s}`) + res.sendStatus(500); return + } else if (!found_ipv6s.every((found_ipv6) => + found_ipv6.toString().includes(`${env.IPV6_NET}:${subnet}:`))) { + console.log(`Found unmatching IPv6 address subnets for ${req.requester}: ${found_ipv6s}`) + res.sendStatus(500); return + + // Check that the req.requester is "on the list" + // Should never get here because this req.requester can't access this IP! + } else if (found_usernames.length ===0) { + console.log(`Received request from ${req.requester} not in wg.conf!`) + res.sendStatus(500); return + // Check that all usernames are correct or error out + // https://stackoverflow.com/a/35568895 + } else if (!found_usernames.every( (v,i,r) => v === r[0] )) { + console.log(`Found unmatching usernames for ${req.requester}: ${found_usernames.toString()}`) + res.sendStatus(500); return + + // Everything looks good! Proceed + } else { + const username = found_usernames[0] + console.log(`${req.requester} must be ${username}`) + + // Find next available host part in config + const used_ipv4_hosts = found_ipv4s.map((found_ipv4) => + found_ipv4.toString().split('.')[3].split('/')[0]) + const used_ipv6_hosts = found_ipv6s.map((found_ipv6) => + found_ipv6.toString().split(':')[3].split('/')[0]) + let host = 1 + while ([...used_ipv4_hosts,...used_ipv6_hosts].includes(host.toString())) host++ + + // Create IP Addresses and keys + const ipv4_addr = `${env.IPV4_NET}.${subnet}.${host}` + const ipv6_addr = `${env.IPV6_NET}:${subnet}:${host}` + const keypair = await wg.generateKeypair() + const pubkey = keypair[0] + const privkey = keypair[1] + + // Create [Peer] sections for each env.SERVERS + let client_peers = [] + for (const server of env.SERVERS) { + const psk = await wg.generatePSK() + client_peers.push(` +[Peer] # ${server.host}.${env.TLD} +PublicKey = ${server.pubkey} +PresharedKey = ${psk} +AllowedIPs = ${server.ipv4}/32, ${server.ipv6}/128 +Endpoint = ${server.endpoint} +PersistentKeepAlive = 25`) + const server_config = `\n +[Peer] # ${hostname}.${username}.${env.TLD} +PublicKey = ${pubkey} +PresharedKey = ${psk} +AllowedIPs = ${ipv4_addr}/32, ${ipv6_addr}/128` + if (server.host===env.LOCAL_SERVER) { + // Add server_config to wg0.conf + fs.appendFile(env.WG_CONFIG_FILE,server_config) + } else { + //TODO: Send server_config to `${server.host}.gf4` + console.log(`Sending config to ${server.host}.gf4`) + } + } + + //TODO: Nameserver config + + // Generate config + const listen_port = Math.floor(50000 + Math.random() * 10000) + const config = `[Interface] +PrivateKey = ${privkey} +Address = ${ipv4_addr}/${env.IPV4_CIDR}, ${ipv6_addr}/${env.IPV6_CIDR} +DNS = ${req.DNS_SERVERS_STRING} +ListenPort = ${listen_port} +PostUp = resolvectl domain ${env.TLD} ${env.TLD} +${client_peers.join('\n')}` + + // Send config to user + res.setHeader('content-type', 'text/plain') + res.send(config) + } + +} \ No newline at end of file diff --git a/del.js b/del.js new file mode 100644 index 0000000..2cb6a71 --- /dev/null +++ b/del.js @@ -0,0 +1,12 @@ +'use strict' +const fs = require('fs').promises +const env = require('./env.json') + +module.exports = async (req, res) => { + const privkey = req.query['del'] + + console.log(`Received request from ${req.requester} to delete ${privkey}`) + res.setHeader('content-type', 'text/plain') + res.send(`Delete ${privkey}`) + +} \ No newline at end of file diff --git a/index.js b/index.js index 44ba47b..1a6e58d 100644 --- a/index.js +++ b/index.js @@ -1,7 +1,4 @@ 'use strict' -const fs = require('fs').promises -const app = require('express')() -const wg = require('./wireguard') const env = require('./env.json') // Determine DNS servers string from environment file @@ -11,145 +8,21 @@ for (const server of env.SERVERS.filter((server) => server.dns) ){ if (server.ipv6) DNS_SERVERS.push(server.ipv6) } const DNS_SERVERS_STRING = DNS_SERVERS.join(', ') +const getDnsServers = (req, res, next) => { + req.DNS_SERVERS_STRING = DNS_SERVERS_STRING + next() +} -// API -app.get('/', async (req,res) => { +// Determine request IP +const getRequester = (req, res, next) => { + req.requester = (env.ENV==='prod') + ?req.ip.replace('::ffff:','') + :'10.4.1.1' + next() +} - const privkey = req.query['del'] - const hostname = req.query['add'] - const requester = (env.ENV==='prod') - ?req.ip.replace('::ffff:','') - :'10.4.1.1' - - - // Delete a peer - if (privkey) { - console.log(`Received request from ${requester} to delete ${privkey}`) - res.setHeader('content-type', 'text/plain') - res.send(`Delete ${privkey}`) - } - - // Add a peer - else if (hostname) { - console.log(`New peer request from ${requester}`) - - // Determine user subnet - let subnet - if (requester.includes(env.IPV4_NET)) subnet = requester.split('.').slice(-2,-1)[0] - else if (requester.includes(env.IPV6_NET)) subnet = requester.split(':').slice(-2,-1)[0] - else console.log(`Received add request from ${requester}, which does not appear to be from the network.`) - - // Read wg.conf file for this user's other devices - const userpeers = (await fs.readFile(env.WG_CONFIG_FILE)).toString() - .split('\n\n').filter( (paragraph) => { - return paragraph.includes('[Peer]') - }).filter( (peer) => { - return peer.includes(`${env.IPV4_NET}.${subnet}`) || peer.includes(`${env.IPV6_NET}:${subnet}`) - }) - let found_usernames = [] - let found_ipv6s = [] - let found_ipv4s = [] - for (const userpeer of userpeers) { - for (const line of userpeer.split('\n')) { - if (line.includes('[Peer]')) { - // Check if host exists - if (line.includes(`# ${hostname}.`)) { - console.log(`Host already exists for ${hostname}`) - res.sendStatus(500); return - } - found_usernames.push(line.split('.').slice(-2,-1)[0]) - } - else if (line.includes('AllowedIPs')) { - const ips = line.split('=')[1].split(',') - found_ipv4s.push(ips.filter( (ip) => ip.includes(env.IPV4_NET) )[0].trim()) - found_ipv6s.push(ips.filter( (ip) => ip.includes(env.IPV6_NET) )[0].trim()) - } - } - } - // Check that all IP addresses are in correct subnet or error out - if (!found_ipv4s.every((found_ipv4) => - found_ipv4.toString().includes(`${env.IPV4_NET}.${subnet}.`))) { - console.log(`Found unmatching IPv4 address subnets for ${requester}: ${found_ipv4s}`) - res.sendStatus(500); return - } else if (!found_ipv6s.every((found_ipv6) => - found_ipv6.toString().includes(`${env.IPV6_NET}:${subnet}:`))) { - console.log(`Found unmatching IPv6 address subnets for ${requester}: ${found_ipv6s}`) - res.sendStatus(500); return - - // Check that the requester is "on the list" - // Should never get here because this requester can't access this IP! - } else if (found_usernames.length ===0) { - console.log(`Received request from ${requester} not in wg.conf!`) - res.sendStatus(500); return - // Check that all usernames are correct or error out - // https://stackoverflow.com/a/35568895 - } else if (!found_usernames.every( (v,i,r) => v === r[0] )) { - console.log(`Found unmatching usernames for ${requester}: ${found_usernames.toString()}`) - res.sendStatus(500); return - - // Everything looks good! Proceed - } else { - const username = found_usernames[0] - console.log(`${requester} must be ${username}`) - - // Find next available host part in config - const used_ipv4_hosts = found_ipv4s.map((found_ipv4) => - found_ipv4.toString().split('.')[3].split('/')[0]) - const used_ipv6_hosts = found_ipv6s.map((found_ipv6) => - found_ipv6.toString().split(':')[3].split('/')[0]) - let host = 1 - while ([...used_ipv4_hosts,...used_ipv6_hosts].includes(host.toString())) host++ - - // Create IP Addresses and keys - const ipv4_addr = `${env.IPV4_NET}.${subnet}.${host}` - const ipv6_addr = `${env.IPV6_NET}:${subnet}:${host}` - const keypair = await wg.generateKeypair() - const pubkey = keypair[0] - const privkey = keypair[1] - - // Create [Peer] sections for each env.SERVERS - let client_peers = [] - for (const server of env.SERVERS) { - const psk = await wg.generatePSK() - client_peers.push(` -[Peer] # ${server.host}.${env.TLD} -PublicKey = ${server.pubkey} -PresharedKey = ${psk} -AllowedIPs = ${server.ipv4}/32, ${server.ipv6}/128 -Endpoint = ${server.endpoint} -PersistentKeepAlive = 25`) - const server_config = `\n -[Peer] # ${hostname}.${username}.${env.TLD} -PublicKey = ${pubkey} -PresharedKey = ${psk} -AllowedIPs = ${ipv4_addr}/32, ${ipv6_addr}/128` - if (server.host===env.LOCAL_SERVER) { - // Add server_config to wg0.conf - fs.appendFile(env.WG_CONFIG_FILE,server_config) - } else { - //TODO: Send server_config to `${server.host}.gf4` - console.log(`Sending config to ${server.host}.gf4`) - } - } - - //TODO: Nameserver config - - // Generate config - const listen_port = Math.floor(50000 + Math.random() * 10000) - const config = `[Interface] -PrivateKey = ${privkey} -Address = ${ipv4_addr}/${env.IPV4_CIDR}, ${ipv6_addr}/${env.IPV6_CIDR} -DNS = ${DNS_SERVERS_STRING} -ListenPort = ${listen_port} -PostUp = resolvectl domain ${env.TLD} ${env.TLD} -${client_peers.join('\n')}` - - // Send config to user - res.setHeader('content-type', 'text/plain') - res.send(config) - } - } - - // Invalid querystring - else { console.log(`Invalid querystring: ${req.query}`) } -}).listen(env.PORT) +// Main +require('express')() + .get('/add', getRequester, getDnsServers, require('./add.js')) + .get('/del', getRequester, require('./del.js')) + .listen(env.PORT)