From bed0300e4b687aec2043197a2049f73c42d866b4 Mon Sep 17 00:00:00 2001 From: David White Date: Fri, 9 Feb 2024 14:52:50 +0000 Subject: [PATCH 01/18] First commit --- ZelBack/config/default.js | 1 + .../src/services/appVerificationService.js | 170 ++++++++++++++++++ ZelBack/src/services/appsService.js | 1 + ZelBack/src/services/dockerService.js | 48 +++++ ZelBack/src/services/fluxNetworkHelper.js | 53 +++++- ZelBack/src/services/pgpService.js | 4 +- ZelBack/src/services/serviceHelper.js | 24 ++- ZelBack/src/services/serviceManager.js | 5 + 8 files changed, 301 insertions(+), 5 deletions(-) create mode 100644 ZelBack/src/services/appVerificationService.js diff --git a/ZelBack/config/default.js b/ZelBack/config/default.js index f17c7830a..84aadbc80 100644 --- a/ZelBack/config/default.js +++ b/ZelBack/config/default.js @@ -9,6 +9,7 @@ module.exports = { server: { allowedPorts: [16127, 16137, 16147, 16157, 16167, 16177, 16187, 16197], apiport: 16127, // homeport is -1, ssl port is +1 + appVerificationAddress: "169.254.42.42", }, database: { url: '127.0.0.1', diff --git a/ZelBack/src/services/appVerificationService.js b/ZelBack/src/services/appVerificationService.js new file mode 100644 index 000000000..594b0ab5d --- /dev/null +++ b/ZelBack/src/services/appVerificationService.js @@ -0,0 +1,170 @@ +/** + * GetNodeIdentity | NodeBApp -> NodeB (so other end can get public pgp key) + * NodeIdentitySent | NodeBApp -> NodeAApp + * ChallengeRequest | NodeAApp -> NodeA (contains target ip, apiport via identity) + * ChallengeCreated | NodeA (Gets pgp pubkey from ip apiport) + * ChallengeSent | NodeAApp -> NodeBApp + * ChallengeDecryptRequest | NodeBApp -> NodeB + * ChallengeDecrypted | NodeB + * DecryptedSent | NodeBApp -> NodeAApp + * Verified | NodeAApp has now verified NodeBApp + */ + +const config = require('config'); +const dockerService = require('./dockerService'); +const serviceHelper = require('./serviceHelper'); +const pgpSerivce = require('./pgpService'); +const messageHelper = require('./messageHelper'); +const daemonServiceUtils = require('./daemonService/daemonServiceUtils') + +const { randomBytes } = require('node:crypto'); +const express = require('express'); + +let server = null; + +async function generateChallengeMessage(req, res) { + const parsedBody = serviceHelper.ensureObject(req.body); + const { identity } = parsedBody; + + if (!identity) { + res.statusMessage = "Authenticating node identity is required"; + res.status(422).end(); + return; + } + + const app = await dockerService.getAppNameByContainerIp(req.socket.remoteAddress); + if (!app) { + res.statusMessage = "You are not authorized for this endpoint"; + res.status(403).end(); + return; + } + + const fluxnode = daemonServiceUtils.executeCall("listfluxnodes", [identity.txhash]); + + if (!fluxnode) { + res.statusMessage = 'Unable to find node identity in deterministicfluxnodelist' + res.status(422).end(); + return; + } + + // this is ridiculous having to do this all the time. The node identity should always include the port + const [ip, apiport] = fluxnode.ip.includes(":") ? fluxnode.ip.split(":") : [fluxnode.ip, "16127"] + + const message = randomBytes(16).toString('hex'); + const toEncrypt = JSON.stringify({ app, message }) + + // https://1-2-3-4-16127.node.api.runonflux.io/flux/pgp + const hyphenEncodedHostname = `${ip.split(".").join("-")}-${apiport}` + const pgpEndpoint = `http://${hyphenEncodedHostname}.node.api.runonflux.io/flux/pgp` + + const { data: pgpPubKeyRes } = await serviceHelper.axiosGet(pgpEndpoint, { timeout: 2000 }) + + if (!pgpPubKeyRes?.status === "success") { + res.statusMessage = "Unable to retrieve pgp key for target" + res.status(422).end(); + } + + const encrypted = await pgpSerivce.encryptMessage(toEncrypt, [pgpPubKeyRes.data]) + + const dataMessage = messageHelper.createDataMessage({ message, encrypted }); + return res ? res.json(dataMessage) : dataMessage; +} + +async function getNodeIdentity(req, res) { + const app = await dockerService.getAppNameByContainerIp(req.socket.remoteAddress); + if (!app) { + res.statusMessage = "You are not authorized for this endpoint"; + res.status(403).end(); + return; + } + + let outPoint = null; + try { + // this is reliant on fluxd running + const res = await generalService.obtainNodeCollateralInformation(); + outPoint = { txhash: res.txhash, outidx: res.txindex }; + } catch { + log.error('Error getting collateral info from daemon.'); + } + + if (!outPoint) { + res.statusMessage = 'Unable to get node identity.. try again later' + res.status(503).end(); + return; + } + + const message = messageHelper.createDataMessage(outPoint); + + return res ? res.json(message) : message; +} + +async function decryptChallengeMessage(req, res) { + const app = await dockerService.getAppNameByContainerIp(req.socket.remoteAddress); + if (!app) { + res.statusMessage = "You are not authorized for this endpoint"; + res.status(403).end(); + return; + } + + const parsedBody = serviceHelper.ensureObject(req.body); + const { encrypted } = parsedBody; + + if (!encrypted) { + res.statusMessage = "Encrypted message not provided"; + res.status(422).end(); + return; + } + + const pgpPrivateKey = userconfig.initial.pgpPrivateKey; + + if (!pgpPrivateKey) { + res.statusMessage = "Pgp key not set" + res.status(500).end(); + return; + } + + const decrypted = await pgpSerivce.decryptMessage(encrypted, pgpPrivateKey) + + if (!decrypted) { + res.statusMessage = "Unable to decrypt message"; + res.status(500).end(); + } + + const challenge = JSON.parse(decrypted); + + if (challenge.app !== app) { + res.status(403).end(); + return; + } + + const dataMessage = messageHelper.createDataMessage(challenge.message); + return res ? res.json(dataMessage) : dataMessage; +} + +function start() { + if (server) return; + + const app = express(); + app.use(express.json()); + app.post("/createchallenge", generateChallengeMessage) + app.post("/decryptchallenge", decryptChallengeMessage) + app.get("/nodeindentity", getNodeIdentity) + app.all('*', (_, res) => res.status(404).end()); + + const bindAddress = config.server.appVerificationAddress; + server = app.listen(80, bindAddress, () => { + console.log(`Server listening on port: 80 address: ${bindAddress}`) + }); +} + +function stop() { + if (server) { + server.close(); + server = null; + } +} + +module.exports = { + start, + stop, +}; diff --git a/ZelBack/src/services/appsService.js b/ZelBack/src/services/appsService.js index 758d4733d..751b2aba2 100644 --- a/ZelBack/src/services/appsService.js +++ b/ZelBack/src/services/appsService.js @@ -3383,6 +3383,7 @@ async function registerAppLocally(appSpecs, componentSpecs, res) { if (!fluxNet) { throw new Error(`Flux App network of ${appName} failed to initiate. Range already assigned to different application.`); } + await fluxNetworkHelper.allowDockerNetworksToAppVerification(fluxNet.IPAM.Config[0].Subnet); log.info(serviceHelper.ensureString(fluxNet)); const fluxNetResponse = { status: `Docker network of ${appName} initiated.`, diff --git a/ZelBack/src/services/dockerService.js b/ZelBack/src/services/dockerService.js index e5b2895be..96c0b1024 100644 --- a/ZelBack/src/services/dockerService.js +++ b/ZelBack/src/services/dockerService.js @@ -164,6 +164,52 @@ async function getDockerContainerByIdOrName(idOrName) { const dockerContainer = docker.getContainer(myContainer.Id); return dockerContainer; } + +/** + * + * @returns {Promise} + */ +async function getFluxDockerNetworkSubnets() { + const fluxNetworks = await docker.listNetworks({ + filters: JSON.stringify({ + name: ['fluxDockerNetwork'], + }) + }) + + const subnets = fluxNetworks.map((network) => network.IPAM.Config[0].Subnet); + + return subnets; +} + +async function getAppNameByContainerIp(ip) { + const fluxNetworks = await docker.listNetworks({ + filters: JSON.stringify({ + name: ['fluxDockerNetwork'], + }) + }) + + const fluxNetworkNames = fluxNetworks.map(n => n.Name) + + const networkPromises = [] + fluxNetworkNames.forEach((networkName) => { + const dockerNetwork = docker.getNetwork(networkName) + networkPromises.push(dockerNetwork.inspect()); + }) + + const fluxNetworkData = await Promise.all(networkPromises); + + let appName = null; + for (const fluxNetwork of fluxNetworkData) { + const subnet = fluxNetwork.IPAM.Config[0].Subnet + if (serviceHelper.ipInSubnet(ip, subnet)) { + appName = fluxNetwork.Name.split("_")[1]; + break; + } + } + + return appName; +} + /** * Returns low-level information about a container. * @@ -972,6 +1018,8 @@ module.exports = { createFluxDockerNetwork, getDockerContainerOnly, getDockerContainerByIdOrName, + getFluxDockerNetworkSubnets, + getAppNameByContainerIp, createFluxAppDockerNetwork, removeFluxAppDockerNetwork, pruneNetworks, diff --git a/ZelBack/src/services/fluxNetworkHelper.js b/ZelBack/src/services/fluxNetworkHelper.js index ff3cd383a..d2b3e59d7 100644 --- a/ZelBack/src/services/fluxNetworkHelper.js +++ b/ZelBack/src/services/fluxNetworkHelper.js @@ -20,6 +20,7 @@ const daemonServiceWalletRpcs = require('./daemonService/daemonServiceWalletRpcs const benchmarkService = require('./benchmarkService'); const verificationHelper = require('./verificationHelper'); const fluxCommunicationUtils = require('./fluxCommunicationUtils'); +const dockerService = require('./dockerService') const { outgoingConnections, outgoingPeers, incomingPeers, incomingConnections, } = require('./utils/establishedConnections'); @@ -1244,7 +1245,7 @@ async function allowPortApi(req, res) { /** * To check if a firewall is active. - * @returns {boolean} True if a firewall is active. Otherwise false. + * @returns {Promise} True if a firewall is active. Otherwise false. */ async function isFirewallActive() { try { @@ -1262,6 +1263,35 @@ async function isFirewallActive() { } } +/** + * + * @param {string} network + * docker network including mask to allow to verification. For example: 172.23.123.0/24 + * @returns {Promise} + */ +async function allowOnlyDockerNetworksToAppVerification() { + const firewallActive = await isFirewallActive(); + + if (!firewallActive) return; + + const appVerificationAddress = config.server.appVerificationAddress; + const allowDockerNetworks = `LANG="en_US.UTF-8" && sudo ufw allow from 172.23.0.0/16 proto tcp to ${appVerificationAddress}/32 port 80`; + // have to use iptables here as ufw won't filter loopback + const denyAllElse = `LANG="en_US.UTF-8" && sudo iptables -I INPUT -i lo ! -s 172.23.0.0/16 -d ${appVerificationAddress}/32 -J DROP`; + const cmdResA = await cmdAsync(allowDockerNetworks); + if (serviceHelper.ensureString(cmdResA).includes('updated') || serviceHelper.ensureString(cmdResA).includes('existing') || serviceHelper.ensureString(cmdResA).includes('added')) { + log.info(`Firewall adjusted for network: ${network} to address: ${appVerificationAddress}/32`); + } else { + log.warn(`Failed to adjust Firewall for network: ${network} to address: ${appVerificationAddress}/32`); + } + const cmdResB = await cmdAsync(denyAllElse); + if (serviceHelper.ensureString(cmdResB).includes('updated') || serviceHelper.ensureString(cmdResB).includes('existing') || serviceHelper.ensureString(cmdResB).includes('added')) { + log.info(`Firewall adjusted to deny access to: ${appVerificationAddress}/32`); + } else { + log.warn(`Failed to adjust Firewall access to: ${appVerificationAddress}/32`); + } +} + /** * To adjust a firewall to allow ports for Flux. */ @@ -1276,6 +1306,7 @@ async function adjustFirewall() { const fluxCommunicationPorts = config.server.allowedPorts; ports = ports.concat(fluxCommunicationPorts); const firewallActive = await isFirewallActive(); + if (firewallActive) { // eslint-disable-next-line no-restricted-syntax for (const port of ports) { @@ -1445,6 +1476,24 @@ async function installNetcat() { } } +/** + * Adds the 169.254 adddress to the loopback interface for use with the fluxapp + * verification service. + */ +async function addAppVerificationIpToLoopback() { + + try { + const cmdAsync = util.promisify(nodecmd.get); + // redirect stderr to make this idempotent, could also check exists first with: + // ip -f inet addr show lo | grep 169.254.42.42/32 + const ip = config.server.appVerificationAddress; + const exec = `sudo ip addr add ${ip}/32 dev lo 2>/dev/null`; + await cmdAsync(exec); + } catch (error) { + log.error(error); + } +} + module.exports = { minVersionSatisfy, isFluxAvailable, @@ -1472,6 +1521,8 @@ module.exports = { allowPort, allowOutPort, isFirewallActive, + addAppVerificationIpToLoopback, + allowOnlyDockerNetworksToAppVerification, // Exports for testing purposes setStoredFluxBenchAllowed, getStoredFluxBenchAllowed, diff --git a/ZelBack/src/services/pgpService.js b/ZelBack/src/services/pgpService.js index 392439eb4..95673ccb6 100644 --- a/ZelBack/src/services/pgpService.js +++ b/ZelBack/src/services/pgpService.js @@ -101,7 +101,7 @@ async function generateIdentity() { * To encrypt a message with an array of encryption public keys * @param {string} message Message to encrypt * @param {array} encryptionKeys Armored version of array of public key - * @returns {string} Return armored version of encrypted message + * @returns {Promise} Return armored version of encrypted message */ async function encryptMessage(message, encryptionKeys) { try { @@ -124,7 +124,7 @@ async function encryptMessage(message, encryptionKeys) { * To decrypt a message with an armored private key * @param {string} encryptedMessage Message to encrypt * @param {string} decryptionKey Armored version of private key - * @returns {string} Return plain text message + * @returns {Promise} Return plain text message */ async function decryptMessage(encryptedMessage, decryptionKey = userconfig.initial.pgpPrivateKey) { try { diff --git a/ZelBack/src/services/serviceHelper.js b/ZelBack/src/services/serviceHelper.js index 85dc168ae..2eb75d873 100644 --- a/ZelBack/src/services/serviceHelper.js +++ b/ZelBack/src/services/serviceHelper.js @@ -145,10 +145,10 @@ function isDecimalLimit(value, decimals = 8) { * To handle timeouts on axios connection. * @param {string} url URL. * @param {object} options Options object. - * @returns {object} Response. + * @returns {Promise} Response. */ // helper function for timeout on axios connection -const axiosGet = (url, options = {}) => { +const axiosGet = async (url, options = {}) => { if (!options.timeout) { // eslint-disable-next-line no-param-reassign options.timeout = 20000; @@ -200,6 +200,25 @@ function commandStringToArray(command) { return splitargs(command); } +/** + * To confirm if ip is in subnet + * @param {string} ip + * @param {string} subnet + * @returns {Boolean} + */ +function ipInSubnet(ip, subnet) { + let [network, mask] = subnet.split("/"); + + ip = Number(ip.split('.').reduce( + (ipInt, octet) => (ipInt << 8) + parseInt(octet || 0, 10), 0) + ); + network = Number(network.split('.').reduce( + (ipInt, octet) => (ipInt << 8) + parseInt(octet || 0, 10), 0) + ); + mask = parseInt('1'.repeat(mask) + '0'.repeat(32 - mask), 2); + return (ip & mask) == (network & mask); +} + module.exports = { ensureBoolean, ensureNumber, @@ -212,4 +231,5 @@ module.exports = { isDecimalLimit, dockerBufferToString, commandStringToArray, + ipInSubnet, }; diff --git a/ZelBack/src/services/serviceManager.js b/ZelBack/src/services/serviceManager.js index c9c6fefef..688c71325 100644 --- a/ZelBack/src/services/serviceManager.js +++ b/ZelBack/src/services/serviceManager.js @@ -14,6 +14,7 @@ const geolocationService = require('./geolocationService'); const upnpService = require('./upnpService'); const syncthingService = require('./syncthingService'); const pgpService = require('./pgpService'); +const appVerificationService = require('./appVerificationService') const apiPort = userconfig.initial.apiport || config.server.apiport; const development = userconfig.initial.development || false; @@ -35,6 +36,10 @@ async function startFluxFunctions() { }, 1 * 60 * 60 * 1000); // every 1 hours } + fluxNetworkHelper.addAppVerificationIpToLoopback(); + fluxNetworkHelper.allowOnlyDockerNetworksToAppVerification(); + appVerificationService.start(); + fluxNetworkHelper.installNetcat(); log.info('Initiating MongoDB connection'); await dbHelper.initiateDB(); // either true or throws error From da9c6e6e83c4f740a84ed589b27d8632f6b68c83 Mon Sep 17 00:00:00 2001 From: David White Date: Fri, 9 Feb 2024 15:13:33 +0000 Subject: [PATCH 02/18] Add missing log import and fix missing asyncCmd --- .../src/services/appVerificationService.js | 3 ++- ZelBack/src/services/fluxNetworkHelper.js | 26 ++++++++++++------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/ZelBack/src/services/appVerificationService.js b/ZelBack/src/services/appVerificationService.js index 594b0ab5d..c83562d83 100644 --- a/ZelBack/src/services/appVerificationService.js +++ b/ZelBack/src/services/appVerificationService.js @@ -11,6 +11,7 @@ */ const config = require('config'); +const log = require('../lib/log'); const dockerService = require('./dockerService'); const serviceHelper = require('./serviceHelper'); const pgpSerivce = require('./pgpService'); @@ -153,7 +154,7 @@ function start() { const bindAddress = config.server.appVerificationAddress; server = app.listen(80, bindAddress, () => { - console.log(`Server listening on port: 80 address: ${bindAddress}`) + log.info(`Server listening on port: 80 address: ${bindAddress}`) }); } diff --git a/ZelBack/src/services/fluxNetworkHelper.js b/ZelBack/src/services/fluxNetworkHelper.js index d2b3e59d7..3624a0941 100644 --- a/ZelBack/src/services/fluxNetworkHelper.js +++ b/ZelBack/src/services/fluxNetworkHelper.js @@ -1278,17 +1278,23 @@ async function allowOnlyDockerNetworksToAppVerification() { const allowDockerNetworks = `LANG="en_US.UTF-8" && sudo ufw allow from 172.23.0.0/16 proto tcp to ${appVerificationAddress}/32 port 80`; // have to use iptables here as ufw won't filter loopback const denyAllElse = `LANG="en_US.UTF-8" && sudo iptables -I INPUT -i lo ! -s 172.23.0.0/16 -d ${appVerificationAddress}/32 -J DROP`; - const cmdResA = await cmdAsync(allowDockerNetworks); - if (serviceHelper.ensureString(cmdResA).includes('updated') || serviceHelper.ensureString(cmdResA).includes('existing') || serviceHelper.ensureString(cmdResA).includes('added')) { - log.info(`Firewall adjusted for network: ${network} to address: ${appVerificationAddress}/32`); - } else { - log.warn(`Failed to adjust Firewall for network: ${network} to address: ${appVerificationAddress}/32`); + const cmdAsync = util.promisify(nodecmd.get); + try { + const cmdResA = await cmdAsync(allowDockerNetworks); + if (serviceHelper.ensureString(cmdResA).includes('updated') || serviceHelper.ensureString(cmdResA).includes('existing') || serviceHelper.ensureString(cmdResA).includes('added')) { + log.info(`Firewall adjusted for network: ${network} to address: ${appVerificationAddress}/32`); + } else { + log.warn(`Failed to adjust Firewall for network: ${network} to address: ${appVerificationAddress}/32`); + } + const cmdResB = await cmdAsync(denyAllElse); + if (serviceHelper.ensureString(cmdResB).includes('updated') || serviceHelper.ensureString(cmdResB).includes('existing') || serviceHelper.ensureString(cmdResB).includes('added')) { + log.info(`Firewall adjusted to deny access to: ${appVerificationAddress}/32`); + } else { + log.warn(`Failed to adjust Firewall access to: ${appVerificationAddress}/32`); + } } - const cmdResB = await cmdAsync(denyAllElse); - if (serviceHelper.ensureString(cmdResB).includes('updated') || serviceHelper.ensureString(cmdResB).includes('existing') || serviceHelper.ensureString(cmdResB).includes('added')) { - log.info(`Firewall adjusted to deny access to: ${appVerificationAddress}/32`); - } else { - log.warn(`Failed to adjust Firewall access to: ${appVerificationAddress}/32`); + catch (err) { + log.error(err) } } From 34ce14bb576affb6d1a611bdd527cd9be5435920 Mon Sep 17 00:00:00 2001 From: David White Date: Fri, 9 Feb 2024 15:32:46 +0000 Subject: [PATCH 03/18] Make ip add for verification service idempotent --- ZelBack/src/services/fluxNetworkHelper.js | 29 ++++++++++++++++------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/ZelBack/src/services/fluxNetworkHelper.js b/ZelBack/src/services/fluxNetworkHelper.js index 3624a0941..edab53ce6 100644 --- a/ZelBack/src/services/fluxNetworkHelper.js +++ b/ZelBack/src/services/fluxNetworkHelper.js @@ -1487,16 +1487,29 @@ async function installNetcat() { * verification service. */ async function addAppVerificationIpToLoopback() { + const cmdAsync = util.promisify(nodecmd.get); + + // could also check exists first with: + // ip -f inet addr show lo | grep 169.254.42.42/32 + const ip = config.server.appVerificationAddress; + const addIp = `sudo ip addr add ${ip}/32 dev lo`; + let ok = false; try { - const cmdAsync = util.promisify(nodecmd.get); - // redirect stderr to make this idempotent, could also check exists first with: - // ip -f inet addr show lo | grep 169.254.42.42/32 - const ip = config.server.appVerificationAddress; - const exec = `sudo ip addr add ${ip}/32 dev lo 2>/dev/null`; - await cmdAsync(exec); - } catch (error) { - log.error(error); + await cmdAsync(addIp); + ok = true; + } catch (err) { + if (serviceHelper.ensureString(err).includes('File exists')) { + ok = true; + } else { + log.error(err); + } + } + + if (ok) { + log.info(`appVerification IP: ${ip} added to loopback interface`); + } else { + log.warn(`Failed to add appVerification IP ${ip} to loopback interface`); } } From b2326da35d0807660718d5eb1fbac44510b43eef Mon Sep 17 00:00:00 2001 From: David White Date: Fri, 9 Feb 2024 15:42:27 +0000 Subject: [PATCH 04/18] Fix up catch for set verification ip --- ZelBack/src/services/fluxNetworkHelper.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ZelBack/src/services/fluxNetworkHelper.js b/ZelBack/src/services/fluxNetworkHelper.js index edab53ce6..29e2f3ce7 100644 --- a/ZelBack/src/services/fluxNetworkHelper.js +++ b/ZelBack/src/services/fluxNetworkHelper.js @@ -1499,7 +1499,7 @@ async function addAppVerificationIpToLoopback() { await cmdAsync(addIp); ok = true; } catch (err) { - if (serviceHelper.ensureString(err).includes('File exists')) { + if (err.message.includes('File exists')) { ok = true; } else { log.error(err); From 8ef42775e85f6b386de4f29d0f26b4b0c2bef7a9 Mon Sep 17 00:00:00 2001 From: David White Date: Fri, 9 Feb 2024 15:44:53 +0000 Subject: [PATCH 05/18] Fix up logging --- ZelBack/src/services/fluxNetworkHelper.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/ZelBack/src/services/fluxNetworkHelper.js b/ZelBack/src/services/fluxNetworkHelper.js index 29e2f3ce7..da8218a50 100644 --- a/ZelBack/src/services/fluxNetworkHelper.js +++ b/ZelBack/src/services/fluxNetworkHelper.js @@ -1274,17 +1274,18 @@ async function allowOnlyDockerNetworksToAppVerification() { if (!firewallActive) return; + const fluxAppDockerNetworks = '172.23.0.0/16'; const appVerificationAddress = config.server.appVerificationAddress; - const allowDockerNetworks = `LANG="en_US.UTF-8" && sudo ufw allow from 172.23.0.0/16 proto tcp to ${appVerificationAddress}/32 port 80`; + const allowDockerNetworks = `LANG="en_US.UTF-8" && sudo ufw allow from ${fluxAppDockerNetworks} proto tcp to ${appVerificationAddress}/32 port 80`; // have to use iptables here as ufw won't filter loopback - const denyAllElse = `LANG="en_US.UTF-8" && sudo iptables -I INPUT -i lo ! -s 172.23.0.0/16 -d ${appVerificationAddress}/32 -J DROP`; + const denyAllElse = `LANG="en_US.UTF-8" && sudo iptables -I INPUT -i lo ! -s ${fluxAppDockerNetworks} -d ${appVerificationAddress}/32 -J DROP`; const cmdAsync = util.promisify(nodecmd.get); try { const cmdResA = await cmdAsync(allowDockerNetworks); if (serviceHelper.ensureString(cmdResA).includes('updated') || serviceHelper.ensureString(cmdResA).includes('existing') || serviceHelper.ensureString(cmdResA).includes('added')) { - log.info(`Firewall adjusted for network: ${network} to address: ${appVerificationAddress}/32`); + log.info(`Firewall adjusted for network: ${fluxAppDockerNetworks} to address: ${appVerificationAddress}/32`); } else { - log.warn(`Failed to adjust Firewall for network: ${network} to address: ${appVerificationAddress}/32`); + log.warn(`Failed to adjust Firewall for network: ${fluxAppDockerNetworks} to address: ${appVerificationAddress}/32`); } const cmdResB = await cmdAsync(denyAllElse); if (serviceHelper.ensureString(cmdResB).includes('updated') || serviceHelper.ensureString(cmdResB).includes('existing') || serviceHelper.ensureString(cmdResB).includes('added')) { From 609ab1f48b4c874be836709986a03066e57dce24 Mon Sep 17 00:00:00 2001 From: David White Date: Fri, 9 Feb 2024 15:45:54 +0000 Subject: [PATCH 06/18] Fix typo --- ZelBack/src/services/fluxNetworkHelper.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ZelBack/src/services/fluxNetworkHelper.js b/ZelBack/src/services/fluxNetworkHelper.js index da8218a50..ae7b9302f 100644 --- a/ZelBack/src/services/fluxNetworkHelper.js +++ b/ZelBack/src/services/fluxNetworkHelper.js @@ -1278,7 +1278,7 @@ async function allowOnlyDockerNetworksToAppVerification() { const appVerificationAddress = config.server.appVerificationAddress; const allowDockerNetworks = `LANG="en_US.UTF-8" && sudo ufw allow from ${fluxAppDockerNetworks} proto tcp to ${appVerificationAddress}/32 port 80`; // have to use iptables here as ufw won't filter loopback - const denyAllElse = `LANG="en_US.UTF-8" && sudo iptables -I INPUT -i lo ! -s ${fluxAppDockerNetworks} -d ${appVerificationAddress}/32 -J DROP`; + const denyAllElse = `LANG="en_US.UTF-8" && sudo iptables -I INPUT -i lo ! -s ${fluxAppDockerNetworks} -d ${appVerificationAddress}/32 -j DROP`; const cmdAsync = util.promisify(nodecmd.get); try { const cmdResA = await cmdAsync(allowDockerNetworks); From 494433ab3c52ccc35b0637c61a999a26c397698c Mon Sep 17 00:00:00 2001 From: David White Date: Fri, 9 Feb 2024 15:48:17 +0000 Subject: [PATCH 07/18] Fix up logging --- ZelBack/src/services/fluxNetworkHelper.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/ZelBack/src/services/fluxNetworkHelper.js b/ZelBack/src/services/fluxNetworkHelper.js index ae7b9302f..5b8d8b9a3 100644 --- a/ZelBack/src/services/fluxNetworkHelper.js +++ b/ZelBack/src/services/fluxNetworkHelper.js @@ -1287,12 +1287,9 @@ async function allowOnlyDockerNetworksToAppVerification() { } else { log.warn(`Failed to adjust Firewall for network: ${fluxAppDockerNetworks} to address: ${appVerificationAddress}/32`); } - const cmdResB = await cmdAsync(denyAllElse); - if (serviceHelper.ensureString(cmdResB).includes('updated') || serviceHelper.ensureString(cmdResB).includes('existing') || serviceHelper.ensureString(cmdResB).includes('added')) { - log.info(`Firewall adjusted to deny access to: ${appVerificationAddress}/32`); - } else { - log.warn(`Failed to adjust Firewall access to: ${appVerificationAddress}/32`); - } + // this doesn't give any output + await cmdAsync(denyAllElse); + log.info(`Firewall adjusted to deny access to: ${appVerificationAddress}/32`); } catch (err) { log.error(err) From e0988278cd292c92c6d47f7fd801e93fa4886219 Mon Sep 17 00:00:00 2001 From: David White Date: Fri, 9 Feb 2024 15:56:31 +0000 Subject: [PATCH 08/18] Add hostname for app verification to docker create --- ZelBack/src/services/dockerService.js | 1 + 1 file changed, 1 insertion(+) diff --git a/ZelBack/src/services/dockerService.js b/ZelBack/src/services/dockerService.js index 96c0b1024..fe26273bf 100644 --- a/ZelBack/src/services/dockerService.js +++ b/ZelBack/src/services/dockerService.js @@ -633,6 +633,7 @@ async function appDockerCreate(appSpecifications, appName, isComponent, fullAppS 'max-size': '20m', }, }, + ExtraHosts: [`app.identity.service:${config.server.appVerificationAddress}`] }, }; From 50d5cd0517a5fc511312b3bef8d7aff3a06da004 Mon Sep 17 00:00:00 2001 From: David White Date: Fri, 9 Feb 2024 16:38:08 +0000 Subject: [PATCH 09/18] Import missing generalService --- ZelBack/src/services/appVerificationService.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ZelBack/src/services/appVerificationService.js b/ZelBack/src/services/appVerificationService.js index c83562d83..5afcf2f87 100644 --- a/ZelBack/src/services/appVerificationService.js +++ b/ZelBack/src/services/appVerificationService.js @@ -16,7 +16,8 @@ const dockerService = require('./dockerService'); const serviceHelper = require('./serviceHelper'); const pgpSerivce = require('./pgpService'); const messageHelper = require('./messageHelper'); -const daemonServiceUtils = require('./daemonService/daemonServiceUtils') +const daemonServiceUtils = require('./daemonService/daemonServiceUtils'); +const generalService = require('./generalService'); const { randomBytes } = require('node:crypto'); const express = require('express'); From e4b1f9d7585b55f61f837127c8bd74c4bf9d94d2 Mon Sep 17 00:00:00 2001 From: David White Date: Fri, 9 Feb 2024 17:16:52 +0000 Subject: [PATCH 10/18] Fix up identity check --- ZelBack/src/services/appVerificationService.js | 12 ++++++++---- .../src/services/daemonService/daemonServiceUtils.js | 2 +- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/ZelBack/src/services/appVerificationService.js b/ZelBack/src/services/appVerificationService.js index 5afcf2f87..c5e2b3f1b 100644 --- a/ZelBack/src/services/appVerificationService.js +++ b/ZelBack/src/services/appVerificationService.js @@ -21,6 +21,7 @@ const generalService = require('./generalService'); const { randomBytes } = require('node:crypto'); const express = require('express'); +const c = require('config'); let server = null; @@ -28,8 +29,8 @@ async function generateChallengeMessage(req, res) { const parsedBody = serviceHelper.ensureObject(req.body); const { identity } = parsedBody; - if (!identity) { - res.statusMessage = "Authenticating node identity is required"; + if (!identity || identity.length !== 64) { + res.statusMessage = "Authenticating node identity is required (txid)"; res.status(422).end(); return; } @@ -41,14 +42,17 @@ async function generateChallengeMessage(req, res) { return; } - const fluxnode = daemonServiceUtils.executeCall("listfluxnodes", [identity.txhash]); + const fluxnodeRes = await daemonServiceUtils.executeCall("listfluxnodes", [identity]); - if (!fluxnode) { + if (!fluxnodeRes || fluxnodeRes.status !== "success" || !fluxnodeRes.data.length) { res.statusMessage = 'Unable to find node identity in deterministicfluxnodelist' res.status(422).end(); return; } + // check if more than one?!? + const fluxnode = fluxnodeRes.data[0]; + // this is ridiculous having to do this all the time. The node identity should always include the port const [ip, apiport] = fluxnode.ip.includes(":") ? fluxnode.ip.split(":") : [fluxnode.ip, "16127"] diff --git a/ZelBack/src/services/daemonService/daemonServiceUtils.js b/ZelBack/src/services/daemonService/daemonServiceUtils.js index d2180a0fa..4ed7b2c1d 100644 --- a/ZelBack/src/services/daemonService/daemonServiceUtils.js +++ b/ZelBack/src/services/daemonService/daemonServiceUtils.js @@ -37,7 +37,7 @@ let daemonCallRunning = false; * To execute a remote procedure call (RPC). * @param {string} rpc Remote procedure call. * @param {string[]} params RPC parameters. - * @returns {object} Message. + * @returns {Promise} Message. */ async function executeCall(rpc, params) { const rpcparameters = params || []; From bcdca42e03eb334cc7a0e01e25eaacd3f0de2420 Mon Sep 17 00:00:00 2001 From: David White Date: Fri, 9 Feb 2024 17:43:27 +0000 Subject: [PATCH 11/18] Remove extra config import --- ZelBack/src/services/appVerificationService.js | 1 - 1 file changed, 1 deletion(-) diff --git a/ZelBack/src/services/appVerificationService.js b/ZelBack/src/services/appVerificationService.js index c5e2b3f1b..0dca9ac10 100644 --- a/ZelBack/src/services/appVerificationService.js +++ b/ZelBack/src/services/appVerificationService.js @@ -21,7 +21,6 @@ const generalService = require('./generalService'); const { randomBytes } = require('node:crypto'); const express = require('express'); -const c = require('config'); let server = null; From 30151ea7f55af767d2a515b22c09b20200124f9f Mon Sep 17 00:00:00 2001 From: David White Date: Fri, 9 Feb 2024 17:49:41 +0000 Subject: [PATCH 12/18] Remove old flux network allow (not doing this anymore, just allow entire subnet --- ZelBack/src/services/appsService.js | 1 - 1 file changed, 1 deletion(-) diff --git a/ZelBack/src/services/appsService.js b/ZelBack/src/services/appsService.js index 751b2aba2..758d4733d 100644 --- a/ZelBack/src/services/appsService.js +++ b/ZelBack/src/services/appsService.js @@ -3383,7 +3383,6 @@ async function registerAppLocally(appSpecs, componentSpecs, res) { if (!fluxNet) { throw new Error(`Flux App network of ${appName} failed to initiate. Range already assigned to different application.`); } - await fluxNetworkHelper.allowDockerNetworksToAppVerification(fluxNet.IPAM.Config[0].Subnet); log.info(serviceHelper.ensureString(fluxNet)); const fluxNetResponse = { status: `Docker network of ${appName} initiated.`, From ec76b6661b43f692c584589c7794e4aa1eeb08e1 Mon Sep 17 00:00:00 2001 From: David White Date: Fri, 9 Feb 2024 17:51:18 +0000 Subject: [PATCH 13/18] Remove ununsed docker service --- ZelBack/src/services/fluxNetworkHelper.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ZelBack/src/services/fluxNetworkHelper.js b/ZelBack/src/services/fluxNetworkHelper.js index 5b8d8b9a3..a4bbf75f0 100644 --- a/ZelBack/src/services/fluxNetworkHelper.js +++ b/ZelBack/src/services/fluxNetworkHelper.js @@ -20,7 +20,7 @@ const daemonServiceWalletRpcs = require('./daemonService/daemonServiceWalletRpcs const benchmarkService = require('./benchmarkService'); const verificationHelper = require('./verificationHelper'); const fluxCommunicationUtils = require('./fluxCommunicationUtils'); -const dockerService = require('./dockerService') + const { outgoingConnections, outgoingPeers, incomingPeers, incomingConnections, } = require('./utils/establishedConnections'); From 177210a8cb6b52c7a4facbbbde82bc9f4f69fc87 Mon Sep 17 00:00:00 2001 From: David White Date: Sat, 10 Feb 2024 07:39:17 +0000 Subject: [PATCH 14/18] Lint --- ZelBack/config/default.js | 2 +- .../src/services/appVerificationService.js | 62 +++++++++---------- ZelBack/src/services/dockerService.js | 23 +++---- ZelBack/src/services/fluxNetworkHelper.js | 7 +-- ZelBack/src/services/serviceHelper.js | 23 ++++--- ZelBack/src/services/serviceManager.js | 2 +- 6 files changed, 59 insertions(+), 60 deletions(-) diff --git a/ZelBack/config/default.js b/ZelBack/config/default.js index 84aadbc80..afe5fdb19 100644 --- a/ZelBack/config/default.js +++ b/ZelBack/config/default.js @@ -9,7 +9,7 @@ module.exports = { server: { allowedPorts: [16127, 16137, 16147, 16157, 16167, 16177, 16187, 16197], apiport: 16127, // homeport is -1, ssl port is +1 - appVerificationAddress: "169.254.42.42", + appVerificationAddress: '169.254.42.42', }, database: { url: '127.0.0.1', diff --git a/ZelBack/src/services/appVerificationService.js b/ZelBack/src/services/appVerificationService.js index 0dca9ac10..f8ecadf0b 100644 --- a/ZelBack/src/services/appVerificationService.js +++ b/ZelBack/src/services/appVerificationService.js @@ -29,22 +29,22 @@ async function generateChallengeMessage(req, res) { const { identity } = parsedBody; if (!identity || identity.length !== 64) { - res.statusMessage = "Authenticating node identity is required (txid)"; + res.statusMessage = 'Authenticating node identity is required (txid)'; res.status(422).end(); return; } const app = await dockerService.getAppNameByContainerIp(req.socket.remoteAddress); if (!app) { - res.statusMessage = "You are not authorized for this endpoint"; + res.statusMessage = 'You are not authorized for this endpoint'; res.status(403).end(); return; } - const fluxnodeRes = await daemonServiceUtils.executeCall("listfluxnodes", [identity]); + const fluxnodeRes = await daemonServiceUtils.executeCall('listfluxnodes', [identity]); - if (!fluxnodeRes || fluxnodeRes.status !== "success" || !fluxnodeRes.data.length) { - res.statusMessage = 'Unable to find node identity in deterministicfluxnodelist' + if (!fluxnodeRes || fluxnodeRes.status !== 'success' || !fluxnodeRes.data.length) { + res.statusMessage = 'Unable to find node identity in deterministicfluxnodelist'; res.status(422).end(); return; } @@ -53,32 +53,32 @@ async function generateChallengeMessage(req, res) { const fluxnode = fluxnodeRes.data[0]; // this is ridiculous having to do this all the time. The node identity should always include the port - const [ip, apiport] = fluxnode.ip.includes(":") ? fluxnode.ip.split(":") : [fluxnode.ip, "16127"] + const [ip, apiport] = fluxnode.ip.includes(':') ? fluxnode.ip.split(':') : [fluxnode.ip, '16127']; const message = randomBytes(16).toString('hex'); - const toEncrypt = JSON.stringify({ app, message }) + const toEncrypt = JSON.stringify({ app, message }); // https://1-2-3-4-16127.node.api.runonflux.io/flux/pgp - const hyphenEncodedHostname = `${ip.split(".").join("-")}-${apiport}` - const pgpEndpoint = `http://${hyphenEncodedHostname}.node.api.runonflux.io/flux/pgp` + const hyphenEncodedHostname = `${ip.split('.').join('-')}-${apiport}`; + const pgpEndpoint = `http://${hyphenEncodedHostname}.node.api.runonflux.io/flux/pgp`; - const { data: pgpPubKeyRes } = await serviceHelper.axiosGet(pgpEndpoint, { timeout: 2000 }) + const { data: pgpPubKeyRes } = await serviceHelper.axiosGet(pgpEndpoint, { timeout: 2000 }); - if (!pgpPubKeyRes?.status === "success") { - res.statusMessage = "Unable to retrieve pgp key for target" + if (!pgpPubKeyRes?.status === 'success') { + res.statusMessage = 'Unable to retrieve pgp key for target'; res.status(422).end(); } - const encrypted = await pgpSerivce.encryptMessage(toEncrypt, [pgpPubKeyRes.data]) + const encrypted = await pgpSerivce.encryptMessage(toEncrypt, [pgpPubKeyRes.data]); const dataMessage = messageHelper.createDataMessage({ message, encrypted }); - return res ? res.json(dataMessage) : dataMessage; + res.json(dataMessage); } async function getNodeIdentity(req, res) { const app = await dockerService.getAppNameByContainerIp(req.socket.remoteAddress); if (!app) { - res.statusMessage = "You are not authorized for this endpoint"; + res.statusMessage = 'You are not authorized for this endpoint'; res.status(403).end(); return; } @@ -86,27 +86,26 @@ async function getNodeIdentity(req, res) { let outPoint = null; try { // this is reliant on fluxd running - const res = await generalService.obtainNodeCollateralInformation(); - outPoint = { txhash: res.txhash, outidx: res.txindex }; + const collateral = await generalService.obtainNodeCollateralInformation(); + outPoint = { txhash: collateral.txhash, outidx: collateral.txindex }; } catch { log.error('Error getting collateral info from daemon.'); } if (!outPoint) { - res.statusMessage = 'Unable to get node identity.. try again later' + res.statusMessage = 'Unable to get node identity.. try again later'; res.status(503).end(); return; } const message = messageHelper.createDataMessage(outPoint); - - return res ? res.json(message) : message; + res.json(message); } async function decryptChallengeMessage(req, res) { const app = await dockerService.getAppNameByContainerIp(req.socket.remoteAddress); if (!app) { - res.statusMessage = "You are not authorized for this endpoint"; + res.statusMessage = 'You are not authorized for this endpoint'; res.status(403).end(); return; } @@ -115,23 +114,24 @@ async function decryptChallengeMessage(req, res) { const { encrypted } = parsedBody; if (!encrypted) { - res.statusMessage = "Encrypted message not provided"; + res.statusMessage = 'Encrypted message not provided'; res.status(422).end(); return; } - const pgpPrivateKey = userconfig.initial.pgpPrivateKey; + // eslint-disable-next-line no-undef + const { pgpPrivateKey } = userconfig.initial; if (!pgpPrivateKey) { - res.statusMessage = "Pgp key not set" + res.statusMessage = 'Pgp key not set'; res.status(500).end(); return; } - const decrypted = await pgpSerivce.decryptMessage(encrypted, pgpPrivateKey) + const decrypted = await pgpSerivce.decryptMessage(encrypted, pgpPrivateKey); if (!decrypted) { - res.statusMessage = "Unable to decrypt message"; + res.statusMessage = 'Unable to decrypt message'; res.status(500).end(); } @@ -143,7 +143,7 @@ async function decryptChallengeMessage(req, res) { } const dataMessage = messageHelper.createDataMessage(challenge.message); - return res ? res.json(dataMessage) : dataMessage; + res.json(dataMessage); } function start() { @@ -151,14 +151,14 @@ function start() { const app = express(); app.use(express.json()); - app.post("/createchallenge", generateChallengeMessage) - app.post("/decryptchallenge", decryptChallengeMessage) - app.get("/nodeindentity", getNodeIdentity) + app.post('/createchallenge', generateChallengeMessage); + app.post('/decryptchallenge', decryptChallengeMessage); + app.get('/nodeidentity', getNodeIdentity); app.all('*', (_, res) => res.status(404).end()); const bindAddress = config.server.appVerificationAddress; server = app.listen(80, bindAddress, () => { - log.info(`Server listening on port: 80 address: ${bindAddress}`) + log.info(`Server listening on port: 80 address: ${bindAddress}`); }); } diff --git a/ZelBack/src/services/dockerService.js b/ZelBack/src/services/dockerService.js index fe26273bf..8fbac8b1b 100644 --- a/ZelBack/src/services/dockerService.js +++ b/ZelBack/src/services/dockerService.js @@ -173,8 +173,8 @@ async function getFluxDockerNetworkSubnets() { const fluxNetworks = await docker.listNetworks({ filters: JSON.stringify({ name: ['fluxDockerNetwork'], - }) - }) + }), + }); const subnets = fluxNetworks.map((network) => network.IPAM.Config[0].Subnet); @@ -185,24 +185,25 @@ async function getAppNameByContainerIp(ip) { const fluxNetworks = await docker.listNetworks({ filters: JSON.stringify({ name: ['fluxDockerNetwork'], - }) - }) + }), + }); - const fluxNetworkNames = fluxNetworks.map(n => n.Name) + const fluxNetworkNames = fluxNetworks.map((n) => n.Name); - const networkPromises = [] + const networkPromises = []; fluxNetworkNames.forEach((networkName) => { - const dockerNetwork = docker.getNetwork(networkName) + const dockerNetwork = docker.getNetwork(networkName); networkPromises.push(dockerNetwork.inspect()); - }) + }); const fluxNetworkData = await Promise.all(networkPromises); let appName = null; + // eslint-disable-next-line no-restricted-syntax for (const fluxNetwork of fluxNetworkData) { - const subnet = fluxNetwork.IPAM.Config[0].Subnet + const subnet = fluxNetwork.IPAM.Config[0].Subnet; if (serviceHelper.ipInSubnet(ip, subnet)) { - appName = fluxNetwork.Name.split("_")[1]; + appName = fluxNetwork.Name.split('_')[1]; break; } } @@ -633,7 +634,7 @@ async function appDockerCreate(appSpecifications, appName, isComponent, fullAppS 'max-size': '20m', }, }, - ExtraHosts: [`app.identity.service:${config.server.appVerificationAddress}`] + ExtraHosts: [`app.identity.service:${config.server.appVerificationAddress}`], }, }; diff --git a/ZelBack/src/services/fluxNetworkHelper.js b/ZelBack/src/services/fluxNetworkHelper.js index a4bbf75f0..04e24c22d 100644 --- a/ZelBack/src/services/fluxNetworkHelper.js +++ b/ZelBack/src/services/fluxNetworkHelper.js @@ -1275,7 +1275,7 @@ async function allowOnlyDockerNetworksToAppVerification() { if (!firewallActive) return; const fluxAppDockerNetworks = '172.23.0.0/16'; - const appVerificationAddress = config.server.appVerificationAddress; + const { appVerificationAddress } = config.server; const allowDockerNetworks = `LANG="en_US.UTF-8" && sudo ufw allow from ${fluxAppDockerNetworks} proto tcp to ${appVerificationAddress}/32 port 80`; // have to use iptables here as ufw won't filter loopback const denyAllElse = `LANG="en_US.UTF-8" && sudo iptables -I INPUT -i lo ! -s ${fluxAppDockerNetworks} -d ${appVerificationAddress}/32 -j DROP`; @@ -1290,9 +1290,8 @@ async function allowOnlyDockerNetworksToAppVerification() { // this doesn't give any output await cmdAsync(denyAllElse); log.info(`Firewall adjusted to deny access to: ${appVerificationAddress}/32`); - } - catch (err) { - log.error(err) + } catch (err) { + log.error(err); } } diff --git a/ZelBack/src/services/serviceHelper.js b/ZelBack/src/services/serviceHelper.js index 2eb75d873..1fbbecfac 100644 --- a/ZelBack/src/services/serviceHelper.js +++ b/ZelBack/src/services/serviceHelper.js @@ -145,10 +145,10 @@ function isDecimalLimit(value, decimals = 8) { * To handle timeouts on axios connection. * @param {string} url URL. * @param {object} options Options object. - * @returns {Promise} Response. + * @returns {object} Response. */ // helper function for timeout on axios connection -const axiosGet = async (url, options = {}) => { +const axiosGet = (url, options = {}) => { if (!options.timeout) { // eslint-disable-next-line no-param-reassign options.timeout = 20000; @@ -207,16 +207,15 @@ function commandStringToArray(command) { * @returns {Boolean} */ function ipInSubnet(ip, subnet) { - let [network, mask] = subnet.split("/"); - - ip = Number(ip.split('.').reduce( - (ipInt, octet) => (ipInt << 8) + parseInt(octet || 0, 10), 0) - ); - network = Number(network.split('.').reduce( - (ipInt, octet) => (ipInt << 8) + parseInt(octet || 0, 10), 0) - ); - mask = parseInt('1'.repeat(mask) + '0'.repeat(32 - mask), 2); - return (ip & mask) == (network & mask); + const [network, mask] = subnet.split('/'); + + // eslint-disable-next-line no-bitwise + const ipAsInt = Number(ip.split('.').reduce((ipInt, octet) => (ipInt << 8) + parseInt(octet || 0, 10), 0)); + // eslint-disable-next-line no-bitwise + const networkAsInt = Number(network.split('.').reduce((ipInt, octet) => (ipInt << 8) + parseInt(octet || 0, 10), 0)); + const maskAsInt = parseInt('1'.repeat(mask) + '0'.repeat(32 - mask), 2); + // eslint-disable-next-line no-bitwise + return (ipAsInt & maskAsInt) === (networkAsInt & maskAsInt); } module.exports = { diff --git a/ZelBack/src/services/serviceManager.js b/ZelBack/src/services/serviceManager.js index 688c71325..7b2c660f5 100644 --- a/ZelBack/src/services/serviceManager.js +++ b/ZelBack/src/services/serviceManager.js @@ -14,7 +14,7 @@ const geolocationService = require('./geolocationService'); const upnpService = require('./upnpService'); const syncthingService = require('./syncthingService'); const pgpService = require('./pgpService'); -const appVerificationService = require('./appVerificationService') +const appVerificationService = require('./appVerificationService'); const apiPort = userconfig.initial.apiport || config.server.apiport; const development = userconfig.initial.development || false; From 46089d52761a93abd6ad4b54967300091e90d378 Mon Sep 17 00:00:00 2001 From: David White Date: Sat, 10 Feb 2024 07:59:35 +0000 Subject: [PATCH 15/18] Add middleware handler for malformed JSON --- ZelBack/src/lib/server.js | 15 +++++++++++++-- package.json | 1 - 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/ZelBack/src/lib/server.js b/ZelBack/src/lib/server.js index 8caf42e2d..17b7bbd12 100644 --- a/ZelBack/src/lib/server.js +++ b/ZelBack/src/lib/server.js @@ -1,15 +1,26 @@ const express = require('express'); const eWS = require('express-ws'); -const bodyParser = require('body-parser'); const cors = require('cors'); const morgan = require('morgan'); +function handleError(middleware, req, res, next) { + middleware(req, res, (err) => { + if (err) { + return res.sendStatus(400); + } + + next(); + }); +} + const expressWs = eWS(express()); const { app } = expressWs; app.use(morgan('combined')); -app.use(bodyParser.json()); app.use(cors()); +app.use((req, res, next) => { + handleError(express.json(), req, res, next); +}); require('../routes')(app, expressWs); diff --git a/package.json b/package.json index 5df2fefb3..a8e0147bb 100644 --- a/package.json +++ b/package.json @@ -78,7 +78,6 @@ "axios": "~0.27.2", "bitcoin": "~3.0.3", "bitcoinjs-message": "~2.2.0", - "body-parser": "~1.20.2", "compression": "~1.7.4", "config": "~3.3.9", "cors": "~2.8.5", From b1d3b13c2afabe9a2dee1e6e8473c157cd4dd64f Mon Sep 17 00:00:00 2001 From: David White Date: Sat, 10 Feb 2024 08:35:34 +0000 Subject: [PATCH 16/18] Move middleware to correct place --- ZelBack/src/lib/server.js | 15 ++------------- .../src/services/appVerificationService.js | 19 ++++++++++++++++++- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/ZelBack/src/lib/server.js b/ZelBack/src/lib/server.js index 17b7bbd12..8caf42e2d 100644 --- a/ZelBack/src/lib/server.js +++ b/ZelBack/src/lib/server.js @@ -1,26 +1,15 @@ const express = require('express'); const eWS = require('express-ws'); +const bodyParser = require('body-parser'); const cors = require('cors'); const morgan = require('morgan'); -function handleError(middleware, req, res, next) { - middleware(req, res, (err) => { - if (err) { - return res.sendStatus(400); - } - - next(); - }); -} - const expressWs = eWS(express()); const { app } = expressWs; app.use(morgan('combined')); +app.use(bodyParser.json()); app.use(cors()); -app.use((req, res, next) => { - handleError(express.json(), req, res, next); -}); require('../routes')(app, expressWs); diff --git a/ZelBack/src/services/appVerificationService.js b/ZelBack/src/services/appVerificationService.js index f8ecadf0b..560c88560 100644 --- a/ZelBack/src/services/appVerificationService.js +++ b/ZelBack/src/services/appVerificationService.js @@ -146,11 +146,28 @@ async function decryptChallengeMessage(req, res) { res.json(dataMessage); } +function handleError(middleware, req, res, next) { + middleware(req, res, (err) => { + if (err instanceof SyntaxError && err.status === 400 && 'body' in err) { + res.statusMessage = err.message; + return res.sendStatus(400); + } + else if (err) { + log.error(err); + return res.sendStatus(400); + } + + next(); + }); +} + function start() { if (server) return; const app = express(); - app.use(express.json()); + app.use((req, res, next) => { + handleError(express.json(), req, res, next); + }); app.post('/createchallenge', generateChallengeMessage); app.post('/decryptchallenge', decryptChallengeMessage); app.get('/nodeidentity', getNodeIdentity); From 155da84de796a5d1c5fa4691a1db27e48f5f6404 Mon Sep 17 00:00:00 2001 From: David White Date: Sat, 10 Feb 2024 08:37:26 +0000 Subject: [PATCH 17/18] Add body-parser back in --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index a8e0147bb..5df2fefb3 100644 --- a/package.json +++ b/package.json @@ -78,6 +78,7 @@ "axios": "~0.27.2", "bitcoin": "~3.0.3", "bitcoinjs-message": "~2.2.0", + "body-parser": "~1.20.2", "compression": "~1.7.4", "config": "~3.3.9", "cors": "~2.8.5", From f769352e9864d1ac979a94a16bb4b447cb1cea7a Mon Sep 17 00:00:00 2001 From: David White Date: Sat, 10 Feb 2024 09:43:33 +0000 Subject: [PATCH 18/18] Fix iptables rule so that its idempotent (and refactor) --- ZelBack/src/services/fluxNetworkHelper.js | 26 +++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/ZelBack/src/services/fluxNetworkHelper.js b/ZelBack/src/services/fluxNetworkHelper.js index 04e24c22d..075f820e2 100644 --- a/ZelBack/src/services/fluxNetworkHelper.js +++ b/ZelBack/src/services/fluxNetworkHelper.js @@ -1278,21 +1278,35 @@ async function allowOnlyDockerNetworksToAppVerification() { const { appVerificationAddress } = config.server; const allowDockerNetworks = `LANG="en_US.UTF-8" && sudo ufw allow from ${fluxAppDockerNetworks} proto tcp to ${appVerificationAddress}/32 port 80`; // have to use iptables here as ufw won't filter loopback - const denyAllElse = `LANG="en_US.UTF-8" && sudo iptables -I INPUT -i lo ! -s ${fluxAppDockerNetworks} -d ${appVerificationAddress}/32 -j DROP`; + const denyRule = `INPUT -i lo ! -s ${fluxAppDockerNetworks} -d ${appVerificationAddress}/32 -j DROP` + const checkDenyRule = `LANG="en_US.UTF-8" && sudo iptables -C ${denyRule}` + const denyAllElse = `LANG="en_US.UTF-8" && sudo iptables -I ${denyRule}`; + const cmdAsync = util.promisify(nodecmd.get); + try { - const cmdResA = await cmdAsync(allowDockerNetworks); - if (serviceHelper.ensureString(cmdResA).includes('updated') || serviceHelper.ensureString(cmdResA).includes('existing') || serviceHelper.ensureString(cmdResA).includes('added')) { + const cmd = await cmdAsync(allowDockerNetworks); + if (serviceHelper.ensureString(cmd).includes('updated') || serviceHelper.ensureString(cmd).includes('existing') || serviceHelper.ensureString(cmd).includes('added')) { log.info(`Firewall adjusted for network: ${fluxAppDockerNetworks} to address: ${appVerificationAddress}/32`); } else { log.warn(`Failed to adjust Firewall for network: ${fluxAppDockerNetworks} to address: ${appVerificationAddress}/32`); } - // this doesn't give any output - await cmdAsync(denyAllElse); - log.info(`Firewall adjusted to deny access to: ${appVerificationAddress}/32`); } catch (err) { log.error(err); } + + const denied = await cmdAsync(checkDenyRule).catch(async (err) => { + if (err.message.includes("Bad rule")) { + try { + await cmdAsync(denyAllElse); + log.info(`Firewall adjusted to deny access to: ${appVerificationAddress}/32`); + } catch (err) { + log.error(err); + } + } + }); + + if (denied) log.info(`Fireall already denying access to ${appVerificationAddress}/32`) } /**