add ability to delete peerings

+ format all files
+ fix "selected_peering" not being defined in /peerings/edit
This commit is contained in:
lare 2022-12-07 17:00:52 +01:00
parent ff4865f4b6
commit 537400f77a
10 changed files with 464 additions and 238 deletions

View file

@ -1,29 +1,29 @@
{ {
"nodes": { "nodes": {
"<nodename>": { "<nodename>": {
"endpoint": "<clearnet-fqdn/ip-address>", //optional, recommended, default: None/null "endpoint": "<clearnet-fqdn/ip-address>", //optional, recommended, default: None/null
"api-con": "http://<node-(internal)-ip/hostname>:<port>/", // required "api-con": "http://<node-(internal)-ip/hostname>:<port>/", // required
"#comment": "/* from here: data to be displayed on the webinterface */", "#comment": "/* from here: data to be displayed on the webinterface */",
"country": "...", // Countrycode: 2 capital letters "country": "...", // Countrycode: 2 capital letters
"city": "...", "city": "...",
"wg-key": "...=", // pubkey of node; required "wg-key": "...=", // pubkey of node; required
"internal-v4": "172.2x.xxx.xxx", //at least one ipv{4,6} addr required "internal-v4": "172.2x.xxx.xxx", //at least one ipv{4,6} addr required
"internal-v6": "fdxx:...", "internal-v6": "fdxx:...",
"internal-v4ll": "169.254.xxx.xxx", "internal-v4ll": "169.254.xxx.xxx",
"internal-v6ll": "fe80::...", "internal-v6ll": "fe80::...",
"note": "...", //optional, special precausions, like only supporting a specific amount of peers/ipv{4,6} in clearnet, etc "note": "...", //optional, special precausions, like only supporting a specific amount of peers/ipv{4,6} in clearnet, etc
"capacity": 100 //optional, default: -1 (infinite); estimated capacity of that node (i.e. OPENVZ(7) only has userspace WG (which consumes memory for every interface created)) "capacity": 100 //optional, default: -1 (infinite); estimated capacity of that node (i.e. OPENVZ(7) only has userspace WG (which consumes memory for every interface created))
} }
}, },
"MNT": "YOUR-MNT", // your MNT tag "MNT": "YOUR-MNT", // your MNT tag
"ASN": "424242000", //Your ASN (used to generate default peer ListenPorts) "ASN": "424242000", //Your ASN (used to generate default peer ListenPorts)
"listen": "0.0.0.0", "listen": "0.0.0.0",
"port": 8042, "port": 8042,
"domain": "example.org", // domain to use for kioubit verification service "domain": "example.org", // domain to use for kioubit verification service
"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/peering-config.json", // optional; default "$PWD/peerings", directory 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
"flask-template-dir": "../frontend/" // optional; default "../frontend" "flask-template-dir": "../frontend/" // optional; default "../frontend"
} }

View file

