[node] implement everything...
This commit is contained in:
parent
1fda3c11a4
commit
99809e1139
9 changed files with 527 additions and 12 deletions
23
.vscode/launch.json
vendored
23
.vscode/launch.json
vendored
|
@ -1,12 +1,33 @@
|
||||||
{
|
{
|
||||||
"configurations": [
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Python: Flask-node",
|
||||||
|
"type": "python",
|
||||||
|
"request": "launch",
|
||||||
|
"module": "flask",
|
||||||
|
"cwd": "${workspaceFolder}/nodes/",
|
||||||
|
"env": {
|
||||||
|
"FLASK_APP": "main.py",
|
||||||
|
"FLASK_DEBUG": "1",
|
||||||
|
"FLASK_RUN_HOST": "::",
|
||||||
|
"FLASK_RUN_PORT": "8142"
|
||||||
|
},
|
||||||
|
"args": ["run", "--no-debugger"],
|
||||||
|
"jinja": true,
|
||||||
|
"justMyCode": true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "Python: Flask-backend",
|
"name": "Python: Flask-backend",
|
||||||
"type": "python",
|
"type": "python",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"module": "flask",
|
"module": "flask",
|
||||||
"cwd": "${workspaceFolder}/web/",
|
"cwd": "${workspaceFolder}/web/",
|
||||||
"env": { "FLASK_APP": "backend/main.py", "FLASK_DEBUG": "1", "FLASK_RUN_HOST": "0.0.0.0", "FLASK_RUN_PORT": "8042" },
|
"env": {
|
||||||
|
"FLASK_APP": "backend/main.py",
|
||||||
|
"FLASK_DEBUG": "1",
|
||||||
|
"FLASK_RUN_HOST": "0.0.0.0",
|
||||||
|
"FLASK_RUN_PORT": "8042"
|
||||||
|
},
|
||||||
"args": ["run", "--no-debugger"],
|
"args": ["run", "--no-debugger"],
|
||||||
"jinja": true,
|
"jinja": true,
|
||||||
"justMyCode": true
|
"justMyCode": true
|
||||||
|
|
3
nodes/.gitignore
vendored
Normal file
3
nodes/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
venv
|
||||||
|
peerings.json
|
||||||
|
config.json
|
405
nodes/main.py
Normal file
405
nodes/main.py
Normal file
|
@ -0,0 +1,405 @@
|
||||||
|
from flask import Flask, request, session, Response, render_template
|
||||||
|
from flask_restful import Resource, Api, reqparse
|
||||||
|
#import pandas as pd
|
||||||
|
#import ast
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import base64
|
||||||
|
import logging
|
||||||
|
import random
|
||||||
|
import time
|
||||||
|
from functools import wraps
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
api = Api(app)
|
||||||
|
|
||||||
|
# used as default value for create/update peering
|
||||||
|
|
||||||
|
|
||||||
|
class NotSpecified:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Config (dict):
|
||||||
|
def __init__(self, configfile: str = None):
|
||||||
|
if configfile:
|
||||||
|
self.configfile = configfile
|
||||||
|
else:
|
||||||
|
if os.path.exists("./node.config.json"):
|
||||||
|
self.configfile = "./node.config.json"
|
||||||
|
elif os.path.exists("./config.json"):
|
||||||
|
self.configfile = "./config.json"
|
||||||
|
elif os.path.exists("/etc/dn42-autopeer/node.config.json"):
|
||||||
|
self.configfile = "/etc/dn42-autopeer/node.config.json"
|
||||||
|
else:
|
||||||
|
raise FileNotFoundError(
|
||||||
|
"no config file found in ./node.config.json, ./config.json or /etc/dn42-autopeer/config.json")
|
||||||
|
self._load_config()
|
||||||
|
self.keys = self._config.keys
|
||||||
|
#self.__getitem__ = self._config.__getitem__
|
||||||
|
super().__init__(self)
|
||||||
|
|
||||||
|
def __contains__(self, o):
|
||||||
|
return self._config.__contains__(o)
|
||||||
|
|
||||||
|
def __delitem__(self, v):
|
||||||
|
raise NotImplementedError()
|
||||||
|
super().__delitem__(self, v)
|
||||||
|
|
||||||
|
def __getitem__(self, k):
|
||||||
|
return self._config[k]
|
||||||
|
|
||||||
|
def _load_config(self):
|
||||||
|
with open(self.configfile) as cf:
|
||||||
|
try:
|
||||||
|
self._config = json.load(cf)
|
||||||
|
except json.decoder.JSONDecodeError:
|
||||||
|
raise SyntaxError(f"no valid JSON found in '{cf.name}'")
|
||||||
|
print(self._config)
|
||||||
|
if not "debug-mode" in self._config:
|
||||||
|
self._config["debug-mode"] = False
|
||||||
|
|
||||||
|
if not "peerings" in self._config:
|
||||||
|
self._config["peerings"] = "./peerings"
|
||||||
|
|
||||||
|
if not "wg-configs" in self._config:
|
||||||
|
self._config["wg-configs"] = "/etc/wireguard/"
|
||||||
|
|
||||||
|
if not os.path.exists(self._config["wg-configs"]):
|
||||||
|
raise FileNotFoundError(f"specified wg-configs '{self._config['wg-configs']}' isn't a directory/doesn't exist")
|
||||||
|
|
||||||
|
if not "wg-commands" in self._config:
|
||||||
|
raise KeyError("wg-commands not specified")
|
||||||
|
|
||||||
|
# check if required keys is a subset of the specified keys
|
||||||
|
if not set(["enable", "disable","up","down"]) <= set(self._config["wg-commands"].keys()):
|
||||||
|
raise KeyError("at least one of 'enable','disable','up','down' in 'wg-commands' not specified")
|
||||||
|
|
||||||
|
if not "bird-peers" in self._config:
|
||||||
|
self._config["bird-peers"] = "/etc/bird/peers/"
|
||||||
|
if not os.path.exists(self._config["bird-peers"]):
|
||||||
|
raise FileNotFoundError(f"specified bird-peers '{self._config['bird-peers']}' isn't a directory/doesn't exist")
|
||||||
|
|
||||||
|
if not "bird-reload" in self._config:
|
||||||
|
self._config["bird-reload"] = "birdc configure"
|
||||||
|
|
||||||
|
if not "templates" in self._config:
|
||||||
|
self._config["templates"] = "./templates"
|
||||||
|
|
||||||
|
logging.info(self._config)
|
||||||
|
|
||||||
|
|
||||||
|
class PeeringManager:
|
||||||
|
def __init__(self, config):
|
||||||
|
self.__config = config
|
||||||
|
self.__peering_file = config["peerings"]
|
||||||
|
|
||||||
|
self.__load_peerings()
|
||||||
|
|
||||||
|
def __load_peerings(self):
|
||||||
|
if not os.path.exists(self.__peering_file):
|
||||||
|
with open(self.__peering_file, "x") as p:
|
||||||
|
json.dump({}, p)
|
||||||
|
try:
|
||||||
|
with open(self.__peering_file, "r") as p:
|
||||||
|
self.peerings = json.load(p)
|
||||||
|
except json.decoder.JSONDecodeError:
|
||||||
|
with open(self.__peering_file, "w") as p:
|
||||||
|
json.dump({}, p)
|
||||||
|
with open(self.__peering_file, "r") as p:
|
||||||
|
self.peerings = json.load(p)
|
||||||
|
|
||||||
|
# self.peerings = {}
|
||||||
|
# missing_peerings = False
|
||||||
|
# for peering in self._peerings:
|
||||||
|
# if os.path.exists(f"{self._peering_dir}/{peering}.json"):
|
||||||
|
# with open(f"{self._peering_dir}/{peering}.json") as peer_cfg:
|
||||||
|
# self.peerings[peering] = json.load(peer_cfg)
|
||||||
|
# else:
|
||||||
|
# logging.warning(f"peering with id {peering} doesn't exist. removing reference in `{self._peering_dir}/peerings.json`")
|
||||||
|
# self._peerings.remove(peering)
|
||||||
|
# missing_peerings = True
|
||||||
|
# if missing_peerings:
|
||||||
|
# with open(f"{self._peering_dir}/peerings.json","w") as p:
|
||||||
|
# json.dump(self._peerings, p, indent=4)
|
||||||
|
def __save_peerings(self):
|
||||||
|
with open(self.__peering_file, "w") as p:
|
||||||
|
json.dump(self.peerings, p, indent=4)
|
||||||
|
self._amounts = None
|
||||||
|
|
||||||
|
def __generate_wg_conf(self, peering: dict):
|
||||||
|
return render_template("wireguard.template.conf", peering=peering)
|
||||||
|
def __generate_bird_conf(self, peering:dict):
|
||||||
|
return render_template("bgp-peer.template.conf", peering=peering)
|
||||||
|
|
||||||
|
def __install_peering(self, mode: str, peering: dict):
|
||||||
|
if mode == "add":
|
||||||
|
wg_conf = self.__generate_wg_conf(peering)
|
||||||
|
bgp_conf = self.__generate_bird_conf(peering)
|
||||||
|
with open(f"{self.__config['wg-configs']}/dn42_{peering['MNT'][:-4].lower()}.conf", "w") as wg_file:
|
||||||
|
wg_file.write(wg_conf)
|
||||||
|
|
||||||
|
wg_enable = subprocess.run(self.__config["wg-commands"]["enable"].replace("{MNT}",peering['MNT'][:-4].lower()).split(" "))
|
||||||
|
print(wg_enable)
|
||||||
|
wg_up = subprocess.run(self.__config["wg-commands"]["up"].replace("{MNT}",peering['MNT'][:-4].lower()).split(" "))
|
||||||
|
print(wg_up)
|
||||||
|
time.sleep(5)
|
||||||
|
with open(f"{self.__config['bird-peers']}/dn42_{peering['MNT'][:-4].lower()}.conf", "w") as bgp_file:
|
||||||
|
bgp_file.write(bgp_conf)
|
||||||
|
bgp_reload = subprocess.run(self.__config["bird-reload"].replace("{MNT}",peering['MNT'][:-4].lower()).split(" "))
|
||||||
|
print(bgp_reload)
|
||||||
|
|
||||||
|
return 200
|
||||||
|
elif mode == "update":
|
||||||
|
wg_conf = self.__generate_wg_conf(peering)
|
||||||
|
bgp_conf = self.__generate_bird_conf(peering)
|
||||||
|
with open(f"{self.__config['wg-configs']}/dn42_{peering['MNT'][:-4].lower()}.conf", "w") as wg_file:
|
||||||
|
wg_file.write(wg_conf)
|
||||||
|
|
||||||
|
wg_down = subprocess.run(self.__config["wg-commands"]["down"].replace("{MNT}",peering['MNT'][:-4].lower()).split(" "))
|
||||||
|
print(wg_down)
|
||||||
|
wg_up = subprocess.run(self.__config["wg-commands"]["up"].replace("{MNT}",peering['MNT'][:-4].lower()).split(" "))
|
||||||
|
print(wg_up)
|
||||||
|
time.sleep(5)
|
||||||
|
with open(f"{self.__config['bird-peers']}/dn42_{peering['MNT'][:-4].lower()}.conf", "w") as bgp_file:
|
||||||
|
bgp_file.write(bgp_conf)
|
||||||
|
bgp_reload = subprocess.run(self.__config["bird-reload"].replace("{MNT}",peering['MNT'][:-4].lower()).split(" "))
|
||||||
|
print(bgp_reload)
|
||||||
|
|
||||||
|
return 200
|
||||||
|
elif mode == "delete":
|
||||||
|
os.remove(f"{self.__config['bird-peers']}/dn42_{peering['MNT'][:-4].lower()}.conf")
|
||||||
|
bgp_reload = subprocess.run(self.__config["bird-reload"].replace("{MNT}",peering['MNT'][:-4].lower()).split(" "))
|
||||||
|
print(bgp_reload)
|
||||||
|
time.sleep(5)
|
||||||
|
wg_down = subprocess.run(self.__config["wg-commands"]["down"].replace("{MNT}",peering['MNT'][:-4].lower()).split(" "))
|
||||||
|
print(wg_down)
|
||||||
|
wg_disable = subprocess.run(self.__config["wg-commands"]["disable"].replace("{MNT}",peering['MNT'][:-4].lower()).split(" "))
|
||||||
|
print(wg_disable)
|
||||||
|
|
||||||
|
return 200
|
||||||
|
|
||||||
|
return 405
|
||||||
|
|
||||||
|
def get_peerings_by_asn(self, asn):
|
||||||
|
if asn in self.peerings:
|
||||||
|
return self.peerings[asn]
|
||||||
|
else:
|
||||||
|
return []
|
||||||
|
|
||||||
|
def exists(self, asn, MNT=NotSpecified, wg_key=NotSpecified, endpoint=NotSpecified, ipv6ll=NotSpecified, ipv4=NotSpecified, ipv6=NotSpecified, bgp_mp=NotSpecified, bgp_enh=NotSpecified):
|
||||||
|
"""checks if a peerings with specific data already exists"""
|
||||||
|
# check if mnt is specified, already exists in the database and if that mnt has the specified ASn -> if not: return False
|
||||||
|
if not asn in self.peerings:
|
||||||
|
return False
|
||||||
|
selected_peerings = self.peerings[asn]
|
||||||
|
# check if the ASn even has peerings
|
||||||
|
if len(selected_peerings) == 0:
|
||||||
|
return False
|
||||||
|
for p in selected_peerings:
|
||||||
|
if (not wg_key or p["wg_key"] == wg_key):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def add_peering(self, MNT, ASN, wg_key, node=NotSpecified, endpoint=NotSpecified, ipv6ll=NotSpecified, ipv4=NotSpecified, ipv6=NotSpecified, bgp_mp=NotSpecified, bgp_enh=NotSpecified):
|
||||||
|
asn = ASN
|
||||||
|
try:
|
||||||
|
if not asn in self.peerings:
|
||||||
|
self.peerings[asn] = []
|
||||||
|
except KeyError:
|
||||||
|
self.peerings[asn] = []
|
||||||
|
|
||||||
|
for p in self.peerings[asn]:
|
||||||
|
if p["wg_key"] == wg_key:
|
||||||
|
return False, 409
|
||||||
|
|
||||||
|
new_peering = {"MNT": MNT, "ASN": asn, "node": config["nodename"], "wg_key": wg_key, "endpoint": endpoint if endpoint != NotSpecified else None,
|
||||||
|
"ipv6ll": ipv6ll if ipv6ll != NotSpecified else None, "ipv4": ipv4 if ipv4 != NotSpecified else None, "ipv6": ipv6 if ipv6 != NotSpecified else None,
|
||||||
|
"bgp_mp": bgp_mp if bgp_mp != NotSpecified else True, "bgp_enh": bgp_enh if bgp_enh != NotSpecified else True}
|
||||||
|
self.peerings[asn].append(new_peering)
|
||||||
|
|
||||||
|
self.__save_peerings()
|
||||||
|
ret_code = self.__install_peering(mode="add", peering=new_peering)
|
||||||
|
if ret_code == 200:
|
||||||
|
return True, 201
|
||||||
|
else:
|
||||||
|
return False, ret_code
|
||||||
|
|
||||||
|
def update_peering(self, ASN, wg_key, MNT=NotSpecified, node=NotSpecified, endpoint=NotSpecified, ipv6ll=NotSpecified, ipv4=NotSpecified, ipv6=NotSpecified, bgp_mp=NotSpecified, bgp_enh=NotSpecified):
|
||||||
|
asn = ASN
|
||||||
|
|
||||||
|
try:
|
||||||
|
if not asn in self.peerings:
|
||||||
|
return False, 404
|
||||||
|
except KeyError:
|
||||||
|
return False, 404
|
||||||
|
|
||||||
|
success = False
|
||||||
|
for pNr in range(len(self.peerings[asn])):
|
||||||
|
if self.peerings[asn][pNr]["node"] == node:
|
||||||
|
old_peering = self.peerings[asn][pNr]
|
||||||
|
new_peering = self.peerings[asn][pNr] = {"MNT": MNT if MNT!=NotSpecified else old_peering["MNT"], "ASN": asn, "node": config["nodename"], "wg_key": wg_key,
|
||||||
|
"endpoint": endpoint if endpoint!=NotSpecified else old_peering["endpoint"], "ipv6ll": ipv6ll if ipv6ll != NotSpecified else old_peering["ipv6ll"], "ipv4": ipv4 if ipv4 != NotSpecified else old_peering["ipv4"], "ipv6": ipv6 if ipv6 != NotSpecified else old_peering["ipv6"], "bgp_mp": bgp_mp if bgp_mp != NotSpecified else old_peering["bgp_mp"], "bgp_enh": bgp_enh if bgp_enh != NotSpecified else old_peering["bgp_enh"]}
|
||||||
|
success = True
|
||||||
|
if not success:
|
||||||
|
return False, 404
|
||||||
|
|
||||||
|
self.__save_peerings()
|
||||||
|
ret_code = self.__install_peering(mode="update", peering=new_peering)
|
||||||
|
if ret_code == 200:
|
||||||
|
return True, 200
|
||||||
|
else:
|
||||||
|
return False, ret_code
|
||||||
|
|
||||||
|
def delete_peering(self, ASN, node, wg_key=None):
|
||||||
|
asn = ASN
|
||||||
|
if not self.exists(asn=asn, wg_key=wg_key):
|
||||||
|
return False, 404
|
||||||
|
for p in self.peerings[asn]:
|
||||||
|
if p["node"] == node:
|
||||||
|
if wg_key and p["wg_key"] != wg_key:
|
||||||
|
continue
|
||||||
|
self.peerings[asn].remove(p)
|
||||||
|
print(self.peerings)
|
||||||
|
self.__save_peerings()
|
||||||
|
ret_code = self.__install_peering(
|
||||||
|
mode="delete", peering=p)
|
||||||
|
if ret_code == 200:
|
||||||
|
return True, 201
|
||||||
|
else:
|
||||||
|
return False, ret_code
|
||||||
|
# if nothing got found (should have been catched by self.exists)
|
||||||
|
return False, 404
|
||||||
|
|
||||||
|
|
||||||
|
config = Config()
|
||||||
|
peerings = PeeringManager(config)
|
||||||
|
|
||||||
|
|
||||||
|
def check_ACL():
|
||||||
|
def wrapper(f):
|
||||||
|
@wraps(f)
|
||||||
|
def decorated(*args, **kwargs):
|
||||||
|
if request.remote_addr in config["ACL"]:
|
||||||
|
return f(*args, **kwargs)
|
||||||
|
else:
|
||||||
|
return Response(response="Unauthorized", status=401)
|
||||||
|
return decorated
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
class PeeringsRoute(Resource):
|
||||||
|
|
||||||
|
@check_ACL()
|
||||||
|
def get(self):
|
||||||
|
parser = reqparse.RequestParser() # initialize
|
||||||
|
parser.add_argument('ASN', required=True)
|
||||||
|
parser.add_argument('node', required=True, default=config["nodename"])
|
||||||
|
parser.add_argument('wg_key')
|
||||||
|
args = parser.parse_args() # parse arguments to dictionary
|
||||||
|
|
||||||
|
requested_peerings = peerings.get_peerings_by_asn(args["ASN"])
|
||||||
|
if requested_peerings:
|
||||||
|
return {"success": True, "asn": args["ASN"], "peerings": requested_peerings}, 200
|
||||||
|
else:
|
||||||
|
return {"success": False, "asn": args["ASN"], "error": "not found", "peerings": []}, 404
|
||||||
|
|
||||||
|
@check_ACL()
|
||||||
|
def post(self):
|
||||||
|
# print(request.get_json())
|
||||||
|
# {'MNT': 'LARE-MNT', 'ASN': '4242423035', 'node': 'node1',
|
||||||
|
# 'wg_key': 'uM92Rks/Em2n7QLGek6OUXyGO1P/hEelMQNLlW85J2o=',
|
||||||
|
# 'endpoint': None, 'ipv6ll': 'fe80::2', 'ipv4': None, 'ipv6': None,
|
||||||
|
# 'bgp_mp': True, 'bgp_enh': True}
|
||||||
|
parser = reqparse.RequestParser() # initialize
|
||||||
|
parser.add_argument('MNT', required=True)
|
||||||
|
parser.add_argument('ASN', required=True)
|
||||||
|
parser.add_argument('node', required=True, default=config["nodename"])
|
||||||
|
parser.add_argument('wg_key', required=True)
|
||||||
|
parser.add_argument('endpoint')
|
||||||
|
parser.add_argument('ipv6ll')
|
||||||
|
parser.add_argument('ipv4')
|
||||||
|
parser.add_argument('ipv6')
|
||||||
|
parser.add_argument('bgp_mp')
|
||||||
|
parser.add_argument('bgp_enh')
|
||||||
|
args = parser.parse_args() # parse arguments to dictionary
|
||||||
|
|
||||||
|
if not peerings.exists(asn=args["ASN"], wg_key=args["wg_key"]):
|
||||||
|
status, code = peerings.add_peering(**args)
|
||||||
|
if status:
|
||||||
|
return {"success": True, "new_peering": args}, 201
|
||||||
|
else:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
code = 409
|
||||||
|
|
||||||
|
if code == 409:
|
||||||
|
error_msg = "already exists"
|
||||||
|
elif code == "50x":
|
||||||
|
error_msg = "abc"
|
||||||
|
|
||||||
|
return {"success": False, "error": error_msg}, code
|
||||||
|
|
||||||
|
@check_ACL()
|
||||||
|
def put(self):
|
||||||
|
|
||||||
|
parser = reqparse.RequestParser() # initialize
|
||||||
|
parser.add_argument('MNT', required=True)
|
||||||
|
parser.add_argument('ASN', required=True)
|
||||||
|
parser.add_argument('node', required=True, default=config["nodename"])
|
||||||
|
parser.add_argument('wg_key', required=True, nullable=False)
|
||||||
|
parser.add_argument('endpoint', store_missing=False)
|
||||||
|
parser.add_argument('ipv6ll', store_missing=False)
|
||||||
|
parser.add_argument('ipv4', store_missing=False)
|
||||||
|
parser.add_argument('ipv6', store_missing=False)
|
||||||
|
parser.add_argument('bgp_mp', store_missing=False)
|
||||||
|
parser.add_argument('bgp_enh', store_missing=False)
|
||||||
|
args = parser.parse_args() # parse arguments to dictionary
|
||||||
|
print(args)
|
||||||
|
if peerings.exists(asn=args["ASN"], wg_key=args["wg_key"]):
|
||||||
|
ret = peerings.update_peering(**args)
|
||||||
|
if ret:
|
||||||
|
return {"success": True, "new_peering": args}, 200
|
||||||
|
else:
|
||||||
|
return {"success": False, "new_peering": args}, 500
|
||||||
|
|
||||||
|
else:
|
||||||
|
return {"success": False, "error": "not found"}, 404
|
||||||
|
|
||||||
|
@check_ACL()
|
||||||
|
def delete(self):
|
||||||
|
parser = reqparse.RequestParser() # initialize
|
||||||
|
parser.add_argument('ASN', required=True, nullable=False)
|
||||||
|
parser.add_argument('node', required=True, default=config["nodename"])
|
||||||
|
parser.add_argument('wg_key', required=True, nullable=False)
|
||||||
|
args = parser.parse_args() # parse arguments to dictionary
|
||||||
|
|
||||||
|
if peerings.exists(asn=args["ASN"], wg_key=args["wg_key"]):
|
||||||
|
print(args)
|
||||||
|
ret, code = peerings.delete_peering(**args)
|
||||||
|
if ret:
|
||||||
|
return {"success": True, "deleted": args}
|
||||||
|
else:
|
||||||
|
code = 404
|
||||||
|
if code == 404:
|
||||||
|
error_msg = "not found"
|
||||||
|
return {"success": False, "error": error_msg}, code
|
||||||
|
|
||||||
|
|
||||||
|
api.add_resource(PeeringsRoute, '/peerings/')
|
||||||
|
app.template_folder = config["templates"]
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if "production" in config and config["production"] == False:
|
||||||
|
logging.getLogger(__name__).setLevel(0)
|
||||||
|
app.run(host=config["listen"], port=config["port"],
|
||||||
|
debug=config["debug-mode"], threaded=True)
|
||||||
|
else:
|
||||||
|
from waitress import serve
|
||||||
|
logging.getLogger(__name__).setLevel(logging.INFO)
|
||||||
|
serve(app, host=config["listen"], port=config["port"])
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
21
nodes/node.config.sample.jsonc
Normal file
21
nodes/node.config.sample.jsonc
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"MNT": "YOUR-MNT", // your MNT tag
|
||||||
|
"ASN": "424242000", //Your ASN (used to generate default peer ListenPorts)
|
||||||
|
"listen": "0.0.0.0",
|
||||||
|
"port": 8142,
|
||||||
|
"nodename": "node123",
|
||||||
|
"ACL": ["127.0.0.1"], // address of the server running the webinterface (for ACL); if "listen" is "::"(or other ipv6) and the the server is connecting via ipv4 use"::ffff:<ipv4>"
|
||||||
|
"peerings": "/path/to/peerings.json", // optional; default "$PWD/peerings.json", file to save existing peerings to
|
||||||
|
"production": true, //optional, default true;
|
||||||
|
"debug-mode": false, // optional; whethet to enable debugging; default false
|
||||||
|
"wg-configs": "/etc/wireguard/", // optional, default: "/etc/wireguard/"; directory where the wireguard configs are located
|
||||||
|
"wg-commands": { // {MNT} will get replaced with the lowercase mnter without "-MNT"
|
||||||
|
"enable": "systemctl enable wg-quick@dn42_{MNT}", //command to execute for enabling the wg-interface
|
||||||
|
"up": "systemctl start wg-quick@dn42_{MNT}", //command to execute for starting the wg-interface
|
||||||
|
"down": "systemctl stop wg-quick@dn42_{MNT}", //command to execute for stopping the wg-interface
|
||||||
|
"disable": "systemctl disable wg-quick@dn42_{MNT}" //command to execute for disabling the wg-interface
|
||||||
|
},
|
||||||
|
"bird-peers": "/etc/bird/peers/", // optional, default: "/etc/bird/peers/"; directory where bird peers are to be located
|
||||||
|
"bird-reload": "birdc configure", // optional, default: "birdc configure"; command to reconfigure bird or other bgp daemon
|
||||||
|
"templates": "templates" //optional, default "$PWD/templates"; directory where "wireguard.template.conf" and "bgo-peer.template.conf" are located
|
||||||
|
}
|
36
nodes/templates/bgp-peer.template.conf
Normal file
36
nodes/templates/bgp-peer.template.conf
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
{% if peering["bgp_mp"] %}
|
||||||
|
protocol bgp dn42_{{peering["MNT"][:-4].lower()}} from dnpeers {
|
||||||
|
neighbor {{peering["ipv6ll"]}} as {{peering["ASN"]}};
|
||||||
|
interface "dn42_{{peering['MNT'][:-4].lower()}}";
|
||||||
|
passive off;
|
||||||
|
|
||||||
|
ipv4 {
|
||||||
|
{# TODO: implement pinging peer for bgp-communities
|
||||||
|
#import where dn42_import_filter(x,y,z);
|
||||||
|
#export where dn42_export_filter(x,y,z);
|
||||||
|
#}
|
||||||
|
extended next hop {% if peering["bgp_enh"] %}on{%else%}off{%endif%};
|
||||||
|
};
|
||||||
|
ipv6 {
|
||||||
|
{# TODO: implement pinging peer for bgp-communities
|
||||||
|
#import where dn42_import_filter(x,y,z);
|
||||||
|
#export where dn42_export_filter(x,y,z);
|
||||||
|
#}
|
||||||
|
extended next hop {% if peering["bgp_enh"] %}on{%else%}off{%endif%};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
{%else%}
|
||||||
|
protocol bgp dn42_{{peering["MNT"][:-4].lower()}}_4 from dnpeers {
|
||||||
|
neighbor {{peering["ipv4"]}} as {{peering["ASN"]}};
|
||||||
|
passive off;
|
||||||
|
#import where dn42_import_filter(x,y,z);
|
||||||
|
#export where dn42_export_filter(x,y,z);
|
||||||
|
};
|
||||||
|
|
||||||
|
protocol bgp dn42_{{peering["MNT"][:-4].lower()}}_6 from dnpeers {
|
||||||
|
neighbor {{peering["ipv6"]}} as {{peering["ASN"]}};
|
||||||
|
passive off;
|
||||||
|
#import where dn42_import_filter(x,y,z);
|
||||||
|
#export where dn42_export_filter(x,y,z);
|
||||||
|
}
|
||||||
|
{%endif%}
|
13
nodes/templates/wireguard.template.conf
Normal file
13
nodes/templates/wireguard.template.conf
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
[Interface]
|
||||||
|
PostUp = wg set %i private-key /etc/wireguard/dn42.priv
|
||||||
|
ListenPort = 2{{peering["ASN"][-4:]}}
|
||||||
|
{% if peering["ipv4"] %}PostUp = /sbin/ip addr add dev %i 172.22.125.130/32 peer {{peering["ipv4"]}}/32
|
||||||
|
{%endif%}{% if peering["ipv6"] %}PostUp = /sbin/ip addr add dev %i fe63:5d40:47e5::130/128 peer {{peering["ipv6"]}}/128
|
||||||
|
{%endif%}{% if peering["ipv6ll"] %}PostUp = /sbin/ip addr add dev %i fe80::3035:130/128 peer {{peering["ipv6ll"]}}/128{%endif%}
|
||||||
|
Table = off
|
||||||
|
|
||||||
|
# {{peering["MNT"]}}
|
||||||
|
[Peer]
|
||||||
|
PublicKey = {{peering["wg_key"]}}
|
||||||
|
{% if peering["endpoint"] %}Endpoint = {{peering["endpoint"]}}{%endif%}
|
||||||
|
AllowedIPs = {% if peering["ipv6ll"] %}{{peering["ipv6ll"]}}/128, {%endif%}fd00::/8, 172.20.0.0/14, 10.0.0.0/8
|
|
@ -21,7 +21,7 @@
|
||||||
"port": 8042,
|
"port": 8042,
|
||||||
"domain": "example.org", // domain to use for kioubit verification service (with protocol)
|
"domain": "example.org", // domain to use for kioubit verification service (with protocol)
|
||||||
"base-dir": "/", //optional:directury for which it is reachable (if behind some sort of reverse proxy) default "/"
|
"base-dir": "/", //optional:directury for which it is reachable (if behind some sort of reverse proxy) default "/"
|
||||||
"peerings": "/path/to/peering-config.json", // optional; default "$PWD/peerings", directory to save existing peerings to
|
"peerings": "/path/to/peerings.json", // optional; default "$PWD/peerings.json", file to save existing peerings to
|
||||||
"production": true, //optional, default true;
|
"production": true, //optional, default true;
|
||||||
"debug-mode": false, // optional; whethet to enable debugging; default false
|
"debug-mode": false, // optional; whethet to enable debugging; default false
|
||||||
"flask-secret-key": "<secret-please-replace>", // secret key for session cookies
|
"flask-secret-key": "<secret-please-replace>", // secret key for session cookies
|
||||||
|
|
|
@ -3,6 +3,8 @@ import os
|
||||||
import base64
|
import base64
|
||||||
import logging
|
import logging
|
||||||
import random
|
import random
|
||||||
|
import threading
|
||||||
|
import requests
|
||||||
|
|
||||||
class NodeCommunicator:
|
class NodeCommunicator:
|
||||||
|
|
||||||
|
@ -10,6 +12,10 @@ class NodeCommunicator:
|
||||||
self.name = name
|
self.name = name
|
||||||
self.__config = config
|
self.__config = config
|
||||||
self.__api_addr = config["api-con"]
|
self.__api_addr = config["api-con"]
|
||||||
|
|
||||||
|
def update(self, action:str, updated_peering:dict):
|
||||||
|
print(requests.api.post(self.__api_addr+"peerings", json=updated_peering).content)
|
||||||
|
input()
|
||||||
|
|
||||||
class PeeringManager:
|
class PeeringManager:
|
||||||
|
|
||||||
|
@ -24,6 +30,7 @@ class PeeringManager:
|
||||||
self._nodes[node] = NodeCommunicator(name=node, config=self.__config["nodes"][node])
|
self._nodes[node] = NodeCommunicator(name=node, config=self.__config["nodes"][node])
|
||||||
|
|
||||||
self._amounts = None
|
self._amounts = None
|
||||||
|
self._threads = []
|
||||||
|
|
||||||
def __load_peerings(self):
|
def __load_peerings(self):
|
||||||
if not os.path.exists(self.__peering_file):
|
if not os.path.exists(self.__peering_file):
|
||||||
|
@ -56,12 +63,18 @@ class PeeringManager:
|
||||||
json.dump(self.peerings, p, indent=4)
|
json.dump(self.peerings, p, indent=4)
|
||||||
self._amounts = None
|
self._amounts = None
|
||||||
|
|
||||||
def _update_nodes(self, mode, peering, new_peering=None):
|
def _update_nodes(self, action:str, peering, new_peering=None):
|
||||||
"""mode: "add","update","delete
|
"""mode: "add","update","delete
|
||||||
peering: peering to send to node (included in peering)
|
peering: peering to send to node (included in peering)
|
||||||
new_peering: if mode=="update" the new peering to update to
|
new_peering: if mode=="update" the new peering to update to
|
||||||
"""
|
"""
|
||||||
pass
|
if peering["node"] in self._nodes:
|
||||||
|
thread = threading.Thread(target=self._nodes[peering["node"]].update,kwargs={"action":action,"updated_peering":peering if not new_peering else new_peering,})
|
||||||
|
thread.start()
|
||||||
|
self._threads.append(thread)
|
||||||
|
|
||||||
|
else: return False
|
||||||
|
|
||||||
def _update_amounts(self):
|
def _update_amounts(self):
|
||||||
__new = {}
|
__new = {}
|
||||||
for asn in self.peerings["asn"]:
|
for asn in self.peerings["asn"]:
|
||||||
|
@ -154,12 +167,15 @@ class PeeringManager:
|
||||||
success = False
|
success = False
|
||||||
for pNr in range(len(self.peerings["asn"][asn])):
|
for pNr in range(len(self.peerings["asn"][asn])):
|
||||||
if self.peerings["asn"][asn][pNr]["node"] == node:
|
if self.peerings["asn"][asn][pNr]["node"] == node:
|
||||||
self.peerings["asn"][asn][pNr] = {"MNT": mnt, "ASN": asn, "node": node, "wg_key": wg_key,
|
old_peering = self.peerings["asn"][asn][pNr]
|
||||||
|
new_peering = self.peerings["asn"][asn][pNr] = {"MNT": mnt, "ASN": asn, "node": node, "wg_key": wg_key,
|
||||||
"endpoint": endpoint, "ipv6ll": ipv6ll, "ipv4": ipv4, "ipv6": ipv6, "bgp_mp": bgp_mp, "bgp_enh": bgp_enh}
|
"endpoint": endpoint, "ipv6ll": ipv6ll, "ipv4": ipv4, "ipv6": ipv6, "bgp_mp": bgp_mp, "bgp_enh": bgp_enh}
|
||||||
success = True
|
success = True
|
||||||
if not success:
|
if not success:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
self._save_peerings()
|
self._save_peerings()
|
||||||
|
self._update_nodes("update", old_peering, new_peering=new_peering)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def delete_peering(self, asn, node, mnt, wg_key=None):
|
def delete_peering(self, asn, node, mnt, wg_key=None):
|
||||||
|
|
|
@ -65,13 +65,6 @@ footer.flex {
|
||||||
/* # index.html */
|
/* # index.html */
|
||||||
|
|
||||||
/* based on https://codepen.io/t_afif/pen/RwQZLYb */
|
/* based on https://codepen.io/t_afif/pen/RwQZLYb */
|
||||||
.progress-bar {
|
|
||||||
--w: 100px; /* the width*/
|
|
||||||
|
|
||||||
--background: lightgrey; /* the background color */
|
|
||||||
width: var(--w);
|
|
||||||
margin: 0 10px;
|
|
||||||
}
|
|
||||||
.progress {
|
.progress {
|
||||||
-webkit-appearance: none;
|
-webkit-appearance: none;
|
||||||
-moz-appearance: none;
|
-moz-appearance: none;
|
||||||
|
@ -81,6 +74,13 @@ footer.flex {
|
||||||
border-radius: 10em;
|
border-radius: 10em;
|
||||||
background: var(--background);
|
background: var(--background);
|
||||||
}
|
}
|
||||||
|
.progress-bar {
|
||||||
|
--w: 100px; /* the width*/
|
||||||
|
|
||||||
|
--background: lightgrey; /* the background color */
|
||||||
|
width: var(--w);
|
||||||
|
margin: 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.progress-value[value]{
|
.progress-value[value]{
|
||||||
--color: /* the progress color */
|
--color: /* the progress color */
|
||||||
|
|
Loading…
Add table
Reference in a new issue