Compare commits

..

22 commits
v1.0 ... master

Author SHA1 Message Date
1f242ba597 nodes: use ASN instead of asn in peerings.update() 2025-01-26 16:08:27 +01:00
d34136b42f [backend] longer timeout for node communication and retrying with other if add/update fails with 409/404 2024-09-29 20:09:45 +02:00
b3337a5b3d [frontend] edit page: show the asn if login is admin 2024-09-01 18:17:09 +02:00
067803081d [backend] change default values in Peeringmanager.exists()
the large if clause there would return false if the requested peering doesn't have bgp_mp and bgp_enh
2024-09-01 14:29:19 +02:00
376571c410 [web] allow admin to view/create/edit peerings of others
also: fix typo in peering database + provide migration
2024-09-01 14:28:59 +02:00
41e2290365 [node] also try ipv6ll in bgp-config + reformat
[frontend] don't show enh v6 on
2024-04-23 18:07:48 +02:00
f01559844d [nodes] replace "-" with "_", so bird doesn't complain
(thanks to BAS-DINGEMANS-99-MNT for having "-"  in their name)
2023-09-05 07:48:24 +02:00
b3eb047a6f [web] also return http_code when trying to create peering when already exists 2023-07-21 11:08:27 +02:00
1894a6dce9 [nodes] decrease sleep from 5 to 3 seconds 2023-06-14 08:53:03 +02:00
09da5ec830 [web] notify user if something failed in the backend 2023-06-14 08:52:17 +02:00
67f10102b3 [web][kverify] use new api vesion
- data["mnt] is now a list of strings instead of string
2023-06-09 20:31:45 +02:00
23fa5ba1a6 [web] add new Kioubit Auth button 2023-06-05 00:19:23 +02:00
4139c66381 [node] add typehints for argument parser 2023-04-22 17:02:19 +02:00
d0a21adab4 [web] allow owner (specified in config) to use debug login even if debug login isn't enabled 2023-04-22 16:24:09 +02:00
cb03ba5556 [node] use ipv4 or ipv6 if no ipv6 ll is specified 2023-04-22 15:59:08 +02:00
c27951ef0c [web] change check for allowed4/6 when checking peering data 2023-02-24 12:28:05 +01:00
e6e38675b9 [Web] fix ipv4,6 not being able to be set if only one range is in allowed
+ add hints for form for the ip addresses
2023-02-05 21:10:32 +01:00
b44f89b664 [docs] add absolute path for example systemd service 2023-01-21 16:20:58 +01:00
c02e8ae3c0 [hotfix][nodes] only use ASn in interface name
- it is only 15 chars by default (change requires kernel recompile)
- thanks to @famfo for have a long mntner name
2023-01-21 12:12:00 +01:00
ad9caf6798 removr "todo"s in readme 2023-01-19 15:49:50 +01:00
b231b6a835 [hotfix][nodes] fix "asn" not being uppercase in bird conf 2023-01-16 18:38:55 +01:00
0f1cbb3a11 allow multiple peerings per mntner and node (not per ASN) 2023-01-16 18:06:10 +01:00
19 changed files with 255 additions and 162 deletions

View file

@ -5,10 +5,10 @@ This is my (LARE-MNT) implementation of an auto peering system
It consists of two parts: It consists of two parts:
1. the "server" 1. the "server"
- handling user auth (via kioubit) - handling user auth (via kioubit)
- add/edit/delete peerings (#todo) - add/edit/delete peerings
- communicate new/update peerings with nodes #todo - communicate new/update peerings with nodes
2. the "nodes daemon" #todo 2. the "nodes daemon"
- reveives new/updated peering configs from the "server" #todo - reveives new/updated peering configs from the "server"
## Installation ## Installation

View file

@ -27,7 +27,7 @@ Type=simple
Restart=on-failure Restart=on-failure
RestartSec=5s RestartSec=5s
WorkingDirectory=</path/to/autopeering/>web WorkingDirectory=</path/to/autopeering/>web
ExecStart=start.sh ExecStart=</path/to/autopeering>/web/start.sh
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target
@ -72,7 +72,7 @@ Type=simple
Restart=on-failure Restart=on-failure
RestartSec=5s RestartSec=5s
WorkingDirectory=</path/to/autopeering>/nodes WorkingDirectory=</path/to/autopeering>/nodes
ExecStart=start.sh ExecStart=</path/to/autopeering>/nodes/start.sh
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target

View file

@ -130,52 +130,53 @@ class PeeringManager:
def __generate_wg_conf(self, peering: dict): def __generate_wg_conf(self, peering: dict):
return render_template("wireguard.template.conf", peering=peering) return render_template("wireguard.template.conf", peering=peering)
def __generate_bird_conf(self, peering:dict): def __generate_bird_conf(self, peering: dict):
peering["MNT"] = peering["MNT"].replace("-","_")
return render_template("bgp-peer.template.conf", peering=peering) return render_template("bgp-peer.template.conf", peering=peering)
def __install_peering(self, mode: str, peering: dict): def __install_peering(self, mode: str, peering: dict):
if mode == "add": if mode == "add":
wg_conf = self.__generate_wg_conf(peering) wg_conf = self.__generate_wg_conf(peering)
bgp_conf = self.__generate_bird_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: with open(f"{self.__config['wg-configs']}/dn42_{peering['ASN'][-6:] if len(peering['ASN']) >=6 else peering['ASN']}.conf", "w") as wg_file:
wg_file.write(wg_conf) wg_file.write(wg_conf)
wg_enable = subprocess.run(self.__config["wg-commands"]["enable"].replace("{MNT}",peering['MNT'][:-4].lower()).split(" ")) wg_enable = subprocess.run(self.__config["wg-commands"]["enable"].replace("{PEERING}",peering['ASN'][-6:] if len(peering["ASN"]) >=6 else peering["ASN"]).split(" "))
print(wg_enable) print(wg_enable)
wg_up = subprocess.run(self.__config["wg-commands"]["up"].replace("{MNT}",peering['MNT'][:-4].lower()).split(" ")) wg_up = subprocess.run(self.__config["wg-commands"]["up"].replace("{PEERING}",peering['ASN'][-6:] if len(peering["ASN"]) >=6 else peering["ASN"]).split(" "))
print(wg_up) print(wg_up)
time.sleep(5) time.sleep(3)
with open(f"{self.__config['bird-peers']}/dn42_{peering['MNT'][:-4].lower()}.conf", "w") as bgp_file: with open(f"{self.__config['bird-peers']}/dn42_{peering['MNT'][:-4].lower()}_{peering['ASN'][-4:]}.conf", "w") as bgp_file:
bgp_file.write(bgp_conf) bgp_file.write(bgp_conf)
bgp_reload = subprocess.run(self.__config["bird-reload"].replace("{MNT}",peering['MNT'][:-4].lower()).split(" ")) bgp_reload = subprocess.run(self.__config["bird-reload"].replace("{PEERING}",peering['ASN'][-6:] if len(peering["ASN"]) >=6 else peering["ASN"]).split(" "))
print(bgp_reload) print(bgp_reload)
return 200 return 200
elif mode == "update": elif mode == "update":
wg_conf = self.__generate_wg_conf(peering) wg_conf = self.__generate_wg_conf(peering)
bgp_conf = self.__generate_bird_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: with open(f"{self.__config['wg-configs']}/dn42_{peering['ASN'][-6:] if len(peering['ASN']) >=6 else peering['ASN']}.conf", "w") as wg_file:
wg_file.write(wg_conf) wg_file.write(wg_conf)
wg_down = subprocess.run(self.__config["wg-commands"]["down"].replace("{MNT}",peering['MNT'][:-4].lower()).split(" ")) wg_down = subprocess.run(self.__config["wg-commands"]["down"].replace("{PEERING}",peering['ASN'][-6:] if len(peering["ASN"]) >=6 else peering["ASN"]).split(" "))
print(wg_down) print(wg_down)
wg_up = subprocess.run(self.__config["wg-commands"]["up"].replace("{MNT}",peering['MNT'][:-4].lower()).split(" ")) wg_up = subprocess.run(self.__config["wg-commands"]["up"].replace("{PEERING}",peering['ASN'][-6:] if len(peering["ASN"]) >=6 else peering["ASN"]).split(" "))
print(wg_up) print(wg_up)
time.sleep(5) time.sleep(3)
with open(f"{self.__config['bird-peers']}/dn42_{peering['MNT'][:-4].lower()}.conf", "w") as bgp_file: with open(f"{self.__config['bird-peers']}/dn42_{peering['MNT'][:-4].lower()}_{peering['ASN'][-4:]}.conf", "w") as bgp_file:
bgp_file.write(bgp_conf) bgp_file.write(bgp_conf)
bgp_reload = subprocess.run(self.__config["bird-reload"].replace("{MNT}",peering['MNT'][:-4].lower()).split(" ")) bgp_reload = subprocess.run(self.__config["bird-reload"].replace("{PEERING}",peering['ASN'][-6:] if len(peering["ASN"]) >=6 else peering["ASN"]).split(" "))
print(bgp_reload) print(bgp_reload)
return 200 return 200
elif mode == "delete": elif mode == "delete":
os.remove(f"{self.__config['bird-peers']}/dn42_{peering['MNT'][:-4].lower()}.conf") os.remove(f"{self.__config['bird-peers']}/dn42_{peering['MNT'][:-4].lower()}_{peering['ASN'][-4:]}.conf")
bgp_reload = subprocess.run(self.__config["bird-reload"].replace("{MNT}",peering['MNT'][:-4].lower()).split(" ")) bgp_reload = subprocess.run(self.__config["bird-reload"].replace("{PEERING}",peering['ASN'][-6:] if len(peering["ASN"]) >=6 else peering["ASN"]).split(" "))
print(bgp_reload) print(bgp_reload)
time.sleep(5) time.sleep(3)
wg_down = subprocess.run(self.__config["wg-commands"]["down"].replace("{MNT}",peering['MNT'][:-4].lower()).split(" ")) wg_down = subprocess.run(self.__config["wg-commands"]["down"].replace("{PEERING}",peering['ASN'][-6:] if len(peering["ASN"]) >=6 else peering["ASN"]).split(" "))
print(wg_down) print(wg_down)
wg_disable = subprocess.run(self.__config["wg-commands"]["disable"].replace("{MNT}",peering['MNT'][:-4].lower()).split(" ")) wg_disable = subprocess.run(self.__config["wg-commands"]["disable"].replace("{PEERING}",peering['ASN'][-6:] if len(peering["ASN"]) >=6 else peering["ASN"]).split(" "))
print(wg_disable) print(wg_disable)
return 200 return 200
@ -228,7 +229,6 @@ class PeeringManager:
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): 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 asn = ASN
try: try:
if not asn in self.peerings: if not asn in self.peerings:
return False, 404 return False, 404
@ -239,8 +239,10 @@ class PeeringManager:
for pNr in range(len(self.peerings[asn])): for pNr in range(len(self.peerings[asn])):
if self.peerings[asn][pNr]["node"] == node: if self.peerings[asn][pNr]["node"] == node:
old_peering = self.peerings[asn][pNr] 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, new_peering = self.peerings[asn][pNr] = {"MNT": MNT if MNT!=NotSpecified else old_peering["MNT"], "ASN": asn, "node": config["nodename"],
"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"]} "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 success = True
if not success: if not success:
return False, 404 return False, 404
@ -301,9 +303,9 @@ class PeeringsRoute(Resource):
requested_peerings = peerings.get_peerings_by_asn(args["ASN"]) requested_peerings = peerings.get_peerings_by_asn(args["ASN"])
if requested_peerings: if requested_peerings:
return {"success": True, "asn": args["ASN"], "peerings": requested_peerings}, 200 return {"success": True, "ASN": args["ASN"], "peerings": requested_peerings}, 200
else: else:
return {"success": False, "asn": args["ASN"], "error": "not found", "peerings": []}, 404 return {"success": False, "ASN": args["ASN"], "error": "not found", "peerings": []}, 404
@check_ACL() @check_ACL()
def post(self): def post(self):
@ -321,8 +323,8 @@ class PeeringsRoute(Resource):
parser.add_argument('ipv6ll') parser.add_argument('ipv6ll')
parser.add_argument('ipv4') parser.add_argument('ipv4')
parser.add_argument('ipv6') parser.add_argument('ipv6')
parser.add_argument('bgp_mp') parser.add_argument('bgp_mp', type = bool)
parser.add_argument('bgp_enh') parser.add_argument('bgp_enh', type = bool)
args = parser.parse_args() # parse arguments to dictionary args = parser.parse_args() # parse arguments to dictionary
if not peerings.exists(asn=args["ASN"], wg_key=args["wg_key"]): if not peerings.exists(asn=args["ASN"], wg_key=args["wg_key"]):
@ -353,8 +355,8 @@ class PeeringsRoute(Resource):
parser.add_argument('ipv6ll', store_missing=False) parser.add_argument('ipv6ll', store_missing=False)
parser.add_argument('ipv4', store_missing=False) parser.add_argument('ipv4', store_missing=False)
parser.add_argument('ipv6', store_missing=False) parser.add_argument('ipv6', store_missing=False)
parser.add_argument('bgp_mp', store_missing=False) parser.add_argument('bgp_mp', type=bool, store_missing=False)
parser.add_argument('bgp_enh', store_missing=False) parser.add_argument('bgp_enh', type=bool, store_missing=False)
args = parser.parse_args() # parse arguments to dictionary args = parser.parse_args() # parse arguments to dictionary
print(args) print(args)
if peerings.exists(asn=args["ASN"], wg_key=args["wg_key"]): if peerings.exists(asn=args["ASN"], wg_key=args["wg_key"]):