@ -1,15 +1,20 @@
#! /usr/bin/env python3 #! /usr/bin/env python3
import base64, os, json, time, logging import base64
import os
import json
import time
import logging
import OpenSSL import OpenSSL
from OpenSSL.crypto import load_publickey, FILETYPE_PEM, verify, X509 from OpenSSL.crypto import load_publickey, FILETYPE_PEM, verify, X509
PUBKEY_FILE = os.path.dirname(__file__)+"/kioubit-auth-pubkey.pem" PUBKEY_FILE = os.path.dirname(__file__)+"/kioubit-auth-pubkey.pem"
class AuthVerifyer (): class AuthVerifyer ():
def __init__(self,domain, pubkey=PUBKEY_FILE): def __init__(self, domain, pubkey=PUBKEY_FILE):
self.domain = domain self.domain = domain
with open(pubkey) as pk: with open(pubkey) as pk:
pk_content = "" pk_content = ""
@ -19,13 +24,13 @@ class AuthVerifyer ():
pkey = load_publickey(FILETYPE_PEM, pk_content) pkey = load_publickey(FILETYPE_PEM, pk_content)
self.x509 = X509() self.x509 = X509()
self.x509.set_pubkey(pkey) self.x509.set_pubkey(pkey)
logging.debug(self.x509) logging.debug(self.x509)
def verify(self, params, signature): def verify(self, params, signature):
# logging.debug(type(sig)) # logging.debug(type(sig))
#OpenSSL_verify(self.pubkey, sig # OpenSSL_verify(self.pubkey, sig
#, base64.b64decode(params), "sha512") # , base64.b64decode(params), "sha512")
sig = base64.b64decode(signature) sig = base64.b64decode(signature)
logging.info(f"sig: {sig}") logging.info(f"sig: {sig}")
logging.info(f"params: {params}") logging.info(f"params: {params}")
@ -33,10 +38,10 @@ class AuthVerifyer ():
verify(self.x509, sig, params, 'sha512') verify(self.x509, sig, params, 'sha512')
except OpenSSL.crypto.Error: except OpenSSL.crypto.Error:
return False, "Signature Failed" return False, "Signature Failed"
try: try:
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"
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
@ -46,11 +51,12 @@ class AuthVerifyer ():
logging.debug(user_data) logging.debug(user_data)
return True, user_data return True, user_data
if __name__ == "__main__": if __name__ == "__main__":
example_com_verifier = AuthVerifyer("example.com") example_com_verifier = AuthVerifyer("example.com")
logging.info (example_com_verifier.verify( logging.info(example_com_verifier.verify(
params=b"eyJhc24iOiI0MjQyNDIzMDM1IiwidGltZSI6MTY2ODI2NjkyNiwiYWxsb3dlZDQiOiIxNzIuMjIuMTI1LjEyOFwvMjYsMTcyLjIwLjAuODFcLzMyIiwiYWxsb3dlZDYiOiJmZDYzOjVkNDA6NDdlNTo6XC80OCxmZDQyOmQ0MjpkNDI6ODE6OlwvNjQiLCJtbnQiOiJMQVJFLU1OVCIsImF1dGh0eXBlIjoibG9naW5jb2RlIiwiZG9tYWluIjoic3ZjLmJ1cmJsZS5kbjQyIn0=", params=b"eyJhc24iOiI0MjQyNDIzMDM1IiwidGltZSI6MTY2ODI2NjkyNiwiYWxsb3dlZDQiOiIxNzIuMjIuMTI1LjEyOFwvMjYsMTcyLjIwLjAuODFcLzMyIiwiYWxsb3dlZDYiOiJmZDYzOjVkNDA6NDdlNTo6XC80OCxmZDQyOmQ0MjpkNDI6ODE6OlwvNjQiLCJtbnQiOiJMQVJFLU1OVCIsImF1dGh0eXBlIjoibG9naW5jb2RlIiwiZG9tYWluIjoic3ZjLmJ1cmJsZS5kbjQyIn0=",
signature=b"MIGIAkIBAmwz3sQ1vOkH8+8e0NJ8GsUqKSaazIWmYDp60sshlTo7gCAopZOZ6/+tD6s+oEGM1i5mKGbHgK9ROATQLHxUZecCQgCa2N828uNn76z1Yg63/c7veMVIiK4l1X9TCUepJnJ3mCto+7ogCP+2vQm6GHipSNRF4wnt6tZbir0HZvrqEnRAmA==" signature=b"MIGIAkIBAmwz3sQ1vOkH8+8e0NJ8GsUqKSaazIWmYDp60sshlTo7gCAopZOZ6/+tD6s+oEGM1i5mKGbHgK9ROATQLHxUZecCQgCa2N828uNn76z1Yg63/c7veMVIiK4l1X9TCUepJnJ3mCto+7ogCP+2vQm6GHipSNRF4wnt6tZbir0HZvrqEnRAmA=="
) ) ))
#params = "eyJhc24iOiI0MjQyNDIzMDM1IiwidGltZSI6MTY2ODI1NjI5NSwiYWxsb3dlZDQiOiIxNzIuMjIuMTI1LjEyOFwvMjYsMTcyLjIwLjAuODFcLzMyIiwiYWxsb3dlZDYiOiJmZDYzOjVkNDA6NDdlNTo6XC80OCxmZDQyOmQ0MjpkNDI6ODE6OlwvNjQiLCJtbnQiOiJMQVJFLU1OVCIsImF1dGh0eXBlIjoibG9naW5jb2RlIiwiZG9tYWluIjoic3ZjLmJ1cmJsZS5kbjQyIn0=", #params = "eyJhc24iOiI0MjQyNDIzMDM1IiwidGltZSI6MTY2ODI1NjI5NSwiYWxsb3dlZDQiOiIxNzIuMjIuMTI1LjEyOFwvMjYsMTcyLjIwLjAuODFcLzMyIiwiYWxsb3dlZDYiOiJmZDYzOjVkNDA6NDdlNTo6XC80OCxmZDQyOmQ0MjpkNDI6ODE6OlwvNjQiLCJtbnQiOiJMQVJFLU1OVCIsImF1dGh0eXBlIjoibG9naW5jb2RlIiwiZG9tYWluIjoic3ZjLmJ1cmJsZS5kbjQyIn0=",
#signature = 'MIGHAkFy1m+9ahjIc5cJk/p+RiXJbhbWT5rPSJNg9Q3c8UTAM4F7lz2OqdWHw6GZN5NQgvqm6OB3Y751djYwCd54y2Kn4wJCAcBaOrtSclxkGIleVx183PhTnSr97r2F089PsDzNXIBvH5pYUwvJX7hG0op0f5tPm7fl12HOOrr8Q6kWW+XTrgGX' #signature = 'MIGHAkFy1m+9ahjIc5cJk/p+RiXJbhbWT5rPSJNg9Q3c8UTAM4F7lz2OqdWHw6GZN5NQgvqm6OB3Y751djYwCd54y2Kn4wJCAcBaOrtSclxkGIleVx183PhTnSr97r2F089PsDzNXIBvH5pYUwvJX7hG0op0f5tPm7fl12HOOrr8Q6kWW+XTrgGX'

View file

