From f0894c22e052c85b574016a62ce602e41bf3586b Mon Sep 17 00:00:00 2001 From: RunOnFlux Date: Mon, 25 Nov 2024 11:37:38 +0000 Subject: [PATCH] Update from https://github.com/RunOnFlux/flux/commit/55b1b129454dcf611cf6d982032857897ae46e2e --- services/dockerService.js | 32 +++++++++++ services/fluxNetworkHelper.js | 76 +++++++++++++++++++++++++ services/fluxNodeService.js | 101 +++++++++++++++++++++++++++++++++ services/geolocationService.js | 10 +++- services/serviceManager.js | 10 ++-- 5 files changed, 222 insertions(+), 7 deletions(-) create mode 100644 services/fluxNodeService.js diff --git a/services/dockerService.js b/services/dockerService.js index 19aa1570..d9ca5037 100644 --- a/services/dockerService.js +++ b/services/dockerService.js @@ -681,6 +681,7 @@ async function appDockerCreate(appSpecifications, appName, isComponent, fullAppS 'max-size': '20m', }, }, + ExtraHosts: [`fluxnode.service:${config.server.fluxNodeServiceAddress}`], }, }; @@ -1121,6 +1122,36 @@ async function dockerLogsFix() { } } +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; + // eslint-disable-next-line no-restricted-syntax + 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; +} + module.exports = { appDockerCreate, appDockerUpdateCpu, @@ -1166,4 +1197,5 @@ module.exports = { pruneNetworks, pruneVolumes, removeFluxAppDockerNetwork, + getAppNameByContainerIp, }; diff --git a/services/fluxNetworkHelper.js b/services/fluxNetworkHelper.js index 79882c78..73bf9fcf 100644 --- a/services/fluxNetworkHelper.js +++ b/services/fluxNetworkHelper.js @@ -1657,6 +1657,80 @@ async function allowNodeToBindPrivilegedPorts() { } } +/** + * docker network including mask to allow to verification. For example: 172.23.123.0/24 + * @returns {Promise} + */ +async function allowOnlyDockerNetworksToFluxNodeService() { + const firewallActive = await isFirewallActive(); + + if (!firewallActive) return; + + const fluxAppDockerNetworks = '172.23.0.0/16'; + const { fluxNodeServiceAddress } = config.server; + const allowDockerNetworks = `LANG="en_US.UTF-8" && sudo ufw allow from ${fluxAppDockerNetworks} proto tcp to ${fluxNodeServiceAddress}/32 port 80`; + // have to use iptables here as ufw won't filter loopback + const denyRule = `INPUT -i lo ! -s ${fluxAppDockerNetworks} -d ${fluxNodeServiceAddress}/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 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: ${fluxNodeServiceAddress}/32`); + } else { + log.warn(`Failed to adjust Firewall for network: ${fluxAppDockerNetworks} to address: ${fluxNodeServiceAddress}/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: ${fluxNodeServiceAddress}/32`); + } catch (error) { + log.error(error); + } + } + }); + + if (denied) log.info(`Fireall already denying access to ${fluxNodeServiceAddress}/32`); +} + +/** + * Adds the 169.254 adddress to the loopback interface for use with the flux node service. + */ +async function addFluxNodeServiceIpToLoopback() { + const cmdAsync = util.promisify(nodecmd.get); + + // could also check exists first with: + // ip -f inet addr show lo | grep 169.254.43.43/32 + const ip = config.server.fluxNodeServiceAddress; + const addIp = `sudo ip addr add ${ip}/32 dev lo`; + + let ok = false; + try { + await cmdAsync(addIp); + ok = true; + } catch (err) { + if (err.message.includes('File exists')) { + ok = true; + } else { + log.error(err); + } + } + + if (ok) { + log.info(`fluxNodeService IP: ${ip} added to loopback interface`); + } else { + log.warn(`Failed to add fluxNodeService IP ${ip} to loopback interface`); + } +} + module.exports = { isFluxAvailable, checkFluxAvailability, @@ -1704,4 +1778,6 @@ module.exports = { allowNodeToBindPrivilegedPorts, removeDockerContainerAccessToNonRoutable, getMaxNumberOfIpChanges, + allowOnlyDockerNetworksToFluxNodeService, + addFluxNodeServiceIpToLoopback, }; diff --git a/services/fluxNodeService.js b/services/fluxNodeService.js new file mode 100644 index 00000000..72afe069 --- /dev/null +++ b/services/fluxNodeService.js @@ -0,0 +1,101 @@ +/** + * Service that will be available to all docker apps on the network to get Host information + * Host Public IP + * Host Unique Identifier + * Host Geolocation + */ + +const config = require('config'); +const log = require('../lib/log'); +const messageHelper = require('./messageHelper'); +const geolocationService = require('./geolocationService'); +const fluxNetworkHelper = require('./fluxNetworkHelper'); +const generalService = require('./generalService'); +const dockerService = require('./dockerService'); + +const express = require('express'); + +let server = null; + +async function getHostInfo(req, res) { + try { + const app = await dockerService.getAppNameByContainerIp(req.socket.remoteAddress); + if (!app) { + const errMessage = messageHelper.errUnauthorizedMessage(); + res.json(errMessage); + } else { + const hostInfo = {}; + const nodeCollateralInfo = await generalService.obtainNodeCollateralInformation().catch(() => { throw new Error('Host Identifier information not available at the moment'); }); + hostInfo.id = nodeCollateralInfo.txhash + nodeCollateralInfo.txindex; + const myIP = await fluxNetworkHelper.getMyFluxIPandPort(); + if (myIP) { + hostInfo.ip = myIP.split(':')[0]; + const myGeo = await geolocationService.getNodeGeolocation(); + if (myGeo) { + delete myGeo.ip; + delete myGeo.org; + hostInfo.geo = myGeo; + } else { + throw new Error('Geolocation information not available at the moment'); + } + } else { + throw new Error('Host IP information not available at the moment'); + } + + const message = messageHelper.createDataMessage(hostInfo); + res.json(message); + } + } catch (error) { + log.error(`getHostInfo: ${error}`); + const errorResponse = messageHelper.createErrorMessage( + error.message || error, + error.name, + error.code, + ); + res.json(errorResponse); + } +} + +function handleError(middleware, req, res, next) { + // eslint-disable-next-line consistent-return + middleware(req, res, (err) => { + if (err instanceof SyntaxError && err.status === 400 && 'body' in err) { + res.statusMessage = err.message; + return res.sendStatus(400); + } + if (err) { + log.error(err); + return res.sendStatus(400); + } + + next(); + }); +} + +function start() { + if (server) return; + + const app = express(); + app.use((req, res, next) => { + handleError(express.json(), req, res, next); + }); + app.get('/hostinfo', getHostInfo); + app.all('*', (_, res) => res.status(404).end()); + + const bindAddress = config.server.fluxNodeServiceAddress; + server = app.listen(80, bindAddress, () => { + log.info(`Server listening on port: 80 address: ${bindAddress}`); + }); +} + +function stop() { + if (server) { + server.close(); + server = null; + } +} + +module.exports = { + start, + stop, +}; diff --git a/services/geolocationService.js b/services/geolocationService.js index 123110f2..086303b3 100644 --- a/services/geolocationService.js +++ b/services/geolocationService.js @@ -15,7 +15,11 @@ async function setNodeGeolocation() { try { const myIP = await fluxNetworkHelper.getMyFluxIPandPort(); if (!myIP) { - throw new Error('Flux IP not detected. Flux geolocation service is awaiting'); + log.error('Flux IP not detected. Flux geolocation service is awaiting'); + setTimeout(() => { + setNodeGeolocation(); + }, 10 * 1000); + return; } if (!storedGeolocation || myIP !== storedIp || execution % 4 === 0) { log.info(`Checking geolocation of ${myIP}`); @@ -66,9 +70,9 @@ async function setNodeGeolocation() { } } execution += 1; - setTimeout(() => { // executes again in 12h + setTimeout(() => { // executes again in 24h setNodeGeolocation(); - }, 12 * 60 * 60 * 1000); + }, 24 * 60 * 60 * 1000); } catch (error) { log.error(`Failed to get Geolocation with ${error}`); log.error(error); diff --git a/services/serviceManager.js b/services/serviceManager.js index 3873af0e..b35e1fa1 100644 --- a/services/serviceManager.js +++ b/services/serviceManager.js @@ -17,6 +17,7 @@ const pgpService = require('./pgpService'); const dockerService = require('./dockerService'); const backupRestoreService = require('./backupRestoreService'); const systemService = require('./systemService'); +const fluxNodeService = require('./fluxNodeService'); const apiPort = userconfig.initial.apiport || config.server.apiport; const development = userconfig.initial.development || false; @@ -37,6 +38,9 @@ async function startFluxFunctions() { upnpService.adjustFirewallForUPNP(); }, 1 * 60 * 60 * 1000); // every 1 hours } + await fluxNetworkHelper.addFluxNodeServiceIpToLoopback(); + await fluxNetworkHelper.allowOnlyDockerNetworksToFluxNodeService(); + fluxNodeService.start(); await daemonServiceUtils.buildFluxdClient(); log.info('Checking docker log for corruption...'); await dockerService.dockerLogsFix(); @@ -123,10 +127,8 @@ async function startFluxFunctions() { setInterval(() => { appsService.restorePortsSupport(); // restore fluxos and apps ports/upnp }, 10 * 60 * 1000); // every 10 minutes - setTimeout(() => { - log.info('Starting setting Node Geolocation'); - geolocationService.setNodeGeolocation(); - }, 90 * 1000); + log.info('Starting setting Node Geolocation'); + geolocationService.setNodeGeolocation(); setTimeout(() => { const { daemon: { zmqport } } = config; log.info(`Ensuring zmq is enabled for fluxd on port: ${zmqport}`);