mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-05-27 10:44:33 +00:00
wireguard: Only use network manager for connections to servers
- Don't create network link. This don't persist across reboots and it is the job of Network Manager. - Move NM settings code to regular plinth process instead of superuser. Permission for managing NM connections from the service daemon is granted by PolKit. - Use interface name to identify the connection as it seems to be simply to do so than the public key. Public key is not easy to retrieve from NM connection. - Merge code for adding and editing the connection to avoid repetition. - Add icon to the edit button. - Throw 404 error when incorrect client is specified. - Fix issue with storing preshared key. - Show formatting date in case of last connected time. - Show formatted sizes for data transmitted. Signed-off-by: Sunil Mohan Adapa <sunil@medhas.org> Reviewed-by: James Valleroy <jvalleroy@mailbox.org>
This commit is contained in:
parent
b96c5e5433
commit
71c7ab4a9d
@ -24,9 +24,6 @@ import json
|
|||||||
import os
|
import os
|
||||||
import pathlib
|
import pathlib
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
|
||||||
|
|
||||||
from plinth import network
|
|
||||||
|
|
||||||
PUBLIC_KEY_HELP = 'Public key for the client'
|
PUBLIC_KEY_HELP = 'Public key for the client'
|
||||||
|
|
||||||
@ -57,17 +54,6 @@ def parse_arguments():
|
|||||||
help='Remove a client')
|
help='Remove a client')
|
||||||
remove_client.add_argument('publickey', help=PUBLIC_KEY_HELP)
|
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',
|
modify_server = subparsers.add_parser('modify-server',
|
||||||
help='Modify a server')
|
help='Modify a server')
|
||||||
modify_server.add_argument('--endpoint', required=True,
|
modify_server.add_argument('--endpoint', required=True,
|
||||||
@ -185,129 +171,6 @@ def subcommand_remove_client(arguments):
|
|||||||
check=True)
|
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():
|
def main():
|
||||||
"""Parse arguments and perform all duties."""
|
"""Parse arguments and perform all duties."""
|
||||||
arguments = parse_arguments()
|
arguments = parse_arguments()
|
||||||
|
|||||||
@ -18,6 +18,7 @@
|
|||||||
FreedomBox app for wireguard.
|
FreedomBox app for wireguard.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import datetime
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
@ -27,10 +28,12 @@ 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
|
||||||
from plinth.utils import format_lazy
|
from plinth.utils import format_lazy, import_from_gi
|
||||||
|
|
||||||
from .manifest import clients # noqa, pylint: disable=unused-import
|
from .manifest import clients # noqa, pylint: disable=unused-import
|
||||||
|
|
||||||
|
nm = import_from_gi('NM', '1.0')
|
||||||
|
|
||||||
version = 1
|
version = 1
|
||||||
|
|
||||||
managed_packages = ['wireguard']
|
managed_packages = ['wireguard']
|
||||||
@ -116,15 +119,74 @@ def get_public_key():
|
|||||||
return 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():
|
def get_info():
|
||||||
"""Return server and clients info."""
|
"""Return server and clients info."""
|
||||||
output = actions.superuser_run('wireguard', ['get-info'])
|
output = actions.superuser_run('wireguard', ['get-info'])
|
||||||
info = json.loads(output)
|
status = json.loads(output)
|
||||||
my_server_info = info.pop(SERVER_INTERFACE, {})
|
|
||||||
my_client_servers = []
|
nm_info = get_nm_info()
|
||||||
for interface in info.values():
|
|
||||||
if interface['peers']:
|
my_server_info = status.pop(SERVER_INTERFACE, {})
|
||||||
my_client_servers.append(interface['peers'][0])
|
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 {
|
return {
|
||||||
'my_server': {
|
'my_server': {
|
||||||
|
|||||||
@ -31,30 +31,55 @@ class AddClientForm(forms.Form):
|
|||||||
|
|
||||||
class AddServerForm(forms.Form):
|
class AddServerForm(forms.Form):
|
||||||
"""Form to add server."""
|
"""Form to add server."""
|
||||||
endpoint = forms.CharField(
|
peer_endpoint = forms.CharField(
|
||||||
label=_('Endpoint'), strip=True,
|
label=_('Endpoint'), strip=True,
|
||||||
help_text=_('Server endpoint with the form "ip:port".'))
|
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,
|
label=_('Client IP address provided by server'), strip=True,
|
||||||
help_text=_('IP address assigned to the client on the VPN after '
|
help_text=_('IP address assigned to the client on the VPN after '
|
||||||
'connecting to the endpoint.'))
|
'connecting to the endpoint.'))
|
||||||
|
|
||||||
public_key = forms.CharField(
|
private_key = forms.CharField(
|
||||||
label=_('Public key of the server'), strip=True,
|
|
||||||
help_text=_('Public key of the server.'))
|
|
||||||
|
|
||||||
client_private_key = forms.CharField(
|
|
||||||
label=_('Private key of the client'), strip=True,
|
label=_('Private key of the client'), strip=True,
|
||||||
help_text=_('Optional. A new key is generated if left blank.'),
|
help_text=_('Optional. A new key is generated if left blank.'),
|
||||||
required=False)
|
required=False)
|
||||||
|
|
||||||
pre_shared_key = forms.CharField(
|
preshared_key = forms.CharField(
|
||||||
label=_('Pre-shared key'), strip=True, required=False,
|
label=_('Pre-shared key'), strip=True, required=False,
|
||||||
help_text=_('Optional. A shared secret key provided by the server to '
|
help_text=_('Optional. A shared secret key provided by the server to '
|
||||||
'add an additional layer of encryption.'))
|
'add an additional layer of encryption.'))
|
||||||
|
|
||||||
all_outgoing_traffic = forms.BooleanField(
|
default_route = forms.BooleanField(
|
||||||
label=_('Use this connection to send all outgoing traffic'),
|
label=_('Use this connection to send all outgoing traffic'),
|
||||||
required=False,
|
required=False,
|
||||||
help_text=_('Use this connection to send all outgoing traffic.'))
|
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
|
||||||
|
|||||||
@ -43,7 +43,6 @@
|
|||||||
<td>{{ peer.latest_handshake }}</td>
|
<td>{{ peer.latest_handshake }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
{% else %}
|
{% else %}
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="3">
|
<td colspan="3">
|
||||||
@ -72,18 +71,17 @@
|
|||||||
<th>{% trans "Last Connected Time" %}</th>
|
<th>{% trans "Last Connected Time" %}</th>
|
||||||
</tr>
|
</tr>
|
||||||
{% if client_peers %}
|
{% if client_peers %}
|
||||||
{% for peer in client_peers %}
|
{% for interface, server in client_peers.items %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ peer.endpoint }}</td>
|
<td>{{ server.peers.0.endpoint }}</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="{% url 'wireguard:show-server' peer.public_key|urlencode:'' %}">
|
<a href="{% url 'wireguard:show-server' interface %}">
|
||||||
{{ peer.public_key }}
|
{{ server.peers.0.public_key }}
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td>{{ peer.latest_handshake }}</td>
|
<td>{{ server.peers.0.status.latest_handshake }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
{% else %}
|
{% else %}
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="4">
|
<td colspan="4">
|
||||||
|
|||||||
@ -28,9 +28,18 @@
|
|||||||
<p>
|
<p>
|
||||||
{% trans "Are you sure that you want to delete this server?" %}
|
{% trans "Are you sure that you want to delete this server?" %}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<table class="table table-bordered table-condensed table striped">
|
||||||
<b>{{ public_key }}</b>
|
<tbody>
|
||||||
</p>
|
<tr>
|
||||||
|
<th>{% trans "Endpoint" %}</th>
|
||||||
|
<td>{{ peer_endpoint }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>{% trans "Public Key" %}</th>
|
||||||
|
<td>{{ peer_public_key }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
<form class="form" method="post">
|
<form class="form" method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|||||||
@ -32,8 +32,8 @@
|
|||||||
|
|
||||||
<h4>{% trans "Status" %}</h4>
|
<h4>{% trans "Status" %}</h4>
|
||||||
<p>{% trans "Client Public Key:" %} {{ client.public_key }}</p>
|
<p>{% trans "Client Public Key:" %} {{ client.public_key }}</p>
|
||||||
<p>{% trans "Data transmitted:" %} {{ client.transfer_tx }}</p>
|
<p>{% trans "Data transmitted:" %} {{ client.transfer_tx|filesizeformat }}</p>
|
||||||
<p>{% trans "Data received:" %} {{ client.transfer_rx }}</p>
|
<p>{% trans "Data received:" %} {{ client.transfer_rx|filesizeformat }}</p>
|
||||||
<p>{% trans "Latest handshake:" %} {{ client.latest_handshake }}</p>
|
<p>{% trans "Latest handshake:" %} {{ client.latest_handshake }}</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
|
|||||||
@ -24,21 +24,43 @@
|
|||||||
|
|
||||||
<h3>{{ title }}</h3>
|
<h3>{{ title }}</h3>
|
||||||
|
|
||||||
<h4>{% trans "Server Information" %}</h4>
|
<table class="table table-bordered table-condensed table-striped">
|
||||||
<p>{% trans "Endpoint:" %} {{ server.endpoint }}</p>
|
<tbody>
|
||||||
<p>{% trans "Public Key:" %} {{ server.public_key }}</p>
|
<tr>
|
||||||
<p>{% trans "Pre-shared key:" %} {{ server.preshared_key }}</p>
|
<th>{% trans "Endpoint:" %}</th>
|
||||||
<p>{% trans "Data transmitted:" %} {{ server.transfer_tx }}</p>
|
<td>{{ server.peers.0.endpoint }}</td>
|
||||||
<p>{% trans "Data received:" %} {{ server.transfer_rx }}</p>
|
</tr>
|
||||||
<p>{% trans "Latest handshake:" %} {{ server.latest_handshake }}</p>
|
<tr>
|
||||||
|
<th>{% trans "Public Key:" %}</th>
|
||||||
|
<td>{{ server.peers.0.public_key }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>{% trans "Pre-shared key:" %}</th>
|
||||||
|
<td>{{ server.peers.0.preshared_key }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>{% trans "Data transmitted:" %}</th>
|
||||||
|
<td>{{ server.peers.0.status.transfer_tx|filesizeformat }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>{% trans "Data received:" %}</th>
|
||||||
|
<td>{{ server.peers.0.status.transfer_rx|filesizeformat }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>{% trans "Latest handshake:" %}</th>
|
||||||
|
<td>{{ server.peers.0.status.latest_handshake }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<a class="btn btn-default"
|
<a class="btn btn-default"
|
||||||
href="{% url 'wireguard:edit-server' server.public_key|urlencode:'' %}">
|
href="{% url 'wireguard:edit-server' interface %}">
|
||||||
|
<span class="fa fa-pencil-square-o" aria-hidden="true"></span>
|
||||||
{% trans "Edit Server" %}
|
{% trans "Edit Server" %}
|
||||||
</a>
|
</a>
|
||||||
<a class="btn btn-default"
|
<a class="btn btn-default"
|
||||||
href="{% url 'wireguard:delete-server' server.public_key|urlencode:'' %}">
|
href="{% url 'wireguard:delete-server' interface %}">
|
||||||
<span class="fa fa-trash-o" aria-hidden="true"></span>
|
<span class="fa fa-trash-o" aria-hidden="true"></span>
|
||||||
{% trans "Delete Server" %}
|
{% trans "Delete Server" %}
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@ -34,10 +34,10 @@ urlpatterns = [
|
|||||||
views.DeleteClientView.as_view(), name='delete-client'),
|
views.DeleteClientView.as_view(), name='delete-client'),
|
||||||
url(r'^apps/wireguard/server/add/$', views.AddServerView.as_view(),
|
url(r'^apps/wireguard/server/add/$', views.AddServerView.as_view(),
|
||||||
name='add-server'),
|
name='add-server'),
|
||||||
url(r'^apps/wireguard/server/(?P<public_key>[^/]+)/show/$',
|
url(r'^apps/wireguard/server/(?P<interface>wg[0-9]+)/show/$',
|
||||||
views.ShowServerView.as_view(), name='show-server'),
|
views.ShowServerView.as_view(), name='show-server'),
|
||||||
url(r'^apps/wireguard/server/(?P<public_key>[^/]+)/edit/$',
|
url(r'^apps/wireguard/server/(?P<interface>wg[0-9]+)/edit/$',
|
||||||
views.EditServerView.as_view(), name='edit-server'),
|
views.EditServerView.as_view(), name='edit-server'),
|
||||||
url(r'^apps/wireguard/server/(?P<public_key>[^/]+)/delete/$',
|
url(r'^apps/wireguard/server/(?P<interface>wg[0-9]+)/delete/$',
|
||||||
views.DeleteServerView.as_view(), name='delete-server'),
|
views.DeleteServerView.as_view(), name='delete-server'),
|
||||||
]
|
]
|
||||||
|
|||||||
45
plinth/modules/wireguard/utils.py
Normal file
45
plinth/modules/wireguard/utils.py
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
#
|
||||||
|
# This file is part of FreedomBox.
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero 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 Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
"""
|
||||||
|
Utilities for managing WireGuard.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
from plinth import network
|
||||||
|
|
||||||
|
|
||||||
|
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 add_server(settings):
|
||||||
|
"""Add a server."""
|
||||||
|
interface_name = find_next_interface()
|
||||||
|
settings['common']['name'] = 'WireGuard-' + interface_name
|
||||||
|
settings['common']['interface'] = interface_name
|
||||||
|
network.add_connection(settings)
|
||||||
@ -18,22 +18,21 @@
|
|||||||
Views for WireGuard application.
|
Views for WireGuard application.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import json
|
|
||||||
import tempfile
|
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
|
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.messages.views import SuccessMessageMixin
|
from django.contrib.messages.views import SuccessMessageMixin
|
||||||
|
from django.http import Http404
|
||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
from django.utils.translation import ugettext as _
|
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
|
from plinth import actions, network
|
||||||
from plinth.views import AppView
|
from plinth.views import AppView
|
||||||
|
|
||||||
from . import forms
|
from . import forms, utils
|
||||||
|
|
||||||
|
|
||||||
class WireguardView(AppView):
|
class WireguardView(AppView):
|
||||||
@ -159,29 +158,7 @@ class AddServerView(SuccessMessageMixin, FormView):
|
|||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
"""Add the server."""
|
"""Add the server."""
|
||||||
endpoint = form.cleaned_data.get('endpoint')
|
utils.add_server(form.get_settings())
|
||||||
client_ip_address = form.cleaned_data.get('client_ip_address')
|
|
||||||
public_key = form.cleaned_data.get('public_key')
|
|
||||||
client_private_key = form.cleaned_data.get('client_private_key')
|
|
||||||
pre_shared_key = form.cleaned_data.get('pre_shared_key')
|
|
||||||
all_outgoing_traffic = form.cleaned_data.get('all_outgoing_traffic')
|
|
||||||
args = [
|
|
||||||
'add-server', '--endpoint', endpoint, '--client-ip',
|
|
||||||
client_ip_address, '--public-key', public_key
|
|
||||||
]
|
|
||||||
secret_args = {}
|
|
||||||
if client_private_key:
|
|
||||||
secret_args['client_private_key'] = client_private_key
|
|
||||||
|
|
||||||
if pre_shared_key:
|
|
||||||
secret_args['pre_shared_key'] = pre_shared_key
|
|
||||||
|
|
||||||
if all_outgoing_traffic:
|
|
||||||
args.append('--all-outgoing')
|
|
||||||
|
|
||||||
actions.superuser_run('wireguard', args,
|
|
||||||
input=json.dumps(secret_args).encode())
|
|
||||||
|
|
||||||
return super().form_valid(form)
|
return super().form_valid(form)
|
||||||
|
|
||||||
|
|
||||||
@ -192,15 +169,16 @@ class ShowServerView(SuccessMessageMixin, TemplateView):
|
|||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
"""Return additional context data for rendering the template."""
|
"""Return additional context data for rendering the template."""
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
context['title'] = _('Show Server')
|
context['title'] = _('Server Information')
|
||||||
|
|
||||||
public_key = urllib.parse.unquote(self.kwargs['public_key'])
|
interface = self.kwargs['interface']
|
||||||
info = wireguard.get_info()
|
info = wireguard.get_info()
|
||||||
context.update(info)
|
server = info['my_client']['servers'].get(interface)
|
||||||
for server in info['my_client']['servers']:
|
if not server:
|
||||||
if server['public_key'] == public_key:
|
raise Http404
|
||||||
context['server'] = server
|
|
||||||
|
|
||||||
|
context['interface'] = interface
|
||||||
|
context['server'] = server
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
@ -220,48 +198,31 @@ class EditServerView(SuccessMessageMixin, FormView):
|
|||||||
def get_initial(self):
|
def get_initial(self):
|
||||||
"""Get initial form data."""
|
"""Get initial form data."""
|
||||||
initial = super().get_initial()
|
initial = super().get_initial()
|
||||||
public_key = urllib.parse.unquote(self.kwargs['public_key'])
|
interface = self.kwargs['interface']
|
||||||
info = wireguard.get_info()
|
info = wireguard.get_nm_info()
|
||||||
for server in info['my_client']['servers']:
|
server = info.get(interface)
|
||||||
if server['public_key'] == public_key:
|
if not server:
|
||||||
initial['endpoint'] = server['endpoint']
|
raise Http404
|
||||||
initial['client_ip_address'] = ''
|
|
||||||
initial['public_key'] = server['public_key']
|
|
||||||
pre_shared_key = server['preshared_key']
|
|
||||||
if pre_shared_key == '(none)':
|
|
||||||
initial['pre_shared_key'] = ''
|
|
||||||
else:
|
|
||||||
initial['pre_shared_key'] = server['preshared_key']
|
|
||||||
|
|
||||||
initial['all_outgoing_traffic'] = False
|
initial['ip_address'] = server.get('ip_address')
|
||||||
|
if server['peers']:
|
||||||
|
peer = server['peers'][0]
|
||||||
|
initial['peer_endpoint'] = peer['endpoint']
|
||||||
|
initial['peer_public_key'] = peer['public_key']
|
||||||
|
initial['private_key'] = server['private_key']
|
||||||
|
initial['preshared_key'] = peer['preshared_key']
|
||||||
|
initial['default_route'] = server['default_route']
|
||||||
|
|
||||||
return initial
|
return initial
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
"""Update the server."""
|
"""Update the server."""
|
||||||
endpoint = form.cleaned_data.get('endpoint')
|
settings = form.get_settings()
|
||||||
client_ip_address = form.cleaned_data.get('client_ip_address')
|
interface = self.kwargs['interface']
|
||||||
public_key = form.cleaned_data.get('public_key')
|
settings['common']['interface'] = interface
|
||||||
client_private_key = form.client_data.get('client_private_key')
|
settings['common']['name'] = 'WireGuard-' + interface
|
||||||
pre_shared_key = form.cleaned_data.get('pre_shared_key')
|
connection = network.get_connection_by_interface_name(interface)
|
||||||
all_outgoing_traffic = form.cleaned_data.get('all_outgoing_traffic')
|
network.edit_connection(connection, settings)
|
||||||
args = [
|
|
||||||
'modify-server', '--endpoint', endpoint, '--client-ip',
|
|
||||||
client_ip_address, '--public-key', public_key
|
|
||||||
]
|
|
||||||
secret_args = {}
|
|
||||||
if client_private_key:
|
|
||||||
secret_args['client_private_key'] = client_private_key
|
|
||||||
|
|
||||||
if pre_shared_key:
|
|
||||||
secret_args['pre_shared_key'] = pre_shared_key
|
|
||||||
|
|
||||||
if all_outgoing_traffic:
|
|
||||||
args.append('--all-outgoing')
|
|
||||||
|
|
||||||
actions.superuser_run('wireguard', args,
|
|
||||||
input=json.dumps(secret_args).encode())
|
|
||||||
|
|
||||||
return super().form_valid(form)
|
return super().form_valid(form)
|
||||||
|
|
||||||
|
|
||||||
@ -273,12 +234,24 @@ class DeleteServerView(SuccessMessageMixin, TemplateView):
|
|||||||
"""Return additional context data for rendering the template."""
|
"""Return additional context data for rendering the template."""
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
context['title'] = _('Delete Server')
|
context['title'] = _('Delete Server')
|
||||||
context['public_key'] = urllib.parse.unquote(self.kwargs['public_key'])
|
|
||||||
|
interface = self.kwargs['interface']
|
||||||
|
info = wireguard.get_nm_info()
|
||||||
|
server = info.get(interface)
|
||||||
|
if not server:
|
||||||
|
raise Http404
|
||||||
|
|
||||||
|
context['interface'] = interface
|
||||||
|
if server['peers']:
|
||||||
|
peer = server['peers'][0]
|
||||||
|
context['peer_endpoint'] = peer['endpoint']
|
||||||
|
context['peer_public_key'] = peer['public_key']
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def post(self, request, public_key):
|
def post(self, request, interface):
|
||||||
"""Delete the server."""
|
"""Delete the server."""
|
||||||
public_key = urllib.parse.unquote(public_key)
|
connection = network.get_connection_by_interface_name(interface)
|
||||||
actions.superuser_run('wireguard', ['remove-server', public_key])
|
network.delete_connection(connection.get_uuid())
|
||||||
messages.success(request, _('Server deleted.'))
|
messages.success(request, _('Server deleted.'))
|
||||||
return redirect('wireguard:index')
|
return redirect('wireguard:index')
|
||||||
|
|||||||
@ -470,6 +470,8 @@ def _update_wireless_settings(connection, wireless):
|
|||||||
|
|
||||||
def _update_wireguard_settings(connection, wireguard):
|
def _update_wireguard_settings(connection, wireguard):
|
||||||
"""Create/edit WireGuard settings for network manager connections."""
|
"""Create/edit WireGuard settings for network manager connections."""
|
||||||
|
settings = connection.get_setting_by_name('wireguard')
|
||||||
|
if not settings:
|
||||||
settings = nm.SettingWireGuard.new()
|
settings = nm.SettingWireGuard.new()
|
||||||
connection.add_setting(settings)
|
connection.add_setting(settings)
|
||||||
|
|
||||||
@ -478,9 +480,15 @@ def _update_wireguard_settings(connection, wireguard):
|
|||||||
peer = nm.WireGuardPeer.new()
|
peer = nm.WireGuardPeer.new()
|
||||||
peer.set_endpoint(wireguard['peer_endpoint'], False)
|
peer.set_endpoint(wireguard['peer_endpoint'], False)
|
||||||
peer.set_public_key(wireguard['peer_public_key'], False)
|
peer.set_public_key(wireguard['peer_public_key'], 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.set_preshared_key(wireguard['preshared_key'], False)
|
||||||
|
|
||||||
peer.append_allowed_ip('0.0.0.0/0', False)
|
peer.append_allowed_ip('0.0.0.0/0', False)
|
||||||
peer.append_allowed_ip('::/0', False)
|
peer.append_allowed_ip('::/0', False)
|
||||||
|
settings.clear_peers()
|
||||||
settings.append_peer(peer)
|
settings.append_peer(peer)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user