From 2012c229734b678b039eb9032afd7100235d9182 Mon Sep 17 00:00:00 2001 From: Eduardo Silva Date: Thu, 29 Feb 2024 23:07:40 -0300 Subject: [PATCH] Firewall rule management --- firewall/admin.py | 15 +- firewall/forms.py | 52 ++- .../0003_firewallsettings_forwardrule.py | 61 +++ .../0004_rename_forwardrule_firewallrule.py | 18 + ...ttings_created_firewallsettings_updated.py | 23 + .../0006_alter_firewallsettings_created.py | 18 + .../0007_firewallsettings_pending_changes.py | 18 + ...llsettings_global_firewallsettings_name.py | 22 + ..._remove_firewallrule_wireguard_instance.py | 17 + .../0010_alter_firewallrule_firewall_chain.py | 18 + firewall/models.py | 53 +++ firewall/views.py | 62 ++- templates/base.html | 1 + templates/firewall/firewall_nav_tabs.html | 12 + templates/firewall/firewall_rule_list.html | 175 +++++++ templates/firewall/manage_firewall_rule.html | 434 ++++++++++++++++++ templates/firewall/redirect_rule_list.html | 123 ++--- wireguard/models.py | 2 +- wireguard_webadmin/urls.py | 5 +- 19 files changed, 1062 insertions(+), 67 deletions(-) create mode 100644 firewall/migrations/0003_firewallsettings_forwardrule.py create mode 100644 firewall/migrations/0004_rename_forwardrule_firewallrule.py create mode 100644 firewall/migrations/0005_firewallsettings_created_firewallsettings_updated.py create mode 100644 firewall/migrations/0006_alter_firewallsettings_created.py create mode 100644 firewall/migrations/0007_firewallsettings_pending_changes.py create mode 100644 firewall/migrations/0008_remove_firewallsettings_global_firewallsettings_name.py create mode 100644 firewall/migrations/0009_remove_firewallrule_wireguard_instance.py create mode 100644 firewall/migrations/0010_alter_firewallrule_firewall_chain.py create mode 100644 templates/firewall/firewall_nav_tabs.html create mode 100644 templates/firewall/firewall_rule_list.html create mode 100644 templates/firewall/manage_firewall_rule.html diff --git a/firewall/admin.py b/firewall/admin.py index e8acf03..006c919 100644 --- a/firewall/admin.py +++ b/firewall/admin.py @@ -1,5 +1,5 @@ from django.contrib import admin -from firewall.models import RedirectRule +from firewall.models import RedirectRule, FirewallRule, FirewallSettings class RedirectRuleAdmin(admin.ModelAdmin): @@ -8,3 +8,16 @@ class RedirectRuleAdmin(admin.ModelAdmin): admin.site.register(RedirectRule, RedirectRuleAdmin) + +class FirewallRuleAdmin(admin.ModelAdmin): + list_display = ('firewall_chain', 'description', 'in_interface', 'out_interface', 'source_ip', 'source_netmask', 'source_peer_include_networks', 'not_source', 'destination_ip', 'destination_netmask', 'destination_peer_include_networks', 'not_destination', 'protocol', 'destination_port', 'state_new', 'state_related', 'state_established', 'state_invalid', 'state_untracked', 'not_state', 'rule_action', 'sort_order') + search_fields = ('firewall_chain', 'description', 'in_interface', 'out_interface', 'source_ip', 'source_netmask', 'source_peer_include_networks', 'not_source', 'destination_ip', 'destination_netmask', 'destination_peer_include_networks', 'not_destination', 'protocol', 'destination_port', 'state_new', 'state_related', 'state_established', 'state_invalid', 'state_untracked', 'not_state', 'rule_action', 'sort_order') + +admin.site.register(FirewallRule, FirewallRuleAdmin) + + +class FirewallSettingsAdmin(admin.ModelAdmin): + list_display = ('wan_interface', 'default_forward_policy', 'default_output_policy', 'allow_peer_to_peer', 'allow_instance_to_instance') + +admin.site.register(FirewallSettings, FirewallSettingsAdmin) + diff --git a/firewall/forms.py b/firewall/forms.py index 720b5db..7f3a5d6 100644 --- a/firewall/forms.py +++ b/firewall/forms.py @@ -1,5 +1,5 @@ -from firewall.models import RedirectRule -from wireguard.models import Peer, WireGuardInstance +from firewall.models import RedirectRule, FirewallRule, FirewallSettings +from wireguard.models import Peer, WireGuardInstance, NETMASK_CHOICES from django import forms @@ -34,7 +34,7 @@ class RedirectRuleForm(forms.ModelForm): raise forms.ValidationError("Port 8000 (tcp) is reserved for wireguard-webadmin.") if protocol == 'udp': - if WireGuardInstance.objects.filter(udp_port=port).exists(): + if WireGuardInstance.objects.filter(listen_port=port).exists(): raise forms.ValidationError("Port " + str(port) + " (udp) is already in use by a WireGuard instance.") if peer and ip_address: @@ -49,4 +49,48 @@ class RedirectRuleForm(forms.ModelForm): if ip_address: cleaned_data['peer'] = None - return cleaned_data \ No newline at end of file + return cleaned_data + + +class FirewallRuleForm(forms.ModelForm): + firewall_settings, firewall_settings_created = FirewallSettings.objects.get_or_create(name='global') + interface_list = [('', '------'),] + interface_list.append((firewall_settings.wan_interface, firewall_settings.wan_interface + ' (WAN)')) + + for wireguard_instance in WireGuardInstance.objects.all().order_by('instance_id'): + wireguard_instance_interface = 'wg'+ str(wireguard_instance.instance_id) + interface_list.append((wireguard_instance_interface, wireguard_instance_interface)) + + interface_list.append(('wg+', 'wg+ (Any WireGuard Interface)')) + + description = forms.CharField(label='Description', required=False) + firewall_chain = forms.ChoiceField(label='Firewall Chain', choices=[('forward', 'FORWARD'), ('postrouting', 'POSTROUTING (nat)')], initial='forward') + in_interface = forms.ChoiceField(label='In Interface', choices=interface_list, required=False) + out_interface = forms.ChoiceField(label='Out Interface', choices=interface_list, required=False) + source_ip = forms.GenericIPAddressField(label='Source IP', required=False) + source_netmask = forms.IntegerField(label='Source Netmask', initial=32, min_value=0, max_value=32) + source_peer = forms.ModelMultipleChoiceField(label='Source Peer', queryset=Peer.objects.all(), required=False) + source_peer_include_networks = forms.BooleanField(label='Source Peer Include Networks', required=False) + not_source = forms.BooleanField(label='Not Source', required=False) + destination_ip = forms.GenericIPAddressField(label='Destination IP', required=False) + destination_netmask = forms.IntegerField(label='Destination Netmask', initial=32, min_value=0, max_value=32) + destination_peer = forms.ModelMultipleChoiceField(label='Destination Peer', queryset=Peer.objects.all(), required=False) + destination_peer_include_networks = forms.BooleanField(label='Destination Peer Include Networks', required=False) + not_destination = forms.BooleanField(label='Not Destination', required=False) + protocol = forms.ChoiceField(label='Protocol', choices=[('', 'all'), ('tcp', 'TCP'), ('udp', 'UDP'), ('both', 'TCP+UDP'), ('icmp', 'ICMP')], required=False) + destination_port = forms.CharField(label='Destination Port', required=False) + state_new = forms.BooleanField(label='State NEW', required=False) + state_related = forms.BooleanField(label='State RELATED', required=False) + state_established = forms.BooleanField(label='State ESTABLISHED', required=False) + state_invalid = forms.BooleanField(label='State INVALID', required=False) + state_untracked = forms.BooleanField(label='State UNTRACKED', required=False) + not_state = forms.BooleanField(label='Not State', required=False) + rule_action = forms.ChoiceField(label='Rule Action', initial='accept', choices=[('accept', 'ACCEPT'), ('reject', 'REJECT'), ('drop', 'DROP'), ('masquerade', 'MASQUERADE')]) + sort_order = forms.IntegerField(label='Sort Order', initial=0, min_value=0) + + class Meta: + model = FirewallRule + fields = ['description', 'firewall_chain', 'in_interface', 'out_interface', 'source_ip', 'source_netmask', 'source_peer', 'source_peer_include_networks', 'not_source', 'destination_ip', 'destination_netmask', 'destination_peer', 'destination_peer_include_networks', 'not_destination', 'protocol', 'destination_port', 'state_new', 'state_related', 'state_established', 'state_invalid', 'state_untracked', 'not_state', 'rule_action', 'sort_order'] + + + diff --git a/firewall/migrations/0003_firewallsettings_forwardrule.py b/firewall/migrations/0003_firewallsettings_forwardrule.py new file mode 100644 index 0000000..fdf2786 --- /dev/null +++ b/firewall/migrations/0003_firewallsettings_forwardrule.py @@ -0,0 +1,61 @@ +# Generated by Django 5.0.2 on 2024-02-28 15:37 + +import django.db.models.deletion +import uuid +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('firewall', '0002_redirectrule_masquerade_source_and_more'), + ('wireguard', '0018_wireguardinstance_legacy_firewall'), + ] + + operations = [ + migrations.CreateModel( + name='FirewallSettings', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('global', models.CharField(max_length=6, unique=True)), + ('default_forward_policy', models.CharField(choices=[('accept', 'ACCEPT'), ('reject', 'REJECT'), ('drop', 'DROP')], default='accept', max_length=6)), + ('default_output_policy', models.CharField(choices=[('accept', 'ACCEPT'), ('reject', 'REJECT'), ('drop', 'DROP')], default='accept', max_length=6)), + ('allow_peer_to_peer', models.BooleanField(default=True)), + ('allow_instance_to_instance', models.BooleanField(default=True)), + ('wan_interface', models.CharField(default='eth0', max_length=12)), + ], + ), + migrations.CreateModel( + name='ForwardRule', + fields=[ + ('description', models.CharField(blank=True, max_length=100, null=True)), + ('firewall_chain', models.CharField(choices=[('FORWARD', 'FORWARD'), ('OUTPUT', 'OUTPUT'), ('POSTROUTING', 'POSTROUTING (nat)')], default='FORWARD', max_length=12)), + ('in_interface', models.CharField(blank=True, default='', max_length=12, null=True)), + ('out_interface', models.CharField(blank=True, default='', max_length=12, null=True)), + ('source_ip', models.GenericIPAddressField(blank=True, null=True, protocol='IPv4')), + ('source_netmask', models.PositiveIntegerField(choices=[(8, '/8 (255.0.0.0)'), (9, '/9 (255.128.0.0)'), (10, '/10 (255.192.0.0)'), (11, '/11 (255.224.0.0)'), (12, '/12 (255.240.0.0)'), (13, '/13 (255.248.0.0)'), (14, '/14 (255.252.0.0)'), (15, '/15 (255.254.0.0)'), (16, '/16 (255.255.0.0)'), (17, '/17 (255.255.128.0)'), (18, '/18 (255.255.192.0)'), (19, '/19 (255.255.224.0)'), (20, '/20 (255.255.240.0)'), (21, '/21 (255.255.248.0)'), (22, '/22 (255.255.252.0)'), (23, '/23 (255.255.254.0)'), (24, '/24 (255.255.255.0)'), (25, '/25 (255.255.255.128)'), (26, '/26 (255.255.255.192)'), (27, '/27 (255.255.255.224)'), (28, '/28 (255.255.255.240)'), (29, '/29 (255.255.255.248)'), (30, '/30 (255.255.255.252)'), (32, '/32 (255.255.255.255)')], default=32)), + ('source_peer_include_networks', models.BooleanField(default=False)), + ('not_source', models.BooleanField(default=False)), + ('destination_ip', models.GenericIPAddressField(blank=True, null=True, protocol='IPv4')), + ('destination_netmask', models.PositiveIntegerField(choices=[(8, '/8 (255.0.0.0)'), (9, '/9 (255.128.0.0)'), (10, '/10 (255.192.0.0)'), (11, '/11 (255.224.0.0)'), (12, '/12 (255.240.0.0)'), (13, '/13 (255.248.0.0)'), (14, '/14 (255.252.0.0)'), (15, '/15 (255.254.0.0)'), (16, '/16 (255.255.0.0)'), (17, '/17 (255.255.128.0)'), (18, '/18 (255.255.192.0)'), (19, '/19 (255.255.224.0)'), (20, '/20 (255.255.240.0)'), (21, '/21 (255.255.248.0)'), (22, '/22 (255.255.252.0)'), (23, '/23 (255.255.254.0)'), (24, '/24 (255.255.255.0)'), (25, '/25 (255.255.255.128)'), (26, '/26 (255.255.255.192)'), (27, '/27 (255.255.255.224)'), (28, '/28 (255.255.255.240)'), (29, '/29 (255.255.255.248)'), (30, '/30 (255.255.255.252)'), (32, '/32 (255.255.255.255)')], default=32)), + ('destination_peer_include_networks', models.BooleanField(default=False)), + ('not_destination', models.BooleanField(default=False)), + ('protocol', models.CharField(blank=True, choices=[('', 'all'), ('tcp', 'TCP'), ('udp', 'UDP'), ('both', 'TCP+UDP'), ('icmp', 'ICMP')], default='', max_length=4, null=True)), + ('destination_port', models.CharField(blank=True, max_length=11, null=True)), + ('state_new', models.BooleanField(default=False)), + ('state_related', models.BooleanField(default=False)), + ('state_established', models.BooleanField(default=False)), + ('state_invalid', models.BooleanField(default=False)), + ('state_untracked', models.BooleanField(default=False)), + ('not_state', models.BooleanField(default=False)), + ('rule_action', models.CharField(choices=[('accept', 'ACCEPT'), ('reject', 'REJECT'), ('drop', 'DROP'), ('masquerade', 'MASQUERADE')], default='accept', max_length=10)), + ('sort_order', models.PositiveIntegerField(default=0)), + ('created', models.DateTimeField(auto_now_add=True)), + ('updated', models.DateTimeField(auto_now=True)), + ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('destination_peer', models.ManyToManyField(blank=True, related_name='forward_rules_as_destination', to='wireguard.peer')), + ('source_peer', models.ManyToManyField(blank=True, related_name='forward_rules_as_source', to='wireguard.peer')), + ('wireguard_instance', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='wireguard.wireguardinstance')), + ], + ), + ] diff --git a/firewall/migrations/0004_rename_forwardrule_firewallrule.py b/firewall/migrations/0004_rename_forwardrule_firewallrule.py new file mode 100644 index 0000000..c83a3a7 --- /dev/null +++ b/firewall/migrations/0004_rename_forwardrule_firewallrule.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.2 on 2024-02-29 13:02 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('firewall', '0003_firewallsettings_forwardrule'), + ('wireguard', '0018_wireguardinstance_legacy_firewall'), + ] + + operations = [ + migrations.RenameModel( + old_name='ForwardRule', + new_name='FirewallRule', + ), + ] diff --git a/firewall/migrations/0005_firewallsettings_created_firewallsettings_updated.py b/firewall/migrations/0005_firewallsettings_created_firewallsettings_updated.py new file mode 100644 index 0000000..79e6613 --- /dev/null +++ b/firewall/migrations/0005_firewallsettings_created_firewallsettings_updated.py @@ -0,0 +1,23 @@ +# Generated by Django 5.0.2 on 2024-02-29 13:29 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('firewall', '0004_rename_forwardrule_firewallrule'), + ] + + operations = [ + migrations.AddField( + model_name='firewallsettings', + name='created', + field=models.DateTimeField(auto_now=True), + ), + migrations.AddField( + model_name='firewallsettings', + name='updated', + field=models.DateTimeField(auto_now=True), + ), + ] diff --git a/firewall/migrations/0006_alter_firewallsettings_created.py b/firewall/migrations/0006_alter_firewallsettings_created.py new file mode 100644 index 0000000..172b69a --- /dev/null +++ b/firewall/migrations/0006_alter_firewallsettings_created.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.2 on 2024-02-29 13:29 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('firewall', '0005_firewallsettings_created_firewallsettings_updated'), + ] + + operations = [ + migrations.AlterField( + model_name='firewallsettings', + name='created', + field=models.DateTimeField(auto_now_add=True), + ), + ] diff --git a/firewall/migrations/0007_firewallsettings_pending_changes.py b/firewall/migrations/0007_firewallsettings_pending_changes.py new file mode 100644 index 0000000..240bd45 --- /dev/null +++ b/firewall/migrations/0007_firewallsettings_pending_changes.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.2 on 2024-02-29 14:02 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('firewall', '0006_alter_firewallsettings_created'), + ] + + operations = [ + migrations.AddField( + model_name='firewallsettings', + name='pending_changes', + field=models.BooleanField(default=False), + ), + ] diff --git a/firewall/migrations/0008_remove_firewallsettings_global_firewallsettings_name.py b/firewall/migrations/0008_remove_firewallsettings_global_firewallsettings_name.py new file mode 100644 index 0000000..e3bced2 --- /dev/null +++ b/firewall/migrations/0008_remove_firewallsettings_global_firewallsettings_name.py @@ -0,0 +1,22 @@ +# Generated by Django 5.0.2 on 2024-02-29 17:08 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('firewall', '0007_firewallsettings_pending_changes'), + ] + + operations = [ + migrations.RemoveField( + model_name='firewallsettings', + name='global', + ), + migrations.AddField( + model_name='firewallsettings', + name='name', + field=models.CharField(default='global', max_length=6, unique=True), + ), + ] diff --git a/firewall/migrations/0009_remove_firewallrule_wireguard_instance.py b/firewall/migrations/0009_remove_firewallrule_wireguard_instance.py new file mode 100644 index 0000000..0e196b2 --- /dev/null +++ b/firewall/migrations/0009_remove_firewallrule_wireguard_instance.py @@ -0,0 +1,17 @@ +# Generated by Django 5.0.2 on 2024-03-01 00:12 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('firewall', '0008_remove_firewallsettings_global_firewallsettings_name'), + ] + + operations = [ + migrations.RemoveField( + model_name='firewallrule', + name='wireguard_instance', + ), + ] diff --git a/firewall/migrations/0010_alter_firewallrule_firewall_chain.py b/firewall/migrations/0010_alter_firewallrule_firewall_chain.py new file mode 100644 index 0000000..7ec28ca --- /dev/null +++ b/firewall/migrations/0010_alter_firewallrule_firewall_chain.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.2 on 2024-03-01 01:44 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('firewall', '0009_remove_firewallrule_wireguard_instance'), + ] + + operations = [ + migrations.AlterField( + model_name='firewallrule', + name='firewall_chain', + field=models.CharField(choices=[('forward', 'FORWARD'), ('postrouting', 'POSTROUTING (nat)')], default='forward', max_length=12), + ), + ] diff --git a/firewall/models.py b/firewall/models.py index d6b0532..3137379 100644 --- a/firewall/models.py +++ b/firewall/models.py @@ -1,5 +1,6 @@ from django.db import models from wireguard.models import Peer, WireGuardInstance +from wireguard.models import NETMASK_CHOICES import uuid @@ -24,3 +25,55 @@ class RedirectRule(models.Model): unique_together = ['port', 'protocol'] +class FirewallRule(models.Model): + description = models.CharField(max_length=100, blank=True, null=True) + firewall_chain = models.CharField(max_length=12, default='forward', choices=[('forward', 'FORWARD'), ('postrouting', 'POSTROUTING (nat)')]) + + in_interface = models.CharField(max_length=12, default='', blank=True, null=True) + out_interface = models.CharField(max_length=12, default='', blank=True, null=True) + + source_ip = models.GenericIPAddressField(blank=True, null=True, protocol='IPv4') + source_netmask = models.PositiveIntegerField(default=32, choices=NETMASK_CHOICES) + source_peer = models.ManyToManyField(Peer, related_name="forward_rules_as_source", blank=True) + source_peer_include_networks = models.BooleanField(default=False) + not_source = models.BooleanField(default=False) + + destination_ip = models.GenericIPAddressField(blank=True, null=True, protocol='IPv4') + destination_netmask = models.PositiveIntegerField(default=32, choices=NETMASK_CHOICES) + destination_peer = models.ManyToManyField(Peer, related_name="forward_rules_as_destination", blank=True) + destination_peer_include_networks = models.BooleanField(default=False) + not_destination = models.BooleanField(default=False) + + protocol = models.CharField(max_length=4, default='', blank=True, null=True, choices=[('', 'all'), ('tcp', 'TCP'), ('udp', 'UDP'), ('both', 'TCP+UDP'), ('icmp', 'ICMP'),]) + destination_port = models.CharField(max_length=11, blank=True, null=True) + + state_new = models.BooleanField(default=False) + state_related = models.BooleanField(default=False) + state_established = models.BooleanField(default=False) + state_invalid = models.BooleanField(default=False) + state_untracked = models.BooleanField(default=False) + not_state = models.BooleanField(default=False) + + rule_action = models.CharField(max_length=10, default='accept', choices=[('accept', 'ACCEPT'), ('reject', 'REJECT'), ('drop', 'DROP'), ('masquerade', 'MASQUERADE')]) + + sort_order = models.PositiveIntegerField(default=0) + created = models.DateTimeField(auto_now_add=True) + updated = models.DateTimeField(auto_now=True) + uuid = models.UUIDField(primary_key=True, editable=False, default=uuid.uuid4) + + def __str__(self): + return str(self.uuid) + + +class FirewallSettings(models.Model): + name = models.CharField(max_length=6, default='global', unique=True) + default_forward_policy = models.CharField(max_length=6, default='accept', choices=[('accept', 'ACCEPT'), ('reject', 'REJECT'), ('drop', 'DROP')]) + default_output_policy = models.CharField(max_length=6, default='accept', choices=[('accept', 'ACCEPT'), ('reject', 'REJECT'), ('drop', 'DROP')]) + allow_peer_to_peer = models.BooleanField(default=True) + allow_instance_to_instance = models.BooleanField(default=True) + wan_interface = models.CharField(max_length=12, default='eth0') + pending_changes = models.BooleanField(default=False) + + created = models.DateTimeField(auto_now_add=True) + updated = models.DateTimeField(auto_now=True) + diff --git a/firewall/views.py b/firewall/views.py index 35715ef..62775cf 100644 --- a/firewall/views.py +++ b/firewall/views.py @@ -1,6 +1,6 @@ from django.shortcuts import render, get_object_or_404, redirect -from firewall.models import RedirectRule -from firewall.forms import RedirectRuleForm +from firewall.models import RedirectRule, FirewallRule, FirewallSettings +from firewall.forms import RedirectRuleForm, FirewallRuleForm from django.contrib import messages from wireguard.models import WireGuardInstance from user_manager.models import UserAcl @@ -15,7 +15,8 @@ def view_redirect_rule_list(request): context = { 'page_title': 'Port Forward List', 'pending_changes_warning': pending_changes_warning, - 'redirect_rule_list': RedirectRule.objects.all().order_by('wireguard_instance', 'protocol', 'port') + 'redirect_rule_list': RedirectRule.objects.all().order_by('port'), + 'current_chain': 'portforward', } return render(request, 'firewall/redirect_rule_list.html', context=context) @@ -52,4 +53,57 @@ def manage_redirect_rule(request): context['form'] = form context['instance'] = instance - return render(request, 'firewall/manage_redirect_rule.html', context=context) \ No newline at end of file + return render(request, 'firewall/manage_redirect_rule.html', context=context) + + +def view_firewall_rule_list(request): + wireguard_instances = WireGuardInstance.objects.all().order_by('instance_id') + current_chain = request.GET.get('chain', 'forward') + if current_chain not in ['forward', 'portforward', 'postrouting']: + current_chain = 'forward' + if wireguard_instances.filter(pending_changes=True).exists(): + pending_changes_warning = True + else: + pending_changes_warning = False + context = { + 'page_title': 'Firewall Rule List', + 'pending_changes_warning': pending_changes_warning, + 'firewall_rule_list': FirewallRule.objects.filter(firewall_chain=current_chain).order_by('sort_order'), + 'current_chain': current_chain, + } + return render(request, 'firewall/firewall_rule_list.html', context=context) + + +def manage_firewall_rule(request): + if not UserAcl.objects.filter(user=request.user).filter(user_level__gte=40).exists(): + return render(request, 'access_denied.html', {'page_title': 'Access Denied'}) + context = {'page_title': 'Manage Firewall Rule'} + instance = None + uuid = request.GET.get('uuid', None) + if uuid: + instance = get_object_or_404(FirewallRule, uuid=uuid) + if request.GET.get('action') == 'delete': + if request.GET.get('confirmation') == 'delete': + firewall_settings, firewall_settings_created = FirewallSettings.objects.get_or_create(name='global') + firewall_settings.pending_changes = True + firewall_settings.save() + messages.success(request, 'Firewall rule deleted successfully') + else: + messages.warning(request, 'Error deleting Firewall rule|Confirmation did not match. Firewall rule was not deleted.') + return redirect('/firewall/rule_list/') + + if request.method == 'POST': + form = FirewallRuleForm(request.POST, instance=instance) + if form.is_valid(): + firewall_settings, firewall_settings_created = FirewallSettings.objects.get_or_create(name='global') + firewall_settings.pending_changes = True + firewall_settings.save() + form.save() + messages.success(request, 'Firewall rule saved successfully') + return redirect('/firewall/rule_list/') + else: + form = FirewallRuleForm(instance=instance) + context['form'] = form + context['instance'] = instance + + return render(request, 'firewall/manage_firewall_rule.html', context=context) diff --git a/templates/base.html b/templates/base.html index d49e3da..c5e6036 100644 --- a/templates/base.html +++ b/templates/base.html @@ -25,6 +25,7 @@ + {% block page_custom_head %}{% endblock%} {% load custom_tags %} {% tag_webadmin_version as webadmin_version %} diff --git a/templates/firewall/firewall_nav_tabs.html b/templates/firewall/firewall_nav_tabs.html new file mode 100644 index 0000000..9457ef3 --- /dev/null +++ b/templates/firewall/firewall_nav_tabs.html @@ -0,0 +1,12 @@ + \ No newline at end of file diff --git a/templates/firewall/firewall_rule_list.html b/templates/firewall/firewall_rule_list.html new file mode 100644 index 0000000..031e2d7 --- /dev/null +++ b/templates/firewall/firewall_rule_list.html @@ -0,0 +1,175 @@ +{% extends 'base.html' %} + +{% block page_custom_head %} + +{% endblock%} + +{% block content %} + +
+
+ {% include "firewall/firewall_nav_tabs.html" %} +
+
+ + + + + + + + + + + + + + + + + + {% for rule in firewall_rule_list %} + + + + + + + + + + + + + + {% comment%} + + {% endcomment %} + + + {% endfor %} + + +
#InOutSourceDestinationProtocolPortStateAction
{{ rule.sort_order }}{% if rule.description %}{% endif %}{{ rule.in_interface }}{{ rule.out_interface }} + {% if rule.source_ip %}{% if rule.not_source %}! {% endif %}{{ rule.source_ip }}/{{ rule.source_netmask }}
{% endif%} + {% for peer in rule.source_peer.all %}{% if rule.not_source %}! {% endif %}{{ peer }}{% if rule.source_peer_include_networks %} +{% endif %}
{% endfor %} + +
+ {% if rule.destination_ip %}{% if rule.not_destination %}! {% endif %}{{ rule.destination_ip }}/{{ rule.destination_netmask }}
{% endif%} + {% for peer in rule.destination_peer.all %}{% if rule.not_destination %}! {% endif %}{{ peer }}{% if rule.destination_peer_include_networks %} +{% endif %}
{% endfor %} +
{{ rule.get_protocol_display }}{{ rule.destination_port }} + {% if rule.state_new %}{% if rule.not_state %}! {% endif %}New
{% endif %} + {% if rule.state_related %}{% if rule.not_state %}! {% endif %}Related
{% endif %} + {% if rule.state_established %}{% if rule.not_state %}! {% endif %}Established
{% endif %} + {% if rule.state_invalid %}{% if rule.not_state %}! {% endif %}Invalid
{% endif %} + {% if rule.state_untracked %}{% if rule.not_state %}! {% endif %}Untracked
{% endif %} +
{{ rule.get_rule_action_display }}{{ rule. }} + +
+ + Create Firewall Rule +
+
+
+
+ + + + + + + +{% endblock %} + +{% block custom_page_scripts %} +{% comment %} + +{% endcomment %} + + +{% endblock %} \ No newline at end of file diff --git a/templates/firewall/manage_firewall_rule.html b/templates/firewall/manage_firewall_rule.html new file mode 100644 index 0000000..aecb501 --- /dev/null +++ b/templates/firewall/manage_firewall_rule.html @@ -0,0 +1,434 @@ +{% extends "base.html" %} + +{% block content %} +
+
+
+