@ -2,21 +2,30 @@
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
import json, os, base64, logging, random import json
import os
import base64
import logging
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
import kioubit_verify import kioubit_verify
app = Flask(__name__) app = Flask(__name__)
class Config (dict): class Config (dict):
def __init__(self, configfile:str = None): def __init__(self, configfile: str = None):
if configfile: if configfile:
self.configfile = configfile self.configfile = configfile
else: else:
if os.path.exists("./config.json"): self.configfile = "./config.json" if os.path.exists("./config.json"):
elif os.path.exists("/etc/dn42-autopeer/config.json"): self.configfile = "/etc/dn42-autopeer/config,json" self.configfile = "./config.json"
else: raise FileNotFoundError("no config file found in ./config.json or /etc/dn42-autopeer/config.json") elif os.path.exists("/etc/dn42-autopeer/config.json"):
self.configfile = "/etc/dn42-autopeer/config,json"
else:
raise FileNotFoundError(
"no config file found in ./config.json or /etc/dn42-autopeer/config.json")
self._load_config() self._load_config()
self.keys = self._config.keys self.keys = self._config.keys
#self.__getitem__ = self._config.__getitem__ #self.__getitem__ = self._config.__getitem__
@ -27,29 +36,31 @@ class Config (dict):
def __delitem__(self, v): def __delitem__(self, v):
raise NotImplementedError() raise NotImplementedError()
super().__delitem__(self,v) super().__delitem__(self, v)
def __getitem__(self, k): def __getitem__(self, k):
return self._config[k] return self._config[k]
def _load_config(self): def _load_config(self):
with open(self.configfile) as cf: with open(self.configfile) as cf:
try: try:
self._config = json.load(cf) self._config = json.load(cf)
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 not "flask-template-dir" in self._config:
self._config["flask-template-dir"] = "../frontend" self._config["flask-template-dir"] = "../frontend"
if not "debug-mode" in self._config: if not "debug-mode" in self._config:
self._config["debug-mode"] = False self._config["debug-mode"] = False
if not "base-dir" in self._config: if not "base-dir" in self._config:
self._config["base-dir"] = "/" self._config["base-dir"] = "/"
if not "peerings-data" in self._config: if not "peerings-data" in self._config:
self._config["peering-data"] = "./peerings" self._config["peering-data"] = "./peerings"
logging.info(self._config) logging.info(self._config)
class PeeringManager: class PeeringManager:
def __init__(self, peerings_file): def __init__(self, peerings_file):
@ -59,15 +70,15 @@ 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({"mnter": {}, "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: except json.decoder.JSONDecodeError:
with open(self._peering_file, "w") as p: with open(self._peering_file, "w") as p:
json.dump({"mnter":{},"asn":{}}, p) json.dump({"mnter": {}, "asn": {}}, p)
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)
# self.peerings = {} # self.peerings = {}
@ -87,6 +98,30 @@ class PeeringManager:
with open(self._peering_file, "w") as p: with open(self._peering_file, "w") as p:
json.dump(self.peerings, p, indent=4) json.dump(self.peerings, p, indent=4)
def _update_nodes(mode, peering, new_peering=None):
"""mode: "add","update","delete
peering: peering to send to node (included in peering)
new_peering: if mode=="update" the new peering to update to
"""
pass
def exists(self, asn, node, mnt=None, wg_key=None, endpoint=None, ipv6ll=None, ipv4=None, ipv6=None, bgp_mp=True, bgp_enh=True):
"""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 mnt and not (mnt in self.peerings["mnter"] and asn in self.peerings["mnter"][mnt]):
return False
selected_peerings = self.peerings["asn"][asn]
# check if the ASn even has peerings
if len(selected_peerings) == 0:
return False
for p in selected_peerings:
if p["node"] == node:
if (not wg_key or p["wg_key"] == wg_key) and (not endpoint or p["endpoint"] == endpoint) \
and (not ipv6ll or p["ipv6ll"] == ipv6ll) and (not ipv4 or p["ipv4"] == ipv4) and (not ipv6 or p["ipv6"] == ipv6)\
and (not bgp_mp or p["bgp_mp"] == bgp_mp) and (not bgp_enh or p["bgp_enh"] == bgp_enh):
return True
return False
def get_peerings_by_mnt(self, mnt): def get_peerings_by_mnt(self, mnt):
# print(self.peerings) # print(self.peerings)
try: try:
@ -101,7 +136,7 @@ class PeeringManager:
except KeyError: except KeyError:
return {} return {}
def add_peering(self, mnt, asn, node, 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):
# 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 not asn in self.peerings["mnter"][mnt]:
@ -115,16 +150,19 @@ class PeeringManager:
self.peerings["asn"][asn] = [] self.peerings["asn"][asn] = []
except KeyError: except KeyError:
self.peerings["asn"][asn] = [] self.peerings["asn"][asn] = []
# 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: return False if peering["node"] == node:
return False
self.peerings["asn"][asn].append({"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})
self.peerings["asn"][asn].append({"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})
self._save_peerings() self._save_peerings()
return True return True
def update_peering(self, mnt, asn, node, 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 not asn in self.peerings["mnter"][mnt]:
@ -138,26 +176,42 @@ class PeeringManager:
return False return False
except KeyError: except KeyError:
return False return False
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, "endpoint": endpoint,"ipv6ll":ipv6ll,"ipv4":ipv4,"ipv6":ipv6, "bgp_mp":bgp_mp, "bgp_enh":bgp_enh} 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}
success = True success = True
if not success: return False if not success:
return False
self._save_peerings() self._save_peerings()
return True return True
def delete_peering(self, asn, node, mnt, wg_key=None):
if not self.exists(asn, node, mnt=mnt, wg_key=wg_key):
return False
for p in self.peerings["asn"][asn]:
if p["node"] == node:
if wg_key and p["wg_key"] != wg_key:
continue
self.peerings["asn"][asn].remove(p)
self._save_peerings()
return True
# if nothing got found (should have been catched by self.exists)
return False
config = Config() config = Config()
peerings = PeeringManager(config["peerings"]) peerings = PeeringManager(config["peerings"])
kverifyer = kioubit_verify.AuthVerifyer(config["domain"]) kverifyer = kioubit_verify.AuthVerifyer(config["domain"])
def check_peering_data(form): def check_peering_data(form):
new_peering = {} new_peering = {}
# errors = 0 # errors = 0
## 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"] new_peering["peer-asn"] = session["user-data"]["asn"]
new_peering["peer-wgkey"] = form["peer-wgkey"] new_peering["peer-wgkey"] = form["peer-wgkey"]
@ -167,7 +221,7 @@ def check_peering_data(form):
raise ValueError("peer-endpoint") raise ValueError("peer-endpoint")
else: else:
new_peering["peer-endpoint"] = None new_peering["peer-endpoint"] = None
if "peer-v6ll-enabled" in form and form["peer-v6ll-enabled"] == "on": if "peer-v6ll-enabled" in form and form["peer-v6ll-enabled"] == "on":
new_peering["peer-v6ll"] = form["peer-v6ll"] new_peering["peer-v6ll"] = form["peer-v6ll"]
if new_peering["peer-v6ll"] == "": if new_peering["peer-v6ll"] == "":
@ -197,7 +251,7 @@ def check_peering_data(form):
print(new_peering) print(new_peering)
## check wireguard key # check wireguard key
wg_key_invalid = False wg_key_invalid = False
if len(new_peering["peer-wgkey"]) != 44: if len(new_peering["peer-wgkey"]) != 44:
wg_key_invalid = True wg_key_invalid = True
@ -205,32 +259,35 @@ def check_peering_data(form):
base64.b64decode(new_peering["peer-wgkey"]) base64.b64decode(new_peering["peer-wgkey"])
except: except:
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"
## check endpoint # check endpoint
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
try: try:
if not (new_peering["peer-v6ll"] or new_peering["peer-v4"] or new_peering["peer-v6"]): if not (new_peering["peer-v6ll"] or new_peering["peer-v4"] or new_peering["peer-v6"]):
return False, "at least one of the ip addresses must be enabled and specified" return False, "at least one of the ip addresses must be enabled and specified"
except KeyError: except KeyError:
return False, "one of the values isn't valid" return False, "one of the values isn't valid"
## check if supplied ip addresses are valid # check if supplied ip addresses are valid
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: raise ValueError() if not ipv6ll.version == 6:
if not ipv6ll.is_link_local: raise ValueError() raise ValueError()
if not ipv6ll.is_link_local:
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: raise ValueError() if not ipv4.version == 4:
raise ValueError()
if ipv4.is_link_local: if ipv4.is_link_local:
pass pass
elif ipv4.is_private: elif ipv4.is_private:
@ -243,12 +300,16 @@ def check_peering_data(form):
is_in_allowed = True 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: raise ValueError() else:
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: raise ValueError() if not ipv6.version == 6:
if not ipv6.is_private: raise ValueError() raise ValueError()
if ipv6.is_link_local: raise ValueError() if not ipv6.is_private:
raise ValueError()
if ipv6.is_link_local:
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"].split(","): for allowed6 in session["user-data"]["allowed6"].split(","):
@ -269,6 +330,8 @@ def check_peering_data(form):
except ValueError: except ValueError:
... ...
return True, new_peering return True, new_peering
def auth_required(): def auth_required():
def wrapper(f): def wrapper(f):
@wraps(f) @wraps(f)
@ -283,17 +346,17 @@ def auth_required():
@app.route("/api/auth/kverify", methods=["GET", "POST"]) @app.route("/api/auth/kverify", methods=["GET", "POST"])
def kioubit_auth(): def kioubit_auth():
try: try:
params = request.args["params"] params = request.args["params"]
signature = request.args["signature"] signature = request.args["signature"]
except KeyError: except KeyError:
return render_template("login.html", session=session,config=config,return_addr=session["return_url"], msg='"params" or "signature" missing') return render_template("login.html", session=session, config=config, return_addr=session["return_url"], msg='"params" or "signature" missing')
success, msg = kverifyer.verify(params, signature) success, msg = kverifyer.verify(params, signature)
try: logging.debug(base64.b64decode(params)) try:
except: logging.debug("invalid Base64 data provided") logging.debug(base64.b64decode(params))
except:
logging.debug("invalid Base64 data provided")
if success: if success:
session["user-data"] = msg session["user-data"] = msg
@ -304,68 +367,89 @@ def kioubit_auth():
return redirect(f"{config['base-dir']}peerings") return redirect(f"{config['base-dir']}peerings")
else: else:
try: try:
return render_template("login.html", session=session,config=config,return_addr=session["return_url"], msg=msg) return render_template("login.html", session=session, config=config, return_addr=session["return_url"], msg=msg)
except KeyError: except KeyError:
return render_template("login.html", session=session,config=config,return_addr=f"{config['base-dir']}peerings", msg=msg) return render_template("login.html", session=session, config=config, return_addr=f"{config['base-dir']}peerings", msg=msg)
@app.route("/logout") @app.route("/logout")
def logout(): def logout():
session.clear() session.clear()
return redirect("/") return redirect("/")
@app.route("/login",methods=["GET","POST"])
@app.route("/login", methods=["GET", "POST"])
def login(): def login():
print(session) print(session)
if request.method == "GET": if request.method == "GET":
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"]:
try: try:
print(request.form) print(request.form)
if request.form["theanswer"] != "42": if request.form["theanswer"] != "42":
msg = "what is the answer for everything?" msg = "what is the answer for everything?"
return render_template("login.html", session=session,config=config,return_addr=session["return_url"], msg=msg) return render_template("login.html", session=session, config=config, return_addr=session["return_url"], msg=msg)
mnt = request.form["mnt"] mnt = request.form["mnt"]
if not mnt.upper().endswith("-MNT"): raise ValueError if not mnt.upper().endswith("-MNT"):
raise ValueError
asn = request.form["asn"] asn = request.form["asn"]
asn = asn[2:] if asn[:2].lower() == "as" else asn asn = asn[2:] if asn[:2].lower() == "as" else asn
int(asn) int(asn)
if "allowed4" in request.form: if "allowed4" in request.form:
allowed4 = request.form["allowed4"] allowed4 = request.form["allowed4"]
v4_ranges = allowed4.split(",") if "," in allowed4 else [allowed4] v4_ranges = allowed4.split(
",") if "," in allowed4 else [allowed4]
for v4_range in v4_ranges: for v4_range in v4_ranges:
IPv4Network(v4_range) IPv4Network(v4_range)
else: else:
allowed4 = None allowed4 = None
if "allowed6" in request.form: if "allowed6" in request.form:
allowed6 = request.form["allowed6"] allowed6 = request.form["allowed6"]
v6_ranges = allowed6.split(",") if "," in allowed6 else [allowed6] v6_ranges = allowed6.split(
",") if "," in allowed6 else [allowed6]
for v6_range in v6_ranges: for v6_range in v6_ranges:
IPv6Network(v6_range) IPv6Network(v6_range)
else: else:
allowed6 = None allowed6 = None
session["user-data"] = {'asn':asn,'allowed4': allowed4, 'allowed6': allowed6,'mnt':mnt, 'authtype': "debug"} session["user-data"] = {'asn': asn, 'allowed4': allowed4,
'allowed6': allowed6, 'mnt': mnt, 'authtype': "debug"}
session["login"] = mnt session["login"] = mnt
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"
return render_template("login.html", session=session,config=config,return_addr=session["return_url"], msg=msg) return render_template("login.html", session=session, config=config, return_addr=session["return_url"], msg=msg)
except KeyError: except KeyError:
msg = "not all required field were specified" msg = "not all required field were specified"
return render_template("login.html", session=session,config=config,return_addr=session["return_url"], msg=msg) return render_template("login.html", session=session, config=config, return_addr=session["return_url"], msg=msg)
elif request.method == "POST" and not config["debug-mode"]: elif request.method == "POST" and not config["debug-mode"]:
abort(405) abort(405)
return redirect(request.args["return"]) return redirect(request.args["return"])
@app.route("/peerings/delete", methods=["GET","DELETE"]) @app.route("/peerings/delete", methods=["GET", "POST", "DELETE"])
@auth_required() @auth_required()
def peerings_delete(): def peerings_delete():
return f"{request.method} /peerings/delete?{str(request.args)}{str(request.form)}" if request.method == "GET":
return render_template("peerings-delete.html", session=session, config=config, request_args=request.args)
@app.route("/peerings/edit", methods=["GET","POST"]) return f"{request.method} /peerings/delete?{str(request.args)}{str(request.form)}"
elif request.method in ["POST", "DELETE"]:
if not 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")
if not peerings.exists(request.args["asn"], request.args["node"], mnt=session["login"]):
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))
if not peerings.delete_peering(request.args["asn"], request.args["node"], mnt=session["login"]):
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",
"node": request.args["node"], "asn": request.args["asn"]}
return redirect("../peerings")
return f"{request.method} /peerings/delete {request.args} {request.form}"
@app.route("/peerings/edit", methods=["GET", "POST"])
@auth_required() @auth_required()
def peerings_edit(): def peerings_edit():
print(session) print(session)
@ -380,57 +464,67 @@ def peerings_edit():
print(p) print(p)
break break
return render_template("peerings-edit.html", session=session, config=config, mnt_peerings=mnt_peerings, selected_peering=selected_peering) return render_template("peerings-edit.html", session=session, config=config, mnt_peerings=mnt_peerings, selected_peering=selected_peering)
else: else:
return render_template("peerings-edit.html", session=session, config=config, mnt_peerings=mnt_peerings, selected_peering=None) return render_template("peerings-edit.html", session=session, config=config, mnt_peerings=mnt_peerings, selected_peering=None)
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 not "node" in request.args or not request.args["node"]:
return render_template("peerings-edit.html", session=session,config=config, peerings=peerings, msg="no node specified, please click one of the buttons above") return render_template("peerings-edit.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)
print(peering_valid) print(peering_valid)
print(peering_or_msg) print(peering_or_msg)
selected_peering = None
mnt_peerings = peerings.get_peerings_by_mnt(session["login"])
for p in mnt_peerings:
if p["node"] == request.args["node"] and p["ASN"] == request.args["asn"]:
selected_peering = p
print(p)
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), 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"]["mnt"], session["user-data"]["asn"], request.args["node"], 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["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"]):
return render_template("peerings-edit.html", session=session,config=config, peerings=peerings, msg="such a peering doesn't exist(yet)"), 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")
return f"{request.method} /peerings/edit?{str(request.args)}{str(request.form)}" return f"{request.method} /peerings/edit?{str(request.args)}{str(request.form)}"
@app.route("/peerings/new", methods=["GET","POST"])
@app.route("/peerings/new", methods=["GET", "POST"])
@auth_required() @auth_required()
def peerings_new(): def peerings_new():
print(session) print(session)
if request.method == "GET": if request.method == "GET":
if "node" in request.args and request.args["node"] in config["nodes"]: if "node" in request.args and request.args["node"] in config["nodes"]:
return render_template("peerings-new.html", config=config, selected_node=request.args["node"], peerings=peerings) return render_template("peerings-new.html", config=config, selected_node=request.args["node"], peerings=peerings)
else: else:
return render_template("peerings-new.html", session=session,config=config, peerings=peerings) return render_template("peerings-new.html", session=session, config=config, peerings=peerings)
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 not "node" 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"]["mnt"], session["user-data"]["asn"], request.args["node"], 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.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"]):
return render_template("peerings-new.html", session=session,config=config, peerings=peerings, msg="this ASN already has a peering with the requested node"), 400 return render_template("peerings-new.html", session=session, config=config, peerings=peerings, msg="this ASN already has a peering with the requested node"), 400
return redirect(f"{config['base-dir']}peerings") return redirect(f"{config['base-dir']}peerings")
@app.route("/peerings", methods=["GET","POST","DELETE"])
@app.route("/peerings", methods=["GET", "POST", "DELETE"])
@auth_required() @auth_required()
def peerings_view(): def peerings_view():
print(session) print(session)
if request.method == "GET": if request.method == "GET":
if "node" in request.args and request.args["node"] in config["nodes"]: if "node" in request.args and request.args["node"] in config["nodes"]:
return render_template("peerings.html", config=config, selected_node=request.args["node"], peerings=peerings) return render_template("peerings.html", config=config, selected_node=request.args["node"], peerings=peerings)
else: else:
return render_template("peerings.html", session=session,config=config, peerings=peerings) return render_template("peerings.html", session=session, config=config, peerings=peerings)
elif request.method == "POST": elif request.method == "POST":
return peerings_new() return peerings_new()
elif request.method == "DELETE": elif request.method == "DELETE":
@ -438,6 +532,7 @@ def peerings_view():
else: else:
abort(405) abort(405)
@app.route("/") @app.route("/")
def index(): def index():
print(session) print(session)
@ -446,19 +541,20 @@ def index():
# print (node) # print (node)
return render_template("index.html", session=session, config=config._config) return render_template("index.html", session=session, config=config._config)
def main(): def main():
app.static_folder= config["flask-template-dir"]+"/static/" app.static_folder = config["flask-template-dir"]+"/static/"
app.template_folder=config["flask-template-dir"] app.template_folder = config["flask-template-dir"]
app.secret_key = config["flask-secret-key"] app.secret_key = config["flask-secret-key"]
if "production" in config and config["production"] == False: if "production" in config and config["production"] == False:
logging.getLogger(__name__).setLevel(0) logging.getLogger(__name__).setLevel(0)
app.run(host=config["listen"], port=config["port"], debug=config["debug-mode"], threaded=True) app.run(host=config["listen"], port=config["port"],
debug=config["debug-mode"], threaded=True)
else: else:
from waitress import serve from waitress import serve
logging.getLogger(__name__).setLevel(logging.INFO) logging.getLogger(__name__).setLevel(logging.INFO)
serve(app, host=config["listen"], port=config["port"]) serve(app, host=config["listen"], port=config["port"])
if __name__ == "__main__": if __name__ == "__main__":
main() main()

View file

@ -1,22 +1,29 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{{config["MNT"]}} Autopeering</title> <title>{{config["MNT"]}} Autopeering</title>
<link rel="stylesheet" href="{{config['base-dir']}}static/style.css"> <link rel="stylesheet" href="{{config['base-dir']}}static/style.css" />
</head> </head>
<body> <body>
<header class="flex flex-row"><div></div><a href="{{config['base-dir']}}">{{config["MNT"]}} Autopeering</a>{% if "login" in session %} <div><a href="{{config['base-dir']}}peerings">manage</a> <a href="{{config['base-dir']}}logout">logout</a></div>{% else %} <a href="{{config['base-dir']}}login?return=/peerings">login</a>{%endif%}</header> <header class="flex flex-row">
<div class="content flex"> <div></div>
{% block content %} <a href="{{config['base-dir']}}">{{config["MNT"]}} Autopeering</a>{% if
{% endblock %} "login" in session %}
</div> <div>
<a href="{{config['base-dir']}}peerings">manage</a>
<a href="{{config['base-dir']}}logout">logout</a>
</div>
{% else %}
<a href="{{config['base-dir']}}login?return=/peerings">login</a>{%endif%}
</header>
<div class="content flex">{% block content %} {% endblock %}</div>
<footer class="flex"> <footer class="flex">
<div></div> <div></div>
<div></div> <div></div>
<div> &#127279; LARE-MNT</div> <div>&#127279; LARE-MNT</div>
</footer> </footer>
</body> </body>
</html> </html>

View file

@ -1,28 +1,25 @@
{% extends 'base.html' %} {% extends 'base.html' %} {% block content %}
<div></div>
{% block content %}
<div> <div>
<table>
<thead>
<tr>
<th>NodeName</th>
<th>Country</th>
<th>City</th>
<th>peerings</th>
<th>Peer!</th>
</tr>
</thead>
{% for node in config["nodes"] %}
<tr>
<td>{{node}}</td>
<td>{{config["nodes"][node]["country"]}}</td>
<td>{{config["nodes"][node]["city"]}}</td>
<td></td>
<td><a href="peerings/new?node={{node}}">peer</a></td>
</tr>
{% endfor %}
</table>
</div> </div>
<div> {% endblock %}
<table>
<thead>
<tr>
<th>NodeName</th>
<th>Country</th>
<th>City</th>
<th>peerings</th>
<th>Peer!</th>
</tr>
</thead>
{% for node in config["nodes"] %}
<tr>
<td>{{node}}</td>
<td>{{config["nodes"][node]["country"]}}</td>
<td>{{config["nodes"][node]["city"]}}</td>
<td></td>
<td><a href="peerings/new?node={{node}}">peer</a></td>
</tr>
{% endfor %}
</table>
</div>
{% endblock %}

View file

@ -1,31 +1,64 @@
{% extends 'base.html' %} {% extends 'base.html' %} {% block content %} {% if msg %}
<div style="background-color: red">{{msg}}</div>
{% block content %}
{% if msg %}
<div style="background-color: red;">
{{msg}}
</div>
{% endif %} {% endif %}
<form action="https://dn42.g-load.eu/auth/"> <form action="https://dn42.g-load.eu/auth/">
<link rel="stylesheet" href="https://dn42.g-load.eu/auth/assets/button-font/auth.css"> <link
<input type="hidden" id="return" name="return" value='{{"http://"+config["domain"]+"/api/auth/kverify"}}'> rel="stylesheet"
<button type="submit" class="kioubit-btn kioubit-btn-primary kioubit-btn-dark kioubit-btn-main"> href="https://dn42.g-load.eu/auth/assets/button-font/auth.css"
/>
<input
type="hidden"
id="return"
name="return"
value='{{"http://"+config["domain"]+"/api/auth/kverify"}}'
/>
<button
type="submit"
class="kioubit-btn kioubit-btn-primary kioubit-btn-dark kioubit-btn-main"
>
<span class="icon-kioubit-auth"></span>Authenticate with Kioubit.dn42 <span class="icon-kioubit-auth"></span>Authenticate with Kioubit.dn42
</button> </button>
</form> </form>
{% if config["debug-mode"] %} {% if config["debug-mode"] %}
<form action="" method="post" class="flex"> <form action="" method="post" class="flex">
<label for="debug">Debug login, if you see this in Production contact {{config["MNT"]}}</label><br> <label for="debug"
<input type="text" name="mnt" id="mnt" placeholder="YOUR-MNT" required><br> >Debug login, if you see this in Production contact {{config["MNT"]}}</label
<input type="text" name="asn" id="asn" placeholder="AS4242420000" required><br> ><br />
<input type="text" name="allowed4" id="allowed4" placeholder="ipv4 subnet (optional)"><br> <input
<input type="text" name="allowed6" id="allowed6" placeholder="ipv6 subnet (optional)"><br> type="text"
<input type="number" name="theanswer" id="theanswer" placeholder="The answer for everything" required><br> name="mnt"
<input type="submit" value="login"> id="mnt"
placeholder="YOUR-MNT"
required
/><br />
<input
type="text"
name="asn"
id="asn"
placeholder="AS4242420000"
required
/><br />
<input
type="text"
name="allowed4"
id="allowed4"
placeholder="ipv4 subnet (optional)"
/><br />
<input
type="text"
name="allowed6"
id="allowed6"
placeholder="ipv6 subnet (optional)"
/><br />
<input
type="number"
name="theanswer"
id="theanswer"
placeholder="The answer for everything"
required
/><br />
<input type="submit" value="login" />
</form> </form>
{% endif %} {% endif %} {% endblock %}
{% endblock %}

View file

@ -0,0 +1,45 @@
{% extends 'base.html' %} {% block content %}
<form class="flex" action="" method="post">
<div id="warning" style="background-color: yellow">
{% if msg %}{{msg}}{% else %}confirm the deletion of this peering{% endif %}
</div>
<table>
<tr>
<td><label for="asn">ASn</label></td>
<td>
<input
type="text"
id="asn"
readonly="readonly"
value="{{request_args['asn']}}"
/>
</td>
</tr>
<tr>
<td><label for="node">node</label></td>
<td>
<input
type="text"
id="node"
readonly="readonly"
value="{{request_args['node']}}"
/>
</td>
</tr>
<tr>
<td><label for="confirm">confirm</label></td>
<td>
<input type="checkbox" name="confirm" id="delete-confirm" required />
</td>
</tr>
</table>
<div>
<a href="../peerings">
<input type="button" class="button-green" value="back"
/></a>
<button type="submit" class="button-red">confirm</button>
</div>
</form>
{% endblock %}

View file

@ -182,7 +182,7 @@
{% endfor %} {% endfor %}
</div> </div>
<form action="" method="post" class="flex" onsubmit="return form_validate(this)"> <form action="" method="post" class="flex" onsubmit="return form_validate(this)">
<div id="peer-invalid-note" style="background-color:red;">{% if msg %}{{msg}}{%endif%}</div> <div id="peer-invalid-note" style="background-color:red;">{% if msg %}{{msg}}{%elif not selected_node%}please select one of the nodes above{%endif%}</div>
<table> <table>
<tr> <tr>
<td><label for="peer-asn">Your ASN</label></td> <td><label for="peer-asn">Your ASN</label></td>
@ -283,8 +283,8 @@ protocol bgp dn42_{{config["MNT"][:-4].lower()}}_v6 from dnpeers {
}; };
} }
</pre> </pre>
</div>
</div> </div>
<script> <script>
document.onload(); document.onload();

View file

@ -1,39 +1,58 @@
{% extends 'base.html' %} {% extends 'base.html' %} {% block content %}
{% block content %}
<div> <div>
<a href="peerings/new"><button>add new</button></a> <a href="peerings/new"
><button class="button-green default-border-color">add new</button></a
>
</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["login"]) %}
<div class="peering"> <div class="peering">
<div> <div>
<div>Node: {{peering["node"]}}</div> <div>Node: {{peering["node"]}}</div>
</div>
<div>
<table>
<tr><td>ASN:</td><td>{{peering["ASN"]}}</td></tr>
<tr><td>WG-PublicKey:</td><td>{{peering["wg_key"][:8]}}...</td></tr>
</table>
</div>
<div>
<table>
{% if peering["ipv6ll"] %}<tr><td>ipv6 linklocal:</td><td>{{peering["ipv6ll"]}}</td></tr>{% endif %}
{% if peering["ipv4"] %}<tr><td>ipv4:</td><td>{{peering["ipv4"]}}</td></tr>{% endif %}
{% if peering["ipv6"] %}<tr><td>ipv6:</td><td>{{peering["ipv6"]}}</td></tr>{% endif %}
</table>
</div>
<!-- <div>{{peering}}</div> -->
<div>
<a href="peerings/edit?node={{peering['node']}}&asn={{peering['ASN']}}">
<button class="peering-edit">edit</button>
</a>
<a href="peerings/delete?node={{peering['node']}}&asn={{peering['ASN']}}">
<button class="peering-delete">delete</button>
</a>
</div>
</div> </div>
{% endfor %} <div>
<table>
<tr>
<td>ASN:</td>
<td>{{peering["ASN"]}}</td>
</tr>
<tr>
<td>WG-PublicKey:</td>
<td>{{peering["wg_key"][:8]}}...</td>
</tr>
</table>
</div>
<div>
<table>
{% if peering["ipv6ll"] %}
<tr>
<td>ipv6 linklocal:</td>
<td>{{peering["ipv6ll"]}}</td>
</tr>
{% endif %} {% if peering["ipv4"] %}
<tr>
<td>ipv4:</td>
<td>{{peering["ipv4"]}}</td>
</tr>
{% endif %} {% if peering["ipv6"] %}
<tr>
<td>ipv6:</td>
<td>{{peering["ipv6"]}}</td>
</tr>
{% endif %}
</table>
</div>
<!-- <div>{{peering}}</div> -->
<div>
<a href="peerings/edit?node={{peering['node']}}&asn={{peering['ASN']}}">
<button class="button-blue">edit</button>
</a>
<a href="peerings/delete?node={{peering['node']}}&asn={{peering['ASN']}}">
<button class="button-red">delete</button>
</a>
</div>
</div>
{% endfor %}
</div> </div>
{% endblock %} {% endblock %}

View file

@ -17,6 +17,7 @@ header {
background-color: var(--other-background); background-color: var(--other-background);
height: 50px; height: 50px;
} }
a { a {
color: var(--text-color); color: var(--text-color);
} }
@ -27,6 +28,7 @@ a {
margin: auto; margin: auto;
padding-bottom: 50px; padding-bottom: 50px;
} }
.content>* { .content>* {
padding: 5%; padding: 5%;
} }
@ -35,6 +37,7 @@ a {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
.flex-row { .flex-row {
flex-direction: row; flex-direction: row;
} }
@ -54,6 +57,7 @@ footer {
height: 40px; height: 40px;
background: var(--other-background); background: var(--other-background);
} }
footer.flex { footer.flex {
flex-direction: row; flex-direction: row;
} }
@ -63,14 +67,14 @@ footer.flex {
padding: 1%; padding: 1%;
} }
.example-config > pre{ .example-config>pre {
border-color: var(--other-background); border-color: var(--other-background);
border-radius: 10px; border-radius: 10px;
padding: 10px; padding: 10px;
border-style: groove; border-style: groove;
} }
form > div > * { form>div>* {
margin: 10px; margin: 10px;
} }
@ -85,40 +89,59 @@ form > div > * {
width: 100%; width: 100%;
margin: 10px; margin: 10px;
} }
.peering > div {
.peering>div {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
.peering > div > * {
.peering>div>* {
padding: 10px; padding: 10px;
} }
button { button,
input[type=button] {
background-color: #00000020; background-color: #00000020;
border-color: var(--text-color); border-color: var(--text-color);
border-width: 5px; border-width: 5px;
padding: 10px; padding: 10px;
} }
button.button-selected{
button.button-selected,
input[type=button].button-selected {
background-color: var(--other-background); background-color: var(--other-background);
color: var(--text-color-dark); color: var(--text-color-dark);
} }
.peering>* { .peering>* {
width:auto; width: auto;
align-items: center; align-items: center;
} }
.peering-edit { .button-blue {
border-color: lightblue; border-color: lightblue;
} }
.peering-edit:hover { .button-blue:hover {
background-color: #87cefaaa; background-color: #87cefaaa;
} }
.peering-delete { .button-red {
border-color: darkred; border-color: darkred;
} }
.peering-delete:hover {
.button-red:hover {
background-color: #ff0000aa; background-color: #ff0000aa;
}
.button-green {
border-color: lightgreen;
}
.button-green:hover {
background-color: greenyellow;
}
.default-border-color {
border-color: inherit;
} }