Draft code for Wireguard support

This commit is contained in:
Mattias Loverot 2024-10-17 09:32:41 +02:00
parent 9f7743e5da
commit 351ab1ecb0
14 changed files with 421 additions and 14 deletions

View File

@ -577,6 +577,10 @@ class BaseInterface(models.Model):
def count_fhrp_groups(self): def count_fhrp_groups(self):
return self.fhrp_group_assignments.count() return self.fhrp_group_assignments.count()
@property
def wireguard_config(self):
return self.wireguard_configs.first()
class Interface(ModularComponentModel, BaseInterface, CabledObjectModel, PathEndpoint, TrackingModelMixin): class Interface(ModularComponentModel, BaseInterface, CabledObjectModel, PathEndpoint, TrackingModelMixin):
""" """
@ -734,6 +738,12 @@ class Interface(ModularComponentModel, BaseInterface, CabledObjectModel, PathEnd
object_id_field='assigned_object_id', object_id_field='assigned_object_id',
related_query_name='interface', related_query_name='interface',
) )
wireguard_configs = GenericRelation(
to='vpn.WireguardConfig',
content_type_field='tunnel_interface_type',
object_id_field='tunnel_interface_id',
related_query_name='interface'
)
clone_fields = ( clone_fields = (
'device', 'module', 'parent', 'bridge', 'lag', 'type', 'mgmt_only', 'mtu', 'mode', 'speed', 'duplex', 'rf_role', 'device', 'module', 'parent', 'bridge', 'lag', 'type', 'mgmt_only', 'mtu', 'mode', 'speed', 'duplex', 'rf_role',

View File

@ -234,6 +234,7 @@ VPN_MENU = Menu(
get_model_item('vpn', 'ipsecproposal', _('IPSec Proposals')), get_model_item('vpn', 'ipsecproposal', _('IPSec Proposals')),
get_model_item('vpn', 'ipsecpolicy', _('IPSec Policies')), get_model_item('vpn', 'ipsecpolicy', _('IPSec Policies')),
get_model_item('vpn', 'ipsecprofile', _('IPSec Profiles')), get_model_item('vpn', 'ipsecprofile', _('IPSec Profiles')),
get_model_item('vpn', 'wireguardconfig', _('Wireguard Configs')),
), ),
), ),
), ),

View File

@ -37,10 +37,12 @@
<th scope="row">{% trans "Encapsulation" %}</th> <th scope="row">{% trans "Encapsulation" %}</th>
<td>{{ object.get_encapsulation_display }}</td> <td>{{ object.get_encapsulation_display }}</td>
</tr> </tr>
<tr> {% if not object.is_wireguard %}
<th scope="row">{% trans "IPSec profile" %}</th> <tr>
<td>{{ object.ipsec_profile|linkify|placeholder }}</td> <th scope="row">{% trans "IPSec profile" %}</th>
</tr> <td>{{ object.ipsec_profile|linkify|placeholder }}</td>
</tr>
{% endif %}
<tr> <tr>
<th scope="row">{% trans "Tunnel ID" %}</th> <th scope="row">{% trans "Tunnel ID" %}</th>
<td>{{ object.tunnel_id|placeholder }}</td> <td>{{ object.tunnel_id|placeholder }}</td>

View File

@ -0,0 +1,32 @@
{% extends 'generic/object.html' %}
{% load helpers %}
{% load plugins %}
{% load i18n %}
{# TODO: Fix this template.. #}
{% block content %}
<div class="row">
<div class="col col-md-6">
<div class="card">
<h2 class="card-header">{% trans "Tunnel" %}</h2>
<table class="table table-hover attr-table">
<tr>
<th scope="row">{% trans "Listen port" %}</th>
<td>{{ object.listen_port }}</td>
</tr>
<tr>
<th scope="row">{% trans "Allowed ips" %}</th>
<td>{{ object.allowed_ips }}</td>
</tr>
</table>
</div>
{% plugin_left_page object %}
</div>
<div class="col col-md-6">
{% include 'inc/panels/custom_fields.html' %}
{% include 'inc/panels/tags.html' %}
{% include 'inc/panels/comments.html' %}
{% plugin_right_page object %}
</div>
</div>
{% endblock %}

View File

@ -372,6 +372,12 @@ class VMInterface(ComponentModel, BaseInterface, TrackingModelMixin):
object_id_field='assigned_object_id', object_id_field='assigned_object_id',
related_query_name='vminterface', related_query_name='vminterface',
) )
wireguard_configs = GenericRelation(
to='vpn.WireguardConfig',
content_type_field='tunnel_interface_type',
object_id_field='tunnel_interface_id',
related_query_name='vminterface',
)
class Meta(ComponentModel.Meta): class Meta(ComponentModel.Meta):
verbose_name = _('interface') verbose_name = _('interface')