Manage Firewall Rule

+
+
+ {% csrf_token %} +
+ +
+
+

+ +

+
+
+ {% comment %} +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
{% endcomment %} +
+
+
+
+
+ + +
+ +
+
+
+ + +
+
+ + +
+ +
+
+ +
+
Advanced VPN Firewall Configuration
+

+ This interface serves as a comprehensive tool for managing firewall rules, enabling users to implement advanced traffic policies between VPN peers and networks. It simplifies establishing firewall rules, packet filtering, and NAT configurations, allowing for precise control over network security. Users can define source and destination IP addresses, ports, protocols, and actions to tailor traffic flow, ensuring a secure and efficient networking environment. +

+
+
+
+ + + + +
+
+ + +
+
+

+ +

+
+
+
+
+ +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ +
+
+ + +
+
+

+ +

+
+
+ +
+
+
+
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
Source Selection
+

+ You have the option to apply this rule to a specific IP address or network and/or to multiple peers.

+ Enabling the "Include peer networks" option will automatically include all Allowed IPs associated with each selected peer.

+ Please note that selecting multiple peers with included networks on both the source and destination ends may result in a rapid increase in the number of firewall rules generated, depending on your configuration.

+ The "Not Source" option negates the selected source IP, network, or peer(s). +

+ +
+
+
+ + +
+
+ + +
+
+

