diff --git a/actions/wireguard b/actions/wireguard index 7221ae907..3856f795e 100755 --- a/actions/wireguard +++ b/actions/wireguard @@ -24,9 +24,6 @@ import json import os import pathlib import subprocess -import sys - -from plinth import network PUBLIC_KEY_HELP = 'Public key for the client' @@ -57,17 +54,6 @@ def parse_arguments(): help='Remove a client') remove_client.add_argument('publickey', help=PUBLIC_KEY_HELP) - add_server = subparsers.add_parser('add-server', help='Add a server') - add_server.add_argument('--endpoint', required=True, - help='Server endpoint') - add_server.add_argument('--client-ip', required=True, - help='Client IP address provided by server') - add_server.add_argument('--public-key', required=True, - help='Public key of the server') - add_server.add_argument( - '--all-outgoing', action='store_true', - help='Use this connection to send all outgoing traffic') - modify_server = subparsers.add_parser('modify-server', help='Modify a server') modify_server.add_argument('--endpoint', required=True, @@ -185,129 +171,6 @@ def subcommand_remove_client(arguments): check=True) -def _find_next_interface(): - """Find next unused wireguard interface name.""" - output = subprocess.check_output(['wg', 'show', - 'interfaces']).decode().strip() - interfaces = output.split() - interface_num = 1 - new_interface_name = 'wg1' - while new_interface_name in interfaces: - interface_num += 1 - new_interface_name = 'wg' + str(interface_num) - - return new_interface_name - - -def _get_connection_settings(name, interface, endpoint, client_ip, public_key, - client_private_key, pre_shared_key): - """Return settings for Network Manager connection.""" - if not client_private_key: - with PRIVATE_KEY_PATH.open() as private_key_file: - client_private_key = private_key_file.read().strip() - - return { - 'common': { - 'name': name, - 'type': 'wireguard', - 'interface': interface, - 'zone': 'internal', - }, - 'ipv4': { - 'method': 'manual', - 'address': client_ip, - 'netmask': '', - 'gateway': '', - 'dns': '', - 'second_dns': '', - }, - 'wireguard': { - 'private_key': client_private_key, - 'peer_endpoint': endpoint, - 'peer_public_key': public_key, - 'preshared_key': pre_shared_key, - }, - } - - -def subcommand_add_server(arguments): - """Add a server.""" - secret_args = json.loads(sys.stdin.read() or '{}') - new_interface_name = _find_next_interface() - - subprocess.run( - ['ip', 'link', 'add', 'dev', new_interface_name, 'type', 'wireguard'], - check=True) - - connection_name = 'WireGuard-' + new_interface_name - settings = _get_connection_settings(connection_name, new_interface_name, - arguments.endpoint, - arguments.client_ip, - arguments.public_key, - secret_args.get('client_private_key'), - secret_args.get('pre_shared_key')) - network.add_connection(settings) - - -def subcommand_modify_server(arguments): - """Modify a server.""" - secret_args = json.loads(sys.stdin.read() or '{}') - - interfaces = _get_info() - interfaces.pop(SERVER_INTERFACE, None) - interface_to_modify = None - for interface in interfaces.values(): - if interface['peers']: - peer = interface['peers'][0] - if peer['public_key'] == arguments.public_key: - interface_to_modify = interface['interface_name'] - - if interface_to_modify: - connection = network.get_connection_by_interface_name( - interface_to_modify) - settings = _get_connection_settings( - 'WireGuard-' + interface_to_modify, interface_to_modify, - arguments.endpoint, arguments.client_ip, arguments.public_key, - secret_args.get('client_private_key'), - secret_args.get('pre_shared_key')) - - if connection: - network.edit_connection(connection, settings) - - else: - # XXX: raise error? - network.add_connection(settings) - - else: - raise InterfaceNotFoundError( - 'Interface with peer %s not found' % arguments.public_key) - - -def subcommand_remove_server(arguments): - """Remove a server.""" - interfaces = _get_info() - interfaces.pop(SERVER_INTERFACE, None) - interface_to_remove = None - for interface in interfaces.values(): - if interface['peers']: - peer = interface['peers'][0] - if peer['public_key'] == arguments.publickey: - interface_to_remove = interface['interface_name'] - - if interface_to_remove: - connection = network.get_connection_by_interface_name( - interface_to_remove) - if connection: - network.delete_connection(connection.get_uuid()) - - subprocess.run(['ip', 'link', 'delete', interface_to_remove], - check=True) - - else: - raise InterfaceNotFoundError( - 'Interface with peer %s not found' % arguments.publickey) - - def main(): """Parse arguments and perform all duties.""" arguments = parse_arguments() diff --git a/plinth/modules/wireguard/__init__.py b/plinth/modules/wireguard/__init__.py index 21f24aa80..7cdb630e0 100644 --- a/plinth/modules/wireguard/__init__.py +++ b/plinth/modules/wireguard/__init__.py @@ -18,6 +18,7 @@ FreedomBox app for wireguard. """ +import datetime import json from django.urls import reverse_lazy @@ -27,10 +28,12 @@ from plinth import actions from plinth import app as app_module from plinth import cfg, frontpage, menu from plinth.modules.firewall.components import Firewall -from plinth.utils import format_lazy +from plinth.utils import format_lazy, import_from_gi from .manifest import clients # noqa, pylint: disable=unused-import +nm = import_from_gi('NM', '1.0') + version = 1 managed_packages = ['wireguard'] @@ -116,15 +119,74 @@ def get_public_key(): return public_key +def get_nm_info(): + """Get information from network manager.""" + client = nm.Client.new(None) + + connections = {} + for connection in client.get_connections(): + if connection.get_connection_type() != 'wireguard': + continue + + settings = connection.get_setting_by_name('wireguard') + secrets = connection.get_secrets('wireguard') + connection.update_secrets('wireguard', secrets) + + info = {} + info['interface'] = connection.get_interface_name() + info['private_key'] = settings.get_private_key() + info['listen_port'] = settings.get_listen_port() + info['fwmark'] = settings.get_fwmark() + info['mtu'] = settings.get_mtu() + info['default_route'] = settings.get_ip4_auto_default_route() + info['peers'] = [] + for peer_index in range(settings.get_peers_len()): + peer = settings.get_peer(peer_index) + peer_info = { + 'endpoint': peer.get_endpoint(), + 'public_key': peer.get_public_key(), + 'preshared_key': peer.get_preshared_key(), + 'persistent_keepalive': peer.get_persistent_keepalive(), + 'allowed_ips': [] + } + for index in range(peer.get_allowed_ips_len()): + allowed_ip = peer.get_allowed_ip(index, None) + peer_info['allowed_ips'].append(allowed_ip) + + info['peers'].append(peer_info) + + settings_ipv4 = connection.get_setting_ip4_config() + if settings_ipv4 and settings_ipv4.get_num_addresses(): + info['ip_address'] = settings_ipv4.get_address(0).get_address() + + connections[info['interface']] = info + + return connections + + def get_info(): """Return server and clients info.""" output = actions.superuser_run('wireguard', ['get-info']) - info = json.loads(output) - my_server_info = info.pop(SERVER_INTERFACE, {}) - my_client_servers = [] - for interface in info.values(): - if interface['peers']: - my_client_servers.append(interface['peers'][0]) + status = json.loads(output) + + nm_info = get_nm_info() + + my_server_info = status.pop(SERVER_INTERFACE, {}) + my_client_servers = {} + + for interface, info in nm_info.items(): + my_client_servers[interface] = info + + if interface not in status: + continue + + for info_peer in info['peers']: + for status_peer in status[interface]['peers']: + if info_peer['public_key'] == status_peer['public_key']: + info_peer['status'] = status_peer + status_peer['latest_handshake'] = \ + datetime.datetime.fromtimestamp( + int(status_peer['latest_handshake'])) return { 'my_server': { diff --git a/plinth/modules/wireguard/forms.py b/plinth/modules/wireguard/forms.py index 1c4059c1a..9dc7ef977 100644 --- a/plinth/modules/wireguard/forms.py +++ b/plinth/modules/wireguard/forms.py @@ -31,30 +31,55 @@ class AddClientForm(forms.Form): class AddServerForm(forms.Form): """Form to add server.""" - endpoint = forms.CharField( + peer_endpoint = forms.CharField( label=_('Endpoint'), strip=True, help_text=_('Server endpoint with the form "ip:port".')) - client_ip_address = forms.CharField( + peer_public_key = forms.CharField( + label=_('Public key of the server'), strip=True, + help_text=_('Public key of the server.')) + + ip_address = forms.CharField( label=_('Client IP address provided by server'), strip=True, help_text=_('IP address assigned to the client on the VPN after ' 'connecting to the endpoint.')) - public_key = forms.CharField( - label=_('Public key of the server'), strip=True, - help_text=_('Public key of the server.')) - - client_private_key = forms.CharField( + private_key = forms.CharField( label=_('Private key of the client'), strip=True, help_text=_('Optional. A new key is generated if left blank.'), required=False) - pre_shared_key = forms.CharField( + preshared_key = forms.CharField( label=_('Pre-shared key'), strip=True, required=False, help_text=_('Optional. A shared secret key provided by the server to ' 'add an additional layer of encryption.')) - all_outgoing_traffic = forms.BooleanField( + default_route = forms.BooleanField( label=_('Use this connection to send all outgoing traffic'), required=False, help_text=_('Use this connection to send all outgoing traffic.')) + + def get_settings(self): + """Return NM settings dict from cleaned data.""" + settings = { + 'common': { + 'type': 'wireguard', + 'zone': 'internal', + }, + 'ipv4': { + 'method': 'manual', + 'address': self.cleaned_data['ip_address'], + 'netmask': '', + 'gateway': '', + 'dns': '', + 'second_dns': '', + }, + 'wireguard': { + 'peer_endpoint': self.cleaned_data['peer_endpoint'], + 'peer_public_key': self.cleaned_data['peer_public_key'], + 'private_key': self.cleaned_data['private_key'], + 'preshared_key': self.cleaned_data['preshared_key'], + 'default_route': self.cleaned_data['default_route'], + } + } + return settings diff --git a/plinth/modules/wireguard/templates/wireguard.html b/plinth/modules/wireguard/templates/wireguard.html index 7734d7f47..753836e44 100644 --- a/plinth/modules/wireguard/templates/wireguard.html +++ b/plinth/modules/wireguard/templates/wireguard.html @@ -43,7 +43,6 @@
{% trans "Are you sure that you want to delete this server?" %}
-- {{ public_key }} -
+| {% trans "Endpoint" %} | +{{ peer_endpoint }} | +
|---|---|
| {% trans "Public Key" %} | +{{ peer_public_key }} | +