From 74c3e4a898f77c3717c85b796ecf20a6ff6b3b86 Mon Sep 17 00:00:00 2001 From: Mikael Nordin Date: Wed, 25 Nov 2020 18:15:17 +0100 Subject: [PATCH] Initial working code --- README.md | 12 +- logo.png | Bin 0 -> 7699 bytes wireguide | 388 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 399 insertions(+), 1 deletion(-) create mode 100644 logo.png create mode 100755 wireguide diff --git a/README.md b/README.md index b5df30a..be85e5e 100644 --- a/README.md +++ b/README.md @@ -1 +1,11 @@ -# wireguide \ No newline at end of file +# wireguide +WireGUIde is a graphical user interface for WireGuard: https://www.wireguard.com/ + +It makes use of: + * libnm (https://developer.gnome.org/libnm/stable/usage.html) + * wxPython (https://wxpython.org/) + * GObject Introspection (https://gi.readthedocs.io/en/latest/) + +Thanks to Jan Bodnar of zetcode.com for the valuable tutorial on wxPython dialogs: +* http://zetcode.com/wxpython/dialogs/ + diff --git a/logo.png b/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..9ada82d554931f2ab94ef4e525edde46c4631629 GIT binary patch literal 7699 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 E1M+Eq+5i9m literal 0 HcmV?d00001 diff --git a/wireguide b/wireguide new file mode 100755 index 0000000..505ce10 --- /dev/null +++ b/wireguide @@ -0,0 +1,388 @@ +#!/usr/bin/env python3 +""" +This is a program that can manage Wireguard Configuration graphically + +You will need some dependencies: + sudo apt install wxpython python3-networkmanager + +""" +import configparser +import os +import re +import sys +import uuid +from socket import AF_INET + +import gi +import wx +import wx.adv + +gi.require_version("NM", "1.0") +from gi.repository import NM # pylint: disable=wrong-import-position + + +class WireFrame(wx.Frame): # pylint: disable=too-many-ancestors,too-many-instance-attributes + """ + The WireGUIde wx.Frame + """ + def __init__(self, *args, **kw): + super().__init__(*args, **kw) + + self.version = 0.1 + + # Get active conns from NetworkManager + self.client = NM.Client.new(None) + self.client.checkpoint_adjust_rollback_timeout( + "/org/freedesktop/NetworkManager", 60, None, None, None) + self.conns = self.get_wg_conns() + self.num_conns = len(self.conns) + self.active_conns = self.get_wg_aconns() + self.num_aconns = len(self.active_conns) + + # Set up for loaded configs + self.inactive_conns = get_inactive_conns() + self.num_inactive_conns = len(self.inactive_conns) + + # create a panel in the frame + self.pnl = wx.Panel(self) + + # and create a sizer to manage the layout of child widgets + self.sizer = wx.BoxSizer(wx.VERTICAL) + self.write_to_sizer() + self.pnl.SetSizer(self.sizer) + + # create a menu bar + self.make_menu_bar() + + # and a status bar + self.CreateStatusBar() + self.status = str(self.num_aconns) + " active connection(s)" + self.SetStatusText(self.status) + + self.timer = wx.Timer(self) + self.count = 0 + + self.Bind(wx.EVT_TIMER, self.do_on_timer) + self.Bind(wx.EVT_PAINT, self.timing) + self.Show() + + def timing(self, event): # pylint: disable=unused-argument + """ + Start a timer + """ + self.timer.Start(20) + + def do_on_timer(self, event): # pylint: disable=unused-argument + """ + Do stuff to redraw the window when the timer times out + Ths is because connections might change outside of WireGUIde + """ + self.count += 1 + if self.count == 5: + self.client.reload_connections_async() + self.active_conns = self.get_wg_aconns() + self.num_aconns = len(self.active_conns) + self.conns = self.get_wg_conns() + self.num_conns = len(self.conns) + self.write_to_sizer() + self.count = 0 + + def write_to_sizer(self): + """ + We use the BoxSizer to hold our configs + This method redraws the sizer + """ + self.sizer.Clear(delete_windows=True) + if self.num_aconns > 0: + hd1 = wx.StaticText(self.pnl) + hd1.SetLabelMarkup("Active connections") + self.sizer.Add(hd1, 0, wx.ALIGN_CENTER) + for conn in self.active_conns: + statstr = wx.StaticText(self.pnl) + info = get_info_as_text(conn) + statstr.SetLabelMarkup(info) + self.sizer.Add(statstr, + wx.SizerFlags().Border(wx.TOP | wx.LEFT, 25)) + dbtn = wx.Button(self.pnl, -1, "Deactivate") + self.sizer.Add(dbtn, 0, wx.ALIGN_CENTER) + self.Bind(wx.EVT_BUTTON, self.deactivate_button_clicked, dbtn) + if self.num_inactive_conns > 0: + hd2 = wx.StaticText(self.pnl) + hd2.SetLabelMarkup("Imported configs") + self.sizer.Add(hd2, 0, wx.ALIGN_CENTER) + if self.inactive_conns: + for conn in self.inactive_conns: + statstr = wx.StaticText(self.pnl) + info = get_info_as_text(conn) + statstr.SetLabelMarkup(info) + self.sizer.Add( + statstr, + wx.SizerFlags().Border(wx.TOP | wx.LEFT, 25)) + btn = wx.Button(self.pnl, -1, "Activate") + self.sizer.Add(btn, 0, wx.ALIGN_CENTER) + self.Bind(wx.EVT_BUTTON, self.activate_button_clicked, btn) + if (self.num_aconns == 0) and (self.num_inactive_conns == 0): + hd0 = wx.StaticText(self.pnl) + hd0.SetLabelMarkup("No configs available") + missingstr = wx.StaticText(self.pnl) + missingstr.SetLabelMarkup( + "Please choose a config file from the file menu (CTRL+O).") + self.sizer.Add(hd0, 0, wx.ALIGN_CENTER) + self.sizer.Add(missingstr, 0, wx.ALIGN_LEFT) + + self.sizer.Layout() + + def get_next_int_name(self): + """ + Determins what we should call the next interface + """ + temp = [] + for conn in self.conns: + temp.append(re.findall(r'\d+', conn.get_interface_name())) + for conn in self.inactive_conns: + temp.append(re.findall(r'\d+', conn.get_interface_name())) + numbers = [int(item) for sublist in temp for item in sublist] + if not numbers: + num = 0 + else: + numbers.sort(reverse=True) + num = numbers[0] + 1 + return "wg" + str(num) + + def create_conn_from_file(self, pathname): + """ + Read a WireGuardUI config file and convert it in to + an object that can be user by NetworkManager + """ + filename = os.path.basename(pathname) + try: + config = configparser.ConfigParser() + config.read(pathname) + iname = self.get_next_int_name() + profile = NM.SimpleConnection.new() + s_con = NM.SettingConnection.new() + s_con.set_property(NM.SETTING_CONNECTION_ID, iname) + s_con.set_property(NM.SETTING_CONNECTION_INTERFACE_NAME, iname) + s_con.set_property(NM.SETTING_CONNECTION_UUID, str(uuid.uuid4())) + s_con.set_property(NM.SETTING_CONNECTION_TYPE, + NM.SETTING_WIREGUARD_SETTING_NAME) + + s_wireguard = NM.SettingWireGuard.new() + s_wireguard.set_property(NM.SETTING_WIREGUARD_PRIVATE_KEY, + config['Interface']['PrivateKey']) + s_peer = NM.WireGuardPeer.new() + s_peer.set_endpoint(config['Peer']['Endpoint'], False) + s_peer.set_public_key(config['Peer']['PublicKey'], False) + s_peer.append_allowed_ip(config['Peer']['AllowedIPs'], False) + s_wireguard.append_peer(s_peer) + + s_ip4 = NM.SettingIP4Config.new() + s_ip4_address = NM.IPAddress(AF_INET, + config['Interface']['Address'], + int(32)) + s_ip4.set_property(NM.SETTING_IP_CONFIG_METHOD, "manual") + s_ip4.add_address(s_ip4_address) + s_ip4.add_dns(config['Interface']['DNS']) + + profile.add_setting(s_con) + profile.add_setting(s_ip4) + profile.add_setting(s_wireguard) + return profile + + except IOError: + wx.LogError("Cannot open file '%s'." % filename) + return None + + def make_menu_bar(self): + """ + A menu bar is composed of menus, which are composed of menu items. + This method builds a set of menus and binds handlers to be called + when the menu item is selected. + """ + + file_menu = wx.Menu() + file_chooser_item = file_menu.Append(-1, "&Open...\tCtrl-O", + "Select WireGuard config file") + file_menu.AppendSeparator() + exit_item = file_menu.Append(wx.ID_EXIT) + + help_menu = wx.Menu() + about_item = help_menu.Append(wx.ID_ABOUT) + + menu_bar = wx.MenuBar() + menu_bar.Append(file_menu, "&File") + menu_bar.Append(help_menu, "&Help") + + self.SetMenuBar(menu_bar) + + self.Bind(wx.EVT_MENU, self.file_chooser_clicked, file_chooser_item) + self.Bind(wx.EVT_MENU, self.exit_clicked, exit_item) + self.Bind(wx.EVT_MENU, self.about_clicked, about_item) + + def activate_button_clicked(self, event): # pylint: disable=unused-argument + """ + This activates an imported config + """ + conn = self.inactive_conns.pop(0) + self.num_inactive_conns = len(self.inactive_conns) + self.client.add_connection_async(conn, False, None, self.add_callback, + None) + + def deactivate_button_clicked(self, event): # pylint: disable=unused-argument + """ + This deactivates an active config + """ + conn = self.active_conns[0] + self.client.deactivate_connection_async(conn, None, self.de_callback, + conn) + conn.get_connection().delete_async(None, None, None) + + def de_callback(self, client, result, data): # pylint: disable=unused-argument + """ + This is here to let us know if the deactivation was successful or not + """ + try: + client.deactivate_connection_finish(result) + print( + "The connection profile has been successfully removed from NetworkManager." + ) + except Exception as exception: # pylint: disable=broad-except + sys.stderr.write("Error: %s\n" % exception) + self.active_conns = self.get_wg_aconns() + self.num_aconns = len(self.active_conns) + self.conns = self.get_wg_conns() + self.num_conns = len(self.conns) + self.write_to_sizer() + + def add_callback(self, client, result, data): # pylint: disable=unused-argument + """ + This is here to let us know if the connection was successful or not + """ + try: + client.add_connection_finish(result) + print( + "The connection profile has been successfully added to NetworkManager." + ) + except Exception as exception: # pylint: disable=broad-except + sys.stderr.write("Error: %s\n" % exception) + self.active_conns = self.get_wg_aconns() + self.num_aconns = len(self.active_conns) + self.conns = self.get_wg_conns() + self.num_conns = len(self.conns) + self.write_to_sizer() + + def exit_clicked(self, event): # pylint: disable=unused-argument + """ + Close the frame, terminating the application. + """ + self.Close(True) + + def file_chooser_clicked(self, event): # pylint: disable=unused-argument + """ + This is what happens when you click on the file chooser + """ + with wx.FileDialog(self, + "Open WireGuard config file", + wildcard="WireGuard files (*.conf)|*.conf", + style=wx.FD_OPEN + | wx.FD_FILE_MUST_EXIST) as file_dialog: + + if file_dialog.ShowModal() == wx.ID_CANCEL: + return # the user changed their mind + + # Proceed loading the file chosen by the user + pathname = file_dialog.GetPath() + new_conn = self.create_conn_from_file(pathname) + self.inactive_conns.append(new_conn) + self.num_inactive_conns += 1 + self.write_to_sizer() + + def get_wg_aconns(self): + """ + Reads all active wireguard connections from NetworkManager + and returns them as objects in an array + """ + mconns = [] + wgconns = self.client.get_active_connections() + for conn in wgconns: + if conn.get_connection_type() == NM.SETTING_WIREGUARD_SETTING_NAME: + mconns.append(conn) + return mconns + + def get_wg_conns(self): + """ + Reads all current wireguard connections from NetworkManager + and returns them as objects in an array + """ + mconns = [] + wgconns = self.client.get_connections() + for conn in wgconns: + if conn.get_connection_type() == NM.SETTING_WIREGUARD_SETTING_NAME: + mconns.append(conn) + return mconns + + def about_clicked(self, event): # pylint: disable=unused-argument + """Display an About Dialog""" + about = "WireGUIde is a GUI for WireGuard." + lic_text = """ + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see .""" + #wx.MessageBox(about, "About WireGUIde" ,wx.OK | wx.ICON_INFORMATION) + info = wx.adv.AboutDialogInfo() + info.SetIcon(wx.Icon('logo.png', wx.BITMAP_TYPE_PNG)) + info.SetName('WireGUIde') + info.SetVersion(str(self.version)) + info.SetDescription(about) + info.SetCopyright('(C) 2020 Mikael Nordin') + info.SetWebSite('https://github.com/mickenordin') + info.SetLicence(lic_text) + info.AddDeveloper('Mikael Nordin') + info.AddDocWriter('Mikael Nordin') + info.AddArtist('Mikael Nordin') + + wx.adv.AboutBox(info) + + +def get_info_as_text(aconn): + """ + Returns info about a connection as text + """ + try: + conn = aconn.get_connection() + except Exception: # pylint: disable=broad-except + conn = aconn + mid = conn.get_id() + miname = conn.get_interface_name() + muuid = conn.get_uuid() + info = "id: " + mid + '\n' + info += "interface name: " + miname + '\n' + info += "uuid: " + muuid + '\n' + return info + + +def get_inactive_conns(): + """ + TODO: Not implemented yet + """ + minactive_conns = [] + #return empty array for now, we cant read configs from stash yet + return minactive_conns + + +if __name__ == '__main__': + # When this module is run (not imported) then create the app, the + # frame, show it, and start the event loop. + app = wx.App() + frm = WireFrame(None, title='WireGUIde') + frm.Show() + app.MainLoop()