mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-05-27 10:44:33 +00:00
wireguard: Accept/use netmask with IP address for server connection
- Currently, the value is hard-coded as /24. Instead take this as input and use that value. Tests: - Entering invalid IPv4 address results in 'Enter a valid IPv4 address' error message during form submission. - Entering invalid prefix such as /33 results in 'Enter a valid network prefix or net mask.' error during form submission. - Both /32 and /255.255.255.255 formats are accepted. - The description text for the form field 'IP address' is as expected. - Changing the value of default route and IP address + netmask reflects in the status page. Correct values is shown in the edit server and server status page. - Not providing a netmask results in /32 being assigned. - Unit and functional tests for wireguard pass. There are some intermittent failures with functional tests that are unrelated to the patch. - Setting the /32 prefix results in correct routing table as shown by 'ip route show table all'. No default routes are network routes are present. 'traceroute 1.1.1.1' shows route taken via regular network. - Setting the /24 prefix results in correct routing table. No default routes are present. However, for the /24 network a route is present with device wg1. 'traceroute 1.1.1.1' shows route taken via regular network. - Enabling the default route results in correct routing table. Default route is shown for device wg1 with high priority. 'traceroute 1.1.1.1' shows route taken via WireGuard network. Signed-off-by: Sunil Mohan Adapa <sunil@medhas.org> Reviewed-by: James Valleroy <jvalleroy@mailbox.org>
This commit is contained in:
parent
ad9ebe2301
commit
4b24fda3f5
@ -5,10 +5,10 @@ Forms for wireguard module.
|
|||||||
|
|
||||||
import base64
|
import base64
|
||||||
import binascii
|
import binascii
|
||||||
|
import ipaddress
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.core.validators import validate_ipv4_address
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
KEY_LENGTH = 32
|
KEY_LENGTH = 32
|
||||||
@ -55,6 +55,16 @@ def validate_endpoint(endpoint):
|
|||||||
raise ValidationError('Invalid endpoint.')
|
raise ValidationError('Invalid endpoint.')
|
||||||
|
|
||||||
|
|
||||||
|
def validate_ipv4_address_with_network(value: str):
|
||||||
|
"""Check that value is a valid IPv4 address with an optional network."""
|
||||||
|
try:
|
||||||
|
ipaddress.IPv4Interface(value)
|
||||||
|
except ipaddress.AddressValueError:
|
||||||
|
raise ValidationError(_('Enter a valid IPv4 address.'))
|
||||||
|
except ipaddress.NetmaskValueError:
|
||||||
|
raise ValidationError(_('Enter a valid network prefix or net mask.'))
|
||||||
|
|
||||||
|
|
||||||
class AddClientForm(forms.Form):
|
class AddClientForm(forms.Form):
|
||||||
"""Form to add client."""
|
"""Form to add client."""
|
||||||
public_key = forms.CharField(
|
public_key = forms.CharField(
|
||||||
@ -78,12 +88,15 @@ class AddServerForm(forms.Form):
|
|||||||
'Example: MConEJFIg6+DFHg2J1nn9SNLOSE9KR0ysdPgmPjibEs= .'),
|
'Example: MConEJFIg6+DFHg2J1nn9SNLOSE9KR0ysdPgmPjibEs= .'),
|
||||||
validators=[validate_key])
|
validators=[validate_key])
|
||||||
|
|
||||||
ip_address = forms.CharField(
|
ip_address_and_network = 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 this machine on the VPN after '
|
help_text=_(
|
||||||
'connecting to the endpoint. This value is usually '
|
'IP address assigned to this machine on the VPN after connecting '
|
||||||
'provided by the server operator. Example: 192.168.0.10.'),
|
'to the endpoint. This value is usually provided by the server '
|
||||||
validators=[validate_ipv4_address])
|
'operator. Example: 192.168.0.10. You can also specify the '
|
||||||
|
'network. This will allow reaching machines in the network. '
|
||||||
|
'Examples: 10.68.12.43/24 or 10.68.12.43/255.255.255.0.'),
|
||||||
|
validators=[validate_ipv4_address_with_network])
|
||||||
|
|
||||||
private_key = forms.CharField(
|
private_key = forms.CharField(
|
||||||
label=_('Private key of this machine'), strip=True, help_text=_(
|
label=_('Private key of this machine'), strip=True, help_text=_(
|
||||||
@ -107,9 +120,18 @@ class AddServerForm(forms.Form):
|
|||||||
'Typically checked for a VPN service through which all traffic '
|
'Typically checked for a VPN service through which all traffic '
|
||||||
'is sent.'))
|
'is sent.'))
|
||||||
|
|
||||||
def get_settings(self):
|
def get_settings(self) -> dict[str, dict]:
|
||||||
"""Return NM settings dict from cleaned data."""
|
"""Return NM settings dict from cleaned data."""
|
||||||
ip_address = self.cleaned_data['ip_address']
|
ip_address_and_network = self.cleaned_data['ip_address_and_network']
|
||||||
|
ip_address_and_network = ipaddress.IPv4Interface(
|
||||||
|
ip_address_and_network)
|
||||||
|
ip_address = str(ip_address_and_network.ip)
|
||||||
|
prefixlen = ip_address_and_network.network.prefixlen
|
||||||
|
if self.cleaned_data['default_route']:
|
||||||
|
allowed_ips = ['0.0.0.0/0', '::/0']
|
||||||
|
else:
|
||||||
|
allowed_ips = [f'{ip_address}/{prefixlen}']
|
||||||
|
|
||||||
settings = {
|
settings = {
|
||||||
'common': {
|
'common': {
|
||||||
'type': 'wireguard',
|
'type': 'wireguard',
|
||||||
@ -118,7 +140,7 @@ class AddServerForm(forms.Form):
|
|||||||
'ipv4': {
|
'ipv4': {
|
||||||
'method': 'manual',
|
'method': 'manual',
|
||||||
'address': ip_address,
|
'address': ip_address,
|
||||||
'netmask': '255.255.255.0',
|
'netmask': str(ip_address_and_network.netmask),
|
||||||
'gateway': '',
|
'gateway': '',
|
||||||
'dns': '',
|
'dns': '',
|
||||||
'second_dns': '',
|
'second_dns': '',
|
||||||
@ -126,10 +148,9 @@ class AddServerForm(forms.Form):
|
|||||||
'wireguard': {
|
'wireguard': {
|
||||||
'peer_endpoint': self.cleaned_data['peer_endpoint'],
|
'peer_endpoint': self.cleaned_data['peer_endpoint'],
|
||||||
'peer_public_key': self.cleaned_data['peer_public_key'],
|
'peer_public_key': self.cleaned_data['peer_public_key'],
|
||||||
'ip_address': ip_address,
|
|
||||||
'private_key': self.cleaned_data['private_key'],
|
'private_key': self.cleaned_data['private_key'],
|
||||||
'preshared_key': self.cleaned_data['preshared_key'],
|
'preshared_key': self.cleaned_data['preshared_key'],
|
||||||
'default_route': self.cleaned_data['default_route'],
|
'allowed_ips': allowed_ips,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return settings
|
return settings
|
||||||
|
|||||||
@ -36,9 +36,9 @@
|
|||||||
<th>{% trans "Public key of this machine:" %}</th>
|
<th>{% trans "Public key of this machine:" %}</th>
|
||||||
<td>{{ server.public_key }}</td>
|
<td>{{ server.public_key }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="server-ip-address">
|
<tr class="server-ip-address-and-network">
|
||||||
<th>{% trans "IP address of this machine:" %}</th>
|
<th>{% trans "IP address of this machine:" %}</th>
|
||||||
<td>{{ server.ip_address }}</td>
|
<td>{{ server.ip_address_and_network }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="server-default-route">
|
<tr class="server-default-route">
|
||||||
<th>
|
<th>
|
||||||
|
|||||||
@ -6,7 +6,9 @@ Tests for wireguard module forms.
|
|||||||
import pytest
|
import pytest
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
|
|
||||||
from plinth.modules.wireguard.forms import validate_endpoint, validate_key
|
from plinth.modules.wireguard.forms import (validate_endpoint,
|
||||||
|
validate_ipv4_address_with_network,
|
||||||
|
validate_key)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('key', [
|
@pytest.mark.parametrize('key', [
|
||||||
@ -62,3 +64,31 @@ def test_validate_endpoint_invalid_patterns(endpoint):
|
|||||||
"""Test that invalid wireguard endpoint patterns are rejected."""
|
"""Test that invalid wireguard endpoint patterns are rejected."""
|
||||||
with pytest.raises(ValidationError):
|
with pytest.raises(ValidationError):
|
||||||
validate_endpoint(endpoint)
|
validate_endpoint(endpoint)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('value', [
|
||||||
|
'1.2.3.4',
|
||||||
|
'1.2.3.4/0',
|
||||||
|
'1.2.3.4/32',
|
||||||
|
'1.2.3.4/24',
|
||||||
|
'1.2.3.4/255.255.255.0',
|
||||||
|
'1.2.3.4/0.0.0.255',
|
||||||
|
])
|
||||||
|
def test_validate_ipv4_address_with_network_valid_patterns(value):
|
||||||
|
"""Test validating IPv4 address with network works for valid values."""
|
||||||
|
validate_ipv4_address_with_network(value)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('value', [
|
||||||
|
'::1',
|
||||||
|
'1.2.3.4/',
|
||||||
|
'invalid-ip/24',
|
||||||
|
'1.2.3.4/x',
|
||||||
|
'1.2.3.4/-1',
|
||||||
|
'1.2.3.4/33',
|
||||||
|
'1.2.3.4/9.8.7.6',
|
||||||
|
])
|
||||||
|
def test_validate_ipv4_address_with_network_invalid_patterns(value):
|
||||||
|
"""Test validating IPv4 address with network works for invalid values."""
|
||||||
|
with pytest.raises(ValidationError):
|
||||||
|
validate_ipv4_address_with_network(value)
|
||||||
|
|||||||
@ -23,14 +23,14 @@ class TestWireguardApp(functional.BaseAppTests):
|
|||||||
{
|
{
|
||||||
'peer_endpoint': 'wg1.example.org:1234',
|
'peer_endpoint': 'wg1.example.org:1234',
|
||||||
'peer_public_key': 'HBCqZk4B93N6q19zNleJkAVs+PEfWAPgPpKnrhL/CVw=',
|
'peer_public_key': 'HBCqZk4B93N6q19zNleJkAVs+PEfWAPgPpKnrhL/CVw=',
|
||||||
'ip_address': '10.0.0.2',
|
'ip_address_and_network': '10.0.0.2/32',
|
||||||
'private_key': '',
|
'private_key': '',
|
||||||
'preshared_key': ''
|
'preshared_key': ''
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'peer_endpoint': 'wg2.example.org:5678',
|
'peer_endpoint': 'wg2.example.org:5678',
|
||||||
'peer_public_key': 'Z/iHo0vaeSN8Ykk5KwhQ819MMU5nyzD7y7xFFthlxXI=',
|
'peer_public_key': 'Z/iHo0vaeSN8Ykk5KwhQ819MMU5nyzD7y7xFFthlxXI=',
|
||||||
'ip_address': '192.168.0.2',
|
'ip_address_and_network': '192.168.0.2/24',
|
||||||
'private_key': 'QC2xEZMn3bgNsSVFrU51+ALSUiUaWg6gRWigh3EeVm0=',
|
'private_key': 'QC2xEZMn3bgNsSVFrU51+ALSUiUaWg6gRWigh3EeVm0=',
|
||||||
'preshared_key': 'AHxZ4Rr8Ij4L1aq+ceusSIgBfluqiI9Vb5I2UtQFanI='
|
'preshared_key': 'AHxZ4Rr8Ij4L1aq+ceusSIgBfluqiI9Vb5I2UtQFanI='
|
||||||
},
|
},
|
||||||
@ -70,7 +70,8 @@ class TestWireguardApp(functional.BaseAppTests):
|
|||||||
# Start the server on FreedomBox, if needed.
|
# Start the server on FreedomBox, if needed.
|
||||||
start_server_button = browser.find_by_css('.btn-start-server')
|
start_server_button = browser.find_by_css('.btn-start-server')
|
||||||
if start_server_button:
|
if start_server_button:
|
||||||
start_server_button.first.click()
|
with functional.wait_for_page_update(browser):
|
||||||
|
start_server_button.first.click()
|
||||||
|
|
||||||
browser.find_by_css('.btn-add-client').first.click()
|
browser.find_by_css('.btn-add-client').first.click()
|
||||||
browser.find_by_id('id_public_key').fill(key)
|
browser.find_by_id('id_public_key').fill(key)
|
||||||
@ -135,7 +136,8 @@ class TestWireguardApp(functional.BaseAppTests):
|
|||||||
href.first.click()
|
href.first.click()
|
||||||
assert get_value('peer-endpoint') == config['peer_endpoint']
|
assert get_value('peer-endpoint') == config['peer_endpoint']
|
||||||
assert get_value('peer-public-key') == config['peer_public_key']
|
assert get_value('peer-public-key') == config['peer_public_key']
|
||||||
assert get_value('server-ip-address') == config['ip_address']
|
assert get_value('server-ip-address-and-network'
|
||||||
|
) == config['ip_address_and_network']
|
||||||
assert get_value('peer-preshared-key') == (config['preshared_key']
|
assert get_value('peer-preshared-key') == (config['preshared_key']
|
||||||
or 'None')
|
or 'None')
|
||||||
|
|
||||||
@ -147,7 +149,8 @@ class TestWireguardApp(functional.BaseAppTests):
|
|||||||
browser.find_by_id('id_peer_endpoint').fill(config['peer_endpoint'])
|
browser.find_by_id('id_peer_endpoint').fill(config['peer_endpoint'])
|
||||||
browser.find_by_id('id_peer_public_key').fill(
|
browser.find_by_id('id_peer_public_key').fill(
|
||||||
config['peer_public_key'])
|
config['peer_public_key'])
|
||||||
browser.find_by_id('id_ip_address').fill(config['ip_address'])
|
browser.find_by_id('id_ip_address_and_network').fill(
|
||||||
|
config['ip_address_and_network'])
|
||||||
browser.find_by_id('id_private_key').fill(config['private_key'])
|
browser.find_by_id('id_private_key').fill(config['private_key'])
|
||||||
browser.find_by_id('id_preshared_key').fill(config['preshared_key'])
|
browser.find_by_id('id_preshared_key').fill(config['preshared_key'])
|
||||||
functional.submit(browser, form_class='form-add-server')
|
functional.submit(browser, form_class='form-add-server')
|
||||||
@ -161,7 +164,8 @@ class TestWireguardApp(functional.BaseAppTests):
|
|||||||
browser.find_by_id('id_peer_endpoint').fill(config2['peer_endpoint'])
|
browser.find_by_id('id_peer_endpoint').fill(config2['peer_endpoint'])
|
||||||
browser.find_by_id('id_peer_public_key').fill(
|
browser.find_by_id('id_peer_public_key').fill(
|
||||||
config2['peer_public_key'])
|
config2['peer_public_key'])
|
||||||
browser.find_by_id('id_ip_address').fill(config2['ip_address'])
|
browser.find_by_id('id_ip_address_and_network').fill(
|
||||||
|
config2['ip_address_and_network'])
|
||||||
browser.find_by_id('id_private_key').fill(config2['private_key'])
|
browser.find_by_id('id_private_key').fill(config2['private_key'])
|
||||||
browser.find_by_id('id_preshared_key').fill(config2['preshared_key'])
|
browser.find_by_id('id_preshared_key').fill(config2['preshared_key'])
|
||||||
functional.submit(browser, form_class='form-edit-server')
|
functional.submit(browser, form_class='form-edit-server')
|
||||||
|
|||||||
@ -65,7 +65,9 @@ def get_nm_info():
|
|||||||
|
|
||||||
settings_ipv4 = connection.get_setting_ip4_config()
|
settings_ipv4 = connection.get_setting_ip4_config()
|
||||||
if settings_ipv4 and settings_ipv4.get_num_addresses():
|
if settings_ipv4 and settings_ipv4.get_num_addresses():
|
||||||
info['ip_address'] = settings_ipv4.get_address(0).get_address()
|
address = settings_ipv4.get_address(0)
|
||||||
|
info['ip_address_and_network'] = (address.get_address() + '/' +
|
||||||
|
str(address.get_prefix()))
|
||||||
|
|
||||||
connections[info['interface']] = info
|
connections[info['interface']] = info
|
||||||
|
|
||||||
|
|||||||
@ -101,8 +101,7 @@ class ShowClientView(SuccessMessageMixin, TemplateView):
|
|||||||
context['client'] = server_info['peers'][public_key]
|
context['client'] = server_info['peers'][public_key]
|
||||||
context['endpoints'] = [
|
context['endpoints'] = [
|
||||||
domain + ':' + str(server_info['listen_port'])
|
domain + ':' + str(server_info['listen_port'])
|
||||||
for domain in domains
|
for domain in domains if not domain.endswith('.local')
|
||||||
if not domain.endswith('.local')
|
|
||||||
]
|
]
|
||||||
return context
|
return context
|
||||||
|
|
||||||
@ -227,7 +226,8 @@ class EditServerView(SuccessMessageMixin, FormView):
|
|||||||
if not server:
|
if not server:
|
||||||
raise Http404
|
raise Http404
|
||||||
|
|
||||||
initial['ip_address'] = server.get('ip_address')
|
initial['ip_address_and_network'] = server.get(
|
||||||
|
'ip_address_and_network')
|
||||||
if server['peers']:
|
if server['peers']:
|
||||||
peer = next(peer for peer in server['peers'].values())
|
peer = next(peer for peer in server['peers'].values())
|
||||||
initial['peer_endpoint'] = peer['endpoint']
|
initial['peer_endpoint'] = peer['endpoint']
|
||||||
|
|||||||
@ -507,12 +507,8 @@ def _update_wireguard_settings(connection, wireguard):
|
|||||||
peer.set_preshared_key_flags(nm.SettingSecretFlags.NONE)
|
peer.set_preshared_key_flags(nm.SettingSecretFlags.NONE)
|
||||||
peer.set_preshared_key(wireguard['preshared_key'], False)
|
peer.set_preshared_key(wireguard['preshared_key'], False)
|
||||||
|
|
||||||
if wireguard['default_route']:
|
for allowed_ip in wireguard['allowed_ips']:
|
||||||
peer.append_allowed_ip('0.0.0.0/0', False)
|
peer.append_allowed_ip(allowed_ip, False)
|
||||||
peer.append_allowed_ip('::/0', False)
|
|
||||||
else:
|
|
||||||
ip_addr = wireguard['ip_address']
|
|
||||||
peer.append_allowed_ip(f'{ip_addr}/24', False)
|
|
||||||
|
|
||||||
settings.clear_peers()
|
settings.clear_peers()
|
||||||
settings.append_peer(peer)
|
settings.append_peer(peer)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user