View file

@ -9,11 +9,11 @@
"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
"wg-configs": "/etc/wireguard/", // optional, default: "/etc/wireguard/"; directory where the wireguard configs are located "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" "wg-commands": { // {PEERING} will get replaced with the lowercase mnter without "-MNT" followed by the last four digits of the ASn
"enable": "systemctl enable wg-quick@dn42_{MNT}", //command to execute for enabling the wg-interface "enable": "./wg-services.sh enable {PEERING}", //command to execute for enabling the wg-interface
"up": "systemctl start wg-quick@dn42_{MNT}", //command to execute for starting the wg-interface "up": "./wg-services.sh start {PEERING}", //command to execute for starting the wg-interface
"down": "systemctl stop wg-quick@dn42_{MNT}", //command to execute for stopping the wg-interface "down": "./wg-services.sh stop {PEERING}", //command to execute for stopping the wg-interface
"disable": "systemctl disable wg-quick@dn42_{MNT}" //command to execute for disabling the wg-interface "disable": "./wg-services.sh disable {PEERING}" //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-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 "bird-reload": "birdc configure", // optional, default: "birdc configure"; command to reconfigure bird or other bgp daemon

0
nodes/start.sh Normal file → Executable file
View file

View file

@ -1,7 +1,7 @@
{% if peering["bgp_mp"] %} {% if peering["bgp_mp"] == True %}
protocol bgp dn42_{{peering["MNT"][:-4].lower()}} from dnpeers { protocol bgp dn42_{{peering["MNT"][:-4].lower()}}_{{peering["ASN"][-4:]}} from dnpeers {
neighbor {{peering["ipv6ll"]}} as {{peering["ASN"]}}; neighbor {{peering["ipv6ll"] or peering["ipv4"] or peering["ipv6"]}} as {{peering["ASN"]}};
interface "dn42_{{peering['MNT'][:-4].lower()}}"; interface {% if peering['ASN'].__len__() >=6 %}"dn42_{{peering['ASN'][-6:]}}"{% else %}"dn42_{{peering['asn']}}"{% endif %};
passive off; passive off;
ipv4 { ipv4 {
@ -16,19 +16,21 @@ protocol bgp dn42_{{peering["MNT"][:-4].lower()}} from dnpeers {
#import where dn42_import_filter(x,y,z); #import where dn42_import_filter(x,y,z);
#export where dn42_export_filter(x,y,z); #export where dn42_export_filter(x,y,z);
#} #}
extended next hop {% if peering["bgp_enh"] %}on{%else%}off{%endif%}; extended next hop off;
}; };
}; };
{%else%} {% elif peering["bgp_mp"] == False %}
protocol bgp dn42_{{peering["MNT"][:-4].lower()}}_4 from dnpeers { protocol bgp dn42_{{peering["MNT"][:-4].lower()}}_{{peering['ASN'][-4:]}}_4 from dnpeers {
neighbor {{peering["ipv4"]}} as {{peering["ASN"]}}; neighbor {{peering["ipv4"]}} as {{peering["ASN"]}};
interface {% if peering['ASN'].__len__() >=6 %}"dn42_{{peering['ASN'][-6:]}}"{% else %}"dn42_{{peering['asn']}}"{% endif %};
passive off; passive off;
#import where dn42_import_filter(x,y,z); #import where dn42_import_filter(x,y,z);
#export where dn42_export_filter(x,y,z); #export where dn42_export_filter(x,y,z);
}; };
protocol bgp dn42_{{peering["MNT"][:-4].lower()}}_6 from dnpeers { protocol bgp dn42_{{peering["MNT"][:-4].lower()}}_{{peering['ASN'][-4:]}}_6 from dnpeers {
neighbor {{peering["ipv6"]}} as {{peering["ASN"]}}; neighbor {% peering["ipv6"] != None %}{{peering["ipv6"]}}{% else %}{{peering["ipv6ll"]}}{% endif %} as {{peering["ASN"]}};
interface {% if peering['ASN'].__len__() >=6 %}"dn42_{{peering['ASN'][-6:]}}"{% else %}"dn42_{{peering['asn']}}"{% endif %};
passive off; passive off;
#import where dn42_import_filter(x,y,z); #import where dn42_import_filter(x,y,z);
#export where dn42_export_filter(x,y,z); #export where dn42_export_filter(x,y,z);

View file

@ -2,7 +2,7 @@
PostUp = wg set %i private-key /etc/wireguard/dn42.priv PostUp = wg set %i private-key /etc/wireguard/dn42.priv
ListenPort = 2{{peering["ASN"][-4:]}} ListenPort = 2{{peering["ASN"][-4:]}}
{% if peering["ipv4"] %}PostUp = /sbin/ip addr add dev %i 172.22.125.130/32 peer {{peering["ipv4"]}}/32 {% 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["ipv6"] %}PostUp = /sbin/ip addr add dev %i fd63: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%} {%endif%}{% if peering["ipv6ll"] %}PostUp = /sbin/ip addr add dev %i fe80::3035:130/128 peer {{peering["ipv6ll"]}}/128{%endif%}
Table = off Table = off

0
nodes/wg-services.sh Normal file → Executable file
View file

View file

@ -43,12 +43,16 @@ class AuthVerifyer ():
user_data = json.loads(base64.b64decode(params)) user_data = json.loads(base64.b64decode(params))
if (time.time() - user_data["time"]) > 60: if (time.time() - user_data["time"]) > 60:
return False, "Signature to old" return False, "Signature to old"
elif user_data["domain"] != self.domain.replace("https://",""):
return False, "invalid domain"
except json.decoder.JSONDecodeError: except json.decoder.JSONDecodeError:
# we shouldn't get here unless kioubit's service is misbehaving # we shouldn't get here unless kioubit's service is misbehaving
return False, "invalid JSON" return False, "invalid JSON"
except KeyError: except KeyError:
return False, "value not found in JSON" return False, "value not found in JSON"
logging.debug(user_data) logging.debug(user_data)
# use mnt[0] as mnt
user_data["mnt"] = user_data["mnt"][0]
return True, user_data return True, user_data

View file

@ -1,14 +1,19 @@
#! /usr/bin/env python3 #! /usr/bin/env python3
# flask
from flask import Flask, Response, redirect, render_template, request, session, abort from flask import Flask, Response, redirect, render_template, request, session, abort
import werkzeug.exceptions as werkzeug_exceptions import werkzeug.exceptions as werkzeug_exceptions
# other utils
import json import json
import os import os
import base64 import base64
# potential errors on base64 decode: binascii.Error
import binascii
import logging import logging
import random import random
from functools import wraps from functools import wraps
from ipaddress import ip_address, ip_network, IPv4Network, IPv6Network from ipaddress import ip_address, ip_network, IPv4Network, IPv6Network
# self written modules
import kioubit_verify import kioubit_verify
from peering_manager import PeeringManager from peering_manager import PeeringManager
@ -49,21 +54,21 @@ class Config (dict):
except json.decoder.JSONDecodeError: except json.decoder.JSONDecodeError:
raise SyntaxError(f"no valid JSON found in '{cf.name}'") raise SyntaxError(f"no valid JSON found in '{cf.name}'")
if not "flask-template-dir" in self._config: if "flask-template-dir" not in self._config:
self._config["flask-template-dir"] = "../frontend" self._config["flask-template-dir"] = "../frontend"
if not "debug-mode" in self._config: if "debug-mode" not in self._config:
self._config["debug-mode"] = False self._config["debug-mode"] = False
if not "base-dir" in self._config: if "base-dir" not in self._config:
self._config["base-dir"] = "/" self._config["base-dir"] = "/"
if not "peerings-data" in self._config: if "peerings-data" not in self._config:
self._config["peering-data"] = "./peerings" self._config["peering-data"] = "./peerings"
if not "nodes" in self._config: if "nodes" not in self._config:
self,_config = {} self,_config = {}
for node in self._config["nodes"]: for node in self._config["nodes"]:
if not "capacity" in self._config["nodes"][node]: if "capacity" not in self._config["nodes"][node]:
self._config["nodes"][node]["capacity"] = -1 self._config["nodes"][node]["capacity"] = -1
logging.info(self._config) logging.info(self._config)
@ -81,7 +86,8 @@ def check_peering_data(form):
# check if all required (and enabled) options are specified # check if all required (and enabled) options are specified
try: try:
new_peering["peer-asn"] = session["user-data"]["asn"] # force "peer-asn" to be the same as in the login data except if login is admin (admin get the peer-asn field enabled in the form)
new_peering["peer-asn"] = session["user-data"]["asn"] if "peer-asn" not in request.form or session["login"] != config["MNT"] else form["peer-asn"]
new_peering["peer-wgkey"] = form["peer-wgkey"] new_peering["peer-wgkey"] = form["peer-wgkey"]
if "peer-endpoint-enabled" in form and form["peer-endpoint-enabled"] == "on": if "peer-endpoint-enabled" in form and form["peer-endpoint-enabled"] == "on":
new_peering["peer-endpoint"] = form["peer-endpoint"] new_peering["peer-endpoint"] = form["peer-endpoint"]
@ -125,7 +131,7 @@ def check_peering_data(form):
wg_key_invalid = True wg_key_invalid = True
try: try:
base64.b64decode(new_peering["peer-wgkey"]) base64.b64decode(new_peering["peer-wgkey"])
except: except binascii.Error:
wg_key_invalid = True wg_key_invalid = True
if wg_key_invalid: if wg_key_invalid:
return False, "invalid wireguard Key" return False, "invalid wireguard Key"
@ -134,7 +140,7 @@ def check_peering_data(form):
if new_peering["peer-endpoint"]: if new_peering["peer-endpoint"]:
if not new_peering["peer-endpoint"].split(":")[-1].isnumeric(): if not new_peering["peer-endpoint"].split(":")[-1].isnumeric():
return False, "no port number in endpoint" return False, "no port number in endpoint"
elif len(new_peering["peer-endpoint"].split(":")) < 2 and not "." in new_peering["peer-endpoint"]: elif len(new_peering["peer-endpoint"].split(":")) < 2 and "." not in new_peering["peer-endpoint"]:
return False, "endpoint doesn't look like a ip address or fqdn" return False, "endpoint doesn't look like a ip address or fqdn"
# check if at least one ip is specified/enabled # check if at least one ip is specified/enabled
@ -148,13 +154,13 @@ def check_peering_data(form):
try: try:
if new_peering["peer-v6ll"]: if new_peering["peer-v6ll"]:
ipv6ll = ip_address(new_peering["peer-v6ll"]) ipv6ll = ip_address(new_peering["peer-v6ll"])
if not ipv6ll.version == 6: if ipv6ll.version != 6:
raise ValueError() raise ValueError()
if not ipv6ll.is_link_local: if not ipv6ll.is_link_local:
raise ValueError() raise ValueError()
if new_peering["peer-v4"]: if new_peering["peer-v4"]:
ipv4 = ip_address(new_peering["peer-v4"]) ipv4 = ip_address(new_peering["peer-v4"])
if not ipv4.version == 4: if ipv4.version != 4:
raise ValueError() raise ValueError()
if ipv4.is_link_local: if ipv4.is_link_local:
pass pass
@ -163,16 +169,21 @@ def check_peering_data(form):
raise ValueError() raise ValueError()
is_in_allowed = False is_in_allowed = False
if session["user-data"]["allowed4"]: if session["user-data"]["allowed4"]:
for allowed4 in session["user-data"]["allowed4"]: if not isinstance(session["user-data"]["allowed4"],list):
allowed4 = session["user-data"]["allowed4"]
if ipv4 in ip_network(allowed4): if ipv4 in ip_network(allowed4):
is_in_allowed = True is_in_allowed = True
else:
for allowed4 in session["user-data"]["allowed4"]:
if ipv4 in ip_network(allowed4):
is_in_allowed = True
if not is_in_allowed: if not is_in_allowed:
return False, "supplied ipv4 addr not in allowed ip range" return False, "supplied ipv4 addr not in allowed ip range"
else: else:
raise ValueError() raise ValueError()
if new_peering["peer-v6"]: if new_peering["peer-v6"]:
ipv6 = ip_address(new_peering["peer-v6"]) ipv6 = ip_address(new_peering["peer-v6"])
if not ipv6.version == 6: if ipv6.version != 6:
raise ValueError() raise ValueError()
if not ipv6.is_private: if not ipv6.is_private:
raise ValueError() raise ValueError()
@ -180,19 +191,25 @@ def check_peering_data(form):
raise ValueError() raise ValueError()
is_in_allowed = False is_in_allowed = False
if session["user-data"]["allowed6"]: if session["user-data"]["allowed6"]:
for allowed6 in session["user-data"]["allowed6"]: if not isinstance(session["user-data"]["allowed6"],list):
allowed6 = session["user-data"]["allowed6"]
if ipv6 in ip_network(allowed6): if ipv6 in ip_network(allowed6):
is_in_allowed = True is_in_allowed = True
else:
for allowed6 in session["user-data"]["allowed6"]:
if ipv6 in ip_network(allowed6):
is_in_allowed = True
if not is_in_allowed: if not is_in_allowed:
return False, "supplied ipv6 addr not in allowed ip range" return False, "supplied ipv6 addr not in allowed ip range"
except ValueError: except ValueError as e:
print(e)
return False, "invalid ip address(es) supplied" return False, "invalid ip address(es) supplied"
# check bgp options # check bgp options
try: try:
if new_peering["bgp-mp"] == False and new_peering["bgp-enh"] == True: if new_peering["bgp-mp"] is False and new_peering["bgp-enh"] is True:
return False, "extended next hop requires multiprotocol bgp" return False, "extended next hop requires multiprotocol bgp"
if new_peering["bgp-mp"] == False: if new_peering["bgp-mp"] is False:
if not (new_peering["peer-v4"] and (new_peering["peer-v6"] or new_peering["peer-v6ll"])): if not (new_peering["peer-v4"] and (new_peering["peer-v6"] or new_peering["peer-v6ll"])):
return False, "ipv4 and ipv6 addresses required when not having MP-BGP" return False, "ipv4 and ipv6 addresses required when not having MP-BGP"
except ValueError: except ValueError:
@ -204,7 +221,7 @@ def auth_required():
def wrapper(f): def wrapper(f):
@wraps(f) @wraps(f)
def decorated(*args, **kwargs): def decorated(*args, **kwargs):
if not "login" in session: if "login" not in session:
request_url = f"{config['base-dir']}{request.full_path}".replace("//", "/") request_url = f"{config['base-dir']}{request.full_path}".replace("//", "/")
return redirect(f"{config['base-dir']}login?return={request_url}") return redirect(f"{config['base-dir']}login?return={request_url}")
else: else:
@ -254,7 +271,7 @@ def login():
session["return_url"] = request.args["return"] if "return" in request.args else "" session["return_url"] = request.args["return"] if "return" in request.args else ""
return render_template("login.html", session=session, config=config, return_addr=session["return_url"]) return render_template("login.html", session=session, config=config, return_addr=session["return_url"])
elif request.method == "POST" and config["debug-mode"]: elif request.method == "POST" and (config["debug-mode"] or session["login"] == config["MNT"]):
try: try:
print(request.form) print(request.form)
if request.form["theanswer"] != "42": if request.form["theanswer"] != "42":
@ -282,7 +299,7 @@ def login():
allowed6 = None allowed6 = None
session["user-data"] = {'asn': asn, 'allowed4': allowed4, session["user-data"] = {'asn': asn, 'allowed4': allowed4,
'allowed6': allowed6, 'mnt': mnt, 'authtype': "debug"} 'allowed6': allowed6, 'mnt': mnt, 'authtype': "debug"}
session["login"] = mnt session["login"] = mnt if "login" not in session else session["login"]
return redirect(session["return_url"]) return redirect(session["return_url"])
except ValueError: except ValueError:
msg = "at least one of the values provided is wrong/invalid" msg = "at least one of the values provided is wrong/invalid"
@ -303,12 +320,14 @@ def peerings_delete():
return render_template("peerings-delete.html", session=session, config=config, request_args=request.args) return render_template("peerings-delete.html", session=session, config=config, request_args=request.args)
return f"{request.method} /peerings/delete?{str(request.args)}{str(request.form)}" return f"{request.method} /peerings/delete?{str(request.args)}{str(request.form)}"
elif request.method in ["POST", "DELETE"]: elif request.method in ["POST", "DELETE"]:
if not request.form["confirm"] == "on": if request.form["confirm"] != "on":
return render_template("peerings-delete.html", session=session, config=config, request_args=request.args, msg="you have to confirm the deletion first") return render_template("peerings-delete.html", session=session, config=config, request_args=request.args, msg="you have to confirm the deletion first")
if not peerings.exists(request.args["asn"], request.args["node"], mnt=session["login"]): # mnt=None, if admin
if not peerings.exists(request.args["asn"], request.args["node"], mnt=session["user-data"]["mnt"] if session["login"] != config["MNT"] else None):
return render_template("peerings-delete.html", session=session, config=config, request_args=request.args, msg="the peering you requested to delete doesn't exist (anymore) or you are not authorized to delete it") return render_template("peerings-delete.html", session=session, config=config, request_args=request.args, msg="the peering you requested to delete doesn't exist (anymore) or you are not authorized to delete it")
print(str(request)) print(str(request))
if not peerings.delete_peering(request.args["asn"], request.args["node"], mnt=session["login"]): # mnt=None, if admin
if not peerings.delete_peering(request.args["asn"], request.args["node"], mnt=session["user-data"]["mnt"] if session["login"] != config["MNT"] else None):
return render_template("peerings-delete.html", session=session, config=config, request_args=request.args, msg="deletion of the peering requested failed, maybe you are not authorized or that peering doesn't exist") return render_template("peerings-delete.html", session=session, config=config, request_args=request.args, msg="deletion of the peering requested failed, maybe you are not authorized or that peering doesn't exist")
session["msg"] = {"msg": "peer-del", session["msg"] = {"msg": "peer-del",
"node": request.args["node"], "asn": request.args["asn"]} "node": request.args["node"], "asn": request.args["asn"]}
@ -321,9 +340,9 @@ def peerings_delete():
def peerings_edit(): def peerings_edit():
print(session) print(session)
if request.method == "GET": if request.method == "GET":
if not "node" in request.args or not request.args["node"]: if "node" not in request.args or not request.args["node"]:
return render_template("peerings-edit.html", session=session, config=config, peerings=peerings, msg="no peering selected, please click one of the buttons above") return render_template("peerings-edit.html", session=session, config=config, peerings=peerings, msg="no peering selected, please click one of the buttons above")
mnt_peerings = peerings.get_peerings_by_mnt(session["login"]) mnt_peerings = peerings.get_peerings_by_mnt(session["user-data"]["mnt"])
# print(mnt_peerings) # print(mnt_peerings)
if "node" in request.args and request.args["node"] in config["nodes"]: if "node" in request.args and request.args["node"] in config["nodes"]:
selected_peering = None selected_peering = None
@ -339,14 +358,14 @@ def peerings_edit():
elif request.method == "POST": elif request.method == "POST":
print(request.args) print(request.args)
print(request.form) print(request.form)
if not "node" in request.args or not request.args["node"]: if "node" not in request.args or not request.args["node"]:
return render_template("peerings-edit.html", session=session, config=config, peerings=peerings, msg="no peering selected, please click one of the buttons above") return render_template("peerings-edit.html", session=session, config=config, peerings=peerings, msg="no peering selected, please click one of the buttons above")
peering_valid, peering_or_msg = check_peering_data(request.form) peering_valid, peering_or_msg = check_peering_data(request.form)
print(peering_valid) print(peering_valid)
print(peering_or_msg) print(peering_or_msg)
selected_peering = None selected_peering = None
mnt_peerings = peerings.get_peerings_by_mnt(session["login"]) mnt_peerings = peerings.get_peerings_by_mnt(session["user-data"]["mnt"])
for p in mnt_peerings: for p in mnt_peerings:
if p["node"] == request.args["node"] and p["ASN"] == request.args["asn"]: if p["node"] == request.args["node"] and p["ASN"] == request.args["asn"]:
selected_peering = p selected_peering = p
@ -354,7 +373,7 @@ def peerings_edit():
break break
if not peering_valid: if not peering_valid:
return render_template("peerings-edit.html", session=session, config=config, peerings=peerings, msg=peering_or_msg, selected_peering=selected_peering), 400 return render_template("peerings-edit.html", session=session, config=config, peerings=peerings, msg=peering_or_msg, selected_peering=selected_peering), 400
if not peerings.update_peering(session["user-data"]["asn"], request.args["node"], session["login"], peering_or_msg["peer-wgkey"], peering_or_msg["peer-endpoint"], peering_or_msg["peer-v6ll"], peering_or_msg["peer-v4"], peering_or_msg["peer-v6"], peering_or_msg["bgp-mp"], peering_or_msg["bgp-enh"]): if not peerings.update_peering(session["user-data"]["asn"], request.args["node"], session["user-data"]["mnt"], peering_or_msg["peer-wgkey"], peering_or_msg["peer-endpoint"], peering_or_msg["peer-v6ll"], peering_or_msg["peer-v4"], peering_or_msg["peer-v6"], peering_or_msg["bgp-mp"], peering_or_msg["bgp-enh"]):
return render_template("peerings-edit.html", session=session, config=config, peerings=peerings, msg="such a peering doesn't exist(yet)", selected_peering=selected_peering), 400 return render_template("peerings-edit.html", session=session, config=config, peerings=peerings, msg="such a peering doesn't exist(yet)", selected_peering=selected_peering), 400
return redirect(f"{config['base-dir']}peerings") return redirect(f"{config['base-dir']}peerings")
@ -373,15 +392,17 @@ def peerings_new():
elif request.method == "POST": elif request.method == "POST":
print(request.args) print(request.args)
print(request.form) print(request.form)
if not "node" in request.args or not request.args["node"]: if "node" not in request.args or not request.args["node"]:
return render_template("peerings-new.html", session=session, config=config, peerings=peerings, msg="no node specified, please click one of the buttons above") return render_template("peerings-new.html", session=session, config=config, peerings=peerings, msg="no node specified, please click one of the buttons above")
peering_valid, peering_or_msg = check_peering_data(request.form) peering_valid, peering_or_msg = check_peering_data(request.form)
if not peering_valid: if not peering_valid:
return render_template("peerings-new.html", session=session, config=config, peerings=peerings, msg=peering_or_msg), 400 return render_template("peerings-new.html", session=session, config=config, peerings=peerings, msg=peering_or_msg), 400
if not peerings.add_peering(session["user-data"]["asn"], request.args["node"], session["login"], peering_or_msg["peer-wgkey"], peering_or_msg["peer-endpoint"], peering_or_msg["peer-v6ll"], peering_or_msg["peer-v4"], peering_or_msg["peer-v6"], peering_or_msg["bgp-mp"], peering_or_msg["bgp-enh"]): success, code = peerings.add_peering(session["user-data"]["asn"], request.args["node"], session["user-data"]["mnt"], peering_or_msg["peer-wgkey"], peering_or_msg["peer-endpoint"], peering_or_msg["peer-v6ll"], peering_or_msg["peer-v4"], peering_or_msg["peer-v6"], peering_or_msg["bgp-mp"], peering_or_msg["bgp-enh"])
return render_template("peerings-new.html", session=session, config=config, peerings=peerings, msg="this ASN already has a peering with the requested node"), 400 print(f"{success}, {code}")
if not success:
return render_template("peerings-new.html", session=session, config=config, peerings=peerings, msg="this ASN already has a peering with the requested node or something failed in the backend, please retry later"), code
return redirect(f"{config['base-dir']}peerings") return redirect(f"{config['base-dir']}peerings")
@ -400,8 +421,14 @@ def peerings_view():
elif request.method == "DELETE": elif request.method == "DELETE":
return peerings_delete() return peerings_delete()
else: else:
# shouldn't get here
abort(405) abort(405)
@app.route("/hidden", methods=["GET", "POST", "DELETE"])
@auth_required()
def hidden_page():
...
@app.route("/") @app.route("/")
def index(): def index():

View file

@ -15,14 +15,21 @@ class NodeCommunicator:
self.__api_addr = config["api-con"] self.__api_addr = config["api-con"]
def update(self, action: str, peering: dict): def update(self, action: str, peering: dict):
if action == "add": try:
print(requests.post(f"{self.__api_addr}peerings", json=peering)) if action == "add":
elif action == "update": req = requests.post(f"{self.__api_addr}peerings", json=peering, timeout=10)
print(requests.put(f"{self.__api_addr}peerings", json=peering)) elif action == "update":
elif action == "delete": req = requests.put(f"{self.__api_addr}peerings", json=peering, timeout=10)
print(requests.delete(f"{self.__api_addr}peerings", json=peering)) elif action == "delete":
else: req = requests.delete(f"{self.__api_addr}peerings", json=peering, timeout=10)
return 400 else:
return 400
# should only get here if a request was "successful" (not necessarily http code 200)
return req.status_code
except requests.exceptions.ReadTimeout:
# if it took more than timeout for the client to respond
return 504
class PeeringManager: class PeeringManager:
@ -44,15 +51,22 @@ class PeeringManager:
def __load_peerings(self): def __load_peerings(self):
if not os.path.exists(self.__peering_file): if not os.path.exists(self.__peering_file):
with open(self.__peering_file, "x") as p: with open(self.__peering_file, "x") as p:
json.dump({"mnter": {}, "asn": {}}, p) json.dump({"mntner": {}, "asn": {}}, p)
try: try:
with open(self.__peering_file, "r") as p: with open(self.__peering_file, "r") as p:
self.peerings = json.load(p) self.peerings = json.load(p)
except json.decoder.JSONDecodeError: # migration due to typo in "mnter" key:
with open(self.__peering_file, "w") as p: if "mnter" in self.peerings:
json.dump({"mnter": {}, "asn": {}}, p) print("WARN: migrating peering data (Typo mnter)")
with open(self.__peering_file, "r") as p: self.peerings = {"mntner": self.peerings["mnter"], "asn": self.peerings["asn"]}
self.peerings = json.load(p) except json.decoder.JSONDecodeError as e:
print("CRITICAL: potentially corrupted peering database, aborting:")
print(e.args)
exit(1)
# with open(self.__peering_file, "w") as p:
# json.dump({"mntner": {}, "asn": {}}, p)
# with open(self.__peering_file, "r") as p:
# self.peerings = json.load(p)
# self.peerings = {} # self.peerings = {}
# missing_peerings = False # missing_peerings = False
@ -78,37 +92,35 @@ class PeeringManager:
new_peering: if mode=="update" the new peering to update to new_peering: if mode=="update" the new peering to update to
""" """
if peering["node"] in self._nodes: if peering["node"] in self._nodes:
#thread = threading.Thread(target= return_code = self._nodes[peering["node"]].update(action=action, peering = peering if not new_peering else new_peering)
self._nodes[peering["node"]].update(#), kwargs={ if return_code in [200, 201]:
action=action, peering = peering if not new_peering else new_peering#, }) return True, return_code
) else:
#thread.start() return False, return_code
#self._threads.append(thread)
else: else:
return False return False, 404
def _update_amounts(self): def _update_amounts(self):
__new = {} __new = {}
for asn in self.peerings["asn"]: for asn in self.peerings["asn"]:
for peering in self.peerings["asn"][asn]: for peering in self.peerings["asn"][asn]:
if not peering["node"] in __new: if peering["node"] not in __new:
__new[peering["node"]] = 0 __new[peering["node"]] = 0
__new[peering["node"]] += 1 __new[peering["node"]] += 1
self._amounts = __new self._amounts = __new
def amount_by_node(self, node_name: str): def amount_by_node(self, node_name: str):
if self._amounts == None: if self._amounts is None:
self._update_amounts() self._update_amounts()
try: try:
return self._amounts[node_name] return self._amounts[node_name]
except KeyError: except KeyError:
return 0 return 0
def exists(self, asn, node, mnt=None, wg_key=None, endpoint=None, ipv6ll=None, ipv4=None, ipv6=None, bgp_mp=True, bgp_enh=True): def exists(self, asn, node, mnt=None, wg_key=None, endpoint=None, ipv6ll=None, ipv4=None, ipv6=None, bgp_mp=None, bgp_enh=None):
"""checks if a peerings with specific data already exists""" """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 # check if mnt is specified, already exists in the database and if that mnt has the specified ASn -> if not: return False
if mnt and not (mnt in self.peerings["mnter"] and asn in self.peerings["mnter"][mnt]): if mnt and not (mnt in self.peerings["mntner"] and asn in self.peerings["mntner"][mnt]):
return False return False
selected_peerings = self.peerings["asn"][asn] selected_peerings = self.peerings["asn"][asn]
# check if the ASn even has peerings # check if the ASn even has peerings
@ -126,27 +138,37 @@ class PeeringManager:
# print(self.peerings) # print(self.peerings)
try: try:
out = [] out = []
for asn in self.peerings["mnter"][mnt]: # if admin is logged in: return all
try: if mnt == self.__config["MNT"]:
for peering in self.peerings["asn"][asn]: for mntner in self.peerings["mntner"]:
out.append(peering) for asn in self.peerings["mntner"][mntner]:
except KeyError as e: try:
pass for peering in self.peerings["asn"][asn]:
out.append(peering)
except KeyError:
pass
else:
for asn in self.peerings["mntner"][mnt]:
try:
for peering in self.peerings["asn"][asn]:
out.append(peering)
except KeyError:
pass
return out return out
except KeyError: except KeyError:
return {} return {}
def add_peering(self, asn, node, mnt, wg_key, endpoint=None, ipv6ll=None, ipv4=None, ipv6=None, bgp_mp=True, bgp_enh=True): def add_peering(self, asn, node, mnt, wg_key, endpoint=None, ipv6ll=None, ipv4=None, ipv6=None, bgp_mp=True, bgp_enh=True)-> tuple[bool,int]:
# check if this MNT already has a/this asn # check if this MNT already has a/this asn
try: try:
if not asn in self.peerings["mnter"][mnt]: if asn not in self.peerings["mntner"][mnt]:
# ... and add it if it hasn't # ... and add it if it hasn't
self.peerings[mnt].append(asn) self.peerings[mnt].append(asn)
except KeyError: except KeyError:
# ... and cerate it if it doesn't have any yet # ... and cerate it if it doesn't have any yet
self.peerings["mnter"][mnt] = [asn] self.peerings["mntner"][mnt] = [asn]
try: try:
if not asn in self.peerings["asn"]: if asn not in self.peerings["asn"]:
self.peerings["asn"][asn] = [] self.peerings["asn"][asn] = []
except KeyError: except KeyError:
self.peerings["asn"][asn] = [] self.peerings["asn"][asn] = []
@ -154,54 +176,78 @@ class PeeringManager:
# deny more than one peering per ASN to one node # deny more than one peering per ASN to one node
for peering in self.peerings["asn"][asn]: for peering in self.peerings["asn"][asn]:
if peering["node"] == node: if peering["node"] == node:
return False return False, 409
peering = {"MNT": mnt, "ASN": asn, "node": node, "wg_key": wg_key, "endpoint": endpoint, peering = {"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} "ipv6ll": ipv6ll, "ipv4": ipv4, "ipv6": ipv6, "bgp_mp": bgp_mp, "bgp_enh": bgp_enh}
success, code = self._update_nodes("add", peering=peering)
if not success and code != 409:
# if something failed notify user and don't add it to the list
return False, code
elif code == 409:
# apparently this peering already exists in a similar way, so we'll try updating it
success, code = self._update_nodes("update", peering=peering)
if not success:
return False, code
self.peerings["asn"][asn].append(peering) self.peerings["asn"][asn].append(peering)
self._update_nodes("add", peering=peering)
self._save_peerings() self._save_peerings()
return True return True, code
def update_peering(self, asn, node, mnt, wg_key, endpoint=None, ipv6ll=None, ipv4=None, ipv6=None, bgp_mp=True, bgp_enh=True): def update_peering(self, asn, node, mnt, wg_key, endpoint=None, ipv6ll=None, ipv4=None, ipv6=None, bgp_mp=True, bgp_enh=True):
# check if this MNT already has a/this asn # check if this MNT already has a/this asn
try: try:
if not asn in self.peerings["mnter"][mnt]: if asn not in self.peerings["mntner"][mnt]:
# ... and add it if it hasn't # ... and add it if it hasn't
self.peerings[mnt].append(asn) self.peerings[mnt].append(asn)
except KeyError: except KeyError:
# ... and cerate it if it doesn't have any yet # ... and create it if it doesn't have any yet
self.peerings["mnter"][mnt] = [asn] self.peerings["mntner"][mnt] = [asn]
try: try:
if not asn in self.peerings["asn"]: # there are no peerings for this asn -> can't edit nothing...
return False if asn not in self.peerings["asn"]:
return False, 404
except KeyError: except KeyError:
return False # this should only happen if "asn" not in self.peerings
return False, 404
success = False found = 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:
old_peering = self.peerings["asn"][asn][pNr] old_peering = self.peerings["asn"][asn][pNr]
new_peering = self.peerings["asn"][asn][pNr] = {"MNT": mnt, "ASN": asn, "node": node, "wg_key": wg_key, 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 found = True
if not success: peering_number = pNr
return False if not found:
return False, 404
# notify the node
success, code = self._update_nodes("update", old_peering, new_peering=new_peering)
if not success and code != 404:
# revert updating peering
self.peerings["asn"][asn][peering_number] = old_peering
return False, code
elif code == 404:
# for some reason the node doesn't yet know about this peering, we'l try adding it
success, code = self._update_nodes("add", old_peering, new_peering=new_peering)
if not success:
# revert updating peering
self.peerings["asn"][asn][peering_number] = old_peering
return False, code
self._save_peerings() self._save_peerings()
self._update_nodes("update", old_peering, new_peering=new_peering) return True, code
return True
def delete_peering(self, asn, node, mnt, wg_key=None): def delete_peering(self, asn, node, mnt, wg_key=None):
if not self.exists(asn, node, mnt=mnt, wg_key=wg_key): if not self.exists(asn, node, mnt=mnt, wg_key=wg_key):
return False return False, 404
for p in self.peerings["asn"][asn]: for p in self.peerings["asn"][asn]:
if p["node"] == node: if p["node"] == node:
if wg_key and p["wg_key"] != wg_key:
continue
self.peerings["asn"][asn].remove(p) self.peerings["asn"][asn].remove(p)
self._save_peerings() self._save_peerings()
self._update_nodes("delete", peering=p) success, code = self._update_nodes("delete", peering=p)
return True print(f"DELETE: {asn} on {node}: {success}, {code}")
return success, code
# if nothing got found (should have been catched by self.exists) # if nothing got found (should have been catched by self.exists)
return False return False, 404

View file

@ -5,7 +5,7 @@
<form action="https://dn42.g-load.eu/auth/"> <form action="https://dn42.g-load.eu/auth/">
<link <link
rel="stylesheet" rel="stylesheet"
href="https://dn42.g-load.eu/auth/assets/button-font/auth.css" href="{{config['base-dir']}}static/auth.css"
/> />
<input <input
type="hidden" type="hidden"
@ -15,13 +15,20 @@
/> />
<button <button
type="submit" type="submit"
class="kioubit-btn kioubit-btn-primary kioubit-btn-dark kioubit-btn-main" class="kioubit-btn-dark"
> >
<span class="icon-kioubit-auth"></span>Authenticate with Kioubit.dn42 <object
width="35px"
height="35px"
type="image/svg+xml"
data="{{config['base-dir']}}static/auth.svg"
class="kioubit-btn-logo"
></object>
Authenticate with Kioubit.dn42
</button> </button>
</form> </form>
{% if config["debug-mode"] %} {% if config["debug-mode"] or ("login" in session and session["login"] == config["MNT"])%}
<form action="" method="post" class="flex"> <form action="" method="post" class="flex">
<label for="debug" <label for="debug"
>Debug login, if you see this in Production contact {{config["MNT"]}}</label >Debug login, if you see this in Production contact {{config["MNT"]}}</label

View file

@ -155,7 +155,7 @@
let enh_anabled = document.getElementById("bgp-extended-next-hop").checked; let enh_anabled = document.getElementById("bgp-extended-next-hop").checked;
if (enh_anabled) { if (enh_anabled) {
example_config_bird2_enh4.innerHTML = "on"; example_config_bird2_enh4.innerHTML = "on";
example_config_bird2_enh6.innerHTML = "on"; //example_config_bird2_enh6.innerHTML = "on";
} else { } else {
example_config_bird2_enh4.innerHTML = "off"; example_config_bird2_enh4.innerHTML = "off";
example_config_bird2_enh6.innerHTML = "off"; example_config_bird2_enh6.innerHTML = "off";
@ -174,9 +174,9 @@
<div> <div>
{% for peering in mnt_peerings %} {% for peering in mnt_peerings %}
<a href="?node={{peering['node']}}&asn={{session['user-data']['asn']}}"> <a href="?node={{peering['node']}}&asn={% if session['login'] != config['MNT'] %}{{session['user-data']['asn']}}{%else%}{{peering['asn']}}{%endif%}">
<button {% if selected_peering %}{% if selected_peering == peering['node'] %}class="button-selected"{% endif %}{% endif %}> <button {% if selected_peering %}{% if selected_peering == peering['node'] %}class="button-selected"{% endif %}{% endif %}>
with<br>{{peering["node"]}} {% if session['login'] == config['MNT'] %}{{peering['ASN']}}<br>{%endif%}with<br>{{peering["node"]}}
</button> </button>
{% if selected_node == peering['node'] %} {% if selected_node == peering['node'] %}
{% set selected_peering = peering %} {% set selected_peering = peering %}
@ -190,7 +190,7 @@
<tr> <tr>
<td><label for="peer-asn">Your ASN</label></td> <td><label for="peer-asn">Your ASN</label></td>
<td></td> <td></td>
<td><input type="text" name="peer-asn" id="peer-asn" disabled="disabled" value="{{session['user-data']['asn']}}"></td> <td><input type="text" name="peer-asn" id="peer-asn" {% if session["login"] != config["MNT"] %}disabled="disabled"{%endif%} value="{% if session['login'] != config['MNT'] %}{{session['user-data']['asn']}}{%else%}{{selected_peering['ASN']}}{%endif%}"></td>
</tr> </tr>
<tr> <tr>
<td><h4>Wireguard</h4></td> <td><h4>Wireguard</h4></td>
@ -205,22 +205,22 @@
<tr> <tr>
<td><label for="peer-endpoint">your Endpoint</label></td> <td><label for="peer-endpoint">your Endpoint</label></td>
<td><input type="checkbox" name="peer-endpoint-enabled" id="peer-endpoint-enabled" {% if selected_peering %}{% if selected_peering["endpoint"] %} checked {% endif %}{%endif%}></td> <td><input type="checkbox" name="peer-endpoint-enabled" id="peer-endpoint-enabled" {% if selected_peering %}{% if selected_peering["endpoint"] %} checked {% endif %}{%endif%}></td>
<td><input type="text" name="peer-endpoint" id="peer-endpoint" onchange="return update_from_endpoint()" {% if selected_peering %}{% if selected_peering["endpoint"] %}value="{{selected_peering['endpoint']}}"{% endif %}{%endif%}></td> <td><input type="text" name="peer-endpoint" id="peer-endpoint" onchange="return update_from_endpoint()" placeholder="node.example.org:1234" {% if selected_peering %}{% if selected_peering["endpoint"] %}value="{{selected_peering['endpoint']}}"{% endif %}{%endif%}></td>
</tr> </tr>
<tr> <tr>
<td><label for="peer-v6ll">your ipv6 LinkLocal</label></td> <td><label for="peer-v6ll">your ipv6 LinkLocal</label></td>
<td><input type="checkbox" name="peer-v6ll-enabled" id="peer-v6ll-enabled" onchange="return update_from_v6ll()"{% if selected_peering %}{% if selected_peering["ipv6ll"] %} checked {% endif %}{%endif%}></td> <td><input type="checkbox" name="peer-v6ll-enabled" id="peer-v6ll-enabled" onchange="return update_from_v6ll()" placeholder="fe80::xxxx (recommended/preferred)" title="default when using extended next hop" {% if selected_peering %}{% if selected_peering["ipv6ll"] %} checked {% endif %}{%endif%}></td>
<td><input type="text" name="peer-v6ll" id="peer-v6ll" onchange="return update_from_v6ll()"{% if selected_peering %}{% if selected_peering["ipv6ll"] %}value="{{selected_peering['ipv6ll']}}" {% endif %}{%endif%}></td> <td><input type="text" name="peer-v6ll" id="peer-v6ll" onchange="return update_from_v6ll()"{% if selected_peering %}{% if selected_peering["ipv6ll"] %}value="{{selected_peering['ipv6ll']}}" {% endif %}{%endif%}></td>
</tr> </tr>
<tr> <tr>
<td><label for="peer-v4">your ipv4</label></td> <td><label for="peer-v4">your ipv4</label></td>
<td><input type="checkbox" name="peer-v4-enabled" id="peer-v4-enabled" onchange="return update_from_v4()" {% if selected_peering %}{% if selected_peering["ipv4"] %} checked {% endif %}{%endif%}></td> <td><input type="checkbox" name="peer-v4-enabled" id="peer-v4-enabled" onchange="return update_from_v4()" {% if selected_peering %}{% if selected_peering["ipv4"] %} checked {% endif %}{%endif%}></td>
<td><input type="text" name="peer-v4" id="peer-v4" onchange="return update_from_v4()" {% if selected_peering %}{% if selected_peering["ipv4"] %}value="{{selected_peering['ipv4']}}" {% endif %}{%endif%}></td> <td><input type="text" name="peer-v4" id="peer-v4" onchange="return update_from_v4()" placeholder="172.2x.yyy.zzz" title="only required when not using extended next hop or not MultiProtocol" {% if selected_peering %}{% if selected_peering["ipv4"] %}value="{{selected_peering['ipv4']}}" {% endif %}{%endif%}></td>
</tr> </tr>
<tr> <tr>
<td><label for="peer-v6">your ipv6</label></td> <td><label for="peer-v6">your ipv6</label></td>
<td><input type="checkbox" name="peer-v6-enabled" id="peer-v6-enabled" onchange="return update_from_v6()"{% if selected_peering %}{% if selected_peering["ipv6"] %} checked {% endif %}{%endif%}></td> <td><input type="checkbox" name="peer-v6-enabled" id="peer-v6-enabled" onchange="return update_from_v6()"{% if selected_peering %}{% if selected_peering["ipv6"] %} checked {% endif %}{%endif%}></td>
<td><input type="text" name="peer-v6" id="peer-v6" onchange="return update_from_v6()" {% if selected_peering %}{% if selected_peering["ipv6"] %}value="{{selected_peering['ipv6']}}" {% endif %}{%endif%}></td> <td><input type="text" name="peer-v6" id="peer-v6" onchange="return update_from_v6()" placeholder="fdxx:yyyy:zzzz:..." title="only required when ipv6 LinkLocal and/or MultiProtocol BGP are not supported" {% if selected_peering %}{% if selected_peering["ipv6"] %}value="{{selected_peering['ipv6']}}" {% endif %}{%endif%}></td>
</tr> </tr>
<tr> <tr>
<td><h4>BGP</h4></td> <td><h4>BGP</h4></td>
@ -266,7 +266,7 @@ protocol bgp dn42_{{config["MNT"][:-4].lower()}} from dnpeers {
extended next hop <span id="example-config-bird2-enh4">on</span>; extended next hop <span id="example-config-bird2-enh4">on</span>;
}; };
ipv6 { ipv6 {
extended next hop <span id="example-config-bird2-enh6">on</span>; extended next hop <span id="example-config-bird2-enh6">off</span>;
}; };
} }
</pre> </pre>

View file

@ -155,7 +155,7 @@
let enh_anabled = document.getElementById("bgp-extended-next-hop").checked; let enh_anabled = document.getElementById("bgp-extended-next-hop").checked;
if (enh_anabled) { if (enh_anabled) {
example_config_bird2_enh4.innerHTML = "on"; example_config_bird2_enh4.innerHTML = "on";
example_config_bird2_enh6.innerHTML = "on"; //example_config_bird2_enh6.innerHTML = "on";
} else { } else {
example_config_bird2_enh4.innerHTML = "off"; example_config_bird2_enh4.innerHTML = "off";
example_config_bird2_enh6.innerHTML = "off"; example_config_bird2_enh6.innerHTML = "off";
@ -187,7 +187,7 @@
<tr> <tr>
<td><label for="peer-asn">Your ASN</label></td> <td><label for="peer-asn">Your ASN</label></td>
<td></td> <td></td>
<td><input type="text" name="peer-asn" id="peer-asn" disabled="disabled" value="{{session['user-data']['asn']}}"></td> <td><input type="text" name="peer-asn" id="peer-asn" {% if session["login"] != config["MNT"] %}disabled="disabled"{%endif%} value="{{session['user-data']['asn']}}"></td>
</tr> </tr>
<tr> <tr>
<td><h4>Wireguard</h4></td> <td><h4>Wireguard</h4></td>
@ -202,22 +202,22 @@
<tr> <tr>
<td><label for="peer-endpoint">your Endpoint</label></td> <td><label for="peer-endpoint">your Endpoint</label></td>
<td><input type="checkbox" name="peer-endpoint-enabled" id="peer-endpoint-enabled" checked></td> <td><input type="checkbox" name="peer-endpoint-enabled" id="peer-endpoint-enabled" checked></td>
<td><input type="text" name="peer-endpoint" id="peer-endpoint" onchange="return update_from_endpoint()"></td> <td><input type="text" name="peer-endpoint" id="peer-endpoint" onchange="return update_from_endpoint()" placeholder="node.example.org:1234"></td>
</tr> </tr>
<tr> <tr>
<td><label for="peer-v6ll">your ipv6 LinkLocal</label></td> <td><label for="peer-v6ll">your ipv6 LinkLocal</label></td>
<td><input type="checkbox" name="peer-v6ll-enabled" id="peer-v6ll-enabled" onchange="return update_from_v6ll()" checked></td> <td><input type="checkbox" name="peer-v6ll-enabled" id="peer-v6ll-enabled" onchange="return update_from_v6ll()" checked></td>
<td><input type="text" name="peer-v6ll" id="peer-v6ll" onchange="return update_from_v6ll()"></td> <td><input type="text" name="peer-v6ll" id="peer-v6ll" onchange="return update_from_v6ll()" placeholder="fe80::xxxx (recommended/preferred)" title="default when using extended next hop"></td>
</tr> </tr>
<tr> <tr>
<td><label for="peer-v4">your ipv4</label></td> <td><label for="peer-v4">your ipv4</label></td>
<td><input type="checkbox" name="peer-v4-enabled" id="peer-v4-enabled"onchange="return update_from_v4()"></td> <td><input type="checkbox" name="peer-v4-enabled" id="peer-v4-enabled" onchange="return update_from_v4()"></td>
<td><input type="text" name="peer-v4" id="peer-v4"onchange="return update_from_v4()"></td> <td><input type="text" name="peer-v4" id="peer-v4" onchange="return update_from_v4()" placeholder="172.2x.yyy.zzz" title="only required when not using extended next hop or not MultiProtocol"></td>
</tr> </tr>
<tr> <tr>
<td><label for="peer-v6">your ipv6</label></td> <td><label for="peer-v6">your ipv6</label></td>
<td><input type="checkbox" name="peer-v6-enabled" id="peer-v6-enabled"onchange="return update_from_v6()"></td> <td><input type="checkbox" name="peer-v6-enabled" id="peer-v6-enabled" onchange="return update_from_v6()"></td>
<td><input type="text" name="peer-v6" id="peer-v6"onchange="return update_from_v6()"></td> <td><input type="text" name="peer-v6" id="peer-v6" onchange="return update_from_v6()" placeholder="fdxx:yyyy:zzzz:..." title="only required when ipv6 LinkLocal and/or MultiProtocol BGP are not supported"></td>
</tr> </tr>
<tr> <tr>
<td><h4>BGP</h4></td> <td><h4>BGP</h4></td>
@ -263,7 +263,7 @@ protocol bgp dn42_{{config["MNT"][:-4].lower()}} from dnpeers {
extended next hop <span id="example-config-bird2-enh4">on</span>; extended next hop <span id="example-config-bird2-enh4">on</span>;
}; };
ipv6 { ipv6 {
extended next hop <span id="example-config-bird2-enh6">on</span>; extended next hop <span id="example-config-bird2-enh6">off</span>;
}; };
} }
</pre> </pre>

View file

@ -5,7 +5,7 @@
> >
</div> </div>
<div class="flex flex-row"> <div class="flex flex-row">
{% for peering in peerings.get_peerings_by_mnt(session["login"]) %} {% for peering in peerings.get_peerings_by_mnt(session["user-data"]["mnt"]) %}
<div class="peering"> <div class="peering">
<div> <div>
<div>Node: {{peering["node"]}}</div> <div>Node: {{peering["node"]}}</div>

View file

@ -0,0 +1 @@
.kioubit-btn-dark,.kioubit-btn-light{font-weight:400;font-size:1rem;line-height:1.5;padding:.5em;display:flex;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}.kioubit-btn-dark{color:#fff;background-color:#343a40;vertical-align:middle;border:1px solid transparent;border-radius:.4rem;align-items:center}.kioubit-btn-dark:hover{color:#fff;background-color:#651fff;border-color:#1d2124}.kioubit-btn-dark:focus,.kioubit-btn-light:focus{outline:0;box-shadow:0 0 0 .2rem rgba(82,88,93,.5)}.kioubit-btn-light{color:#fafafa;background-color:#2962ff;border:1px solid transparent;border-radius:.4rem;align-items:center}.kioubit-btn-light:hover{color:#fff;background-color:#311b92;border-color:#1d2124}.kioubit-btn-logo{margin-right:.5em;filter:invert(100%) sepia(5%) saturate(7480%) hue-rotate(81deg) brightness(125%) contrast(106%)}

View file

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -64 1024 1024">
<path d="M375.899 935.372C149.064 861.488 15.555 660.577 28.517 410.41 37.59 225.053 133.509 77.286 295.535-1.782c80.365-40.182 95.919-42.775 216.466-42.775 116.658 0 136.101 3.889 197.023 36.294C899.566 91.545 994.189 262.643 982.523 485.59c-9.073 185.357-104.992 333.124-267.018 412.192-73.884 37.59-101.104 42.775-197.023 45.367-60.922 1.296-124.435-1.296-142.582-7.777zm268.314-124.435c67.403-28.516 167.21-129.62 198.319-203.504 40.182-93.327 40.182-226.835 1.296-314.977-60.922-138.694-198.319-230.724-344.79-230.724-97.215 0-156.841 24.628-238.501 101.104-84.253 79.068-120.547 164.618-121.843 285.165-1.296 270.906 264.425 462.744 505.519 362.937zM311.089 448V156.354h89.438l7.777 256.648 107.585-128.324c102.4-123.139 108.881-128.324 158.137-128.324 28.516 0 51.848 2.592 51.848 5.185 0 3.889-58.329 72.587-128.324 152.952L469.226 460.962l98.511 110.177c54.441 60.922 110.177 123.139 124.435 139.99l24.628 28.516h-50.552c-49.256 0-55.737-6.481-154.248-121.843L408.304 494.663l-3.889 123.139-3.889 121.843h-89.438V447.999z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -1,3 +1,4 @@
Flask Flask
waitress waitress
pyopenssl pyopenssl
requests

0
web/start.sh Normal file → Executable file
View file