mirror of
https://github.com/eduardogsilva/wireguard_webadmin.git
synced 2025-04-19 00:45:16 +00:00
Firewall rule management
This commit is contained in:
parent
9621bf800f
commit
2012c22973
@ -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)
|
||||
|
||||
|
@ -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:
|
||||
@ -50,3 +50,47 @@ class RedirectRuleForm(forms.ModelForm):
|
||||
cleaned_data['peer'] = None
|
||||
|
||||
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']
|
||||
|
||||
|
||||
|
||||
|
61
firewall/migrations/0003_firewallsettings_forwardrule.py
Normal file
61
firewall/migrations/0003_firewallsettings_forwardrule.py
Normal file
@ -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')),
|
||||
],
|
||||
),
|
||||
]
|
18
firewall/migrations/0004_rename_forwardrule_firewallrule.py
Normal file
18
firewall/migrations/0004_rename_forwardrule_firewallrule.py
Normal file
@ -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',
|
||||
),
|
||||
]
|
@ -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),
|
||||
),
|
||||
]
|
18
firewall/migrations/0006_alter_firewallsettings_created.py
Normal file
18
firewall/migrations/0006_alter_firewallsettings_created.py
Normal file
@ -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),
|
||||
),
|
||||
]
|
18
firewall/migrations/0007_firewallsettings_pending_changes.py
Normal file
18
firewall/migrations/0007_firewallsettings_pending_changes.py
Normal file
@ -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),
|
||||
),
|
||||
]
|
@ -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),
|
||||
),
|
||||
]
|
@ -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',
|
||||
),
|
||||
]
|
@ -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),
|
||||
),
|
||||
]
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
||||
@ -53,3 +54,56 @@ def manage_redirect_rule(request):
|
||||
context['instance'] = instance
|
||||
|
||||
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)
|
||||
|
@ -25,6 +25,7 @@
|
||||
<link rel="stylesheet" href="/static/AdminLTE-3.2.0/plugins/daterangepicker/daterangepicker.css">
|
||||
<!-- summernote -->
|
||||
<link rel="stylesheet" href="/static/AdminLTE-3.2.0/plugins/summernote/summernote-bs4.min.css">
|
||||
{% block page_custom_head %}{% endblock%}
|
||||
</head>
|
||||
{% load custom_tags %}
|
||||
{% tag_webadmin_version as webadmin_version %}
|
||||
|
12
templates/firewall/firewall_nav_tabs.html
Normal file
12
templates/firewall/firewall_nav_tabs.html
Normal file
@ -0,0 +1,12 @@
|
||||
<ul class="nav nav-tabs" role="tablist">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if current_chain == "forward" %}active{% endif %}" href="/firewall/rule_list/?chain=forward" role="tab">Forward</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if current_chain == "portforward" %}active{% endif %}" href="/firewall/port_forward/" role="tab">Port Forward</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if current_chain == "postrouting" %}active{% endif %}" href="/firewall/rule_list/?chain=postrouting" role="tab">Post Routing</a>
|
||||
</li>
|
||||
|
||||
</ul>
|
175
templates/firewall/firewall_rule_list.html
Normal file
175
templates/firewall/firewall_rule_list.html
Normal file
@ -0,0 +1,175 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block page_custom_head %}
|
||||
<style>
|
||||
.first-line-container {
|
||||
display: flex;
|
||||
align-items: center; /* Centraliza os itens verticalmente */
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.more-link {
|
||||
margin-left: auto; /* Empurra o link para a direita */
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.more-text {
|
||||
display: none;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
</style>
|
||||
{% endblock%}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="card card-primary card-outline">
|
||||
<div class="card-body">
|
||||
{% include "firewall/firewall_nav_tabs.html" %}
|
||||
<div class="tab-content" id="custom-content-below-tabContent">
|
||||
<div class="tab-pane fade show active" id="custom-content-below-home" role="tabpanel" aria-labelledby="custom-content-below-home-tab">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<th>#</th>
|
||||
<th><i class="fas fa-info-circle"></i></th>
|
||||
<th>In</th>
|
||||
<th>Out</th>
|
||||
<th>Source</th>
|
||||
<th>Destination</th>
|
||||
<th>Protocol</th>
|
||||
<th>Port</th>
|
||||
<th>State</th>
|
||||
<th>Action</th>
|
||||
<th></th>
|
||||
|
||||
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for rule in firewall_rule_list %}
|
||||
|
||||
<tr>
|
||||
<td style="width: 1%; white-space: nowrap;">{{ rule.sort_order }}</td>
|
||||
<td style="width: 1%; white-space: nowrap;">{% if rule.description %}<i class="fas fa-info-circle" title="{{ rule.description }}"></i>{% endif %}</td>
|
||||
<td>{{ rule.in_interface }}</td>
|
||||
<td>{{ rule.out_interface }}</td>
|
||||
<td>
|
||||
{% if rule.source_ip %}{% if rule.not_source %}<span title="Not source">!</span> {% endif %}{{ rule.source_ip }}/{{ rule.source_netmask }}<br>{% endif%}
|
||||
{% for peer in rule.source_peer.all %}{% if rule.not_source %}<span title="Not source">!</span> {% endif %}{{ peer }}{% if rule.source_peer_include_networks %} <span title="Include peer networks">+</span>{% endif %}<br>{% endfor %}
|
||||
|
||||
</td>
|
||||
<td>
|
||||
{% if rule.destination_ip %}{% if rule.not_destination %}<span title="Not destination">!</span> {% endif %}{{ rule.destination_ip }}/{{ rule.destination_netmask }}<br>{% endif%}
|
||||
{% for peer in rule.destination_peer.all %}{% if rule.not_destination %}<span title="Not destination">!</span> {% endif %}{{ peer }}{% if rule.destination_peer_include_networks %} <span title="Include peer networks">+</span>{% endif %}<br>{% endfor %}
|
||||
</td>
|
||||
|
||||
<td>{{ rule.get_protocol_display }}</td>
|
||||
<td>{{ rule.destination_port }}</td>
|
||||
<td>
|
||||
{% if rule.state_new %}{% if rule.not_state %}<span title="Not state">! </span>{% endif %}New<br>{% endif %}
|
||||
{% if rule.state_related %}{% if rule.not_state %}<span title="Not state">! </span>{% endif %}Related<br>{% endif %}
|
||||
{% if rule.state_established %}{% if rule.not_state %}<span title="Not state">! </span>{% endif %}Established<br>{% endif %}
|
||||
{% if rule.state_invalid %}{% if rule.not_state %}<span title="Not state">! </span>{% endif %}Invalid<br>{% endif %}
|
||||
{% if rule.state_untracked %}{% if rule.not_state %}<span title="Not state">! </span>{% endif %}Untracked<br>{% endif %}
|
||||
</td>
|
||||
<td>{{ rule.get_rule_action_display }}</td>
|
||||
{% comment%}
|
||||
<td>{{ rule. }}</td>
|
||||
{% endcomment %}
|
||||
<td style="width: 1%; white-space: nowrap;">
|
||||
<a href="/firewall/manage_firewall_rule/?uuid={{ rule.uuid }}" ><i class="far fa-edit"></i></a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<a href="/firewall/manage_firewall_rule/" class='btn btn-primary'>Create Firewall Rule</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block custom_page_scripts %}
|
||||
{% comment %}
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
document.querySelectorAll('td').forEach(function(td) {
|
||||
// Conta o número de <br> na célula
|
||||
let brCount = (td.innerHTML.match(/<br>/g) || []).length;
|
||||
|
||||
// Aplica a lógica de mostrar/esconder apenas se houver 2 ou mais <br>
|
||||
if (brCount >= 2) {
|
||||
let contentParts = td.innerHTML.split('<br>');
|
||||
// Assume que queremos manter a primeira linha visível, adiciona explicitamente uma quebra de linha antes do conteúdo escondido
|
||||
td.innerHTML = contentParts[0] + '<br>' +
|
||||
'<span style="display: none;">' +
|
||||
contentParts.slice(1).join('<br>') + '</span>' +
|
||||
'<button class="more-btn">Mais</button>';
|
||||
}
|
||||
});
|
||||
|
||||
// Adiciona evento de clique para botões "Mais"
|
||||
document.querySelectorAll('.more-btn').forEach(function(button) {
|
||||
button.addEventListener('click', function() {
|
||||
let moreText = this.previousElementSibling; // O span com o texto extra
|
||||
if (moreText.style.display === "none") {
|
||||
moreText.style.display = "inline";
|
||||
this.textContent = "Menos";
|
||||
} else {
|
||||
moreText.style.display = "none";
|
||||
this.textContent = "Mais";
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
</script>
|
||||
{% endcomment %}
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
document.querySelectorAll('td').forEach(function(td) {
|
||||
let brCount = (td.innerHTML.match(/<br>/g) || []).length;
|
||||
|
||||
if (brCount >= 2) {
|
||||
let contentParts = td.innerHTML.split('<br>');
|
||||
// Mantém a estrutura do contêiner com o texto e o link "More"
|
||||
let firstLineContainer = `<div class="first-line-container">${contentParts[0]}<a href="#" class="more-link">more</a></div>`;
|
||||
|
||||
td.innerHTML = firstLineContainer +
|
||||
'<span class="more-text" style="display: none;">' +
|
||||
contentParts.slice(1).join('<br>') + '</span>';
|
||||
}
|
||||
});
|
||||
|
||||
document.querySelectorAll('.more-link').forEach(function(link) {
|
||||
link.addEventListener('click', function(e) {
|
||||
e.preventDefault(); // Impede a ação padrão do link
|
||||
let moreText = this.parentNode.nextElementSibling; // Seleciona o span corretamente
|
||||
if (moreText.style.display === "none") {
|
||||
moreText.style.display = "inline";
|
||||
this.textContent = "less";
|
||||
} else {
|
||||
moreText.style.display = "none";
|
||||
this.textContent = "more";
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
434
templates/firewall/manage_firewall_rule.html
Normal file
434
templates/firewall/manage_firewall_rule.html
Normal file
@ -0,0 +1,434 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container mt-3">
|
||||
<div class="card card-primary">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Manage Firewall Rule</h3>
|
||||
</div>
|
||||
<form method="post" action="">
|
||||
{% csrf_token %}
|
||||
<div class="accordion" id="firewallRuleAccordion">
|
||||
<!-- General Group -->
|
||||
<div class="card">
|
||||
<div class="card-header" id="headingGeneral">
|
||||
<h2 class="mb-0">
|
||||
<button class="btn btn-link btn-block text-left" type="button" data-toggle="collapse" data-target="#collapseGeneral" aria-expanded="true" aria-controls="collapseGeneral">
|
||||
General
|
||||
</button>
|
||||
</h2>
|
||||
</div>
|
||||
<div id="collapseGeneral" class="collapse show" aria-labelledby="headingGeneral" >
|
||||
{% comment %}
|
||||
<div class="card-body">
|
||||
<div class="form-group">
|
||||
<label for="description">{{ form.description.label }}</label>
|
||||
<input type="text" class="form-control" id="description" name="description" value="{{ form.description.value|default_if_none:""}}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="wireguard_instance">{{ form.wireguard_instance.label }}</label>
|
||||
<select class="form-control" id="wireguard_instance" name="wireguard_instance">
|
||||
{% for instance in form.wireguard_instance.field.queryset %}
|
||||
<option value="{{ instance.pk }}" {% if form.wireguard_instance.value == instance.pk %} selected {% endif %}>{{ instance }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="firewall_chain">{{ form.firewall_chain.label }}</label>
|
||||
<select class="form-control" id="firewall_chain" name="firewall_chain">
|
||||
{% for value, display in form.firewall_chain.field.choices %}
|
||||
<option value="{{ value }}" {% if form.firewall_chain.value == value %} selected {% endif %}>{{ display }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="sort_order">{{ form.sort_order.label }}</label>
|
||||
<input type="number" class="form-control" id="sort_order" name="sort_order" value="{{ form.sort_order.value }}">
|
||||
</div>
|
||||
</div>{% endcomment %}
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-row">
|
||||
<div class="form-group col-md-12">
|
||||
<label for="description">{{ form.description.label }}</label>
|
||||
<input type="text" class="form-control" id="description" name="description" value="{{ form.description.value|default_if_none:'' }}">
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group col-md-6">
|
||||
<label for="firewall_chain">{{ form.firewall_chain.label }}</label>
|
||||
<select class="form-control" id="firewall_chain" name="firewall_chain">
|
||||
{% for value, display in form.firewall_chain.field.choices %}
|
||||
<option value="{{ value }}" {% if form.firewall_chain.value == value %} selected {% endif %}>{{ display }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group col-md-6">
|
||||
<label for="sort_order">{{ form.sort_order.label }}</label>
|
||||
<input type="number" class="form-control" id="sort_order" name="sort_order" value="{{ form.sort_order.value }}">
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<h5>Advanced VPN Firewall Configuration</h5>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Interface Group -->
|
||||
<div class="card">
|
||||
<div class="card-header" id="headingInterface">
|
||||
<h2 class="mb-0">
|
||||
<button class="btn btn-link btn-block text-left collapsed" type="button" data-toggle="collapse" data-target="#collapseInterface" aria-expanded="false" aria-controls="collapseInterface">
|
||||
Interface
|
||||
</button>
|
||||
</h2>
|
||||
</div>
|
||||
<div id="collapseInterface" class="collapse" aria-labelledby="headingInterface" >
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<!-- In Interface -->
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="in_interface">{{ form.in_interface.label }}</label>
|
||||
<select class="form-control" id="in_interface" name="in_interface">
|
||||
{% for value, display in form.in_interface.field.choices %}
|
||||
<option value="{{ value }}" {% if form.in_interface.value == value %} selected {% endif %}>{{ display }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Out Interface -->
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="out_interface">{{ form.out_interface.label }}</label>
|
||||
<select class="form-control" id="out_interface" name="out_interface">
|
||||
{% for value, display in form.out_interface.field.choices %}
|
||||
<option value="{{ value }}" {% if form.out_interface.value == value %} selected {% endif %}>{{ display }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Source Group -->
|
||||
<div class="card">
|
||||
<div class="card-header" id="headingSource">
|
||||
<h2 class="mb-0">
|
||||
<button class="btn btn-link btn-block text-left collapsed" type="button" data-toggle="collapse" data-target="#collapseSource" aria-expanded="false" aria-controls="collapseSource">
|
||||
Source
|
||||
</button>
|
||||
</h2>
|
||||
</div>
|
||||
<div id="collapseSource" class="collapse" aria-labelledby="headingSource" >
|
||||
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-row">
|
||||
<div class="form-group col-md-8">
|
||||
<label for="source_ip">{{ form.source_ip.label }}</label>
|
||||
<input type="text" class="form-control" id="source_ip" name="source_ip" value="{{ form.source_ip.value|default_if_none:'' }}">
|
||||
</div>
|
||||
<div class="form-group col-md-4">
|
||||
<label for="source_netmask">{{ form.source_netmask.label }}</label>
|
||||
<input type="number" class="form-control" id="source_netmask" name="source_netmask" value="{{ form.source_netmask.value }}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="source_peer">{{ form.source_peer.label }}</label>
|
||||
<select class="form-control" id="source_peer" name="source_peer" multiple>
|
||||
{% for peer in form.source_peer.field.queryset %}
|
||||
<option value="{{ peer.pk }}" {% if peer.pk in form.source_peer.value %} selected {% endif %}>{{ peer }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="source_peer_include_networks" name="source_peer_include_networks" {% if form.source_peer_include_networks.value %} checked {% endif %}>
|
||||
<label class="form-check-label" for="source_peer_include_networks">{{ form.source_peer_include_networks.label }}</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="not_source" name="not_source" {% if form.not_source.value %} checked {% endif %}>
|
||||
<label class="form-check-label" for="not_source">{{ form.not_source.label }}</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<h5>Source Selection</h5>
|
||||
<p>
|
||||
You have the option to apply this rule to a specific IP address or network and/or to multiple peers.<br><br>
|
||||
Enabling the "Include peer networks" option will automatically include all Allowed IPs associated with each selected peer.<br><br>
|
||||
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.<br><br>
|
||||
The "Not Source" option negates the selected source IP, network, or peer(s).
|
||||
</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Destination Group -->
|
||||
<div class="card">
|
||||
<div class="card-header" id="headingDestination">
|
||||
<h2 class="mb-0">
|
||||
<button class="btn btn-link btn-block text-left collapsed" type="button" data-toggle="collapse" data-target="#collapseDestination" aria-expanded="false" aria-controls="collapseDestination">
|
||||
Destination
|
||||
</button>
|
||||
</h2>
|
||||
</div>
|
||||
<div id="collapseDestination" class="collapse" aria-labelledby="headingDestination" >
|
||||
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-row">
|
||||
<div class="form-group col-md-8">
|
||||
<label for="destination_ip">{{ form.destination_ip.label }}</label>
|
||||
<input type="text" class="form-control" id="destination_ip" name="destination_ip" value="{{ form.destination_ip.value|default_if_none:'' }}">
|
||||
</div>
|
||||
<div class="form-group col-md-4">
|
||||
<label for="destination_netmask">{{ form.destination_netmask.label }}</label>
|
||||
<input type="number" class="form-control" id="destination_netmask" name="destination_netmask" value="{{ form.destination_netmask.value }}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="destination_peer">{{ form.destination_peer.label }}</label>
|
||||
<select class="form-control" id="destination_peer" name="destination_peer" multiple>
|
||||
{% for peer in form.destination_peer.field.queryset %}
|
||||
<option value="{{ peer.pk }}" {% if peer.pk in form.destination_peer.value %} selected {% endif %}>{{ peer }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="destination_peer_include_networks" name="destination_peer_include_networks" {% if form.destination_peer_include_networks.value %} checked {% endif %}>
|
||||
<label class="form-check-label" for="destination_peer_include_networks">{{ form.destination_peer_include_networks.label }}</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="not_destination" name="not_destination" {% if form.not_destination.value %} checked {% endif %}>
|
||||
<label class="form-check-label" for="not_destination">{{ form.not_destination.label }}</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<h5>Destination Selection</h5>
|
||||
<p>
|
||||
You have the option to apply this rule to a specific IP address or network and/or to multiple peers as the destination.<br><br>
|
||||
Enabling the "Include peer networks" option will automatically include all Allowed IPs associated with each selected peer as the destination.<br><br>
|
||||
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.<br><br>
|
||||
The "Not Destination" option negates the selected destination IP, network, or peer(s).
|
||||
</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Protocol Group -->
|
||||
<div class="card">
|
||||
<div class="card-header" id="headingProtocol">
|
||||
<h2 class="mb-0">
|
||||
<button class="btn btn-link btn-block text-left collapsed" type="button" data-toggle="collapse" data-target="#collapseProtocol" aria-expanded="false" aria-controls="collapseProtocol">
|
||||
Protocol
|
||||
</button>
|
||||
</h2>
|
||||
</div>
|
||||
<div id="collapseProtocol" class="collapse" aria-labelledby="headingProtocol" >
|
||||
|
||||
<div class="card-body">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="row">
|
||||
<!-- Protocol -->
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="protocol">{{ form.protocol.label }}</label>
|
||||
<select class="form-control" id="protocol" name="protocol">
|
||||
{% for value, display in form.protocol.field.choices %}
|
||||
<option value="{{ value }}" {% if form.protocol.value == value %} selected {% endif %}>{{ display }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Destination Port -->
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="destination_port">{{ form.destination_port.label }}</label>
|
||||
<input type="text" class="form-control" id="destination_port" name="destination_port" value="{{ form.destination_port.value|default_if_none:'' }}">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<h5>Protocol and Port</h5>
|
||||
<p>
|
||||
Only the most commonly used protocols are listed here. If you require a specific protocol, please open an issue on GitHub.<br><br>
|
||||
Selecting TCP+UDP will result in the duplication of generated rules.<br><br>
|
||||
Ports can be specified as single numbers (e.g., 8080) or as ranges (e.g., 8001:8999).
|
||||
</p>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Packet State Group -->
|
||||
<div class="card">
|
||||
<div class="card-header" id="headingPacketState">
|
||||
<h2 class="mb-0">
|
||||
<button class="btn btn-link btn-block text-left collapsed" type="button" data-toggle="collapse" data-target="#collapsePacketState" aria-expanded="false" aria-controls="collapsePacketState">
|
||||
Packet State
|
||||
</button>
|
||||
</h2>
|
||||
</div>
|
||||
<div id="collapsePacketState" class="collapse" aria-labelledby="headingPacketState" >
|
||||
<div class="card-body">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="state_new" name="state_new" {% if form.state_new.value %} checked {% endif %}>
|
||||
<label class="form-check-label" for="state_new">{{ form.state_new.label }}</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="state_related" name="state_related" {% if form.state_related.value %} checked {% endif %}>
|
||||
<label class="form-check-label" for="state_related">{{ form.state_related.label }}</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="state_established" name="state_established" {% if form.state_established.value %} checked {% endif %}>
|
||||
<label class="form-check-label" for="state_established">{{ form.state_established.label }}</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="state_invalid" name="state_invalid" {% if form.state_invalid.value %} checked {% endif %}>
|
||||
<label class="form-check-label" for="state_invalid">{{ form.state_invalid.label }}</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="state_untracked" name="state_untracked" {% if form.state_untracked.value %} checked {% endif %}>
|
||||
<label class="form-check-label" for="state_untracked">{{ form.state_untracked.label }}</label>
|
||||
</div>
|
||||
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="not_state" name="not_state" {% if form.not_state.value %} checked {% endif %}>
|
||||
<label class="form-check-label" for="not_state">{{ form.not_state.label }}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Action Group -->
|
||||
<div class="card">
|
||||
<div class="card-header" id="headingAction">
|
||||
<h2 class="mb-0">
|
||||
<button class="btn btn-link btn-block text-left collapsed" type="button" data-toggle="collapse" data-target="#collapseAction" aria-expanded="false" aria-controls="collapseAction">
|
||||
Action
|
||||
</button>
|
||||
</h2>
|
||||
</div>
|
||||
<div id="collapseAction" class="collapse" aria-labelledby="headingAction" >
|
||||
<div class="card-body">
|
||||
<div class="form-group">
|
||||
<label for="rule_action">{{ form.rule_action.label }}</label>
|
||||
<select class="form-control" id="rule_action" name="rule_action">
|
||||
{% for value, display in form.rule_action.field.choices %}
|
||||
<option value="{{ value }}" {% if form.rule_action.value == value %} selected {% endif %}>{{ display }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<button type="submit" class="btn btn-primary">Submit</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
// Array de IDs dos campos a serem ignorados
|
||||
var ignoreFields = ['source_netmask', 'destination_netmask'];
|
||||
|
||||
// Itera por cada painel para verificar se contém dados nos campos
|
||||
$('.collapse').each(function() {
|
||||
var panel = $(this);
|
||||
var shouldOpen = false;
|
||||
|
||||
// Verifica inputs do tipo texto e número, excluindo os ignorados
|
||||
panel.find('input[type=text], input[type=number], textarea').each(function() {
|
||||
if (!ignoreFields.includes(this.id) && $(this).val()) {
|
||||
shouldOpen = true;
|
||||
}
|
||||
});
|
||||
|
||||
// Verifica checkboxes e radios, excluindo os ignorados
|
||||
panel.find('input[type=checkbox], input[type=radio]').each(function() {
|
||||
if (!ignoreFields.includes(this.id) && $(this).is(':checked')) {
|
||||
shouldOpen = true;
|
||||
}
|
||||
});
|
||||
|
||||
// Verifica selects, incluindo múltipla seleção, excluindo os ignorados
|
||||
panel.find('select').each(function() {
|
||||
if (!ignoreFields.includes(this.id) && $(this).find('option:selected').length > 0) {
|
||||
var allUnselected = true;
|
||||
$(this).find('option:selected').each(function() {
|
||||
if ($(this).val()) {
|
||||
allUnselected = false;
|
||||
}
|
||||
});
|
||||
if (!allUnselected) {
|
||||
shouldOpen = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Se dados relevantes foram encontrados e não são para ser ignorados, abre o painel
|
||||
if (shouldOpen) {
|
||||
panel.collapse('show');
|
||||
}
|
||||
});
|
||||
|
||||
// Controla o abrir/fechar dos painéis sem afetar os outros
|
||||
$('.card-header button').on('click', function(e) {
|
||||
e.preventDefault();
|
||||
var target = $(this).attr('data-target');
|
||||
$(target).collapse('toggle');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
@ -2,65 +2,78 @@
|
||||
|
||||
{% block content %}
|
||||
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Instance</th>
|
||||
<th>Protocol</th>
|
||||
<th>Port</th>
|
||||
<th>Destination</th>
|
||||
<th>Allow Forward</th>
|
||||
<th>Masquerade Source</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for redirect_rule in redirect_rule_list %}
|
||||
<tr>
|
||||
<td>{{ redirect_rule.wireguard_instance }}</td>
|
||||
<td>{{ redirect_rule.protocol }}</td>
|
||||
<td>{{ redirect_rule.port }}</td>
|
||||
<td>
|
||||
{% if redirect_rule.peer %}
|
||||
<a href="/peer/manage/?peer={{ redirect_rule.peer.uuid }}">
|
||||
{% 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 %}
|
||||
</a>
|
||||
{% else %}
|
||||
{{ redirect_rule.ip_address }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<div class="card card-primary card-outline">
|
||||
<div class="card-body">
|
||||
{% include "firewall/firewall_nav_tabs.html" %}
|
||||
<div class="tab-content" id="custom-content-below-tabContent">
|
||||
<div class="tab-pane fade show active" id="custom-content-below-home" role="tabpanel" aria-labelledby="custom-content-below-home-tab">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Instance</th>
|
||||
<th>Protocol</th>
|
||||
<th>Port</th>
|
||||
<th>Destination</th>
|
||||
<th>Allow Forward</th>
|
||||
<th>Masquerade Source</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for redirect_rule in redirect_rule_list %}
|
||||
<tr>
|
||||
<td>{{ redirect_rule.wireguard_instance }}</td>
|
||||
<td>{{ redirect_rule.protocol }}</td>
|
||||
<td>{{ redirect_rule.port }}</td>
|
||||
<td>
|
||||
{% if redirect_rule.peer %}
|
||||
<a href="/peer/manage/?peer={{ redirect_rule.peer.uuid }}">
|
||||
{% 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 %}
|
||||
</a>
|
||||
{% else %}
|
||||
{{ redirect_rule.ip_address }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
|
||||
{% if redirect_rule.add_forward_rule %}
|
||||
<i class="fas fa-check"></i>
|
||||
{% else %}
|
||||
<i class="fas fa-times"></i>
|
||||
{% if redirect_rule.add_forward_rule %}
|
||||
<i class="fas fa-check"></i>
|
||||
{% else %}
|
||||
<i class="fas fa-times"></i>
|
||||
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
</td>
|
||||
<td>
|
||||
{% if redirect_rule.masquerade_source %}
|
||||
<i class="fas fa-check"></i>
|
||||
<i class="fas fa-exclamation-triangle" title="This serves as a temporary solution when a peer does not use the VPN as its default gateway. It's important to note that this configuration is not recommended, as it alters the source address of all connections to match the IP address of the WireGuard instance."></i>
|
||||
{% else %}
|
||||
<i class="fas fa-times"></i>
|
||||
</td>
|
||||
<td>
|
||||
{% if redirect_rule.masquerade_source %}
|
||||
<i class="fas fa-check"></i>
|
||||
<i class="fas fa-exclamation-triangle" title="This serves as a temporary solution when a peer does not use the VPN as its default gateway. It's important to note that this configuration is not recommended, as it alters the source address of all connections to match the IP address of the WireGuard instance."></i>
|
||||
{% else %}
|
||||
<i class="fas fa-times"></i>
|
||||
|
||||
{% endif %}
|
||||
|
||||
</td>
|
||||
<td style="width: 1%; white-space: nowrap;">
|
||||
<a href="/firewall/manage_port_forward_rule/?uuid={{ redirect_rule.uuid }}" ><i class="far fa-edit"></i></a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endif %}
|
||||
|
||||
</td>
|
||||
<td style="width: 1%; white-space: nowrap;">
|
||||
<a href="/firewall/manage_port_forward_rule/?uuid={{ redirect_rule.uuid }}" ><i class="far fa-edit"></i></a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<a href="/firewall/manage_port_forward_rule/" class='btn btn-primary'>Create Port forwarding Rule</a>
|
||||
{% endblock %}
|
@ -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):
|
||||
|
@ -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')
|
||||
]
|
||||
|
Loading…
x
Reference in New Issue
Block a user