View File

@ -1,7 +1,8 @@
from netbox.api.fields import ChoiceField, SerializedPKRelatedField from netbox.api.fields import ChoiceField, SerializedPKRelatedField
from netbox.api.serializers import NetBoxModelSerializer from netbox.api.serializers import NetBoxModelSerializer
from vpn.choices import * from vpn.choices import *
from vpn.models import IKEPolicy, IKEProposal, IPSecPolicy, IPSecProfile, IPSecProposal from vpn.models import IKEPolicy, IKEProposal, IPSecPolicy, IPSecProfile, IPSecProposal, \
WireguardConfig
__all__ = ( __all__ = (
'IKEPolicySerializer', 'IKEPolicySerializer',
@ -9,6 +10,7 @@ __all__ = (
'IPSecPolicySerializer', 'IPSecPolicySerializer',
'IPSecProfileSerializer', 'IPSecProfileSerializer',
'IPSecProposalSerializer', 'IPSecProposalSerializer',
'WireguardConfigSerializer',
) )
@ -119,3 +121,11 @@ class IPSecProfileSerializer(NetBoxModelSerializer):
'comments', 'tags', 'custom_fields', 'created', 'last_updated', 'comments', 'tags', 'custom_fields', 'created', 'last_updated',
) )
brief_fields = ('id', 'url', 'display', 'name', 'description') brief_fields = ('id', 'url', 'display', 'name', 'description')
# TODO: Fix missing stuff
class WireguardConfigSerializer(NetBoxModelSerializer):
class Meta:
model = WireguardConfig
fields = ('id', 'tunnel_interface_type')
brief_fields = ('id', 'tunnel_interface_type')

View File

@ -26,12 +26,14 @@ class TunnelEncapsulationChoices(ChoiceSet):
ENCAP_IP_IP = 'ip-ip' ENCAP_IP_IP = 'ip-ip'
ENCAP_IPSEC_TRANSPORT = 'ipsec-transport' ENCAP_IPSEC_TRANSPORT = 'ipsec-transport'
ENCAP_IPSEC_TUNNEL = 'ipsec-tunnel' ENCAP_IPSEC_TUNNEL = 'ipsec-tunnel'
ENCAP_WIREGUARD = 'wireguard'
CHOICES = [ CHOICES = [
(ENCAP_IPSEC_TRANSPORT, _('IPsec - Transport')), (ENCAP_IPSEC_TRANSPORT, _('IPsec - Transport')),
(ENCAP_IPSEC_TUNNEL, _('IPsec - Tunnel')), (ENCAP_IPSEC_TUNNEL, _('IPsec - Tunnel')),
(ENCAP_IP_IP, _('IP-in-IP')), (ENCAP_IP_IP, _('IP-in-IP')),
(ENCAP_GRE, _('GRE')), (ENCAP_GRE, _('GRE')),
(ENCAP_WIREGUARD, _('Wireguard')),
] ]

View File

