wireguard: Handle client connections through network manager

- Any changes done directly using 'wg' command need to be redone after a reboot
  and disable/enable sequence. Let that duty be handled by network manager.

- Handle (none) values for keys and 0 values for latest handshake from 'wg'
  dump command output.

- Don't store public/private keys for wireguard in /var/lib. Let Network Manager
  deal with the storage of secrets.

- Create client connections in the 'external' zone.

- Show allowed IPs for each client in the main page.

- Show server connection public key only for clients. We use different key pairs
  when connecting to each of the servers.

- Separate out configuration information and status information in the show
  page.

- Allocate IP addresses to each of the clients.

Signed-off-by: Sunil Mohan Adapa <sunil@medhas.org>
Reviewed-by: James Valleroy <jvalleroy@mailbox.org>
This commit is contained in:
Sunil Mohan Adapa 2019-11-03 14:13:13 -08:00 committed by James Valleroy
parent 71c7ab4a9d
commit 6e1b0a3642
No known key found for this signature in database
GPG Key ID: 77C0C75E7B650808
9 changed files with 368 additions and 291 deletions

View File

@ -21,99 +21,23 @@ Configuration helper for WireGuard.
import argparse import argparse
import json import json
import os
import pathlib
import subprocess import subprocess
PUBLIC_KEY_HELP = 'Public key for the client'
SERVER_INTERFACE = 'wg0' SERVER_INTERFACE = 'wg0'
KEY_FOLDER = pathlib.Path('/var/lib/freedombox/wireguard')
PRIVATE_KEY_PATH = KEY_FOLDER / 'privatekey'
PUBLIC_KEY_PATH = KEY_FOLDER / 'publickey'
class InterfaceNotFoundError(Exception):
"""Exception raised when no matching interface is found."""
def parse_arguments(): def parse_arguments():
"""Return parsed command line arguments as dictionary.""" """Return parsed command line arguments as dictionary."""
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest='subcommand', help='Sub command') subparsers = parser.add_subparsers(dest='subcommand', help='Sub command')
subparsers.add_parser('setup', help='Setup WireGuard')
subparsers.add_parser('get-info', subparsers.add_parser('get-info',
help='Get info for each configured interface') help='Get info for each configured interface')
add_client = subparsers.add_parser('add-client', help='Add a client')
add_client.add_argument('publickey', help=PUBLIC_KEY_HELP)
remove_client = subparsers.add_parser('remove-client',
help='Remove a client')
remove_client.add_argument('publickey', help=PUBLIC_KEY_HELP)
modify_server = subparsers.add_parser('modify-server',
help='Modify a server')
modify_server.add_argument('--endpoint', required=True,
help='Server endpoint')
modify_server.add_argument('--client-ip', required=True,
help='Client IP address provided by server')
modify_server.add_argument('--public-key', required=True,
help='Public key of the server')
modify_server.add_argument('--pre-shared-key', help='Pre-shared key')
modify_server.add_argument(
'--all-outgoing', action='store_true',
help='Use this connection to send all outgoing traffic')
remove_server = subparsers.add_parser('remove-server',
help='Remove a server')
remove_server.add_argument('publickey', help=PUBLIC_KEY_HELP)
subparsers.required = True subparsers.required = True
return parser.parse_args() return parser.parse_args()
def _generate_key_pair():
"""Generate private/public key pair."""
private_key = subprocess.check_output(['wg', 'genkey'])
public_key = subprocess.check_output(['wg', 'pubkey'], input=private_key)
KEY_FOLDER.mkdir(parents=True, exist_ok=True)
with PUBLIC_KEY_PATH.open(mode='wb') as public_key_file:
public_key_file.write(public_key)
old_umask = os.umask(0o077)
try:
with PRIVATE_KEY_PATH.open(mode='wb') as private_key_file:
private_key_file.write(private_key)
finally:
os.umask(old_umask)
def subcommand_setup(_):
"""Setup WireGuard."""
# Create interface.
try:
subprocess.run(['ip', 'link', 'show', SERVER_INTERFACE],
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
check=True)
except subprocess.CalledProcessError:
subprocess.run([
'ip', 'link', 'add', 'dev', SERVER_INTERFACE, 'type', 'wireguard'
], check=True)
if not (PUBLIC_KEY_PATH.exists() and PRIVATE_KEY_PATH.exists()):
_generate_key_pair()
# Configure interface.
subprocess.run([
'wg', 'set', SERVER_INTERFACE, 'listen-port', '51820', 'private-key',
str(PRIVATE_KEY_PATH)
], check=True)
def _get_info(): def _get_info():
"""Return info for each configured interface.""" """Return info for each configured interface."""
output = subprocess.check_output(['wg', 'show', 'all', output = subprocess.check_output(['wg', 'show', 'all',
@ -125,14 +49,16 @@ def _get_info():
continue continue
fields = line.split() fields = line.split()
fields = [field if field != '(none)' else None for field in fields]
interface_name = fields[0] interface_name = fields[0]
if interface_name in interfaces: if interface_name in interfaces:
latest_handshake = int(fields[5]) if int(fields[5]) else None
peer = { peer = {
'public_key': fields[1], 'public_key': fields[1],
'preshared_key': fields[2], 'preshared_key': fields[2],
'endpoint': fields[3], 'endpoint': fields[3],
'allowed_ips': fields[4], 'allowed_ips': fields[4],
'latest_handshake': fields[5], 'latest_handshake': latest_handshake,
'transfer_rx': fields[6], 'transfer_rx': fields[6],
'transfer_tx': fields[7], 'transfer_tx': fields[7],
'persistent_keepalive': fields[8], 'persistent_keepalive': fields[8],
@ -157,20 +83,6 @@ def subcommand_get_info(_):
print(json.dumps(_get_info())) print(json.dumps(_get_info()))
def subcommand_add_client(arguments):
"""Add a client."""
subprocess.run(
['wg', 'set', SERVER_INTERFACE, 'peer', arguments.publickey],
check=True)
def subcommand_remove_client(arguments):
"""Remove a client."""
subprocess.run(
['wg', 'set', SERVER_INTERFACE, 'peer', arguments.publickey, 'remove'],
check=True)
def main(): def main():
"""Parse arguments and perform all duties.""" """Parse arguments and perform all duties."""
arguments = parse_arguments() arguments = parse_arguments()

View File

@ -18,13 +18,9 @@
FreedomBox app for wireguard. FreedomBox app for wireguard.
""" """
import datetime
import json
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from plinth import actions
from plinth import app as app_module from plinth import app as app_module
from plinth import cfg, frontpage, menu from plinth import cfg, frontpage, menu
from plinth.modules.firewall.components import Firewall from plinth.modules.firewall.components import Firewall
@ -102,98 +98,4 @@ def init():
def setup(helper, old_version=None): def setup(helper, old_version=None):
"""Install and configure the module.""" """Install and configure the module."""
helper.install(managed_packages) helper.install(managed_packages)
helper.call('post', actions.superuser_run, 'wireguard', ['setup'])
helper.call('post', app.enable) helper.call('post', app.enable)
def get_public_key():
"""Return this box's public key."""
public_key_path = '/var/lib/freedombox/wireguard/publickey'
try:
with open(public_key_path) as public_key_file:
public_key = public_key_file.read().strip()
except FileNotFoundError:
public_key = None
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'])
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': {
'public_key': my_server_info.get('public_key'),
'clients': my_server_info.get('peers'),
},
'my_client': {
'servers': my_client_servers,
},
}

View File

@ -64,7 +64,7 @@ class AddServerForm(forms.Form):
settings = { settings = {
'common': { 'common': {
'type': 'wireguard', 'type': 'wireguard',
'zone': 'internal', 'zone': 'external',
}, },
'ipv4': { 'ipv4': {
'method': 'manual', 'method': 'manual',

View File

@ -30,18 +30,22 @@
id="server-peers-list"> id="server-peers-list">
<tr> <tr>
<th>{% trans "Public Key" %}</th> <th>{% trans "Public Key" %}</th>
<th>{% trans "Allowed IPs" %}</th>
<th>{% trans "Last Connected Time" %}</th> <th>{% trans "Last Connected Time" %}</th>
</tr> </tr>
{% if server_peers %} {% if server.peers %}
{% for peer in server_peers %} {% for peer in server.peers.values %}
<tr> {% if peer.public_key %}
<td> <tr>
<a href="{% url 'wireguard:show-client' peer.public_key|urlencode:'' %}"> <td>
{{ peer.public_key }} <a href="{% url 'wireguard:show-client' peer.public_key|urlencode:'' %}">
</a> {{ peer.public_key }}
</td> </a>
<td>{{ peer.latest_handshake }}</td> </td>
</tr> <td>{{ peer.allowed_ips|join:", " }}</td>
<td>{{ peer.status.latest_handshake|default:'' }}</td>
</tr>
{% endif %}
{% endfor %} {% endfor %}
{% else %} {% else %}
<tr> <tr>
@ -54,6 +58,13 @@
{% endif %} {% endif %}
</table> </table>
<p>
{% blocktrans %}
Public key for this {{ box_name }}:
{% endblocktrans %}
<pre>{{ server.public_key }}</pre>
</p>
<a title="{% trans 'Add a new peer' %}" <a title="{% trans 'Add a new peer' %}"
role="button" class="btn btn-default" role="button" class="btn btn-default"
href="{% url 'wireguard:add-client' %}"> href="{% url 'wireguard:add-client' %}">
@ -72,15 +83,19 @@
</tr> </tr>
{% if client_peers %} {% if client_peers %}
{% for interface, server in client_peers.items %} {% for interface, server in client_peers.items %}
<tr> {% for peer in server.peers.values %}
<td>{{ server.peers.0.endpoint }}</td> {% if forloop.first %}
<td> <tr>
<a href="{% url 'wireguard:show-server' interface %}"> <td>{{ peer.endpoint }}</td>
{{ server.peers.0.public_key }} <td>
</a> <a href="{% url 'wireguard:show-server' interface %}">
</td> {{ peer.public_key }}
<td>{{ server.peers.0.status.latest_handshake }}</td> </a>
</tr> </td>
<td>{{ peer.status.latest_handshake|default:'' }}</td>
</tr>
{% endif %}
{% endfor %}
{% endfor %} {% endfor %}
{% else %} {% else %}
<tr> <tr>
@ -100,16 +115,6 @@
{% trans "Add Server" %} {% trans "Add Server" %}
</a> </a>
<h3>{% trans "Connection Information" %}</h3>
<p>
{% blocktrans %}
Public key for this {{ box_name }}:
{% endblocktrans %}
<pre>{{ public_key }}</pre>
</p>
{{ block.super }} {{ block.super }}
{% endblock %} {% endblock %}

View File

@ -22,23 +22,58 @@
{% block content %} {% block content %}
<h3>{{ title }}</h3> <h3>{% trans "Connection Information" %}</h3>
<table class="table table-bordered table-condensed table-striped">
<tbody>
<tr>
<th>{% trans "Client public key:" %}</th>
<td>{{ client.public_key }}</td>
</tr>
<tr>
<th>{% trans "IP address to use:" %}</th>
<td>{{ client.allowed_ips|join:", " }}</td>
</tr>
<tr>
<th>{% trans "Pre-shared key:" %}</th>
<td>{{ client.preshared_key }}</td>
</tr>
<tr>
<th>{% trans "Server endpoints:" %}</th>
<td>
{% for endpoint in endpoints %}
<div>{{ endpoint }}</div>
{% endfor %}
</td>
</tr>
<tr>
<th>{% trans "Server's public key:" %}</th>
<td>{{ server.public_key }}</td>
</tr>
</tbody>
</table>
<h4>{% trans "Connection Information" %}</h4> <h3>{% trans "Status" %}</h3>
<p>{% trans "IP address to use:" %}</p> <table class="table table-bordered table-condensed table-striped">
<p>{% trans "Server endpoints:" %}</p> <tbody>
<p>{% trans "Server's public key:" %} {{ my_server.public_key }}</p> <tr>
<p>{% trans "Pre-shared key:" %}</p> <th>{% trans "Data transmitted:" %}</th>
<td>{{ client.status.transfer_tx|filesizeformat }}</td>
<h4>{% trans "Status" %}</h4> </tr>
<p>{% trans "Client Public Key:" %} {{ client.public_key }}</p> <tr>
<p>{% trans "Data transmitted:" %} {{ client.transfer_tx|filesizeformat }}</p> <th>{% trans "Data received:" %}</th>
<p>{% trans "Data received:" %} {{ client.transfer_rx|filesizeformat }}</p> <td>{{ client.status.transfer_rx|filesizeformat }}</td>
<p>{% trans "Latest handshake:" %} {{ client.latest_handshake }}</p> </tr>
<tr>
<th>{% trans "Latest handshake:" %}</th>
<td>{{ client.status.latest_handshake|default:'' }}</td>
</tr>
</tbody>
</table>
<p> <p>
<a class="btn btn-default" <a class="btn btn-default"
href="{% url 'wireguard:edit-client' client.public_key|urlencode:'' %}"> href="{% url 'wireguard:edit-client' client.public_key|urlencode:'' %}">
<span class="fa fa-pencil-square-o" aria-hidden="true"></span>
{% trans "Edit Client" %} {% trans "Edit Client" %}
</a> </a>
<a class="btn btn-default" <a class="btn btn-default"

View File

@ -26,30 +26,34 @@
<table class="table table-bordered table-condensed table-striped"> <table class="table table-bordered table-condensed table-striped">
<tbody> <tbody>
<tr> {% for peer in server.peers.values %}
<th>{% trans "Endpoint:" %}</th> {% if forloop.first %}
<td>{{ server.peers.0.endpoint }}</td> <tr>
</tr> <th>{% trans "Endpoint:" %}</th>
<tr> <td>{{ peer.endpoint }}</td>
<th>{% trans "Public Key:" %}</th> </tr>
<td>{{ server.peers.0.public_key }}</td> <tr>
</tr> <th>{% trans "Public Key:" %}</th>
<tr> <td>{{ peer.public_key }}</td>
<th>{% trans "Pre-shared key:" %}</th> </tr>
<td>{{ server.peers.0.preshared_key }}</td> <tr>
</tr> <th>{% trans "Pre-shared key:" %}</th>
<tr> <td>{{ peer.preshared_key }}</td>
<th>{% trans "Data transmitted:" %}</th> </tr>
<td>{{ server.peers.0.status.transfer_tx|filesizeformat }}</td> <tr>
</tr> <th>{% trans "Data transmitted:" %}</th>
<tr> <td>{{ peer.status.transfer_tx|filesizeformat }}</td>
<th>{% trans "Data received:" %}</th> </tr>
<td>{{ server.peers.0.status.transfer_rx|filesizeformat }}</td> <tr>
</tr> <th>{% trans "Data received:" %}</th>
<tr> <td>{{ peer.status.transfer_rx|filesizeformat }}</td>
<th>{% trans "Latest handshake:" %}</th> </tr>
<td>{{ server.peers.0.status.latest_handshake }}</td> <tr>
</tr> <th>{% trans "Latest handshake:" %}</th>
<td>{{ peer.status.latest_handshake|default:'' }}</td>
</tr>
{% endif %}
{% endfor %}
</tbody> </tbody>
</table> </table>

View File

@ -18,9 +18,100 @@
Utilities for managing WireGuard. Utilities for managing WireGuard.
""" """
import datetime
import json
import subprocess import subprocess
import time
from plinth import network from plinth import actions, network
from plinth.utils import import_from_gi
nm = import_from_gi('NM', '1.0')
IP_TEMPLATE = '10.84.0.{}'
WIREGUARD_SETTING = nm.SETTING_WIREGUARD_SETTING_NAME
def get_nm_info():
"""Get information from network manager."""
client = network.get_nm_client()
connections = {}
for connection in client.get_connections():
if connection.get_connection_type() != WIREGUARD_SETTING:
continue
settings = connection.get_setting_by_name(WIREGUARD_SETTING)
secrets = connection.get_secrets(WIREGUARD_SETTING)
connection.update_secrets(WIREGUARD_SETTING, secrets)
info = {}
info['interface'] = connection.get_interface_name()
info['private_key'] = settings.get_private_key()
info['public_key'] = None
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'][peer_info['public_key']] = 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'])
status = json.loads(output)
nm_info = get_nm_info()
my_server_info = None
my_client_servers = {}
for interface, info in nm_info.items():
if interface == 'wg0':
my_server_info = info
else:
my_client_servers[interface] = info
if interface not in status:
continue
info['public_key'] = status[interface]['public_key']
for status_peer in status[interface]['peers']:
if status_peer['latest_handshake']:
status_peer['latest_handshake'] = \
datetime.datetime.fromtimestamp(
status_peer['latest_handshake'])
public_key = status_peer['public_key']
info_peer = info['peers'].setdefault(public_key, {})
info_peer['status'] = status_peer
return {
'my_server': my_server_info,
'my_client': {
'servers': my_client_servers,
},
}
def find_next_interface(): def find_next_interface():
@ -38,8 +129,106 @@ def find_next_interface():
def add_server(settings): def add_server(settings):
"""Add a server.""" """Add information for connecting to a server."""
interface_name = find_next_interface() interface_name = find_next_interface()
settings['common']['name'] = 'WireGuard-' + interface_name settings['common']['name'] = 'WireGuard-Client-' + interface_name
settings['common']['interface'] = interface_name settings['common']['interface'] = interface_name
network.add_connection(settings) network.add_connection(settings)
def setup_server():
"""Setup a server connection that clients can connect to."""
process = subprocess.run(['wg', 'genkey'], check=True, capture_output=True)
private_key = process.stdout.decode().strip()
settings = {
'common': {
'name': 'WireGuard-Server-wg0',
'type': WIREGUARD_SETTING,
'zone': 'internal',
'interface': 'wg0'
},
'ipv4': {
'method': 'manual',
'address': IP_TEMPLATE.format(1),
'netmask': '255.255.255.0',
'gateway': '',
'dns': '',
'second_dns': '',
},
'wireguard': {
'private_key': private_key,
'listen_port': 51820,
}
}
network.add_connection(settings)
def _get_next_available_ip_address(settings):
"""Get the next available IP address to allocate to a client."""
allocated_ips = set()
for peer_index in range(settings.get_peers_len()):
peer = settings.get_peer(peer_index)
for ip_index in range(peer.get_allowed_ips_len()):
allowed_ip = peer.get_allowed_ip(ip_index)
# We assume these are simple IP addresses but they can be subnets.
allocated_ips.add(allowed_ip)
for index in range(2, 254):
ip_address = IP_TEMPLATE.format(index)
if ip_address not in allocated_ips:
return ip_address
raise IndexError('Reached client limit')
def _server_connection():
"""Return a server connection. Create one if necessary."""
connection = network.get_connection_by_interface_name('wg0')
if not connection:
setup_server()
for _ in range(10):
# XXX: Improve this waiting by doing a synchronous D-Bus operation to
# add network manager connection instead.
time.sleep(1)
connection = network.get_connection_by_interface_name('wg0')
if connection:
break
if not connection:
raise RuntimeError('Unable to create a server connection.')
# Retrieve secrets so that when the connection is changed, secrets are
# preserved properly.
secrets = connection.get_secrets(WIREGUARD_SETTING)
connection.update_secrets(WIREGUARD_SETTING, secrets)
return connection
def add_client(public_key):
"""Add a permission for a client to connect our server."""
connection = _server_connection()
settings = connection.get_setting_by_name(WIREGUARD_SETTING)
peer, _ = settings.get_peer_by_public_key(public_key)
if peer:
raise ValueError('Peer with public key already exists')
peer = nm.WireGuardPeer.new()
peer.set_public_key(public_key, False)
peer.set_persistent_keepalive(25) # To keep NAT 'connections' alive
peer.append_allowed_ip(_get_next_available_ip_address(settings), False)
settings.append_peer(peer)
connection.commit_changes(True)
def remove_client(public_key):
"""Remove permission for a client to connect our server."""
connection = _server_connection()
settings = connection.get_setting_by_name(WIREGUARD_SETTING)
peer, peer_index = settings.get_peer_by_public_key(public_key)
if not peer:
raise KeyError('Client not found')
settings.remove_peer(peer_index)
connection.commit_changes(True)

View File

@ -29,7 +29,8 @@ from django.utils.translation import ugettext as _
from django.views.generic import FormView, TemplateView from django.views.generic import FormView, TemplateView
import plinth.modules.wireguard as wireguard import plinth.modules.wireguard as wireguard
from plinth import actions, network from plinth import network
from plinth.modules.names.components import DomainName
from plinth.views import AppView from plinth.views import AppView
from . import forms, utils from . import forms, utils
@ -49,9 +50,8 @@ class WireguardView(AppView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
"""Return additional context for rendering the template.""" """Return additional context for rendering the template."""
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context['public_key'] = wireguard.get_public_key() info = utils.get_info()
info = wireguard.get_info() context['server'] = info['my_server']
context['server_peers'] = info['my_server']['clients']
context['client_peers'] = info['my_client']['servers'] context['client_peers'] = info['my_client']['servers']
return context return context
@ -72,7 +72,13 @@ class AddClientView(SuccessMessageMixin, FormView):
def form_valid(self, form): def form_valid(self, form):
"""Add the client.""" """Add the client."""
public_key = form.cleaned_data.get('public_key') public_key = form.cleaned_data.get('public_key')
actions.superuser_run('wireguard', ['add-client', public_key]) try:
utils.add_client(public_key)
except ValueError:
messages.warning(self.request,
_('Client with public key already exists'))
return redirect('wireguard:index')
return super().form_valid(form) return super().form_valid(form)
@ -86,12 +92,17 @@ class ShowClientView(SuccessMessageMixin, TemplateView):
context['title'] = _('Show Client') context['title'] = _('Show Client')
public_key = urllib.parse.unquote(self.kwargs['public_key']) public_key = urllib.parse.unquote(self.kwargs['public_key'])
info = wireguard.get_info() server_info = utils.get_info()['my_server']
context.update(info) if not server_info or public_key not in server_info['peers']:
for client in info['my_server']['clients']: raise Http404
if client['public_key'] == public_key:
context['client'] = client
domains = DomainName.list_names(filter_for_service='wireguard')
context['server'] = server_info
context['client'] = server_info['peers'][public_key]
context['endpoints'] = [
domain + ':' + str(server_info['listen_port'])
for domain in domains
]
return context return context
@ -117,10 +128,17 @@ class EditClientView(SuccessMessageMixin, FormView):
def form_valid(self, form): def form_valid(self, form):
"""Update the client.""" """Update the client."""
old_public_key = form.initial['public_key'] old_public_key = form.initial['public_key']
actions.superuser_run('wireguard', ['remove-client', old_public_key])
public_key = form.cleaned_data.get('public_key') public_key = form.cleaned_data.get('public_key')
actions.superuser_run('wireguard', ['add-client', public_key])
if old_public_key != public_key:
try:
utils.add_client(public_key)
except ValueError:
messages.warning(self.request,
_('Client with public key already exists'))
utils.remove_client(old_public_key)
return super().form_valid(form) return super().form_valid(form)
@ -138,8 +156,12 @@ class DeleteClientView(SuccessMessageMixin, TemplateView):
def post(self, request, public_key): def post(self, request, public_key):
"""Delete the client.""" """Delete the client."""
public_key = urllib.parse.unquote(public_key) public_key = urllib.parse.unquote(public_key)
actions.superuser_run('wireguard', ['remove-client', public_key]) try:
messages.success(request, _('Client deleted.')) utils.remove_client(public_key)
messages.success(request, _('Client deleted.'))
except KeyError:
messages.error(request, _('Client not found'))
return redirect('wireguard:index') return redirect('wireguard:index')
@ -172,7 +194,7 @@ class ShowServerView(SuccessMessageMixin, TemplateView):
context['title'] = _('Server Information') context['title'] = _('Server Information')
interface = self.kwargs['interface'] interface = self.kwargs['interface']
info = wireguard.get_info() info = utils.get_info()
server = info['my_client']['servers'].get(interface) server = info['my_client']['servers'].get(interface)
if not server: if not server:
raise Http404 raise Http404
@ -199,14 +221,14 @@ class EditServerView(SuccessMessageMixin, FormView):
"""Get initial form data.""" """Get initial form data."""
initial = super().get_initial() initial = super().get_initial()
interface = self.kwargs['interface'] interface = self.kwargs['interface']
info = wireguard.get_nm_info() info = utils.get_nm_info()
server = info.get(interface) server = info.get(interface)
if not server: if not server:
raise Http404 raise Http404
initial['ip_address'] = server.get('ip_address') initial['ip_address'] = server.get('ip_address')
if server['peers']: if server['peers']:
peer = server['peers'][0] peer = next(peer for peer in server['peers'].values())
initial['peer_endpoint'] = peer['endpoint'] initial['peer_endpoint'] = peer['endpoint']
initial['peer_public_key'] = peer['public_key'] initial['peer_public_key'] = peer['public_key']
initial['private_key'] = server['private_key'] initial['private_key'] = server['private_key']
@ -220,7 +242,7 @@ class EditServerView(SuccessMessageMixin, FormView):
settings = form.get_settings() settings = form.get_settings()
interface = self.kwargs['interface'] interface = self.kwargs['interface']
settings['common']['interface'] = interface settings['common']['interface'] = interface
settings['common']['name'] = 'WireGuard-' + interface settings['common']['name'] = 'WireGuard-Client-' + interface
connection = network.get_connection_by_interface_name(interface) connection = network.get_connection_by_interface_name(interface)
network.edit_connection(connection, settings) network.edit_connection(connection, settings)
return super().form_valid(form) return super().form_valid(form)
@ -236,14 +258,14 @@ class DeleteServerView(SuccessMessageMixin, TemplateView):
context['title'] = _('Delete Server') context['title'] = _('Delete Server')
interface = self.kwargs['interface'] interface = self.kwargs['interface']
info = wireguard.get_nm_info() info = utils.get_nm_info()
server = info.get(interface) server = info.get(interface)
if not server: if not server:
raise Http404 raise Http404
context['interface'] = interface context['interface'] = interface
if server['peers']: if server['peers']:
peer = server['peers'][0] peer = next(peer for peer in server['peers'].values())
context['peer_endpoint'] = peer['endpoint'] context['peer_endpoint'] = peer['endpoint']
context['peer_public_key'] = peer['public_key'] context['peer_public_key'] = peer['public_key']

View File

@ -477,19 +477,27 @@ def _update_wireguard_settings(connection, wireguard):
settings.set_property(nm.SETTING_WIREGUARD_PRIVATE_KEY, settings.set_property(nm.SETTING_WIREGUARD_PRIVATE_KEY,
wireguard['private_key']) wireguard['private_key'])
peer = nm.WireGuardPeer.new() if 'listen_port' in wireguard:
peer.set_endpoint(wireguard['peer_endpoint'], False) settings.set_property(nm.SETTING_WIREGUARD_LISTEN_PORT,
peer.set_public_key(wireguard['peer_public_key'], False) wireguard['listen_port'])
if wireguard['preshared_key']:
# Flag NONE means that NM should store and retain the secret.
# Default seems to be NOT_REQUIRED in this case.
peer.set_preshared_key_flags(nm.SettingSecretFlags.NONE)
peer.set_preshared_key(wireguard['preshared_key'], False)
peer.append_allowed_ip('0.0.0.0/0', False) if 'peer_public_key' in wireguard:
peer.append_allowed_ip('::/0', False) peer = nm.WireGuardPeer.new()
settings.clear_peers() peer.set_public_key(wireguard['peer_public_key'], False)
settings.append_peer(peer)
if 'peer_endpoint' in wireguard:
peer.set_endpoint(wireguard['peer_endpoint'], False)
if wireguard['preshared_key']:
# Flag NONE means that NM should store and retain the secret.
# Default seems to be NOT_REQUIRED in this case.
peer.set_preshared_key_flags(nm.SettingSecretFlags.NONE)
peer.set_preshared_key(wireguard['preshared_key'], False)
peer.append_allowed_ip('0.0.0.0/0', False)
peer.append_allowed_ip('::/0', False)
settings.clear_peers()
settings.append_peer(peer)
def _update_settings(connection, connection_uuid, settings): def _update_settings(connection, connection_uuid, settings):