Skip to content

Commit

Permalink
Safer iptables set
Browse files Browse the repository at this point in the history
  • Loading branch information
willnode committed Feb 16, 2024
1 parent a041640 commit 436629c
Show file tree
Hide file tree
Showing 11 changed files with 103 additions and 204 deletions.
8 changes: 4 additions & 4 deletions .env.test
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ NGINX_BIN=echo nginx
NGINX_START=echo systemctl start nginx
IPTABLES_PATH=./test/iptables
IPTABLES_OUT=./test/iptables.out
IPTABLES_SAVE=cat ./test/iptables
IPTABLES_LOAD=./test/iptables.out
IPTABLES_SAVE=echo .
IPTABLES_LOAD=echo .
IP6TABLES_PATH=./test/ip6tables
IP6TABLES_OUT=./test/ip6tables.out
IP6TABLES_SAVE=cat ./test/ip6tables
IP6TABLES_LOAD=./test/ip6tables.out
IP6TABLES_SAVE=echo .
IP6TABLES_LOAD=echo .
FASTCGI_LOCATIONS=./test/nginx.out/$
OPENSSL_PATH=./test/openssl
OPENSSL_OUT=./test/openssl.out
Expand Down
15 changes: 2 additions & 13 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "domcloud-bridge",
"version": "0.42.0",
"version": "0.43.0",
"description": "Deployment runner for DOM Cloud",
"main": "app.js",
"engines": {
Expand All @@ -24,7 +24,6 @@
"express": "^4.18.2",
"fast-xml-parser": "^4.3.2",
"nginx-conf": "^2.1.0",
"node-abort-controller": "^3.1.1",
"proper-lockfile": "^4.1.2",
"shelljs": "^0.8.5"
},
Expand Down
27 changes: 17 additions & 10 deletions src/controllers/iptables.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,35 +5,42 @@ import {
import {
checkPost
} from '../util.js';
import shelljs from 'shelljs';

export default function () {
var router = express.Router();
router.get('/show', async function (req, res, next) {
try {
const p = await executor.getParsed();
if (req.query.view === 'raw')
res.send(executor.getRaw(p));
else {
if (req.query.user) {
res.json(executor.getByUsers(p, ...(req.query.user.toString()).split(',')));
} else {
res.json((p));
}
if (req.query.user) {
res.json(executor.getByUsers(p, ...(req.query.user.toString()).split(',')));
} else {
res.json((p));
}
} catch (error) {
next(error);
}
});
router.post('/add', checkPost(['user']), async function (req, res, next) {
try {
res.json(await executor.setAddUser(req.body.user.toString()));
const user = req.body.user.toString();
if (user.match(/[^\w.-]/)) {
throw new Error();
}
const id = shelljs.exec("id -u " + user).stdout.trim();
res.json(await executor.setAddUser(user, id));
} catch (error) {
next(error);
}
});
router.post('/del', checkPost(['user']), async function (req, res, next) {
try {
res.json(await executor.setDelUser(req.body.user.toString()));
const user = req.body.user.toString();
if (user.match(/[^\w.-]/)) {
throw new Error();
}
const id = shelljs.exec("id -u " + user).stdout.trim();
res.json(await executor.setDelUser(user, id));
} catch (error) {
next(error);
}
Expand Down
3 changes: 0 additions & 3 deletions src/controllers/runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,6 @@ import {
dirname
} from 'path';
import axios from 'axios';
import {
AbortController
} from 'node-abort-controller';

/**
* @param {import('stream').Writable} stream
Expand Down
112 changes: 63 additions & 49 deletions src/executor/iptables.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,16 @@
import {
cat,
appendIfNotExist,
deleteIfNotExist,
deleteIfExist,
executeLock,
spawnSudoUtil,
writeTo
} from '../util.js';
import {
encodeIPTables,
parseIptablesDoc
} from '../parsers/iptables.js';
import path from 'path';
const tmpFile = path.join(process.cwd(), '/.tmp/iptables')
const tmpFile6 = path.join(process.cwd(), '/.tmp/ip6tables')

class IptablesExecutor {
getRaw(parsed) {
return encodeIPTables(parsed);
}
/**
* @param {any} parsed
* @param {string[]} users
Expand All @@ -27,77 +20,98 @@ class IptablesExecutor {
parsed.filter?.rules.find(x => x["--uid-owner"] === u)
))
}
/**
*
* @param {string} doc
* @returns {Record<string, string[]>}
*/
parseIptablesDoc(doc = '') {
return doc.split('*').slice(1)
.map(block => '*' + block.trim())
.map(block => block.split("\n").filter(x => !x.startsWith('#')))
.reduce((obj, block) => {
obj[block[0].substring(1)] = block;
return obj;
}, {});
}
encodeIptablesDoc(doc) {
return Object.values(doc).map(x => x.join('\n')).join('\n\n') + '\n';
}
async getParsed() {
await executeLock('iptables', async () => {
await spawnSudoUtil('IPTABLES_GET');
});
return parseIptablesDoc(cat(tmpFile));
return this.parseIptablesDoc(cat(tmpFile));
}
async setAddUser(user) {
async setAddUser(userName, userID = "") {
const v4 = await executeLock('iptables', async () => {
await spawnSudoUtil('IPTABLES_GET');
var p = parseIptablesDoc(cat(tmpFile));
const rules = p.filter.rules;
if (!appendIfNotExist(rules, {
"-A": "OUTPUT",
"-m": "owner",
"--uid-owner": user,
"-j": "REJECT"
})) {
var p = this.parseIptablesDoc(cat(tmpFile));
const rules = p.filter;

const setRules = [
`-A OUTPUT -m owner --uid-owner ${userID} -j REJECT -m comment --comment "${userName}"`,
`-A OUTPUT -m owner --uid-owner ${userName} -j REJECT`,
]

if (!appendIfNotExist(rules, setRules)) {
return "Done unchanged for iptables";
}
writeTo(tmpFile, encodeIPTables(p));
writeTo(tmpFile, this.encodeIptablesDoc(p));
await spawnSudoUtil('IPTABLES_SET');
return "Updated for iptables";
});
const v6 = await executeLock('iptables', async () => {
await spawnSudoUtil('IP6TABLES_GET');
var p = parseIptablesDoc(cat(tmpFile6));
const rules = p.filter.rules;
if (!appendIfNotExist(rules, {
"-A": "OUTPUT",
"-m": "owner",
"--uid-owner": user,
"-j": "REJECT"
})) {
return "Done unchanged for ip6tables";
var p = this.parseIptablesDoc(cat(tmpFile6));
const rules = p.filter;

const setRules = [
`-A OUTPUT -m owner --uid-owner ${userID} -j REJECT -m comment --comment "${userName}"`,
`-A OUTPUT -m owner --uid-owner ${userName} -j REJECT`,
]

if (!appendIfNotExist(rules, setRules)) {
return "Done unchanged for iptables";
}
writeTo(tmpFile6, encodeIPTables(p));
writeTo(tmpFile6, this.encodeIptablesDoc(p));
await spawnSudoUtil('IP6TABLES_SET');
return "Updated for ip6tables";
});
return [v4, v6].join(", ");
}
async setDelUser(user) {
async setDelUser(userName, userID = "") {
const v4 = await executeLock('iptables', async () => {
await spawnSudoUtil('IPTABLES_GET');
var p = parseIptablesDoc(cat(tmpFile));
const rules = p.filter.rules;
if (!deleteIfNotExist(rules, {
"-A": "OUTPUT",
"-m": "owner",
"--uid-owner": user,
"-j": "REJECT"
})) {
var p = this.parseIptablesDoc(cat(tmpFile));
const rules = p.filter;

const setRules = [
`-A OUTPUT -m owner --uid-owner ${userID} -j REJECT -m comment --comment "${userName}"`,
`-A OUTPUT -m owner --uid-owner ${userName} -j REJECT`,
]

if (!deleteIfExist(rules, setRules)) {
return "Done unchanged for iptables";
}
writeTo(tmpFile, encodeIPTables(p));
writeTo(tmpFile, this.encodeIptablesDoc(p));
await spawnSudoUtil('IPTABLES_SET');
return "Updated for iptables";
});
const v6 = await executeLock('iptables', async () => {
await spawnSudoUtil('IP6TABLES_GET');
var p = parseIptablesDoc(cat(tmpFile6));
const rules = p.filter.rules;
if (!deleteIfNotExist(rules, {
"-A": "OUTPUT",
"-m": "owner",
"--uid-owner": user,
"-j": "REJECT"
})) {
return "Done unchanged for ip6tables";
var p = this.parseIptablesDoc(cat(tmpFile6));
const rules = p.filter;

const setRules = [
`-A OUTPUT -m owner --uid-owner ${userID} -j REJECT -m comment --comment "${userName}"`,
`-A OUTPUT -m owner --uid-owner ${userName} -j REJECT`,
]

if (!deleteIfExist(rules, setRules)) {
return "Done unchanged for iptables";
}
writeTo(tmpFile6, encodeIPTables(p));
writeTo(tmpFile6, this.encodeIptablesDoc(p));
await spawnSudoUtil('IP6TABLES_SET');
return "Updated for ip6tables";
});
Expand Down
6 changes: 3 additions & 3 deletions src/executor/named.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {
appendIfNotExist,
cat,
deleteIfNotExist,
deleteIfExist,
executeLock,
spawnSudoUtil,
writeTo
Expand Down Expand Up @@ -134,7 +134,7 @@ class NamedExecutor {
var file = parse(cat(tmpFile));
var arr = getArrayOf(file, type);
var map = mapKey[type](domain, ...("" + value).split(' '));
if (!deleteIfNotExist(arr, map)) {
if (!deleteIfExist(arr, map)) {
return "Done unchanged";
}
file.soa.serial++;
Expand Down Expand Up @@ -168,7 +168,7 @@ class NamedExecutor {
}
}
if (mod.action === 'del') {
if (deleteIfNotExist(arr, map)) {
if (deleteIfExist(arr, map)) {
changecount++;
}
}
Expand Down
12 changes: 6 additions & 6 deletions src/executor/runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ export default async function runConfig(config, domain, writer, sandbox = false)
break;
case 'rename':
if (value && value["new-user"] && await firewallStatus()) {
await iptablesExec.setDelUser(domaindata['Username']);
await iptablesExec.setDelUser(domaindata['Username'], domaindata['User ID']);
}
await writeLog("$> virtualmin rename-domain");
await virtExec("rename-domain", value, {
Expand All @@ -214,7 +214,7 @@ export default async function runConfig(config, domain, writer, sandbox = false)
domain = value["new-domain"];
await new Promise(r => setTimeout(r, 1000));
if (value && value["new-user"] && await firewallStatus()) {
await iptablesExec.setAddUser(value["new-user"]);
await iptablesExec.setAddUser(value["new-user"], domaindata['User ID']);
}
domaindata = await virtualminExec.getDomainInfo(domain);
break;
Expand Down Expand Up @@ -339,11 +339,11 @@ export default async function runConfig(config, domain, writer, sandbox = false)
case 'firewall':
if (value === '' || value === 'on') {
await writeLog("$> Changing firewall protection to " + (value || 'on'));
await writeLog(await iptablesExec.setAddUser(domaindata['Username']));
await writeLog(await iptablesExec.setAddUser(domaindata['Username'], domaindata['User ID']));
firewallStatusCache = true;
} else if (value === 'off') {
await writeLog("$> Changing firewall protection to " + value);
await writeLog(await iptablesExec.setDelUser(domaindata['Username']));
await writeLog(await iptablesExec.setDelUser(domaindata['Username'], domaindata['User ID']));
firewallStatusCache = false;
}
break;
Expand Down Expand Up @@ -902,7 +902,7 @@ export async function runConfigSubdomain(config, domaindata, subdomain, sshExec,
}

if (firewallOn) {
await iptablesExec.setDelUser(domaindata['Username']);
await iptablesExec.setDelUser(domaindata['Username'], domaindata['User ID']);
}
await writeLog("$> " + executedCMDNote);
for (const exec of executedCMD) {
Expand Down Expand Up @@ -934,7 +934,7 @@ export async function runConfigSubdomain(config, domaindata, subdomain, sshExec,
throw error
} finally {
if (config.source && firewallOn) {
await iptablesExec.setAddUser(domaindata['Username']);
await iptablesExec.setAddUser(domaindata['Username'], domaindata['User ID']);
}

if (Array.isArray(config.features)) {
Expand Down
Loading

0 comments on commit 436629c

Please sign in to comment.