@ -1,9 +1,13 @@
from random import choices
from django import forms from django import forms
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from dcim.choices import InterfaceTypeChoices
from dcim.models import Device, Interface from dcim.models import Device, Interface
from ipam.models import IPAddress, RouteTarget, VLAN from ipam.models import IPAddress, RouteTarget, VLAN
from netbox.api.fields import ChoiceField
from netbox.forms import NetBoxModelForm from netbox.forms import NetBoxModelForm
from tenancy.forms import TenancyForm from tenancy.forms import TenancyForm
from utilities.forms.fields import CommentField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, SlugField from utilities.forms.fields import CommentField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, SlugField
@ -20,6 +24,7 @@ __all__ = (
'IPSecPolicyForm', 'IPSecPolicyForm',
'IPSecProfileForm', 'IPSecProfileForm',
'IPSecProposalForm', 'IPSecProposalForm',
'WireguardConfigForm',
'L2VPNForm', 'L2VPNForm',
'L2VPNTerminationForm', 'L2VPNTerminationForm',
'TunnelCreateForm', 'TunnelCreateForm',
@ -57,8 +62,7 @@ class TunnelForm(TenancyForm, NetBoxModelForm):
comments = CommentField() comments = CommentField()
fieldsets = ( fieldsets = (
FieldSet('name', 'status', 'group', 'encapsulation', 'description', 'tunnel_id', 'tags', name=_('Tunnel')), FieldSet('name', 'status', 'group', 'encapsulation', 'description', 'tunnel_id', 'ipsec_profile', 'tags', name=_('Tunnel')),
FieldSet('ipsec_profile', name=_('Security')),
FieldSet('tenant_group', 'tenant', name=_('Tenancy')), FieldSet('tenant_group', 'tenant', name=_('Tenancy')),
) )
@ -68,6 +72,15 @@ class TunnelForm(TenancyForm, NetBoxModelForm):
'name', 'status', 'group', 'encapsulation', 'description', 'tunnel_id', 'ipsec_profile', 'tenant_group', 'name', 'status', 'group', 'encapsulation', 'description', 'tunnel_id', 'ipsec_profile', 'tenant_group',
'tenant', 'comments', 'tags', 'tenant', 'comments', 'tags',
] ]
widgets = {
'encapsulation': HTMXSelect(),
}
def __init__(self, *args, initial=None, **kwargs):
super().__init__(*args, initial=initial, **kwargs)
if get_field_value(self, 'encapsulation') == TunnelEncapsulationChoices.ENCAP_WIREGUARD:
del(self.fields['ipsec_profile'])
class TunnelCreateForm(TunnelForm): class TunnelCreateForm(TunnelForm):
@ -142,8 +155,7 @@ class TunnelCreateForm(TunnelForm):
) )
fieldsets = ( fieldsets = (
FieldSet('name', 'status', 'group', 'encapsulation', 'description', 'tunnel_id', 'tags', name=_('Tunnel')), FieldSet('name', 'status', 'group', 'encapsulation', 'description', 'tunnel_id', 'ipsec_profile', 'tags', name=_('Tunnel')),
FieldSet('ipsec_profile', name=_('Security')),
FieldSet('tenant_group', 'tenant', name=_('Tenancy')), FieldSet('tenant_group', 'tenant', name=_('Tenancy')),
FieldSet( FieldSet(
'termination1_role', 'termination1_type', 'termination1_parent', 'termination1_termination', 'termination1_role', 'termination1_type', 'termination1_parent', 'termination1_termination',
@ -264,12 +276,23 @@ class TunnelTerminationForm(NetBoxModelForm):
def __init__(self, *args, initial=None, **kwargs): def __init__(self, *args, initial=None, **kwargs):
super().__init__(*args, initial=initial, **kwargs) super().__init__(*args, initial=initial, **kwargs)
if (get_field_value(self, 'type') is None and # Mimic HTMXSelect()
self.instance.pk and isinstance(self.instance.termination.parent_object, VirtualMachine)): self.fields['tunnel'].widget.attrs.update({
'hx-get': '.',
'hx-include': '#form_fields',
'hx-target': '#form_fields',
})
tunnel_id = get_field_value(self, 'tunnel')
tunnel = Tunnel.objects.filter(id=tunnel_id).first() if tunnel_id else None
tt_type = get_field_value(self, 'type')
if tt_type is None and self.instance.pk and isinstance(self.instance.termination.parent_object, VirtualMachine):
self.fields['type'].initial = TunnelTerminationTypeChoices.TYPE_VIRTUALMACHINE self.fields['type'].initial = TunnelTerminationTypeChoices.TYPE_VIRTUALMACHINE
# If initial or self.data is set and the type is a VIRTUALMACHINE type, swap the field querysets. # If initial or self.data is set and the type is a VIRTUALMACHINE type, swap the field querysets.
if get_field_value(self, 'type') == TunnelTerminationTypeChoices.TYPE_VIRTUALMACHINE: if tt_type == TunnelTerminationTypeChoices.TYPE_VIRTUALMACHINE:
self.fields['parent'].label = _('Virtual Machine') self.fields['parent'].label = _('Virtual Machine')
self.fields['parent'].queryset = VirtualMachine.objects.all() self.fields['parent'].queryset = VirtualMachine.objects.all()
self.fields['parent'].widget.attrs['selector'] = 'virtualization.virtualmachine' self.fields['parent'].widget.attrs['selector'] = 'virtualization.virtualmachine'
@ -280,6 +303,10 @@ class TunnelTerminationForm(NetBoxModelForm):
self.fields['outside_ip'].widget.add_query_params({ self.fields['outside_ip'].widget.add_query_params({
'virtual_machine_id': '$parent', 'virtual_machine_id': '$parent',
}) })
elif tunnel and tunnel.is_wireguard:
self.fields['termination'].help_text = _('As this is a Wireguard tunnel, only virtual interfaces are available for selection')
if tt_type == TunnelTerminationTypeChoices.TYPE_VIRTUALMACHINE:
self.fields['termination'].widget.add_query_params({'type': InterfaceTypeChoices.TYPE_VIRTUAL})
if self.instance.pk: if self.instance.pk:
self.fields['parent'].initial = self.instance.termination.parent_object self.fields['parent'].initial = self.instance.termination.parent_object
@ -287,9 +314,15 @@ class TunnelTerminationForm(NetBoxModelForm):
def clean(self): def clean(self):
super().clean() super().clean()
termination = self.cleaned_data['termination']
# verify that interface is virtual
is_virtual_interface = termination.type == InterfaceTypeChoices.TYPE_VIRTUAL if self.cleaned_data['type'] == TunnelTerminationTypeChoices.TYPE_DEVICE else True
if self.cleaned_data['tunnel'].encapsulation == TunnelEncapsulationChoices.ENCAP_WIREGUARD and not is_virtual_interface:
raise forms.ValidationError(_('Interface must be virtual for Wireguard tunnels'))
# Set the terminated object # Set the terminated object
self.instance.termination = self.cleaned_data.get('termination') self.instance.termination = termination
class IKEProposalForm(NetBoxModelForm): class IKEProposalForm(NetBoxModelForm):
@ -387,6 +420,71 @@ class IPSecProfileForm(NetBoxModelForm):
] ]
#
# Wireguard Config
#
class WireguardConfigForm(NetBoxModelForm):
type = forms.ChoiceField(
choices=TunnelTerminationTypeChoices,
widget=HTMXSelect(),
label=_('Type')
)
parent = DynamicModelChoiceField(
queryset=Device.objects.all(),
selector=True,
label=_('Device')
)
tunnel_interface = DynamicModelChoiceField(
queryset=Interface.objects.filter(type=InterfaceTypeChoices.TYPE_VIRTUAL),
label=_('Tunnel interface'),
query_params={
'device_id': '$parent',
'type': InterfaceTypeChoices.TYPE_VIRTUAL,
},
help_text=_('Only virtual interfaces are shown'),
)
fieldsets = (
FieldSet('type', 'parent', 'tunnel_interface', 'tags', name=_('Wireguard config')),
FieldSet('private_key', 'public_key', name=_('Keys')),
FieldSet('listen_port', 'allowed_ips', 'fwmark', 'persistent_keepalive_interval', name=_('Parameters')),
)
class Meta:
model = WireguardConfig
fields = [
'type', 'parent', 'tunnel_interface', 'tags', 'private_key', 'public_key', 'listen_port', 'allowed_ips', 'fwmark', 'persistent_keepalive_interval',
]
def __init__(self, *args, initial=None, **kwargs):
super().__init__(*args, initial=initial, **kwargs)
if (get_field_value(self, 'type') is None and
self.instance.pk and isinstance(self.instance.tunnel_interface.parent_object, VirtualMachine)):
self.fields['type'].initial = TunnelTerminationTypeChoices.TYPE_VIRTUALMACHINE
# If initial or self.data is set and the type is a VIRTUALMACHINE type, swap the field querysets.
if get_field_value(self, 'type') == TunnelTerminationTypeChoices.TYPE_VIRTUALMACHINE:
self.fields['parent'].label = _('Virtual Machine')
self.fields['parent'].queryset = VirtualMachine.objects.all()
self.fields['parent'].widget.attrs['selector'] = 'virtualization.virtualmachine'
self.fields['tunnel_interface'].queryset = VMInterface.objects.all()
self.fields['tunnel_interface'].widget.add_query_params({
'virtual_machine_id': '$parent',
})
if self.instance.pk:
self.fields['parent'].initial = self.instance.tunnel_interface.parent_object
self.fields['tunnel_interface'].initial = self.instance.tunnel_interface
def clean(self):
super().clean()
# Set the tunnel_interface object
self.instance.tunnel_interface = self.cleaned_data.get('tunnel_interface')
# #
# L2VPN # L2VPN
# #

View File

@ -0,0 +1,49 @@
# Generated by Django 5.0.7 on 2024-10-15 11:15
import django.contrib.postgres.fields
import django.core.validators
import django.db.models.deletion
import ipam.fields
import taggit.managers
import utilities.json
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('contenttypes', '0002_remove_content_type_name'),
('extras', '0121_customfield_related_object_filter'),
('vpn', '0005_rename_indexes'),
]
operations = [
migrations.CreateModel(
name='WireguardConfig',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)),
('created', models.DateTimeField(auto_now_add=True, null=True)),
('last_updated', models.DateTimeField(auto_now=True, null=True)),
('custom_field_data', models.JSONField(blank=True, default=dict, encoder=utilities.json.CustomFieldJSONEncoder)),
('tunnel_interface_id', models.PositiveBigIntegerField(blank=True, null=True)),
('private_key', models.TextField(blank=True)),
('public_key', models.TextField(blank=True)),
('listen_port', models.PositiveIntegerField(default=51820, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(65535)])),
('allowed_ips', django.contrib.postgres.fields.ArrayField(base_field=ipam.fields.IPNetworkField(), blank=True, null=True, size=None)),
('fwmark', models.PositiveIntegerField(blank=True, null=True)),
('persistent_keepalive_interval', models.PositiveIntegerField(default=0)),
('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')),
('tunnel_interface_type', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to='contenttypes.contenttype')),
],
options={
'verbose_name': 'wireguard config',
'verbose_name_plural': 'wireguard configs',
'ordering': ('pk',),
'indexes': [models.Index(fields=['tunnel_interface_type', 'tunnel_interface_id'], name='vpn_wiregua_tunnel__5cdbbe_idx')],
},
),
migrations.AddConstraint(
model_name='wireguardconfig',
constraint=models.UniqueConstraint(fields=('tunnel_interface_type', 'tunnel_interface_id'), name='vpn_wireguardconfig_tunnel_interface', violation_error_message='An tunnel_interface may only have one wireguard configration.'),
),
]

