From 3340f9c6eeec4a3a08ef35650160dd247d322112 Mon Sep 17 00:00:00 2001 From: Donald Zou Date: Mon, 2 Dec 2024 15:09:54 +0800 Subject: [PATCH 1/8] Update dashboard.py - Updated WireguardConfiguration class to handle awg configuration files - Added AmneziaWireguardConfiguration class as a subclass of WireguardConfiguration --- src/dashboard.py | 86 +++++++++++++++++++++++++++++++++++++----------- 1 file changed, 66 insertions(+), 20 deletions(-) diff --git a/src/dashboard.py b/src/dashboard.py index 4b9c1de..d4fa03b 100644 --- a/src/dashboard.py +++ b/src/dashboard.py @@ -445,7 +445,7 @@ class WireguardConfiguration: def __str__(self): return self.message - def __init__(self, name: str = None, data: dict = None, backup: dict = None, startup: bool = False): + def __init__(self, name: str = None, data: dict = None, backup: dict = None, startup: bool = False, wg: bool = True): self.__parser: configparser.ConfigParser = configparser.ConfigParser(strict=False) @@ -467,13 +467,14 @@ class WireguardConfiguration: self.PostDown: str = "" self.SaveConfig: bool = True self.Name = name - self.__configPath = os.path.join(DashboardConfig.GetConfig("Server", "wg_conf_path")[1], f'{self.Name}.conf') + self.Protocol = "wg" if wg else "awg" + self.__configPath = os.path.join(self.__getProtocolPath(), f'{self.Name}.conf') if wg else os.path.join(DashboardConfig.GetConfig("Server", "awg_conf_path")[1], f'{self.Name}.conf') if name is not None: if data is not None and "Backup" in data.keys(): db = self.__importDatabase( os.path.join( - DashboardConfig.GetConfig("Server", "wg_conf_path")[1], + self.__getProtocolPath(), 'WGDashboard_Backup', data["Backup"].replace(".conf", ".sql"))) else: @@ -484,7 +485,7 @@ class WireguardConfiguration: else: self.Name = data["ConfigurationName"] - self.__configPath = os.path.join(DashboardConfig.GetConfig("Server", "wg_conf_path")[1], f'{self.Name}.conf') + self.__configPath = os.path.join(self.__getProtocolPath(), f'{self.Name}.conf') for i in dir(self): if str(i) in data.keys(): @@ -515,6 +516,10 @@ class WireguardConfiguration: self.toggleConfiguration() print(f"[WGDashboard] Autostart Configuration: {name}") + def __getProtocolPath(self): + return DashboardConfig.GetConfig("Server", "wg_conf_path")[1] if self.Protocol == "wg" \ + else DashboardConfig.GetConfig("Server", "awg_conf_path")[1] + def __initPeersList(self): self.Peers: list[Peer] = [] self.getPeersList() @@ -668,7 +673,7 @@ class WireguardConfiguration: self.RestrictedPeers.append(Peer(i, self)) def configurationFileChanged(self) : - mt = os.path.getmtime(os.path.join(DashboardConfig.GetConfig("Server", "wg_conf_path")[1], f'{self.Name}.conf')) + mt = os.path.getmtime(self.__configPath) changed = self.__configFileModifiedTime is None or self.__configFileModifiedTime != mt self.__configFileModifiedTime = mt return changed @@ -676,7 +681,7 @@ class WireguardConfiguration: def __getPeers(self): if self.configurationFileChanged(): self.Peers = [] - with open(os.path.join(DashboardConfig.GetConfig("Server", "wg_conf_path")[1], f'{self.Name}.conf'), 'r') as configFile: + with open(self.__configPath, 'r') as configFile: p = [] pCounter = -1 content = configFile.read().split('\n') @@ -1034,21 +1039,21 @@ class WireguardConfiguration: } def backupConfigurationFile(self): - if not os.path.exists(os.path.join(DashboardConfig.GetConfig("Server", "wg_conf_path")[1], 'WGDashboard_Backup')): - os.mkdir(os.path.join(DashboardConfig.GetConfig("Server", "wg_conf_path")[1], 'WGDashboard_Backup')) + if not os.path.exists(os.path.join(self.__getProtocolPath(), 'WGDashboard_Backup')): + os.mkdir(os.path.join(self.__getProtocolPath(), 'WGDashboard_Backup')) time = datetime.now().strftime("%Y%m%d%H%M%S") shutil.copy( self.__configPath, - os.path.join(DashboardConfig.GetConfig("Server", "wg_conf_path")[1], 'WGDashboard_Backup', f'{self.Name}_{time}.conf') + os.path.join(self.__getProtocolPath(), 'WGDashboard_Backup', f'{self.Name}_{time}.conf') ) - with open(os.path.join(DashboardConfig.GetConfig("Server", "wg_conf_path")[1], 'WGDashboard_Backup', f'{self.Name}_{time}.sql'), 'w+') as f: + with open(os.path.join(self.__getProtocolPath(), 'WGDashboard_Backup', f'{self.Name}_{time}.sql'), 'w+') as f: for l in self.__dumpDatabase(): f.write(l + "\n") def getBackups(self, databaseContent: bool = False) -> list[dict[str: str, str: str, str: str]]: backups = [] - directory = os.path.join(DashboardConfig.GetConfig("Server", "wg_conf_path")[1], 'WGDashboard_Backup') + directory = os.path.join(self.__getProtocolPath(), 'WGDashboard_Backup') files = [(file, os.path.getctime(os.path.join(directory, file))) for file in os.listdir(directory) if os.path.isfile(os.path.join(directory, file))] files.sort(key=lambda x: x[1], reverse=True) @@ -1060,12 +1065,12 @@ class WireguardConfiguration: d = { "filename": f, "backupDate": date, - "content": open(os.path.join(DashboardConfig.GetConfig("Server", "wg_conf_path")[1], 'WGDashboard_Backup', f), 'r').read() + "content": open(os.path.join(self.__getProtocolPath(), 'WGDashboard_Backup', f), 'r').read() } if f.replace(".conf", ".sql") in list(os.listdir(directory)): d['database'] = True if databaseContent: - d['databaseContent'] = open(os.path.join(DashboardConfig.GetConfig("Server", "wg_conf_path")[1], 'WGDashboard_Backup', f.replace(".conf", ".sql")), 'r').read() + d['databaseContent'] = open(os.path.join(self.__getProtocolPath(), 'WGDashboard_Backup', f.replace(".conf", ".sql")), 'r').read() backups.append(d) return backups @@ -1077,13 +1082,13 @@ class WireguardConfiguration: self.backupConfigurationFile() if self.Status: self.toggleConfiguration() - target = os.path.join(DashboardConfig.GetConfig("Server", "wg_conf_path")[1], 'WGDashboard_Backup', backupFileName) - targetSQL = os.path.join(DashboardConfig.GetConfig("Server", "wg_conf_path")[1], 'WGDashboard_Backup', backupFileName.replace(".conf", ".sql")) + target = os.path.join(self.__getProtocolPath(), 'WGDashboard_Backup', backupFileName) + targetSQL = os.path.join(self.__getProtocolPath(), 'WGDashboard_Backup', backupFileName.replace(".conf", ".sql")) if not os.path.exists(target): return False targetContent = open(target, 'r').read() try: - with open(os.path.join(DashboardConfig.GetConfig("Server", "wg_conf_path")[1], f'{self.Name}.conf'), 'w') as f: + with open(self.__configPath, 'w') as f: f.write(targetContent) except Exception as e: return False @@ -1098,7 +1103,7 @@ class WireguardConfiguration: if backupFileName not in backups: return False try: - os.remove(os.path.join(DashboardConfig.GetConfig("Server", "wg_conf_path")[1], 'WGDashboard_Backup', backupFileName)) + os.remove(os.path.join(self.__getProtocolPath(), 'WGDashboard_Backup', backupFileName)) except Exception as e: return False return True @@ -1108,7 +1113,7 @@ class WireguardConfiguration: self.toggleConfiguration() original = [] dataChanged = False - with open(os.path.join(DashboardConfig.GetConfig("Server", "wg_conf_path")[1], f'{self.Name}.conf'), 'r') as f: + with open(self.__configPath, 'r') as f: original = [l.rstrip("\n") for l in f.readlines()] allowEdit = ["Address", "PreUp", "PostUp", "PreDown", "PostDown", "ListenPort"] start = original.index("[Interface]") @@ -1129,7 +1134,7 @@ class WireguardConfiguration: for line in range(end, len(original)): new.append(original[line]) self.backupConfigurationFile() - with open(os.path.join(DashboardConfig.GetConfig("Server", "wg_conf_path")[1], f'{self.Name}.conf'), 'w') as f: + with open(self.__configPath, 'w') as f: f.write("\n".join(new)) status, msg = self.toggleConfiguration() @@ -1158,7 +1163,7 @@ class WireguardConfiguration: AllPeerJobs.updateJobConfigurationName(self.Name, newConfigurationName) shutil.copy( self.__configPath, - os.path.join(DashboardConfig.GetConfig("Server", "wg_conf_path")[1], f'{newConfigurationName}.conf') + os.path.join(self.__getProtocolPath(), f'{newConfigurationName}.conf') ) self.deleteConfiguration() except Exception as e: @@ -1201,6 +1206,24 @@ class WireguardConfiguration: break return True, availableAddress +""" +AmneziaWG Configuration +""" + +class AmneziaWireguardConfiguration(WireguardConfiguration): + def __init__(self, name: str = None, data: dict = None, backup: dict = None, startup: bool = False): + self.Jc = 0 + self.Jmin = 0 + self.Jmax = 0 + self.S1 = 0 + self.S2 = 0 + self.H1 = 1 + self.H2 = 2 + self.H3 = 3 + self.H4 = 4 + + super().__init__(name, data, backup, startup, wg=False) + """ Peer """ @@ -1384,6 +1407,7 @@ class DashboardConfig: }, "Server": { "wg_conf_path": "/etc/wireguard", + "awg_conf_path": "/etc/amnezia/amneziawg", "app_prefix": "", "app_ip": "0.0.0.0", "app_port": "10086", @@ -1708,6 +1732,7 @@ def API_SignOut(): @app.route(f'{APP_PREFIX}/api/getWireguardConfigurations', methods=["GET"]) def API_getWireguardConfigurations(): InitWireguardConfigurationsList() + InitAmneziaWireguardConfigurationsList() return ResponseObject(data=[wc for wc in WireguardConfigurations.values()]) @app.route(f'{APP_PREFIX}/api/addWireguardConfiguration', methods=["POST"]) @@ -1907,8 +1932,10 @@ def API_updateDashboardConfigurationItem(): if data['section'] == "Server": if data['key'] == 'wg_conf_path': + WireguardConfigurations.clear() WireguardConfigurations.clear() InitWireguardConfigurationsList() + InitAmneziaWireguardConfigurationsList() return ResponseObject(True, data=DashboardConfig.GetConfig(data["section"], data["key"])[1]) @@ -2614,6 +2641,23 @@ def InitWireguardConfigurationsList(startup: bool = False): except WireguardConfiguration.InvalidConfigurationFileException as e: print(f"{i} have an invalid configuration file.") + confs = os.listdir(DashboardConfig.GetConfig("Server", "awg_conf_path")[1]) + confs.sort() + for i in confs: + if RegexMatch("^(.{1,}).(conf)$", i): + i = i.replace('.conf', '') + try: + if i in WireguardConfigurations.keys(): + if WireguardConfigurations[i].configurationFileChanged(): + WireguardConfigurations[i] = AmneziaWireguardConfiguration(i) + else: + WireguardConfigurations[i] = AmneziaWireguardConfiguration(i, startup=startup) + except WireguardConfigurations.InvalidConfigurationFileException as e: + print(f"{i} have an invalid configuration file.") + +def InitAmneziaWireguardConfigurationsList(startup: bool = False): + pass + AllPeerShareLinks: PeerShareLinks = PeerShareLinks() AllPeerJobs: PeerJobs = PeerJobs() JobLogger: PeerJobLogger = PeerJobLogger() @@ -2623,7 +2667,9 @@ _, app_port = DashboardConfig.GetConfig("Server", "app_port") _, WG_CONF_PATH = DashboardConfig.GetConfig("Server", "wg_conf_path") WireguardConfigurations: dict[str, WireguardConfiguration] = {} +AmneziaWireguardConfigurations: dict[str, AmneziaWireguardConfiguration] = {} InitWireguardConfigurationsList(startup=True) +InitAmneziaWireguardConfigurationsList(startup=True) def startThreads(): bgThread = threading.Thread(target=backGroundThread) From 3b019436497236cf01cda6c611d62064386d8e82 Mon Sep 17 00:00:00 2001 From: Donald Zou Date: Mon, 2 Dec 2024 16:34:26 +0800 Subject: [PATCH 2/8] Update dashboard.py --- src/dashboard.py | 95 ++++++++++++++++++++++++++++++++++-------------- 1 file changed, 68 insertions(+), 27 deletions(-) diff --git a/src/dashboard.py b/src/dashboard.py index d4fa03b..3857615 100644 --- a/src/dashboard.py +++ b/src/dashboard.py @@ -796,12 +796,12 @@ class WireguardConfiguration: with open(uid, "w+") as f: f.write(p['preshared_key']) - subprocess.check_output(f"wg set {self.Name} peer {p['id']} allowed-ips {p['allowed_ip'].replace(' ', '')}{f' preshared-key {uid}' if presharedKeyExist else ''}", + subprocess.check_output(f"{self.Protocol} set {self.Name} peer {p['id']} allowed-ips {p['allowed_ip'].replace(' ', '')}{f' preshared-key {uid}' if presharedKeyExist else ''}", shell=True, stderr=subprocess.STDOUT) if presharedKeyExist: os.remove(uid) subprocess.check_output( - f"wg-quick save {self.Name}", shell=True, stderr=subprocess.STDOUT) + f"{self.Protocol}-quick save {self.Name}", shell=True, stderr=subprocess.STDOUT) self.getPeersList() return True except Exception as e: @@ -833,7 +833,7 @@ class WireguardConfiguration: with open(uid, "w+") as f: f.write(p['preshared_key']) - subprocess.check_output(f"wg set {self.Name} peer {p['id']} allowed-ips {p['allowed_ip'].replace(' ', '')}{f' preshared-key {uid}' if presharedKeyExist else ''}", + subprocess.check_output(f"{self.Protocol} set {self.Name} peer {p['id']} allowed-ips {p['allowed_ip'].replace(' ', '')}{f' preshared-key {uid}' if presharedKeyExist else ''}", shell=True, stderr=subprocess.STDOUT) if presharedKeyExist: os.remove(uid) else: @@ -853,7 +853,7 @@ class WireguardConfiguration: found, pf = self.searchPeer(p) if found: try: - subprocess.check_output(f"wg set {self.Name} peer {pf.id} remove", + subprocess.check_output(f"{self.Protocol} set {self.Name} peer {pf.id} remove", shell=True, stderr=subprocess.STDOUT) sqlUpdate("INSERT INTO '%s_restrict_access' SELECT * FROM %s WHERE id = ?" % (self.Name, self.Name,), (pf.id,)) @@ -884,7 +884,7 @@ class WireguardConfiguration: found, pf = self.searchPeer(p) if found: try: - subprocess.check_output(f"wg set {self.Name} peer {pf.id} remove", + subprocess.check_output(f"{self.Protocol} set {self.Name} peer {pf.id} remove", shell=True, stderr=subprocess.STDOUT) sqlUpdate("DELETE FROM '%s' WHERE id = ?" % self.Name, (pf.id,)) numOfDeletedPeers += 1 @@ -903,7 +903,7 @@ class WireguardConfiguration: def __wgSave(self) -> tuple[bool, str] | tuple[bool, None]: try: - subprocess.check_output(f"wg-quick save {self.Name}", shell=True, stderr=subprocess.STDOUT) + subprocess.check_output(f"{self.Protocol}-quick save {self.Name}", shell=True, stderr=subprocess.STDOUT) return True, None except subprocess.CalledProcessError as e: return False, str(e) @@ -912,7 +912,7 @@ class WireguardConfiguration: if not self.getStatus(): self.toggleConfiguration() try: - latestHandshake = subprocess.check_output(f"wg show {self.Name} latest-handshakes", + latestHandshake = subprocess.check_output(f"{self.Protocol} show {self.Name} latest-handshakes", shell=True, stderr=subprocess.STDOUT) except subprocess.CalledProcessError: return "stopped" @@ -938,7 +938,7 @@ class WireguardConfiguration: if not self.getStatus(): self.toggleConfiguration() try: - data_usage = subprocess.check_output(f"wg show {self.Name} transfer", + data_usage = subprocess.check_output(f"{self.Protocol} show {self.Name} transfer", shell=True, stderr=subprocess.STDOUT) data_usage = data_usage.decode("UTF-8").split("\n") data_usage = [p.split("\t") for p in data_usage] @@ -979,7 +979,7 @@ class WireguardConfiguration: if not self.getStatus(): self.toggleConfiguration() try: - data_usage = subprocess.check_output(f"wg show {self.Name} endpoints", + data_usage = subprocess.check_output(f"{self.Protocol} show {self.Name} endpoints", shell=True, stderr=subprocess.STDOUT) except subprocess.CalledProcessError: return "stopped" @@ -994,13 +994,13 @@ class WireguardConfiguration: self.getStatus() if self.Status: try: - check = subprocess.check_output(f"wg-quick down {self.Name}", + check = subprocess.check_output(f"{self.Protocol}-quick down {self.Name}", shell=True, stderr=subprocess.STDOUT) except subprocess.CalledProcessError as exc: return False, str(exc.output.strip().decode("utf-8")) else: try: - check = subprocess.check_output(f"wg-quick up {self.Name}", shell=True, stderr=subprocess.STDOUT) + check = subprocess.check_output(f"{self.Protocol}-quick up {self.Name}", shell=True, stderr=subprocess.STDOUT) except subprocess.CalledProcessError as exc: return False, str(exc.output.strip().decode("utf-8")) self.__parseConfigurationFile() @@ -1035,7 +1035,8 @@ class WireguardConfiguration: "Receive": sum(list(map(lambda x: x.cumu_receive + x.total_receive, self.Peers))) }, "ConnectedPeers": len(list(filter(lambda x: x.status == "running", self.Peers))), - "TotalPeers": len(self.Peers) + "TotalPeers": len(self.Peers), + "Protocol": self.Protocol } def backupConfigurationFile(self): @@ -1224,6 +1225,38 @@ class AmneziaWireguardConfiguration(WireguardConfiguration): super().__init__(name, data, backup, startup, wg=False) + def toJson(self): + self.Status = self.getStatus() + return { + "Status": self.Status, + "Name": self.Name, + "PrivateKey": self.PrivateKey, + "PublicKey": self.PublicKey, + "Address": self.Address, + "ListenPort": self.ListenPort, + "PreUp": self.PreUp, + "PreDown": self.PreDown, + "PostUp": self.PostUp, + "PostDown": self.PostDown, + "SaveConfig": self.SaveConfig, + "DataUsage": { + "Total": sum(list(map(lambda x: x.cumu_data + x.total_data, self.Peers))), + "Sent": sum(list(map(lambda x: x.cumu_sent + x.total_sent, self.Peers))), + "Receive": sum(list(map(lambda x: x.cumu_receive + x.total_receive, self.Peers))) + }, + "ConnectedPeers": len(list(filter(lambda x: x.status == "running", self.Peers))), + "TotalPeers": len(self.Peers), + "Protocol": self.Protocol, + "Jc": self.Jc, + "Jmin": self.Jmin, + "Jmax": self.Jmax, + "S1": self.S1, + "S2": self.S2, + "H1": self.H1, + "H2": self.H2, + "H3": self.H3, + "H4": self.H4 + } """ Peer """ @@ -1298,7 +1331,7 @@ class Peer: f.write(preshared_key) newAllowedIPs = allowed_ip.replace(" ", "") updateAllowedIp = subprocess.check_output( - f"wg set {self.configuration.Name} peer {self.id} allowed-ips {newAllowedIPs}{f' preshared-key {uid}' if pskExist else ''}", + f"{self.configuration.Protocol} set {self.configuration.Name} peer {self.id} allowed-ips {newAllowedIPs}{f' preshared-key {uid}' if pskExist else ''}", shell=True, stderr=subprocess.STDOUT) if pskExist: os.remove(uid) @@ -1306,7 +1339,7 @@ class Peer: if len(updateAllowedIp.decode().strip("\n")) != 0: return ResponseObject(False, "Update peer failed when updating Allowed IPs") - saveConfig = subprocess.check_output(f"wg-quick save {self.configuration.Name}", + saveConfig = subprocess.check_output(f"{self.configuration.Protocol}-quick save {self.configuration.Name}", shell=True, stderr=subprocess.STDOUT) if f"wg showconf {self.configuration.Name}" not in saveConfig.decode().strip('\n'): return ResponseObject(False, @@ -2626,6 +2659,11 @@ def gunicornConfig(): _, app_port = DashboardConfig.GetConfig("Server", "app_port") return app_ip, app_port +def AmneziaWGEnabled(): + from shutil import which + + return which('awg') is not None and which('awg-quick') is not None + def InitWireguardConfigurationsList(startup: bool = False): confs = os.listdir(DashboardConfig.GetConfig("Server", "wg_conf_path")[1]) confs.sort() @@ -2641,19 +2679,22 @@ def InitWireguardConfigurationsList(startup: bool = False): except WireguardConfiguration.InvalidConfigurationFileException as e: print(f"{i} have an invalid configuration file.") - confs = os.listdir(DashboardConfig.GetConfig("Server", "awg_conf_path")[1]) - confs.sort() - for i in confs: - if RegexMatch("^(.{1,}).(conf)$", i): - i = i.replace('.conf', '') - try: - if i in WireguardConfigurations.keys(): - if WireguardConfigurations[i].configurationFileChanged(): - WireguardConfigurations[i] = AmneziaWireguardConfiguration(i) - else: - WireguardConfigurations[i] = AmneziaWireguardConfiguration(i, startup=startup) - except WireguardConfigurations.InvalidConfigurationFileException as e: - print(f"{i} have an invalid configuration file.") + if AmneziaWGEnabled(): + confs = os.listdir(DashboardConfig.GetConfig("Server", "awg_conf_path")[1]) + confs.sort() + for i in confs: + if RegexMatch("^(.{1,}).(conf)$", i): + i = i.replace('.conf', '') + try: + if i in WireguardConfigurations.keys(): + if WireguardConfigurations[i].configurationFileChanged(): + WireguardConfigurations[i] = AmneziaWireguardConfiguration(i) + else: + WireguardConfigurations[i] = AmneziaWireguardConfiguration(i, startup=startup) + except WireguardConfigurations.InvalidConfigurationFileException as e: + print(f"{i} have an invalid configuration file.") + else: + print("AmneziaWG is not installed") def InitAmneziaWireguardConfigurationsList(startup: bool = False): pass From da53bd44d122993da9247a83db3c3de0dc3a4abe Mon Sep 17 00:00:00 2001 From: Donald Zou Date: Mon, 2 Dec 2024 16:38:09 +0800 Subject: [PATCH 3/8] Update dashboard.py --- src/dashboard.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/dashboard.py b/src/dashboard.py index 3857615..5da46ff 100644 --- a/src/dashboard.py +++ b/src/dashboard.py @@ -2665,6 +2665,8 @@ def AmneziaWGEnabled(): return which('awg') is not None and which('awg-quick') is not None def InitWireguardConfigurationsList(startup: bool = False): + print(AmneziaWGEnabled()) + confs = os.listdir(DashboardConfig.GetConfig("Server", "wg_conf_path")[1]) confs.sort() for i in confs: From 807bb97b6a3a024c255060cedb7e28b5205b9c4a Mon Sep 17 00:00:00 2001 From: Donald Zou Date: Mon, 2 Dec 2024 17:36:37 +0800 Subject: [PATCH 4/8] Update some UI to handle Wireguard and AWG --- src/dashboard.py | 195 +++++++++++++++--- .../configurationCard.vue | 12 +- src/static/app/src/css/dashboard.css | 11 + 3 files changed, 189 insertions(+), 29 deletions(-) diff --git a/src/dashboard.py b/src/dashboard.py index 5da46ff..c068c68 100644 --- a/src/dashboard.py +++ b/src/dashboard.py @@ -468,7 +468,7 @@ class WireguardConfiguration: self.SaveConfig: bool = True self.Name = name self.Protocol = "wg" if wg else "awg" - self.__configPath = os.path.join(self.__getProtocolPath(), f'{self.Name}.conf') if wg else os.path.join(DashboardConfig.GetConfig("Server", "awg_conf_path")[1], f'{self.Name}.conf') + self.configPath = os.path.join(self.__getProtocolPath(), f'{self.Name}.conf') if wg else os.path.join(DashboardConfig.GetConfig("Server", "awg_conf_path")[1], f'{self.Name}.conf') if name is not None: if data is not None and "Backup" in data.keys(): @@ -478,14 +478,14 @@ class WireguardConfiguration: 'WGDashboard_Backup', data["Backup"].replace(".conf", ".sql"))) else: - self.__createDatabase() + self.createDatabase() self.__parseConfigurationFile() self.__initPeersList() else: self.Name = data["ConfigurationName"] - self.__configPath = os.path.join(self.__getProtocolPath(), f'{self.Name}.conf') + self.configPath = os.path.join(self.__getProtocolPath(), f'{self.Name}.conf') for i in dir(self): if str(i) in data.keys(): @@ -506,8 +506,8 @@ class WireguardConfiguration: } if "Backup" not in data.keys(): - self.__createDatabase() - with open(self.__configPath, "w+") as configFile: + self.createDatabase() + with open(self.configPath, "w+") as configFile: self.__parser.write(configFile) self.__initPeersList() @@ -526,7 +526,7 @@ class WireguardConfiguration: self.getRestrictedPeersList() def __parseConfigurationFile(self): - with open(self.__configPath, 'r') as f: + with open(self.configPath, 'r') as f: original = [l.rstrip("\n") for l in f.readlines()] try: start = original.index("[Interface]") @@ -562,7 +562,7 @@ class WireguardConfiguration: setattr(self, key, value) except ValueError as e: raise self.InvalidConfigurationFileException( - "[Interface] section not found in " + self.__configPath) + "[Interface] section not found in " + self.configPath) if self.PrivateKey: self.PublicKey = self.__getPublicKey() self.Status = self.getStatus() @@ -574,7 +574,7 @@ class WireguardConfiguration: existingTables = sqlSelect(f"SELECT name FROM sqlite_master WHERE type='table' AND name LIKE '{self.Name}%'").fetchall() - def __createDatabase(self, dbName = None): + def createDatabase(self, dbName = None): if dbName is None: dbName = self.Name @@ -645,7 +645,7 @@ class WireguardConfiguration: def __importDatabase(self, sqlFilePath) -> bool: self.__dropDatabase() - self.__createDatabase() + self.createDatabase() if not os.path.exists(sqlFilePath): return False with open(sqlFilePath, 'r') as f: @@ -673,15 +673,15 @@ class WireguardConfiguration: self.RestrictedPeers.append(Peer(i, self)) def configurationFileChanged(self) : - mt = os.path.getmtime(self.__configPath) + mt = os.path.getmtime(self.configPath) changed = self.__configFileModifiedTime is None or self.__configFileModifiedTime != mt self.__configFileModifiedTime = mt return changed - def __getPeers(self): + def getPeers(self): if self.configurationFileChanged(): self.Peers = [] - with open(self.__configPath, 'r') as configFile: + with open(self.configPath, 'r') as configFile: p = [] pCounter = -1 content = configFile.read().split('\n') @@ -841,7 +841,7 @@ class WireguardConfiguration: if not self.__wgSave(): return ResponseObject(False, "Failed to save configuration through WireGuard") - self.__getPeers() + self.getPeers() return ResponseObject(True, "Allow access successfully") def restrictPeers(self, listOfPublicKeys): @@ -867,7 +867,7 @@ class WireguardConfiguration: if not self.__wgSave(): return ResponseObject(False, "Failed to save configuration through WireGuard") - self.__getPeers() + self.getPeers() if numOfRestrictedPeers == len(listOfPublicKeys): return ResponseObject(True, f"Restricted {numOfRestrictedPeers} peer(s)") @@ -894,7 +894,7 @@ class WireguardConfiguration: if not self.__wgSave(): return ResponseObject(False, "Failed to save configuration through WireGuard") - self.__getPeers() + self.getPeers() if numOfDeletedPeers == len(listOfPublicKeys): return ResponseObject(True, f"Deleted {numOfDeletedPeers} peer(s)") @@ -1008,7 +1008,7 @@ class WireguardConfiguration: return True, None def getPeersList(self): - self.__getPeers() + self.getPeers() return self.Peers def getRestrictedPeersList(self) -> list: @@ -1044,7 +1044,7 @@ class WireguardConfiguration: os.mkdir(os.path.join(self.__getProtocolPath(), 'WGDashboard_Backup')) time = datetime.now().strftime("%Y%m%d%H%M%S") shutil.copy( - self.__configPath, + self.configPath, os.path.join(self.__getProtocolPath(), 'WGDashboard_Backup', f'{self.Name}_{time}.conf') ) with open(os.path.join(self.__getProtocolPath(), 'WGDashboard_Backup', f'{self.Name}_{time}.sql'), 'w+') as f: @@ -1089,7 +1089,7 @@ class WireguardConfiguration: return False targetContent = open(target, 'r').read() try: - with open(self.__configPath, 'w') as f: + with open(self.configPath, 'w') as f: f.write(targetContent) except Exception as e: return False @@ -1114,7 +1114,7 @@ class WireguardConfiguration: self.toggleConfiguration() original = [] dataChanged = False - with open(self.__configPath, 'r') as f: + with open(self.configPath, 'r') as f: original = [l.rstrip("\n") for l in f.readlines()] allowEdit = ["Address", "PreUp", "PostUp", "PreDown", "PostDown", "ListenPort"] start = original.index("[Interface]") @@ -1135,7 +1135,7 @@ class WireguardConfiguration: for line in range(end, len(original)): new.append(original[line]) self.backupConfigurationFile() - with open(self.__configPath, 'w') as f: + with open(self.configPath, 'w') as f: f.write("\n".join(new)) status, msg = self.toggleConfiguration() @@ -1146,7 +1146,7 @@ class WireguardConfiguration: def deleteConfiguration(self): if self.getStatus(): self.toggleConfiguration() - os.remove(self.__configPath) + os.remove(self.configPath) self.__dropDatabase() return True @@ -1156,14 +1156,14 @@ class WireguardConfiguration: try: if self.getStatus(): self.toggleConfiguration() - self.__createDatabase(newConfigurationName) + self.createDatabase(newConfigurationName) sqlUpdate(f'INSERT INTO "{newConfigurationName}" SELECT * FROM "{self.Name}"') sqlUpdate(f'INSERT INTO "{newConfigurationName}_restrict_access" SELECT * FROM "{self.Name}_restrict_access"') sqlUpdate(f'INSERT INTO "{newConfigurationName}_deleted" SELECT * FROM "{self.Name}_deleted"') sqlUpdate(f'INSERT INTO "{newConfigurationName}_transfer" SELECT * FROM "{self.Name}_transfer"') AllPeerJobs.updateJobConfigurationName(self.Name, newConfigurationName) shutil.copy( - self.__configPath, + self.configPath, os.path.join(self.__getProtocolPath(), f'{newConfigurationName}.conf') ) self.deleteConfiguration() @@ -1257,6 +1257,145 @@ class AmneziaWireguardConfiguration(WireguardConfiguration): "H3": self.H3, "H4": self.H4 } + + def createDatabase(self, dbName = None): + if dbName is None: + dbName = self.Name + + existingTables = sqlSelect("SELECT name FROM sqlite_master WHERE type='table'").fetchall() + existingTables = [t['name'] for t in existingTables] + if dbName not in existingTables: + sqlUpdate( + """ + CREATE TABLE '%s'( + id VARCHAR NOT NULL, private_key VARCHAR NULL, DNS VARCHAR NULL, advanced_security VARCHAR NULL, + endpoint_allowed_ip VARCHAR NULL, name VARCHAR NULL, total_receive FLOAT NULL, + total_sent FLOAT NULL, total_data FLOAT NULL, endpoint VARCHAR NULL, + status VARCHAR NULL, latest_handshake VARCHAR NULL, allowed_ip VARCHAR NULL, + cumu_receive FLOAT NULL, cumu_sent FLOAT NULL, cumu_data FLOAT NULL, mtu INT NULL, + keepalive INT NULL, remote_endpoint VARCHAR NULL, preshared_key VARCHAR NULL, + PRIMARY KEY (id) + ) + """ % dbName + ) + + if f'{dbName}_restrict_access' not in existingTables: + sqlUpdate( + """ + CREATE TABLE '%s_restrict_access' ( + id VARCHAR NOT NULL, private_key VARCHAR NULL, DNS VARCHAR NULL, advanced_security VARCHAR NULL, + endpoint_allowed_ip VARCHAR NULL, name VARCHAR NULL, total_receive FLOAT NULL, + total_sent FLOAT NULL, total_data FLOAT NULL, endpoint VARCHAR NULL, + status VARCHAR NULL, latest_handshake VARCHAR NULL, allowed_ip VARCHAR NULL, + cumu_receive FLOAT NULL, cumu_sent FLOAT NULL, cumu_data FLOAT NULL, mtu INT NULL, + keepalive INT NULL, remote_endpoint VARCHAR NULL, preshared_key VARCHAR NULL, + PRIMARY KEY (id) + ) + """ % dbName + ) + if f'{dbName}_transfer' not in existingTables: + sqlUpdate( + """ + CREATE TABLE '%s_transfer' ( + id VARCHAR NOT NULL, total_receive FLOAT NULL, + total_sent FLOAT NULL, total_data FLOAT NULL, + cumu_receive FLOAT NULL, cumu_sent FLOAT NULL, cumu_data FLOAT NULL, time DATETIME + ) + """ % dbName + ) + if f'{dbName}_deleted' not in existingTables: + sqlUpdate( + """ + CREATE TABLE '%s_deleted' ( + id VARCHAR NOT NULL, private_key VARCHAR NULL, DNS VARCHAR NULL, advanced_security VARCHAR NULL, + endpoint_allowed_ip VARCHAR NULL, name VARCHAR NULL, total_receive FLOAT NULL, + total_sent FLOAT NULL, total_data FLOAT NULL, endpoint VARCHAR NULL, + status VARCHAR NULL, latest_handshake VARCHAR NULL, allowed_ip VARCHAR NULL, + cumu_receive FLOAT NULL, cumu_sent FLOAT NULL, cumu_data FLOAT NULL, mtu INT NULL, + keepalive INT NULL, remote_endpoint VARCHAR NULL, preshared_key VARCHAR NULL, + PRIMARY KEY (id) + ) + """ % dbName + ) + + def getPeers(self): + if self.configurationFileChanged(): + self.Peers = [] + with open(self.configPath, 'r') as configFile: + p = [] + pCounter = -1 + content = configFile.read().split('\n') + try: + peerStarts = content.index("[Peer]") + content = content[peerStarts:] + for i in content: + if not RegexMatch("#(.*)", i) and not RegexMatch(";(.*)", i): + if i == "[Peer]": + pCounter += 1 + p.append({}) + p[pCounter]["name"] = "" + else: + if len(i) > 0: + split = re.split(r'\s*=\s*', i, 1) + if len(split) == 2: + p[pCounter][split[0]] = split[1] + + if RegexMatch("#Name# = (.*)", i): + split = re.split(r'\s*=\s*', i, 1) + if len(split) == 2: + p[pCounter]["name"] = split[1] + + for i in p: + if "PublicKey" in i.keys(): + checkIfExist = sqlSelect("SELECT * FROM '%s' WHERE id = ?" % self.Name, + ((i['PublicKey']),)).fetchone() + if checkIfExist is None: + newPeer = { + "id": i['PublicKey'], + "advanced_security": i.get('AdvancedSecurity', 'off'), + "private_key": "", + "DNS": DashboardConfig.GetConfig("Peers", "peer_global_DNS")[1], + "endpoint_allowed_ip": DashboardConfig.GetConfig("Peers", "peer_endpoint_allowed_ip")[ + 1], + "name": i.get("name"), + "total_receive": 0, + "total_sent": 0, + "total_data": 0, + "endpoint": "N/A", + "status": "stopped", + "latest_handshake": "N/A", + "allowed_ip": i.get("AllowedIPs", "N/A"), + "cumu_receive": 0, + "cumu_sent": 0, + "cumu_data": 0, + "traffic": [], + "mtu": DashboardConfig.GetConfig("Peers", "peer_mtu")[1], + "keepalive": DashboardConfig.GetConfig("Peers", "peer_keep_alive")[1], + "remote_endpoint": DashboardConfig.GetConfig("Peers", "remote_endpoint")[1], + "preshared_key": i["PresharedKey"] if "PresharedKey" in i.keys() else "" + } + sqlUpdate( + """ + INSERT INTO '%s' + VALUES (:id, :private_key, :DNS, :advanced_security, :endpoint_allowed_ip, :name, :total_receive, :total_sent, + :total_data, :endpoint, :status, :latest_handshake, :allowed_ip, :cumu_receive, :cumu_sent, + :cumu_data, :mtu, :keepalive, :remote_endpoint, :preshared_key); + """ % self.Name + , newPeer) + self.Peers.append(Peer(newPeer, self)) + else: + sqlUpdate("UPDATE '%s' SET allowed_ip = ? WHERE id = ?" % self.Name, + (i.get("AllowedIPs", "N/A"), i['PublicKey'],)) + self.Peers.append(Peer(checkIfExist, self)) + except Exception as e: + if __name__ == '__main__': + print(f"[WGDashboard] {self.Name} Error: {str(e)}") + else: + self.Peers.clear() + checkIfExist = sqlSelect("SELECT * FROM '%s'" % self.Name).fetchall() + for i in checkIfExist: + self.Peers.append(Peer(i, self)) + """ Peer """ @@ -1282,6 +1421,10 @@ class Peer: self.keepalive = tableData["keepalive"] self.remote_endpoint = tableData["remote_endpoint"] self.preshared_key = tableData["preshared_key"] + + if configuration.Protocol == "awg": + self.advanced_security = tableData["advanced_security"] + self.jobs: list[PeerJob] = [] self.ShareLink: list[PeerShareLink] = [] self.getJobs() @@ -2664,9 +2807,7 @@ def AmneziaWGEnabled(): return which('awg') is not None and which('awg-quick') is not None -def InitWireguardConfigurationsList(startup: bool = False): - print(AmneziaWGEnabled()) - +def InitWireguardConfigurationsList(startup: bool = False): confs = os.listdir(DashboardConfig.GetConfig("Server", "wg_conf_path")[1]) confs.sort() for i in confs: @@ -2695,8 +2836,6 @@ def InitWireguardConfigurationsList(startup: bool = False): WireguardConfigurations[i] = AmneziaWireguardConfiguration(i, startup=startup) except WireguardConfigurations.InvalidConfigurationFileException as e: print(f"{i} have an invalid configuration file.") - else: - print("AmneziaWG is not installed") def InitAmneziaWireguardConfigurationsList(startup: bool = False): pass diff --git a/src/static/app/src/components/configurationListComponents/configurationCard.vue b/src/static/app/src/components/configurationListComponents/configurationCard.vue index 65b23fa..63e5a68 100644 --- a/src/static/app/src/components/configurationListComponents/configurationCard.vue +++ b/src/static/app/src/components/configurationListComponents/configurationCard.vue @@ -49,7 +49,17 @@ export default {
-
{{c.Name}}
+
+ {{c.Name}} + + + WireGuard + + + AmneziaWG + + +
diff --git a/src/static/app/src/css/dashboard.css b/src/static/app/src/css/dashboard.css index f50b5ba..d57571f 100644 --- a/src/static/app/src/css/dashboard.css +++ b/src/static/app/src/css/dashboard.css @@ -1257,4 +1257,15 @@ pre.index-alert { samp{ word-wrap: anywhere; +} + +.amneziawgBg{ + background: rgb(145,199,193); + background: linear-gradient(90deg, rgba(145,199,193,1) 0%, rgba(107,95,161,1) 50%, rgba(227,142,65,1) 100%); +} + +.wireguardBg{ + + background: rgb(125,32,32); + background: linear-gradient(90deg, rgba(125,32,32,1) 0%, rgba(255,56,56,1) 100%); } \ No newline at end of file From 434c236210a64d244c7f75c7b3104f4d15d9455f Mon Sep 17 00:00:00 2001 From: Donald Zou Date: Tue, 3 Dec 2024 02:34:45 +0800 Subject: [PATCH 5/8] Finished adjusting Peers UI for AWG --- src/dashboard.py | 198 +++++++++++++++++- .../configurationComponents/peer.vue | 8 + .../configurationComponents/peerCreate.vue | 36 +++- .../configurationComponents/peerList.vue | 14 +- .../configurationComponents/peerSettings.vue | 26 ++- src/static/app/src/views/newConfiguration.vue | 35 +++- 6 files changed, 300 insertions(+), 17 deletions(-) diff --git a/src/dashboard.py b/src/dashboard.py index c068c68..e535e7c 100644 --- a/src/dashboard.py +++ b/src/dashboard.py @@ -666,7 +666,7 @@ class WireguardConfiguration: s, d = DashboardConfig.GetConfig("WireGuardConfiguration", "autostart") return self.Name in d - def __getRestrictedPeers(self): + def getRestrictedPeers(self): self.RestrictedPeers = [] restricted = sqlSelect("SELECT * FROM '%s_restrict_access'" % self.Name).fetchall() for i in restricted: @@ -1012,7 +1012,7 @@ class WireguardConfiguration: return self.Peers def getRestrictedPeersList(self) -> list: - self.__getRestrictedPeers() + self.getRestrictedPeers() return self.RestrictedPeers def toJson(self): @@ -1382,11 +1382,11 @@ class AmneziaWireguardConfiguration(WireguardConfiguration): :cumu_data, :mtu, :keepalive, :remote_endpoint, :preshared_key); """ % self.Name , newPeer) - self.Peers.append(Peer(newPeer, self)) + self.Peers.append(AmneziaWGPeer(newPeer, self)) else: sqlUpdate("UPDATE '%s' SET allowed_ip = ? WHERE id = ?" % self.Name, (i.get("AllowedIPs", "N/A"), i['PublicKey'],)) - self.Peers.append(Peer(checkIfExist, self)) + self.Peers.append(AmneziaWGPeer(checkIfExist, self)) except Exception as e: if __name__ == '__main__': print(f"[WGDashboard] {self.Name} Error: {str(e)}") @@ -1394,7 +1394,68 @@ class AmneziaWireguardConfiguration(WireguardConfiguration): self.Peers.clear() checkIfExist = sqlSelect("SELECT * FROM '%s'" % self.Name).fetchall() for i in checkIfExist: - self.Peers.append(Peer(i, self)) + self.Peers.append(AmneziaWGPeer(i, self)) + + def addPeers(self, peers: list): + try: + for i in peers: + newPeer = { + "id": i['id'], + "private_key": i['private_key'], + "DNS": i['DNS'], + "endpoint_allowed_ip": i['endpoint_allowed_ip'], + "name": i['name'], + "total_receive": 0, + "total_sent": 0, + "total_data": 0, + "endpoint": "N/A", + "status": "stopped", + "latest_handshake": "N/A", + "allowed_ip": i.get("allowed_ip", "N/A"), + "cumu_receive": 0, + "cumu_sent": 0, + "cumu_data": 0, + "traffic": [], + "mtu": i['mtu'], + "keepalive": i['keepalive'], + "remote_endpoint": DashboardConfig.GetConfig("Peers", "remote_endpoint")[1], + "preshared_key": i["preshared_key"], + "advanced_security": i['advanced_security'] + } + sqlUpdate( + """ + INSERT INTO '%s' + VALUES (:id, :private_key, :DNS, :advanced_security, :endpoint_allowed_ip, :name, :total_receive, :total_sent, + :total_data, :endpoint, :status, :latest_handshake, :allowed_ip, :cumu_receive, :cumu_sent, + :cumu_data, :mtu, :keepalive, :remote_endpoint, :preshared_key); + """ % self.Name + , newPeer) + for p in peers: + presharedKeyExist = len(p['preshared_key']) > 0 + rd = random.Random() + uid = str(uuid.UUID(int=rd.getrandbits(128), version=4)) + if presharedKeyExist: + with open(uid, "w+") as f: + f.write(p['preshared_key']) + + subprocess.check_output( + f"{self.Protocol} set {self.Name} peer {p['id']} allowed-ips {p['allowed_ip'].replace(' ', '')}{f' preshared-key {uid}' if presharedKeyExist else ''} advanced-security {p['advanced_security']}", + shell=True, stderr=subprocess.STDOUT) + if presharedKeyExist: + os.remove(uid) + subprocess.check_output( + f"{self.Protocol}-quick save {self.Name}", shell=True, stderr=subprocess.STDOUT) + self.getPeersList() + return True + except Exception as e: + print(str(e)) + return False + + def getRestrictedPeers(self): + self.RestrictedPeers = [] + restricted = sqlSelect("SELECT * FROM '%s_restrict_access'" % self.Name).fetchall() + for i in restricted: + self.RestrictedPeers.append(AmneziaWGPeer(i, self)) """ Peer @@ -1422,8 +1483,8 @@ class Peer: self.remote_endpoint = tableData["remote_endpoint"] self.preshared_key = tableData["preshared_key"] - if configuration.Protocol == "awg": - self.advanced_security = tableData["advanced_security"] + + self.jobs: list[PeerJob] = [] self.ShareLink: list[PeerShareLink] = [] @@ -1516,6 +1577,7 @@ MTU = {str(self.mtu)} ''' if len(self.DNS) > 0: peerConfiguration += f"DNS = {self.DNS}\n" + peerConfiguration += f''' [Peer] PublicKey = {self.configuration.PublicKey} @@ -1549,7 +1611,116 @@ PersistentKeepalive = {str(self.keepalive)} except Exception as e: return False return True + +class AmneziaWGPeer(Peer): + def __init__(self, tableData, configuration: AmneziaWireguardConfiguration): + self.advanced_security = tableData["advanced_security"] + super().__init__(tableData, configuration) + def downloadPeer(self) -> dict[str, str]: + filename = self.name + if len(filename) == 0: + filename = "UntitledPeer" + filename = "".join(filename.split(' ')) + filename = f"{filename}_{self.configuration.Name}" + illegal_filename = [".", ",", "/", "?", "<", ">", "\\", ":", "*", '|' '\"', "com1", "com2", "com3", + "com4", "com5", "com6", "com7", "com8", "com9", "lpt1", "lpt2", "lpt3", "lpt4", + "lpt5", "lpt6", "lpt7", "lpt8", "lpt9", "con", "nul", "prn"] + for i in illegal_filename: + filename = filename.replace(i, "") + + peerConfiguration = f'''[Interface] +PrivateKey = {self.private_key} +Address = {self.allowed_ip} +MTU = {str(self.mtu)} +Jc = {self.configuration.Jc} +Jmin = {self.configuration.Jmin} +Jmax = {self.configuration.Jmax} +S1 = {self.configuration.S1} +S2 = {self.configuration.S2} +H1 = {self.configuration.H1} +H2 = {self.configuration.H2} +H3 = {self.configuration.H3} +H4 = {self.configuration.H4} +''' + if len(self.DNS) > 0: + peerConfiguration += f"DNS = {self.DNS}\n" + + peerConfiguration += f''' +[Peer] +PublicKey = {self.configuration.PublicKey} +AllowedIPs = {self.endpoint_allowed_ip} +Endpoint = {DashboardConfig.GetConfig("Peers", "remote_endpoint")[1]}:{self.configuration.ListenPort} +PersistentKeepalive = {str(self.keepalive)} +''' + if len(self.preshared_key) > 0: + peerConfiguration += f"PresharedKey = {self.preshared_key}\n" + return { + "fileName": filename, + "file": peerConfiguration + } + + def updatePeer(self, name: str, private_key: str, + preshared_key: str, + dns_addresses: str, allowed_ip: str, endpoint_allowed_ip: str, mtu: int, + keepalive: int, advanced_security: str) -> ResponseObject: + if not self.configuration.getStatus(): + self.configuration.toggleConfiguration() + + existingAllowedIps = [item for row in list( + map(lambda x: [q.strip() for q in x.split(',')], + map(lambda y: y.allowed_ip, + list(filter(lambda k: k.id != self.id, self.configuration.getPeersList()))))) for item in row] + + if allowed_ip in existingAllowedIps: + return ResponseObject(False, "Allowed IP already taken by another peer") + if not ValidateIPAddressesWithRange(endpoint_allowed_ip): + return ResponseObject(False, f"Endpoint Allowed IPs format is incorrect") + if len(dns_addresses) > 0 and not ValidateDNSAddress(dns_addresses): + return ResponseObject(False, f"DNS format is incorrect") + if mtu < 0 or mtu > 1460: + return ResponseObject(False, "MTU format is not correct") + if keepalive < 0: + return ResponseObject(False, "Persistent Keepalive format is not correct") + if advanced_security != "on" and advanced_security != "off": + return ResponseObject(False, "Advanced Security can only be on or off") + if len(private_key) > 0: + pubKey = GenerateWireguardPublicKey(private_key) + if not pubKey[0] or pubKey[1] != self.id: + return ResponseObject(False, "Private key does not match with the public key") + try: + rd = random.Random() + uid = str(uuid.UUID(int=rd.getrandbits(128), version=4)) + pskExist = len(preshared_key) > 0 + + if pskExist: + with open(uid, "w+") as f: + f.write(preshared_key) + newAllowedIPs = allowed_ip.replace(" ", "") + updateAllowedIp = subprocess.check_output( + f"{self.configuration.Protocol} set {self.configuration.Name} peer {self.id} allowed-ips {newAllowedIPs}{f' preshared-key {uid}' if pskExist else ''} advanced-security {advanced_security}", + shell=True, stderr=subprocess.STDOUT) + + if pskExist: os.remove(uid) + + if len(updateAllowedIp.decode().strip("\n")) != 0: + return ResponseObject(False, + "Update peer failed when updating Allowed IPs") + saveConfig = subprocess.check_output(f"{self.configuration.Protocol}-quick save {self.configuration.Name}", + shell=True, stderr=subprocess.STDOUT) + if f"wg showconf {self.configuration.Name}" not in saveConfig.decode().strip('\n'): + return ResponseObject(False, + "Update peer failed when saving the configuration") + sqlUpdate( + '''UPDATE '%s' SET name = ?, private_key = ?, DNS = ?, endpoint_allowed_ip = ?, mtu = ?, + keepalive = ?, preshared_key = ?, advanced_security = ? WHERE id = ?''' % self.configuration.Name, + (name, private_key, dns_addresses, endpoint_allowed_ip, mtu, + keepalive, preshared_key, advanced_security, self.id,) + ) + return ResponseObject() + except subprocess.CalledProcessError as exc: + return ResponseObject(False, exc.output.decode("UTF-8").strip()) + """ Dashboard API Key """ @@ -2161,8 +2332,13 @@ def API_updatePeerSettings(configName): wireguardConfig = WireguardConfigurations[configName] foundPeer, peer = wireguardConfig.searchPeer(id) if foundPeer: + if wireguardConfig.Protocol == 'wg': + return peer.updatePeer(name, private_key, preshared_key, dns_addresses, + allowed_ip, endpoint_allowed_ip, mtu, keepalive) + return peer.updatePeer(name, private_key, preshared_key, dns_addresses, - allowed_ip, endpoint_allowed_ip, mtu, keepalive) + allowed_ip, endpoint_allowed_ip, mtu, keepalive, data.get('advanced_security', 'off')) + return ResponseObject(False, "Peer does not exist") @app.post(f'{APP_PREFIX}/api/resetPeerData/') @@ -2325,7 +2501,8 @@ def API_addPeers(configName): "DNS": dns_addresses, "endpoint_allowed_ip": endpoint_allowed_ip, "mtu": mtu, - "keepalive": keep_alive + "keepalive": keep_alive, + "advanced_security": data.get("advanced_security", "off") }) if len(keyPairs) == 0: return ResponseObject(False, "Generating key pairs by bulk failed") @@ -2352,7 +2529,8 @@ def API_addPeers(configName): "endpoint_allowed_ip": endpoint_allowed_ip, "DNS": dns_addresses, "mtu": mtu, - "keepalive": keep_alive + "keepalive": keep_alive, + "advanced_security": data.get("advanced_security", "off") }] ) return ResponseObject(status) diff --git a/src/static/app/src/components/configurationComponents/peer.vue b/src/static/app/src/components/configurationComponents/peer.vue index be68e5a..2fb19df 100644 --- a/src/static/app/src/components/configurationComponents/peer.vue +++ b/src/static/app/src/components/configurationComponents/peer.vue @@ -81,6 +81,14 @@ export default { {{Peer.allowed_ip}}
+
+ + + + + {{Peer.advanced_security}} + +
+
+
+
+ +
+
+ + +
+ + + + + +
+
+
+
+
diff --git a/src/static/app/src/components/configurationComponents/peerSettings.vue b/src/static/app/src/components/configurationComponents/peerSettings.vue index 4d022e8..ea6f181 100644 --- a/src/static/app/src/components/configurationComponents/peerSettings.vue +++ b/src/static/app/src/components/configurationComponents/peerSettings.vue @@ -61,7 +61,7 @@ export default { }, mounted() { this.$el.querySelectorAll("input").forEach(x => { - x.addEventListener("keyup", () => { + x.addEventListener("change", () => { this.dataChanged = true; }); }) @@ -198,6 +198,30 @@ export default { v-model="this.data.keepalive" id="peer_keep_alive">
+
+ +
+ + + + + +
+
diff --git a/src/static/app/src/views/newConfiguration.vue b/src/static/app/src/views/newConfiguration.vue index da237c6..20bbb2f 100644 --- a/src/static/app/src/views/newConfiguration.vue +++ b/src/static/app/src/views/newConfiguration.vue @@ -24,7 +24,8 @@ export default { PreUp: "", PreDown: "", PostUp: "", - PostDown: "" + PostDown: "", + Protocol: "wg" }, numberOfAvailableIPs: "0", error: false, @@ -143,6 +144,34 @@ export default {
+ +
@@ -303,5 +332,7 @@ export default { \ No newline at end of file From 57583b6747c82aec3b3b67dd515fb4de72cc2c49 Mon Sep 17 00:00:00 2001 From: Donald Zou Date: Wed, 4 Dec 2024 17:50:16 +0800 Subject: [PATCH 6/8] Backup and restore for AWG is done --- src/dashboard.py | 99 +++++++++++-------- .../app/src/components/protocolBadge.vue | 21 ++++ .../backupGroup.vue | 30 +++--- .../confirmBackup.vue | 14 ++- src/static/app/src/router/router.js | 6 +- src/static/app/src/views/newConfiguration.vue | 56 ++++++----- .../app/src/views/restoreConfiguration.vue | 1 + 7 files changed, 144 insertions(+), 83 deletions(-) create mode 100644 src/static/app/src/components/protocolBadge.vue diff --git a/src/dashboard.py b/src/dashboard.py index e535e7c..b0a250f 100644 --- a/src/dashboard.py +++ b/src/dashboard.py @@ -509,6 +509,7 @@ class WireguardConfiguration: self.createDatabase() with open(self.configPath, "w+") as configFile: self.__parser.write(configFile) + print(f"[WGDashboard] Configuration file {self.configPath} created") self.__initPeersList() print(f"[WGDashboard] Initialized Configuration: {name}") @@ -2086,11 +2087,14 @@ def API_getWireguardConfigurations(): def API_addWireguardConfiguration(): data = request.get_json() requiredKeys = [ - "ConfigurationName", "Address", "ListenPort", "PrivateKey" + "ConfigurationName", "Address", "ListenPort", "PrivateKey", "Protocol" ] for i in requiredKeys: if i not in data.keys(): return ResponseObject(False, "Please provide all required parameters.") + + if data.get("Protocol") not in ProtocolsEnabled(): + return ResponseObject(False, "Please provide a valid protocol: wg / awg.") # Check duplicate names, ports, address for i in WireguardConfigurations.values(): @@ -2110,22 +2114,27 @@ def API_addWireguardConfiguration(): "Address") if "Backup" in data.keys(): - if not os.path.exists(os.path.join( - DashboardConfig.GetConfig("Server", "wg_conf_path")[1], - 'WGDashboard_Backup', - data["Backup"])) or not os.path.exists(os.path.join( - DashboardConfig.GetConfig("Server", "wg_conf_path")[1], - 'WGDashboard_Backup', - data["Backup"].replace('.conf', '.sql'))): - return ResponseObject(False, "Backup file does not exist") + path = { + "wg": DashboardConfig.GetConfig("Server", "wg_conf_path")[1], + "awg": DashboardConfig.GetConfig("Server", "awg_conf_path")[1] + } + + if (os.path.exists(os.path.join(path['wg'], 'WGDashboard_Backup', data["Backup"])) and + os.path.exists(os.path.join(path['wg'], 'WGDashboard_Backup', data["Backup"].replace('.conf', '.sql')))): + protocol = "wg" + elif (os.path.exists(os.path.join(path['awg'], 'WGDashboard_Backup', data["Backup"])) and + os.path.exists(os.path.join(path['awg'], 'WGDashboard_Backup', data["Backup"].replace('.conf', '.sql')))): + protocol = "awg" + else: + return ResponseObject(False, "Backup does not exist") shutil.copy( - os.path.join(DashboardConfig.GetConfig("Server", "wg_conf_path")[1], 'WGDashboard_Backup', data["Backup"]), - os.path.join(DashboardConfig.GetConfig("Server", "wg_conf_path")[1], f'{data["ConfigurationName"]}.conf') + os.path.join(path[protocol], 'WGDashboard_Backup', data["Backup"]), + os.path.join(path[protocol], f'{data["ConfigurationName"]}.conf') ) - WireguardConfigurations[data['ConfigurationName']] = WireguardConfiguration(data=data, name=data['ConfigurationName']) + WireguardConfigurations[data['ConfigurationName']] = WireguardConfiguration(data=data, name=data['ConfigurationName']) if protocol == 'wg' else AmneziaWireguardConfiguration(data=data, name=data['ConfigurationName']) else: - WireguardConfigurations[data['ConfigurationName']] = WireguardConfiguration(data=data) + WireguardConfigurations[data['ConfigurationName']] = WireguardConfiguration(data=data) if data.get('Protocol') == 'wg' else AmneziaWireguardConfiguration(data=data) return ResponseObject() @app.get(f'{APP_PREFIX}/api/toggleWireguardConfiguration/') @@ -2197,30 +2206,32 @@ def API_getAllWireguardConfigurationBackup(): b = WireguardConfigurations[i].getBackups(True) if len(b) > 0: data['ExistingConfigurations'][i] = WireguardConfigurations[i].getBackups(True) - - directory = os.path.join(DashboardConfig.GetConfig("Server", "wg_conf_path")[1], 'WGDashboard_Backup') - files = [(file, os.path.getctime(os.path.join(directory, file))) - for file in os.listdir(directory) if os.path.isfile(os.path.join(directory, file))] - files.sort(key=lambda x: x[1], reverse=True) - - for f, ct in files: - if RegexMatch(r"^(.*)_(.*)\.(conf)$", f): - s = re.search(r"^(.*)_(.*)\.(conf)$", f) - name = s.group(1) - if name not in existingConfiguration: - if name not in data['NonExistingConfigurations'].keys(): - data['NonExistingConfigurations'][name] = [] - - date = s.group(2) - d = { - "filename": f, - "backupDate": date, - "content": open(os.path.join(DashboardConfig.GetConfig("Server", "wg_conf_path")[1], 'WGDashboard_Backup', f), 'r').read() - } - if f.replace(".conf", ".sql") in list(os.listdir(directory)): - d['database'] = True - d['databaseContent'] = open(os.path.join(DashboardConfig.GetConfig("Server", "wg_conf_path")[1], 'WGDashboard_Backup', f.replace(".conf", ".sql")), 'r').read() - data['NonExistingConfigurations'][name].append(d) + + for protocol in ProtocolsEnabled(): + directory = os.path.join(DashboardConfig.GetConfig("Server", f"{protocol}_conf_path")[1], 'WGDashboard_Backup') + files = [(file, os.path.getctime(os.path.join(directory, file))) + for file in os.listdir(directory) if os.path.isfile(os.path.join(directory, file))] + files.sort(key=lambda x: x[1], reverse=True) + + for f, ct in files: + if RegexMatch(r"^(.*)_(.*)\.(conf)$", f): + s = re.search(r"^(.*)_(.*)\.(conf)$", f) + name = s.group(1) + if name not in existingConfiguration: + if name not in data['NonExistingConfigurations'].keys(): + data['NonExistingConfigurations'][name] = [] + + date = s.group(2) + d = { + "protocol": protocol, + "filename": f, + "backupDate": date, + "content": open(os.path.join(DashboardConfig.GetConfig("Server", f"{protocol}_conf_path")[1], 'WGDashboard_Backup', f), 'r').read() + } + if f.replace(".conf", ".sql") in list(os.listdir(directory)): + d['database'] = True + d['databaseContent'] = open(os.path.join(DashboardConfig.GetConfig("Server", f"{protocol}_conf_path")[1], 'WGDashboard_Backup', f.replace(".conf", ".sql")), 'r').read() + data['NonExistingConfigurations'][name].append(d) return ResponseObject(data=data) @app.get(f'{APP_PREFIX}/api/createWireguardConfigurationBackup') @@ -2944,6 +2955,9 @@ def API_SystemStatus(): # pass return ResponseObject(data=status) +@app.get(f'{APP_PREFIX}/api/protocolsEnabled') +def API_ProtocolsEnabled(): + return ResponseObject(data=ProtocolsEnabled()) @app.get(f'{APP_PREFIX}/') def index(): @@ -2980,10 +2994,15 @@ def gunicornConfig(): _, app_port = DashboardConfig.GetConfig("Server", "app_port") return app_ip, app_port -def AmneziaWGEnabled(): +def ProtocolsEnabled() -> list[str]: from shutil import which + protocols = [] + if which('awg') is not None and which('awg-quick') is not None: + protocols.append("awg") + if which('wg') is not None and which('wg-quick') is not None: + protocols.append("wg") + return protocols - return which('awg') is not None and which('awg-quick') is not None def InitWireguardConfigurationsList(startup: bool = False): confs = os.listdir(DashboardConfig.GetConfig("Server", "wg_conf_path")[1]) @@ -3000,7 +3019,7 @@ def InitWireguardConfigurationsList(startup: bool = False): except WireguardConfiguration.InvalidConfigurationFileException as e: print(f"{i} have an invalid configuration file.") - if AmneziaWGEnabled(): + if "awg" in ProtocolsEnabled(): confs = os.listdir(DashboardConfig.GetConfig("Server", "awg_conf_path")[1]) confs.sort() for i in confs: diff --git a/src/static/app/src/components/protocolBadge.vue b/src/static/app/src/components/protocolBadge.vue new file mode 100644 index 0000000..53b2c5d --- /dev/null +++ b/src/static/app/src/components/protocolBadge.vue @@ -0,0 +1,21 @@ + + + + + \ No newline at end of file diff --git a/src/static/app/src/components/restoreConfigurationComponents/backupGroup.vue b/src/static/app/src/components/restoreConfigurationComponents/backupGroup.vue index 69e84a1..ec64b5b 100644 --- a/src/static/app/src/components/restoreConfigurationComponents/backupGroup.vue +++ b/src/static/app/src/components/restoreConfigurationComponents/backupGroup.vue @@ -2,12 +2,14 @@ import {onMounted, ref} from "vue"; import dayjs from "dayjs"; import LocaleText from "@/components/text/localeText.vue"; +import ProtocolBadge from "@/components/protocolBadge.vue"; const props = defineProps({ configurationName: String, backups: Array, open: false, - selectedConfigurationBackup: Object + selectedConfigurationBackup: Object, + protocol: Array }) const emit = defineEmits(["select"]) @@ -28,18 +30,20 @@ onMounted(() => {