diff --git a/netbox/dcim/models/device_components.py b/netbox/dcim/models/device_components.py index b1f951541..b32934b24 100644 --- a/netbox/dcim/models/device_components.py +++ b/netbox/dcim/models/device_components.py @@ -577,6 +577,10 @@ class BaseInterface(models.Model): def count_fhrp_groups(self): return self.fhrp_group_assignments.count() + @property + def wireguard_config(self): + return self.wireguard_configs.first() + class Interface(ModularComponentModel, BaseInterface, CabledObjectModel, PathEndpoint, TrackingModelMixin): """ @@ -734,6 +738,12 @@ class Interface(ModularComponentModel, BaseInterface, CabledObjectModel, PathEnd object_id_field='assigned_object_id', 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 = ( 'device', 'module', 'parent', 'bridge', 'lag', 'type', 'mgmt_only', 'mtu', 'mode', 'speed', 'duplex', 'rf_role', diff --git a/netbox/netbox/navigation/menu.py b/netbox/netbox/navigation/menu.py index 9d8ffaaf8..afb32e42d 100644 --- a/netbox/netbox/navigation/menu.py +++ b/netbox/netbox/navigation/menu.py @@ -234,6 +234,7 @@ VPN_MENU = Menu( get_model_item('vpn', 'ipsecproposal', _('IPSec Proposals')), get_model_item('vpn', 'ipsecpolicy', _('IPSec Policies')), get_model_item('vpn', 'ipsecprofile', _('IPSec Profiles')), + get_model_item('vpn', 'wireguardconfig', _('Wireguard Configs')), ), ), ), diff --git a/netbox/templates/vpn/tunnel.html b/netbox/templates/vpn/tunnel.html index b8e95cac3..68b5e4f6b 100644 --- a/netbox/templates/vpn/tunnel.html +++ b/netbox/templates/vpn/tunnel.html @@ -37,10 +37,12 @@ {% trans "Encapsulation" %} {{ object.get_encapsulation_display }} - - {% trans "IPSec profile" %} - {{ object.ipsec_profile|linkify|placeholder }} - + {% if not object.is_wireguard %} + + {% trans "IPSec profile" %} + {{ object.ipsec_profile|linkify|placeholder }} + + {% endif %} {% trans "Tunnel ID" %} {{ object.tunnel_id|placeholder }} diff --git a/netbox/templates/vpn/wireguardconfig.html b/netbox/templates/vpn/wireguardconfig.html new file mode 100644 index 000000000..6caa2a104 --- /dev/null +++ b/netbox/templates/vpn/wireguardconfig.html @@ -0,0 +1,32 @@ +{% extends 'generic/object.html' %} +{% load helpers %} +{% load plugins %} +{% load i18n %} + +{# TODO: Fix this template.. #} +{% block content %} +
+
+
+

{% trans "Tunnel" %}

+ + + + + + + + + +
{% trans "Listen port" %}{{ object.listen_port }}
{% trans "Allowed ips" %}{{ object.allowed_ips }}
+
+ {% plugin_left_page object %} +
+
+ {% include 'inc/panels/custom_fields.html' %} + {% include 'inc/panels/tags.html' %} + {% include 'inc/panels/comments.html' %} + {% plugin_right_page object %} +
+
+{% endblock %} diff --git a/netbox/virtualization/models/virtualmachines.py b/netbox/virtualization/models/virtualmachines.py index 17929f476..406f92713 100644 --- a/netbox/virtualization/models/virtualmachines.py +++ b/netbox/virtualization/models/virtualmachines.py @@ -372,6 +372,12 @@ class VMInterface(ComponentModel, BaseInterface, TrackingModelMixin): object_id_field='assigned_object_id', 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): verbose_name = _('interface') diff --git a/netbox/vpn/api/serializers_/crypto.py b/netbox/vpn/api/serializers_/crypto.py index c11b8de2b..b6a7c4eed 100644 --- a/netbox/vpn/api/serializers_/crypto.py +++ b/netbox/vpn/api/serializers_/crypto.py @@ -1,7 +1,8 @@ from netbox.api.fields import ChoiceField, SerializedPKRelatedField from netbox.api.serializers import NetBoxModelSerializer from vpn.choices import * -from vpn.models import IKEPolicy, IKEProposal, IPSecPolicy, IPSecProfile, IPSecProposal +from vpn.models import IKEPolicy, IKEProposal, IPSecPolicy, IPSecProfile, IPSecProposal, \ + WireguardConfig __all__ = ( 'IKEPolicySerializer', @@ -9,6 +10,7 @@ __all__ = ( 'IPSecPolicySerializer', 'IPSecProfileSerializer', 'IPSecProposalSerializer', + 'WireguardConfigSerializer', ) @@ -119,3 +121,11 @@ class IPSecProfileSerializer(NetBoxModelSerializer): 'comments', 'tags', 'custom_fields', 'created', 'last_updated', ) 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') diff --git a/netbox/vpn/choices.py b/netbox/vpn/choices.py index 751454049..0a1a67430 100644 --- a/netbox/vpn/choices.py +++ b/netbox/vpn/choices.py @@ -26,12 +26,14 @@ class TunnelEncapsulationChoices(ChoiceSet): ENCAP_IP_IP = 'ip-ip' ENCAP_IPSEC_TRANSPORT = 'ipsec-transport' ENCAP_IPSEC_TUNNEL = 'ipsec-tunnel' + ENCAP_WIREGUARD = 'wireguard' CHOICES = [ (ENCAP_IPSEC_TRANSPORT, _('IPsec - Transport')), (ENCAP_IPSEC_TUNNEL, _('IPsec - Tunnel')), (ENCAP_IP_IP, _('IP-in-IP')), (ENCAP_GRE, _('GRE')), + (ENCAP_WIREGUARD, _('Wireguard')), ] diff --git a/netbox/vpn/forms/model_forms.py b/netbox/vpn/forms/model_forms.py index a17ca9a5e..176f9dd7d 100644 --- a/netbox/vpn/forms/model_forms.py +++ b/netbox/vpn/forms/model_forms.py @@ -1,9 +1,13 @@ +from random import choices + from django import forms from django.core.exceptions import ValidationError from django.utils.translation import gettext_lazy as _ +from dcim.choices import InterfaceTypeChoices from dcim.models import Device, Interface from ipam.models import IPAddress, RouteTarget, VLAN +from netbox.api.fields import ChoiceField from netbox.forms import NetBoxModelForm from tenancy.forms import TenancyForm from utilities.forms.fields import CommentField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, SlugField @@ -20,6 +24,7 @@ __all__ = ( 'IPSecPolicyForm', 'IPSecProfileForm', 'IPSecProposalForm', + 'WireguardConfigForm', 'L2VPNForm', 'L2VPNTerminationForm', 'TunnelCreateForm', @@ -57,8 +62,7 @@ class TunnelForm(TenancyForm, NetBoxModelForm): comments = CommentField() fieldsets = ( - FieldSet('name', 'status', 'group', 'encapsulation', 'description', 'tunnel_id', 'tags', name=_('Tunnel')), - FieldSet('ipsec_profile', name=_('Security')), + FieldSet('name', 'status', 'group', 'encapsulation', 'description', 'tunnel_id', 'ipsec_profile', 'tags', name=_('Tunnel')), 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', '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): @@ -142,8 +155,7 @@ class TunnelCreateForm(TunnelForm): ) fieldsets = ( - FieldSet('name', 'status', 'group', 'encapsulation', 'description', 'tunnel_id', 'tags', name=_('Tunnel')), - FieldSet('ipsec_profile', name=_('Security')), + FieldSet('name', 'status', 'group', 'encapsulation', 'description', 'tunnel_id', 'ipsec_profile', 'tags', name=_('Tunnel')), FieldSet('tenant_group', 'tenant', name=_('Tenancy')), FieldSet( 'termination1_role', 'termination1_type', 'termination1_parent', 'termination1_termination', @@ -264,12 +276,23 @@ class TunnelTerminationForm(NetBoxModelForm): 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.termination.parent_object, VirtualMachine)): + # Mimic HTMXSelect() + 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 # 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'].queryset = VirtualMachine.objects.all() self.fields['parent'].widget.attrs['selector'] = 'virtualization.virtualmachine' @@ -280,6 +303,10 @@ class TunnelTerminationForm(NetBoxModelForm): self.fields['outside_ip'].widget.add_query_params({ '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: self.fields['parent'].initial = self.instance.termination.parent_object @@ -287,9 +314,15 @@ class TunnelTerminationForm(NetBoxModelForm): def clean(self): 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 - self.instance.termination = self.cleaned_data.get('termination') + self.instance.termination = termination 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 # diff --git a/netbox/vpn/migrations/0006_wireguardconfig_and_more.py b/netbox/vpn/migrations/0006_wireguardconfig_and_more.py new file mode 100644 index 000000000..eb5549f45 --- /dev/null +++ b/netbox/vpn/migrations/0006_wireguardconfig_and_more.py @@ -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.'), + ), + ] diff --git a/netbox/vpn/models/crypto.py b/netbox/vpn/models/crypto.py index 2769430fd..5ba1ab9fb 100644 --- a/netbox/vpn/models/crypto.py +++ b/netbox/vpn/models/crypto.py @@ -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.validators import MinValueValidator, MaxValueValidator from django.db import models from django.urls import reverse 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 * __all__ = ( @@ -12,9 +18,13 @@ __all__ = ( 'IPSecPolicy', 'IPSecProfile', 'IPSecProposal', + 'WireguardConfig', ) +WIREGUARD_DEFAULT_PORT = 51820 + + # # IKE # @@ -255,3 +265,94 @@ class IPSecProfile(PrimaryModel): def get_absolute_url(self): 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 diff --git a/netbox/vpn/models/tunnels.py b/netbox/vpn/models/tunnels.py index 6f4fa4182..2ce98cf01 100644 --- a/netbox/vpn/models/tunnels.py +++ b/netbox/vpn/models/tunnels.py @@ -103,6 +103,10 @@ class Tunnel(PrimaryModel): def get_status_color(self): return TunnelStatusChoices.colors.get(self.status) + @property + def is_wireguard(self): + return self.encapsulation == TunnelEncapsulationChoices.ENCAP_WIREGUARD + class TunnelTermination(CustomFieldsMixin, CustomLinksMixin, TagsMixin, ChangeLoggedModel): tunnel = models.ForeignKey( diff --git a/netbox/vpn/tables/crypto.py b/netbox/vpn/tables/crypto.py index 474062b39..d71910c88 100644 --- a/netbox/vpn/tables/crypto.py +++ b/netbox/vpn/tables/crypto.py @@ -10,6 +10,7 @@ __all__ = ( 'IPSecPolicyTable', 'IPSecProposalTable', 'IPSecProfileTable', + 'WireguardConfigTable', ) @@ -183,3 +184,36 @@ class IPSecProfileTable(NetBoxTable): 'last_updated', ) 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', + ) diff --git a/netbox/vpn/urls.py b/netbox/vpn/urls.py index 552f0e185..3b97b4d54 100644 --- a/netbox/vpn/urls.py +++ b/netbox/vpn/urls.py @@ -70,6 +70,14 @@ urlpatterns = [ path('ipsec-profiles/delete/', views.IPSecProfileBulkDeleteView.as_view(), name='ipsecprofile_bulk_delete'), path('ipsec-profiles//', 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//', include(get_model_urls('vpn', 'wireguardconfig'))), + # L2VPN path('l2vpns/', views.L2VPNListView.as_view(), name='l2vpn_list'), path('l2vpns/add/', views.L2VPNEditView.as_view(), name='l2vpn_add'), diff --git a/netbox/vpn/views.py b/netbox/vpn/views.py index ac8ce3667..b8d40df25 100644 --- a/netbox/vpn/views.py +++ b/netbox/vpn/views.py @@ -392,6 +392,56 @@ class IPSecProfileBulkDeleteView(generic.BulkDeleteView): 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 class L2VPNListView(generic.ObjectListView):