View File

@ -1,9 +1,15 @@
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.postgres.fields import ArrayField
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.core.validators import MinValueValidator, MaxValueValidator
from django.db import models from django.db import models
from django.urls import reverse from django.urls import reverse
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from netbox.models import PrimaryModel from ipam.constants import SERVICE_PORT_MIN, SERVICE_PORT_MAX
from ipam.fields import IPNetworkField
from netbox.models import PrimaryModel, CustomFieldsMixin, CustomLinksMixin, TagsMixin, \
ChangeLoggedModel
from vpn.choices import * from vpn.choices import *
__all__ = ( __all__ = (
@ -12,9 +18,13 @@ __all__ = (
'IPSecPolicy', 'IPSecPolicy',
'IPSecProfile', 'IPSecProfile',
'IPSecProposal', 'IPSecProposal',
'WireguardConfig',
) )
WIREGUARD_DEFAULT_PORT = 51820
# #
# IKE # IKE
# #
@ -255,3 +265,94 @@ class IPSecProfile(PrimaryModel):
def get_absolute_url(self): def get_absolute_url(self):
return reverse('vpn:ipsecprofile', args=[self.pk]) return reverse('vpn:ipsecprofile', args=[self.pk])
class WireguardConfig(CustomFieldsMixin, CustomLinksMixin, TagsMixin, ChangeLoggedModel):
tunnel_interface_type = models.ForeignKey(
to='contenttypes.ContentType',
on_delete=models.PROTECT,
related_name='+'
)
tunnel_interface_id = models.PositiveBigIntegerField(
blank=True,
null=True
)
tunnel_interface = GenericForeignKey(
ct_field='tunnel_interface_type',
fk_field='tunnel_interface_id'
)
private_key = models.TextField(
verbose_name=_('private key'),
blank=True,
)
public_key = models.TextField(
verbose_name=_('public key'),
blank=True
)
listen_port = models.PositiveIntegerField(
verbose_name=_('listen port'),
default=WIREGUARD_DEFAULT_PORT,
validators=[
MinValueValidator(SERVICE_PORT_MIN),
MaxValueValidator(SERVICE_PORT_MAX)
]
)
allowed_ips = ArrayField(
base_field=IPNetworkField(),
blank=True,
null=True,
verbose_name=_('allowed ips'),
help_text=_(
"Represents the permissible IPv4/IPv6 networks for use by other peers in their "
"'allowed_ips' configuration while creating a tunnel with this peer. "
"Ex: '10.1.1.0/24, 192.168.10.16/32, 2001:DB8:1::/64'"
),
)
fwmark = models.PositiveIntegerField(
null=True,
blank=True,
verbose_name=_('fwmark'),
help_text=_('Optional. Set a 32-bit integer firewall mark (fwmark) for outgoing packets'),
)
persistent_keepalive_interval = models.PositiveIntegerField(
default=0,
verbose_name=_('persistent keepalive interval'),
help_text=_('Persistant keepalive interval in seconds, 0 disables this feature'),
)
class Meta:
ordering = ('pk',)
indexes = (
models.Index(fields=('tunnel_interface_type', 'tunnel_interface_id')),
)
constraints = (
models.UniqueConstraint(
fields=('tunnel_interface_type', 'tunnel_interface_id'),
name='%(app_label)s_%(class)s_tunnel_interface',
violation_error_message=_("An tunnel_interface may only have one wireguard configration.")
),
)
verbose_name = _('wireguard config')
verbose_name_plural = _('wireguard configs')
def __str__(self):
return f'{self.tunnel_interface.name}: Wireguard config'
def get_absolute_url(self):
return reverse('vpn:wireguardconfig', args=[self.pk])
def clean(self):
super().clean()
# Check that the selected termination object is not already
if getattr(self.tunnel_interface, 'wireguard_config', None) and self.tunnel_interface.wireguard_config.pk != self.pk:
raise ValidationError({
'tunnel_interface': _("{name} already has a Wireguard config").format(
name=self.tunnel_interface.name,
)
})
def to_objectchange(self, action):
objectchange = super().to_objectchange(action)
objectchange.related_object = self.tunnel_interface
return objectchange

View File

@ -103,6 +103,10 @@ class Tunnel(PrimaryModel):
def get_status_color(self): def get_status_color(self):
return TunnelStatusChoices.colors.get(self.status) return TunnelStatusChoices.colors.get(self.status)
@property
def is_wireguard(self):
return self.encapsulation == TunnelEncapsulationChoices.ENCAP_WIREGUARD
class TunnelTermination(CustomFieldsMixin, CustomLinksMixin, TagsMixin, ChangeLoggedModel): class TunnelTermination(CustomFieldsMixin, CustomLinksMixin, TagsMixin, ChangeLoggedModel):
tunnel = models.ForeignKey( tunnel = models.ForeignKey(

View File

@ -10,6 +10,7 @@ __all__ = (
'IPSecPolicyTable', 'IPSecPolicyTable',
'IPSecProposalTable', 'IPSecProposalTable',
'IPSecProfileTable', 'IPSecProfileTable',
'WireguardConfigTable',
) )
@ -183,3 +184,36 @@ class IPSecProfileTable(NetBoxTable):
'last_updated', 'last_updated',
) )
default_columns = ('pk', 'name', 'mode', 'ike_policy', 'ipsec_policy', 'description') default_columns = ('pk', 'name', 'mode', 'ike_policy', 'ipsec_policy', 'description')
# TODO: Fix..
class WireguardConfigTable(NetBoxTable):
tunnel_interface_parent = tables.Column(
accessor='tunnel_interface__parent_object',
linkify=True,
orderable=False,
verbose_name=_('Host')
)
tunnel_interface = tables.Column(
verbose_name=_('Tunnel interface'),
linkify=True
)
ip_addresses = columns.ManyToManyColumn(
accessor=tables.A('tunnel_interface__ip_addresses'),
orderable=False,
linkify_item=True,
verbose_name=_('IP Addresses')
)
tags = columns.TagColumn(
url_name='vpn:tunneltermination_list'
)
class Meta(NetBoxTable.Meta):
model = WireguardConfig
fields = (
'pk', 'id', 'tunnel', 'role', 'tunnel_interface_parent', 'tunnel_interface', 'ip_addresses', 'tags',
'created', 'last_updated',
)
default_columns = (
'pk', 'id', 'tunnel_interface_parent', 'tunnel_interface', 'ip_addresses',
)