+ +

+
+
+ +
+
+
+
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
Destination Selection
+

+ You have the option to apply this rule to a specific IP address or network and/or to multiple peers as the destination.

+ Enabling the "Include peer networks" option will automatically include all Allowed IPs associated with each selected peer as the destination.

+ Please note that selecting multiple peers with included networks on both the source and destination ends may result in a rapid increase in the number of firewall rules generated, depending on your configuration.

+ The "Not Destination" option negates the selected destination IP, network, or peer(s). +

+ +
+
+
+ + +
+
+ + +
+
+

+ +

+
+
+ +
+ +
+
+
+ +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ +
+
Protocol and Port
+

+ Only the most commonly used protocols are listed here. If you require a specific protocol, please open an issue on GitHub.

+ Selecting TCP+UDP will result in the duplication of generated rules.

+ Ports can be specified as single numbers (e.g., 8080) or as ranges (e.g., 8001:8999). +

+ + + +
+
+ +
+ + + +
+
+ + +
+
+

+ +

+
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+ + +
+
+
+
+ + +
+
+

+ +

+
+
+
+
+ + +
+
+
+
+
+ +
+
+
+ + + +{% endblock %} diff --git a/templates/firewall/redirect_rule_list.html b/templates/firewall/redirect_rule_list.html index d62bbda..63a4147 100644 --- a/templates/firewall/redirect_rule_list.html +++ b/templates/firewall/redirect_rule_list.html @@ -2,65 +2,78 @@ {% block content %} - - - - - - - - - - - - - - {% for redirect_rule in redirect_rule_list %} - - - - - - - - - {% endfor %} - -
InstanceProtocolPortDestinationAllow ForwardMasquerade SourceActions
{{ redirect_rule.wireguard_instance }}{{ redirect_rule.protocol }}{{ redirect_rule.port }} - {% if redirect_rule.peer %} - - {% if redirect_rule.peer.name %} - {{ redirect_rule.peer.name }} - {% else %} - {{ redirect_rule.peer.public_key|slice:":16" }}{% if redirect_rule.peer.public_key|length > 16 %}...{% endif %} - {% endif %} - - {% else %} - {{ redirect_rule.ip_address }} - {% endif %} - - - {% if redirect_rule.add_forward_rule %} - - {% else %} - - - {% endif %} +
+
+ {% include "firewall/firewall_nav_tabs.html" %} +
+
+ + + + + + + + + + + + + + {% for redirect_rule in redirect_rule_list %} + + + + + + - + + + + {% endfor %} + + +
InstanceProtocolPortDestinationAllow ForwardMasquerade SourceActions
{{ redirect_rule.wireguard_instance }}{{ redirect_rule.protocol }}{{ redirect_rule.port }} + {% if redirect_rule.peer %} + + {% if redirect_rule.peer.name %} + {{ redirect_rule.peer.name }} + {% else %} + {{ redirect_rule.peer.public_key|slice:":16" }}{% if redirect_rule.peer.public_key|length > 16 %}...{% endif %} + {% endif %} + + {% else %} + {{ redirect_rule.ip_address }} + {% endif %} + + + {% if redirect_rule.add_forward_rule %} + + {% else %} + - - {% if redirect_rule.masquerade_source %} - - - {% else %} - + {% endif %} + + + {% if redirect_rule.masquerade_source %} + + + {% else %} + + + {% endif %} + + + +
+ +
+
+
+
- {% endif %} -
- -
Create Port forwarding Rule {% endblock %} \ No newline at end of file diff --git a/wireguard/models.py b/wireguard/models.py index 6bbfbfa..25090a4 100644 --- a/wireguard/models.py +++ b/wireguard/models.py @@ -88,7 +88,7 @@ class Peer(models.Model): if self.name: return self.name else: - return self.public_key + return self.public_key[:16] + "..." class PeerStatus(models.Model): diff --git a/wireguard_webadmin/urls.py b/wireguard_webadmin/urls.py index 33fa656..73a7fd3 100644 --- a/wireguard_webadmin/urls.py +++ b/wireguard_webadmin/urls.py @@ -23,7 +23,7 @@ from user_manager.views import view_user_list, view_manage_user from accounts.views import view_create_first_user, view_login, view_logout from wireguard_tools.views import export_wireguard_configs, download_config_or_qrcode, restart_wireguard_interfaces from api.views import wireguard_status, cron_check_updates, cron_update_peer_latest_handshake -from firewall.views import view_redirect_rule_list, manage_redirect_rule +from firewall.views import view_redirect_rule_list, manage_redirect_rule, view_firewall_rule_list, manage_firewall_rule urlpatterns = [ @@ -48,5 +48,6 @@ urlpatterns = [ path('api/cron_update_peer_latest_handshake/', cron_update_peer_latest_handshake, name='cron_update_peer_latest_handshake'), path('firewall/port_forward/', view_redirect_rule_list, name='redirect_rule_list'), path('firewall/manage_port_forward_rule/', manage_redirect_rule, name='manage_redirect_rule'), - + path('firewall/rule_list/', view_firewall_rule_list, name='firewall_rule_list'), + path('firewall/manage_firewall_rule/', manage_firewall_rule, name='manage_firewall_rule') ]