[diplomate] Skeleton for our UPNP configurator
This commit is contained in:
parent
ba5590ce97
commit
96c6da392f
19 changed files with 18 additions and 362 deletions
1
docker/diplomate/.gitignore
vendored
Normal file
1
docker/diplomate/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
target/
|
5
docker/diplomate/Cargo.lock
generated
Normal file
5
docker/diplomate/Cargo.lock
generated
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
[[package]]
|
||||||
|
name = "diplomate"
|
||||||
|
version = "0.1.0"
|
9
docker/diplomate/Cargo.toml
Normal file
9
docker/diplomate/Cargo.toml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
[package]
|
||||||
|
name = "diplomate"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Quentin <quentin@deuxfleurs.fr>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
3
docker/diplomate/src/main.rs
Normal file
3
docker/diplomate/src/main.rs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
fn main() {
|
||||||
|
println!("Hello, world!");
|
||||||
|
}
|
1
docker/netiquette/.gitignore
vendored
1
docker/netiquette/.gitignore
vendored
|
@ -1 +0,0 @@
|
||||||
node_modules
|
|
|
@ -1,24 +0,0 @@
|
||||||
# netiquette
|
|
||||||
|
|
||||||
```
|
|
||||||
npm install
|
|
||||||
npm test
|
|
||||||
```
|
|
||||||
|
|
||||||
You will probably need to run consul in parallel:
|
|
||||||
|
|
||||||
```
|
|
||||||
consul agent -dev
|
|
||||||
```
|
|
||||||
|
|
||||||
You can register services like that:
|
|
||||||
|
|
||||||
```
|
|
||||||
consul services register -name=toto -tag="public_port=4848"
|
|
||||||
```
|
|
||||||
|
|
||||||
You will need some arguments to run the software:
|
|
||||||
|
|
||||||
```
|
|
||||||
sudo npm start node=rincevent ipt_base=./static.iptables
|
|
||||||
```
|
|
|
@ -1,59 +0,0 @@
|
||||||
'use strict'
|
|
||||||
import consul from 'consul'
|
|
||||||
import { exec } from './src/io/run.mjs'
|
|
||||||
import { readFile } from './src/io/files.mjs'
|
|
||||||
|
|
||||||
import ctlg_control_loop from './src/catalog/control_loop.mjs'
|
|
||||||
import ctlg_consul from './src/catalog/consul.mjs'
|
|
||||||
import inj_iptables from './src/injector/iptables.mjs'
|
|
||||||
|
|
||||||
const get_args = () => process
|
|
||||||
.argv
|
|
||||||
.slice(2)
|
|
||||||
.map(a => a.split('='))
|
|
||||||
.reduce((dict, tuple) => {
|
|
||||||
dict[tuple[0]] = tuple.length > 1 ? tuple[1] : null
|
|
||||||
return dict
|
|
||||||
}, {})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If we have multiple catalogs
|
|
||||||
* we cache the results of the other ones
|
|
||||||
*/
|
|
||||||
function* notifications_aggregator(injectors) {
|
|
||||||
const states = []
|
|
||||||
for(let idx = 0; true; idx++) {
|
|
||||||
yield async (tag_list) => {
|
|
||||||
states[idx] = tag_list
|
|
||||||
const merged = states.reduce((acc, tag) => [...acc, ...tag], [])
|
|
||||||
await Promise.all(injectors.map(notify => notify(merged)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const main = async () => {
|
|
||||||
try {
|
|
||||||
const args = get_args()
|
|
||||||
|
|
||||||
// Initialize all injectors
|
|
||||||
const injectors = [
|
|
||||||
await inj_iptables(args.ipt_base, readFile, exec, console.log),
|
|
||||||
// await inj_upnp
|
|
||||||
]
|
|
||||||
|
|
||||||
// Initialize all catalogs and map them to the injectors
|
|
||||||
const aggr = notifications_aggregator(injectors)
|
|
||||||
const catalogs = [
|
|
||||||
// this catalog is used to defeat deriving config due to single resource updated async. by multiple prog or by external program not tracked by catalogs
|
|
||||||
await ctlg_control_loop(setInterval, 60000, aggr.next().value),
|
|
||||||
await ctlg_consul(args.node, consul(), console.log, aggr.next().value)
|
|
||||||
]
|
|
||||||
|
|
||||||
console.log("[main] initialized")
|
|
||||||
} catch(e) {
|
|
||||||
console.error("initialization failed", e)
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
main()
|
|
74
docker/netiquette/package-lock.json
generated
74
docker/netiquette/package-lock.json
generated
|
@ -1,74 +0,0 @@
|
||||||
{
|
|
||||||
"name": "consul-to-igd",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"lockfileVersion": 1,
|
|
||||||
"requires": true,
|
|
||||||
"dependencies": {
|
|
||||||
"assertion-error": {
|
|
||||||
"version": "1.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz",
|
|
||||||
"integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"chai": {
|
|
||||||
"version": "4.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz",
|
|
||||||
"integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"assertion-error": "^1.1.0",
|
|
||||||
"check-error": "^1.0.2",
|
|
||||||
"deep-eql": "^3.0.1",
|
|
||||||
"get-func-name": "^2.0.0",
|
|
||||||
"pathval": "^1.1.0",
|
|
||||||
"type-detect": "^4.0.5"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"check-error": {
|
|
||||||
"version": "1.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz",
|
|
||||||
"integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"consul": {
|
|
||||||
"version": "0.34.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/consul/-/consul-0.34.1.tgz",
|
|
||||||
"integrity": "sha512-xCLBzPQBgnDgC2LdYnrT/Fc6PglRU6u7EBkpW0ExAx3Am/CdtKcP5o/3jfwOy7PBAwBqnJk3AYdwwGg+arriiQ==",
|
|
||||||
"requires": {
|
|
||||||
"papi": "^0.29.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"deep-eql": {
|
|
||||||
"version": "3.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz",
|
|
||||||
"integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"type-detect": "^4.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"get-func-name": {
|
|
||||||
"version": "2.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz",
|
|
||||||
"integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"papi": {
|
|
||||||
"version": "0.29.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/papi/-/papi-0.29.1.tgz",
|
|
||||||
"integrity": "sha512-Y9ipSMfWuuVFO3zY9PlxOmEg+bQ7CeJ28sa9/a0veYNynLf9fwjR3+3fld5otEy7okUaEOUuCHVH62MyTmACXQ=="
|
|
||||||
},
|
|
||||||
"pathval": {
|
|
||||||
"version": "1.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz",
|
|
||||||
"integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"type-detect": {
|
|
||||||
"version": "4.0.8",
|
|
||||||
"resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
|
|
||||||
"integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==",
|
|
||||||
"dev": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
{
|
|
||||||
"name": "netiquette",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"description": "",
|
|
||||||
"main": "index.mjs",
|
|
||||||
"dependencies": {
|
|
||||||
"consul": "^0.34.1"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"chai": "^4.2.0"
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"start": "node --experimental-modules ./index.mjs",
|
|
||||||
"test": "node --experimental-modules ./test/runner.mjs"
|
|
||||||
},
|
|
||||||
"author": "Quentin",
|
|
||||||
"license": "AGPL-3.0-or-later"
|
|
||||||
}
|
|
|
@ -1,30 +0,0 @@
|
||||||
'use strict'
|
|
||||||
|
|
||||||
let l
|
|
||||||
export default l = async (node, consul, log, notify) => {
|
|
||||||
const watch = consul.watch({ method: consul.catalog.node.services, options: {node: node}})
|
|
||||||
|
|
||||||
const extract_tags = data =>
|
|
||||||
data ?
|
|
||||||
Object
|
|
||||||
.keys(data.Services)
|
|
||||||
.map(k => data.Services[k].Tags)
|
|
||||||
.reduce((acc, v) => [...acc, ...v], []) :
|
|
||||||
[]
|
|
||||||
|
|
||||||
watch.on('error', err => {
|
|
||||||
console.error('error', err)
|
|
||||||
})
|
|
||||||
|
|
||||||
watch.on('change', async (data, res) => {
|
|
||||||
try {
|
|
||||||
const tags = extract_tags(data)
|
|
||||||
log(`[consul] new update, detected ${tags.length} tags`)
|
|
||||||
await notify(tags)
|
|
||||||
} catch(e) {
|
|
||||||
console.error('failed to notify target', e)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
log('[consul] initialized')
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
'use strict'
|
|
||||||
|
|
||||||
let l
|
|
||||||
export default l = async (timer, interval, notify) => {
|
|
||||||
timer(() => {
|
|
||||||
notify([])
|
|
||||||
console.log(`[control_loop] actuation (triggered every ${interval} ms)`)
|
|
||||||
}, interval)
|
|
||||||
console.log("[control_loop] initialized")
|
|
||||||
}
|
|
|
@ -1,53 +0,0 @@
|
||||||
'use strict'
|
|
||||||
|
|
||||||
let l;
|
|
||||||
export default l = async (path, readFile, exec, log) => {
|
|
||||||
|
|
||||||
const load_static_rules = async path =>
|
|
||||||
(await readFile(path, 'utf-8'))
|
|
||||||
.split('\n')
|
|
||||||
.filter(e => e)
|
|
||||||
|
|
||||||
const get_current_rules = async () =>
|
|
||||||
(await exec('iptables -S INPUT'))
|
|
||||||
.stdout
|
|
||||||
.split('\n')
|
|
||||||
.filter(e => e.match(/^-A INPUT/g))
|
|
||||||
|
|
||||||
const compute_rules_to_add = (current, target) =>
|
|
||||||
target.filter(r => !current.includes(r))
|
|
||||||
|
|
||||||
const compute_rules_to_del = (current, target) =>
|
|
||||||
current
|
|
||||||
.filter(r => !target.includes(r))
|
|
||||||
.map(r => r.replace(/^-A INPUT/g, '-D INPUT'))
|
|
||||||
|
|
||||||
const update_rules = async (current, target) =>
|
|
||||||
await Promise.all([
|
|
||||||
...compute_rules_to_del(current, target),
|
|
||||||
...compute_rules_to_add(current, target)
|
|
||||||
].map(r => exec(`iptables ${r}`)))
|
|
||||||
|
|
||||||
const build_target_rules = (tag_list) =>
|
|
||||||
tag_list
|
|
||||||
.map(t => /^public_port=(\d+)(-(\d+))?\/(udp|tcp)/g.exec(t))
|
|
||||||
.filter(t => t)
|
|
||||||
.map(t => new Object({ start: t[1], stop: t[3], protocol: t[4] }))
|
|
||||||
.map(t => t.stop
|
|
||||||
? `-A INPUT -p ${t.protocol} --match multiport --dports ${t.start}:${t.stop} -j ACCEPT`
|
|
||||||
: `-A INPUT -p ${t.protocol} --dport ${t.start} -j ACCEPT`)
|
|
||||||
|
|
||||||
const do_log = (tag_list, r) => {
|
|
||||||
//log('[iptables]', tag_list)
|
|
||||||
log(`[iptables] ran ${r.length} commands`)
|
|
||||||
}
|
|
||||||
|
|
||||||
const static_rules = path ? await load_static_rules(path) : []
|
|
||||||
log(`[iptables] initialized with ${static_rules.length} static rules`)
|
|
||||||
return async tag_list =>
|
|
||||||
do_log(
|
|
||||||
tag_list,
|
|
||||||
await update_rules(
|
|
||||||
await get_current_rules(),
|
|
||||||
[...static_rules, ...build_target_rules(tag_list)]))
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
'use strict'
|
|
||||||
|
|
||||||
import fs from 'fs'
|
|
||||||
|
|
||||||
export const readFile = (file, opts) =>
|
|
||||||
new Promise((resolve, reject) =>
|
|
||||||
fs.readFile(file, opts, (err, data) =>
|
|
||||||
err ? reject(err) : resolve(data)))
|
|
|
@ -1,9 +0,0 @@
|
||||||
'use strict'
|
|
||||||
|
|
||||||
import child_process from 'child_process'
|
|
||||||
|
|
||||||
export const exec = (cmd, opts) =>
|
|
||||||
new Promise((resolve, reject) =>
|
|
||||||
child_process.exec(cmd, opts, (error, stdout, stderr) =>
|
|
||||||
error ? reject({err: error, stdout: stdout, stderr: stderr}) : resolve({stdout: stdout, stderr: stderr})))
|
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
|
|
||||||
-A INPUT -i docker0 -j ACCEPT
|
|
||||||
-A INPUT -s 127.0.0.0/8 -j ACCEPT
|
|
||||||
-A INPUT -s 192.168.1.2/32 -j ACCEPT
|
|
||||||
-A INPUT -s 192.168.1.3/32 -j ACCEPT
|
|
||||||
-A INPUT -s 192.168.1.4/32 -j ACCEPT
|
|
||||||
-A INPUT -p udp -m udp --dport 53 -j ACCEPT
|
|
||||||
-A INPUT -p tcp -m tcp --dport 53 -j ACCEPT
|
|
||||||
-A INPUT -p tcp -m tcp --dport 22 -j ACCEPT
|
|
||||||
-A INPUT -p tcp -m tcp --dport 110 -j ACCEPT
|
|
|
@ -1,10 +0,0 @@
|
||||||
import chai from 'chai'
|
|
||||||
import { readFile } from '../src/io/files.mjs'
|
|
||||||
const expect = chai.expect
|
|
||||||
|
|
||||||
export default [
|
|
||||||
(async () => {
|
|
||||||
const dirname = import.meta.url.replace(/^file:\/\//g, '').replace(/io.mjs$/g, '')
|
|
||||||
expect(await readFile(`${dirname}/../package.json`, 'utf-8')).to.include('Quentin')
|
|
||||||
})
|
|
||||||
]
|
|
|
@ -1,28 +0,0 @@
|
||||||
'use strict'
|
|
||||||
|
|
||||||
import chai from 'chai'
|
|
||||||
import iptables from '../src/injector/iptables.mjs'
|
|
||||||
const expect = chai.expect
|
|
||||||
|
|
||||||
export default [
|
|
||||||
(async () => {
|
|
||||||
const effective_actions = []
|
|
||||||
const expected_actions = [
|
|
||||||
'iptables -A INPUT -p tcp --dport 56 -j ACCEPT',
|
|
||||||
'iptables -A INPUT -p tcp --dport 53 -j ACCEPT',
|
|
||||||
'iptables -A INPUT -p udp --match multiport --dports 25630:25999 -j ACCEPT',
|
|
||||||
'iptables -D INPUT -p tcp --dport 54 -j ACCEPT'
|
|
||||||
]
|
|
||||||
|
|
||||||
const mockLog = () => {}
|
|
||||||
const mockReadFile = (file, opt) => '-A INPUT -p tcp --dport 53 -j ACCEPT'
|
|
||||||
const mockExecCommand = (cmd, opts) => {
|
|
||||||
if (cmd.match(/^iptables -S/g)) return { stdout: '-A INPUT -p tcp --dport 54 -j ACCEPT' }
|
|
||||||
else effective_actions.push(cmd)
|
|
||||||
return { stdout: '' } }
|
|
||||||
|
|
||||||
const fw = await iptables('static', mockReadFile, mockExecCommand, mockLog)
|
|
||||||
await fw(['public_port=56/tcp', 'public_port=25630-25999/udp', 'public_port=13', 'traefik.entrypoints=Host:im.deuxfleurs.fr;PathPrefix:/_matrix'])
|
|
||||||
expect(effective_actions).to.have.members(expected_actions)
|
|
||||||
})
|
|
||||||
]
|
|
|
@ -1,28 +0,0 @@
|
||||||
'use strict'
|
|
||||||
|
|
||||||
import io from './io.mjs'
|
|
||||||
import iptables from './iptables.mjs'
|
|
||||||
|
|
||||||
(async () => {
|
|
||||||
const res = await [
|
|
||||||
...io,
|
|
||||||
...iptables
|
|
||||||
].map(async f => {
|
|
||||||
try {
|
|
||||||
await f()
|
|
||||||
return 'passed'
|
|
||||||
}
|
|
||||||
catch(e) {
|
|
||||||
console.error(e)
|
|
||||||
return 'failed'
|
|
||||||
}
|
|
||||||
}).reduce(async (acc, r) => {
|
|
||||||
const accumulator = await acc
|
|
||||||
const result = await r
|
|
||||||
accumulator.total++
|
|
||||||
accumulator[result]++
|
|
||||||
return accumulator
|
|
||||||
}, {total: 0, passed: 0, failed: 0})
|
|
||||||
|
|
||||||
console.log(`Done. passed: ${res.passed}, failed: ${res.failed}, total: ${res.total}`)
|
|
||||||
})()
|
|
Reference in a new issue