View File

@ -70,6 +70,14 @@ urlpatterns = [
path('ipsec-profiles/delete/', views.IPSecProfileBulkDeleteView.as_view(), name='ipsecprofile_bulk_delete'), path('ipsec-profiles/delete/', views.IPSecProfileBulkDeleteView.as_view(), name='ipsecprofile_bulk_delete'),
path('ipsec-profiles/<int:pk>/', include(get_model_urls('vpn', 'ipsecprofile'))), path('ipsec-profiles/<int:pk>/', include(get_model_urls('vpn', 'ipsecprofile'))),
# Wireguard configs
path('wireguard-configs/', views.WireguardConfigListView.as_view(), name='wireguardconfig_list'),
path('wireguard-configs/add/', views.WireguardConfigEditView.as_view(), name='wireguardconfig_add'),
path('wireguard-configs/import/', views.WireguardConfigBulkImportView.as_view(), name='wireguardconfig_import'),
path('wireguard-configs/edit/', views.WireguardConfigBulkEditView.as_view(), name='wireguardconfig_bulk_edit'),
path('wireguard-configs/delete/', views.WireguardConfigBulkDeleteView.as_view(), name='wireguardconfig_bulk_delete'),
path('wireguard-configs/<int:pk>/', include(get_model_urls('vpn', 'wireguardconfig'))),
# L2VPN # L2VPN
path('l2vpns/', views.L2VPNListView.as_view(), name='l2vpn_list'), path('l2vpns/', views.L2VPNListView.as_view(), name='l2vpn_list'),
path('l2vpns/add/', views.L2VPNEditView.as_view(), name='l2vpn_add'), path('l2vpns/add/', views.L2VPNEditView.as_view(), name='l2vpn_add'),

View File

@ -392,6 +392,56 @@ class IPSecProfileBulkDeleteView(generic.BulkDeleteView):
table = tables.IPSecProfileTable table = tables.IPSecProfileTable
#
# Wireguard configs
#
# TODO: Fix..
class WireguardConfigListView(generic.ObjectListView):
queryset = WireguardConfig.objects.all()
# filterset = filtersets.WireguardConfigFilterSet
# filterset_form = forms.WireguardConfigFilterForm
table = tables.WireguardConfigTable
@register_model_view(WireguardConfig)
class WireguardConfigView(generic.ObjectView):
queryset = WireguardConfig.objects.all()
@register_model_view(WireguardConfig, 'edit')
class WireguardConfigEditView(generic.ObjectEditView):
queryset = WireguardConfig.objects.all()
form = forms.WireguardConfigForm
@register_model_view(WireguardConfig, 'delete')
class WireguardConfigDeleteView(generic.ObjectDeleteView):
queryset = WireguardConfig.objects.all()
# TODO: Fix..
class WireguardConfigBulkImportView(generic.BulkImportView):
queryset = WireguardConfig.objects.all()
# model_form = forms.WireguardConfigImportForm
# TODO: Fix..
class WireguardConfigBulkEditView(generic.BulkEditView):
queryset = WireguardConfig.objects.all()
# filterset = filtersets.WireguardConfigFilterSet
table = tables.WireguardConfigTable
# form = forms.WireguardConfigBulkEditForm
# TODO: Fix..
class WireguardConfigBulkDeleteView(generic.BulkDeleteView):
queryset = WireguardConfig.objects.all()
# filterset = filtersets.WireguardConfigFilterSet
table = tables.WireguardConfigTable
# L2VPN # L2VPN
class L2VPNListView(generic.ObjectListView): class L2VPNListView(generic.ObjectListView):