From 439f758e487ec83ee1a218b840d85ea4b8c35c16 Mon Sep 17 00:00:00 2001 From: FluxST Date: Thu, 14 Sep 2023 00:15:36 +0800 Subject: [PATCH] feat(minecraft): add custom handling for minecraft apps with acl and tcp --- config/appsConfig.js | 3 +- config/default.js | 1 + deployment/default.js.j2 | 1 + src/services/domainService.js | 11 +- src/services/haproxyTemplate.js | 187 ++++++++++++++++++++++---------- src/services/rsync/config.js | 2 +- 6 files changed, 142 insertions(+), 63 deletions(-) diff --git a/config/appsConfig.js b/config/appsConfig.js index 23fd131..8afa633 100644 --- a/config/appsConfig.js +++ b/config/appsConfig.js @@ -2,5 +2,6 @@ module.exports = { mandatoryApps: ['explorer', 'web', 'themok6', 'paoverview', 'eckodexswap', 'HavenNodeMainnet'], ownersApps: [], // Will retrieve only apps of owners specified here whiteListedApps: [], // If there's app in the array, blacklisting will be ignore - blackListedApps: ['Kadena', 'Kadena2', 'PresearchNode*', 'BrokerNode*'], + blackListedApps: ['Kadena', 'Kadena2', 'PresearchNode*', 'BrokerNode*', 'Folding*'], + minecraftApps: ['mcf', '*minecraft*'], }; diff --git a/config/default.js b/config/default.js index c12002c..76817d4 100644 --- a/config/default.js +++ b/config/default.js @@ -47,6 +47,7 @@ module.exports = { ownersApps: appsConfig.ownersApps, // Will retrieve only apps of owners specified here whiteListedApps: appsConfig.whiteListedApps, // If there's app in the array, blacklisting will be ignore blackListedApps: appsConfig.blackListedApps, + minecraftApps: appsConfig.minecraftApps, appSubDomain: 'app2', fdmAppDomain: 'fdm-lb-2-1.runonflux.io', useSubset: false, diff --git a/deployment/default.js.j2 b/deployment/default.js.j2 index 3de9264..1f529fd 100644 --- a/deployment/default.js.j2 +++ b/deployment/default.js.j2 @@ -47,6 +47,7 @@ module.exports = { ownersApps: appsConfig.ownersApps, // Will retrieve only apps of owners specified here whiteListedApps: appsConfig.whiteListedApps, // If there's app in the array, blacklisting will be ignore blackListedApps: appsConfig.blackListedApps, + minecraftApps: appsConfig.minecraftApps, appSubDomain: '{{ appSubDomain }}', fdmAppDomain: '{{ fdmAppDomain }}', useSubset: {{ useSubset }}, diff --git a/src/services/domainService.js b/src/services/domainService.js index 0dbc10d..e022e69 100644 --- a/src/services/domainService.js +++ b/src/services/domainService.js @@ -241,6 +241,7 @@ async function generateAndReplaceMainApplicationHaproxyConfig() { if (app.version <= 3) { for (let i = 0; i < app.ports.length; i += 1) { const configuredApp = { + name: app.name, appName: `${app.name}_${app.ports[i]}`, domain: domains[i], port: app.ports[i], @@ -269,6 +270,7 @@ async function generateAndReplaceMainApplicationHaproxyConfig() { const domainExists = configuredApps.find((a) => a.domain === portDomain.toLowerCase()); if (!domainExists) { const configuredAppCustom = { + name: app.name, appName: `${app.name}_${app.ports[i]}`, domain: portDomain, port: app.ports[i], @@ -282,6 +284,7 @@ async function generateAndReplaceMainApplicationHaproxyConfig() { const domainExistsB = configuredApps.find((a) => a.domain === wwwAdjustedDomain); if (!domainExistsB) { const configuredAppCustom = { + name: app.name, appName: `${app.name}_${app.ports[i]}`, domain: wwwAdjustedDomain, port: app.ports[i], @@ -297,6 +300,7 @@ async function generateAndReplaceMainApplicationHaproxyConfig() { const domainExistsB = configuredApps.find((a) => a.domain === testAdjustedDomain); if (!domainExistsB) { const configuredAppCustom = { + name: app.name, appName: `${app.name}_${app.ports[i]}`, domain: testAdjustedDomain, port: app.ports[i], @@ -311,6 +315,7 @@ async function generateAndReplaceMainApplicationHaproxyConfig() { } } const mainApp = { + name: app.name, appName: `${app.name}_${app.ports[0]}`, domain: domains[domains.length - 1], port: app.ports[0], @@ -323,6 +328,7 @@ async function generateAndReplaceMainApplicationHaproxyConfig() { for (const component of app.compose) { for (let i = 0; i < component.ports.length; i += 1) { const configuredApp = { + name: app.name, appName: `${app.name}_${component.name}_${component.ports[i]}`, domain: domains[j], port: component.ports[i], @@ -351,6 +357,7 @@ async function generateAndReplaceMainApplicationHaproxyConfig() { const domainExists = configuredApps.find((a) => a.domain === portDomain.toLowerCase()); if (!domainExists) { const configuredAppCustom = { + name: app.name, appName: `${app.name}_${component.name}_${component.ports[i]}`, domain: portDomain, port: component.ports[i], @@ -365,6 +372,7 @@ async function generateAndReplaceMainApplicationHaproxyConfig() { const domainExistsB = configuredApps.find((a) => a.domain === wwwAdjustedDomain); if (!domainExistsB) { const configuredAppCustom = { + name: app.name, appName: `${app.name}_${component.name}_${component.ports[i]}`, domain: wwwAdjustedDomain, port: component.ports[i], @@ -380,6 +388,7 @@ async function generateAndReplaceMainApplicationHaproxyConfig() { const domainExistsB = configuredApps.find((a) => a.domain === testAdjustedDomain); if (!domainExistsB) { const configuredAppCustom = { + name: app.name, appName: `${app.name}_${component.name}_${component.ports[i]}`, domain: testAdjustedDomain, port: component.ports[i], @@ -401,6 +410,7 @@ async function generateAndReplaceMainApplicationHaproxyConfig() { const mainDomainExists = configuredApps.find((qw) => qw.domain === domains[domains.length - 1]); if (!mainDomainExists) { const mainApp = { + name: app.name, appName: `${app.name}_${app.compose[q].name}_${app.compose[q].ports[w]}`, domain: domains[domains.length - 1], port: app.compose[q].ports[w], @@ -424,7 +434,6 @@ async function generateAndReplaceMainApplicationHaproxyConfig() { if (configuredApps.length < 10) { throw new Error('PANIC PLEASE DEV HELP ME'); } - const hc = await haproxyTemplate.createAppsHaproxyConfig(configuredApps); console.log(hc); const dataToWrite = hc; diff --git a/src/services/haproxyTemplate.js b/src/services/haproxyTemplate.js index 05148eb..35ea295 100644 --- a/src/services/haproxyTemplate.js +++ b/src/services/haproxyTemplate.js @@ -4,6 +4,7 @@ const configGlobal = require('config'); const fs = require('fs').promises; const log = require('../lib/log'); const { cmdAsync, TEMP_HAPROXY_CONFIG, HAPROXY_CONFIG } = require('./constants'); +const { matchRule } = require('./serviceHelper'); const haproxyPrefix = ` global @@ -103,12 +104,117 @@ function createCertificatesPaths(domains) { return path; } -function generateHaproxyConfig(acls, usebackends, domains, backends, redirects) { +function generateMinecraftSettings(minecraftAppsMap) { + let configs = ''; + for (const port of Object.keys(minecraftAppsMap)) { + const portConf = minecraftAppsMap[port]; + const tempFrontend = ` +frontend minecraft_${port} + bind 0.0.0.0:${port} + tcp-request inspect-delay 5s + tcp-request content accept if { req_ssl_hello_type 1 } + mode tcp + option tcplog + option tcp-check +${portConf.acls.join('\n')} +${portConf.usebackends.join('')} +${portConf.backends.join('\n')}`; + + configs = `${configs}\n\n${tempFrontend}`; + } + + return configs; +} + +function generateHaproxyConfig(acls, usebackends, domains, backends, redirects, minecraftAppsMap = {}) { // eslint-disable-next-line max-len - const config = `${haproxyPrefix}\n\n${acls}\n${usebackends}\n${redirects}\n${httpsPrefix}${certificatePrefix}${createCertificatesPaths(domains)}${certificatesSuffix} ${h2Suffix}\n\n${acls}\n${usebackends}\n${redirects}\n\n${backends}\n${letsEncryptBackend}\n${cloudflareFluxBackend}\n${forbiddenBackend}`; + const minecraftConfig = generateMinecraftSettings(minecraftAppsMap); + const config = ` +${haproxyPrefix} + +${acls} +${usebackends} +${redirects} + +${minecraftConfig} + +${httpsPrefix}${certificatePrefix}${createCertificatesPaths(domains)}${certificatesSuffix} ${h2Suffix} + +${acls} +${usebackends} +${redirects} + +${backends} +${letsEncryptBackend} +${cloudflareFluxBackend} +${forbiddenBackend} +`; return config; } +function generateDomainBackend(app, mode) { + const domainUsed = app.domain.split('.').join(''); + let domainBackend = ` +backend ${domainUsed}backend + mode ${mode}`; + if (app.loadBalance) { + domainBackend += app.loadBalance; + } else if (mode !== 'tcp') { + domainBackend += '\n balance roundrobin'; + domainBackend += '\n cookie FDMSERVERID insert indirect nocache maxlife 8h'; + } + if (app.headers) { + // eslint-disable-next-line no-loop-func + app.headers.forEach((header) => { + domainBackend += `\n ${header}`; + }); + } + // eslint-disable-next-line no-loop-func + app.healthcheck.forEach((hc) => { + domainBackend += `\n ${hc}`; + }); + for (const ip of app.ips) { + const a = ip.split(':')[0].split('.'); + if (!a) { + log.error('STRANGE IP'); + log.error(ip); + // eslint-disable-next-line no-continue + continue; + } + const apiPort = ip.split(':')[1] || 16127; + const cookieConfig = app.loadBalance || mode === 'tcp' ? '' : ` cookie ${ip.split(':')[0]}:${app.port}`; + if (app.ssl) { + const h2Config = app.enableH2 ? h2Suffix : ''; + domainBackend += `\n server ${ip.split(':')[0]}:${apiPort} ${ip.split(':')[0]}:${app.port} check ${app.serverConfig} ssl verify none ${h2Config}${cookieConfig}`; + } else { + domainBackend += `\n server ${ip.split(':')[0]}:${apiPort} ${ip.split(':')[0]}:${app.port} check ${app.serverConfig}${cookieConfig}`; + } + if (app.timeout) { + domainBackend += `\n timeout server ${app.timeout}`; + } + } + return domainBackend; +} + +function generateMinecraftACLs(app) { + console.log(app.domain); + const aclName = app.domain.split('.').join(''); + const appName = app.domain.split('.')[0]; + console.log(appName); + + const nameLength = appName.length + 1; + const domainLength = app.domain.length; + return [ + ` acl ${aclName} req.payload(4,${nameLength}) -m sub ${appName}.`, + ` acl ${aclName} req.payload(5,${nameLength}) -m sub ${appName}.`, + ` acl ${aclName} req.payload(7,${nameLength}) -m sub ${appName}.`, + ` acl ${aclName} req.payload(8,${nameLength}) -m sub ${appName}.`, + ` acl ${aclName} req.payload(1,${domainLength}) -m sub ${app.domain}`, + ` acl ${aclName} req.payload(2,${domainLength}) -m sub ${app.domain}`, + ` acl ${aclName} req.payload(3,${domainLength}) -m sub ${app.domain}`, + ]; +} + function createMainHaproxyConfig(ui, api, fluxIPs) { const uiB = ui.split('.').join(''); let uiBackend = `backend ${uiB}backend @@ -178,7 +284,7 @@ function createMainHaproxyConfig(ui, api, fluxIPs) { const backends = `${uiBackend}\n\n${apiBackend}`; const urls = [ui, api, 'dashboard.zel.network']; - return generateHaproxyConfig(acls, usebackends, urls, backends, redirects); + return generateHaproxyConfig(acls, usebackends, urls, backends, redirects, {}); } // appConfig is an array of object of domain, port, ips @@ -194,6 +300,7 @@ function createAppsHaproxyConfig(appConfig) { // usebackends += ' use_backend forbidden-backend if forbiddenacl\n'; const domains = []; const seenApps = {}; + const minecraftAppsMap = {}; for (const app of appConfig) { if (domains.includes(app.domain)) { // eslint-disable-next-line no-continue @@ -202,68 +309,28 @@ function createAppsHaproxyConfig(appConfig) { if (app.appName in seenApps) { domains.push(app.domain); acls += ` acl ${seenApps[app.appName]} hdr(host) ${app.domain}\n`; + } else if (matchRule(app.name.toLowerCase(), configGlobal.minecraftApps)) { + const domainUsed = app.domain.split('.').join(''); + const { port } = app; + if (!(port in minecraftAppsMap)) { + minecraftAppsMap[port] = { + acls: [], + usebackends: [], + backends: [], + }; + } + const tempMinecraftACLs = generateMinecraftACLs(app); + const domainBackend = generateDomainBackend(app, 'tcp'); + minecraftAppsMap[port].acls = minecraftAppsMap[port].acls.concat(tempMinecraftACLs); + minecraftAppsMap[port].usebackends.push(` use_backend ${domainUsed}backend if ${domainUsed}\n`); + minecraftAppsMap[port].backends.push(domainBackend); } else { const domainUsed = app.domain.split('.').join(''); if (usebackends.includes(` use_backend ${domainUsed}backend if ${domainUsed}\n`)) { // eslint-disable-next-line no-continue continue; } - let domainBackend = `backend ${domainUsed}backend - mode http`; - if (app.loadBalance) { - domainBackend += app.loadBalance; - } else { - domainBackend += '\n balance roundrobin'; - domainBackend += '\n cookie FDMSERVERID insert indirect nocache maxlife 8h'; - } - if (app.headers) { - // eslint-disable-next-line no-loop-func - app.headers.forEach((header) => { - domainBackend += `\n ${header}`; - }); - } - // eslint-disable-next-line no-loop-func - app.healthcheck.forEach((hc) => { - domainBackend += `\n ${hc}`; - }); - for (const ip of app.ips) { - const a = ip.split(':')[0].split('.'); - if (!a) { - log.error('STRANGE IP'); - log.error(ip); - // eslint-disable-next-line no-continue - continue; - } - const apiPort = ip.split(':')[1] || 16127; - // let IpString = ''; - // for (let i = 0; i < 4; i += 1) { - // if (!(a[i])) { - // log.error('STRANGE IP'); - // log.error(ip); - // // eslint-disable-next-line no-continue - // continue; - // } - // if (a[i].length === 3) { - // IpString += a[i]; - // } - // if (a[i].length === 2) { - // IpString = `${IpString}0${a[i]}`; - // } - // if (a[i].length === 1) { - // IpString = `${IpString}00${a[i]}`; - // } - // } - const cookieConfig = app.loadBalance ? '' : ` cookie ${ip.split(':')[0]}:${app.port}`; - if (app.ssl) { - const h2Config = app.enableH2 ? h2Suffix : ''; - domainBackend += `\n server ${ip.split(':')[0]}:${apiPort} ${ip.split(':')[0]}:${app.port} check ${app.serverConfig} ssl verify none ${h2Config}${cookieConfig}`; - } else { - domainBackend += `\n server ${ip.split(':')[0]}:${apiPort} ${ip.split(':')[0]}:${app.port} check ${app.serverConfig}${cookieConfig}`; - } - if (app.timeout) { - domainBackend += `\n timeout server ${app.timeout}`; - } - } + const domainBackend = generateDomainBackend(app, 'http'); backends = `${backends + domainBackend}\n\n`; domains.push(app.domain); acls += ` acl ${domainUsed} hdr(host) ${app.domain}\n`; @@ -273,7 +340,7 @@ function createAppsHaproxyConfig(appConfig) { } const redirects = ''; - return generateHaproxyConfig(acls, usebackends, domains, backends, redirects); + return generateHaproxyConfig(acls, usebackends, domains, backends, redirects, minecraftAppsMap); } async function writeConfig(configName, data) { diff --git a/src/services/rsync/config.js b/src/services/rsync/config.js index ad765e9..9629e7b 100644 --- a/src/services/rsync/config.js +++ b/src/services/rsync/config.js @@ -4,7 +4,7 @@ const path = require('path'); function getHostsToRsync() { // file generated by ansible - // eslint-disable-next-line global-require + // eslint-disable-next-line global-require, import/no-unresolved const rsyncConfig = require('../../../deployment/rsync_config'); const hosts = ini.parse(fs.readFileSync(path.resolve(__dirname, '../../../deployment/hosts.ini'), 'utf-8'));