Legacy firewall migrate routines and export fw rules.

This commit is contained in:
Eduardo Silva 2024-03-04 12:58:33 -03:00
parent de073a4795
commit b6a7cdaac9
14 changed files with 406 additions and 112 deletions

View File

@ -142,8 +142,6 @@ class FirewallSettingsForm(forms.ModelForm):
if not interface.startswith('wg') and interface != 'lo': if not interface.startswith('wg') and interface != 'lo':
interface_choices.append((interface, interface)) interface_choices.append((interface, interface))
#if interface.startswith('wg'):
# list_network_interfaces().remove(interface)
default_forward_policy = forms.ChoiceField(label='Default Forward Policy', choices=[('accept', 'ACCEPT'), ('reject', 'REJECT'), ('drop', 'DROP')], initial='accept') default_forward_policy = forms.ChoiceField(label='Default Forward Policy', choices=[('accept', 'ACCEPT'), ('reject', 'REJECT'), ('drop', 'DROP')], initial='accept')
allow_peer_to_peer = forms.BooleanField(label='Allow Peer to Peer', required=False) allow_peer_to_peer = forms.BooleanField(label='Allow Peer to Peer', required=False)
allow_instance_to_instance = forms.BooleanField(label='Allow Instance to Instance', required=False) allow_instance_to_instance = forms.BooleanField(label='Allow Instance to Instance', required=False)

View File

@ -0,0 +1,18 @@
# Generated by Django 5.0.2 on 2024-03-04 11:21
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('firewall', '0010_alter_firewallrule_firewall_chain'),
]
operations = [
migrations.AddField(
model_name='firewallsettings',
name='last_firewall_reset',
field=models.DateTimeField(blank=True, null=True),
),
]

View File

@ -73,6 +73,7 @@ class FirewallSettings(models.Model):
allow_instance_to_instance = models.BooleanField(default=True) allow_instance_to_instance = models.BooleanField(default=True)
wan_interface = models.CharField(max_length=12, default='eth0') wan_interface = models.CharField(max_length=12, default='eth0')
pending_changes = models.BooleanField(default=False) pending_changes = models.BooleanField(default=False)
last_firewall_reset = models.DateTimeField(blank=True, null=True)
created = models.DateTimeField(auto_now_add=True) created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True) updated = models.DateTimeField(auto_now=True)

View File

@ -1,10 +1,10 @@
from firewall.models import FirewallRule from firewall.models import FirewallRule, FirewallSettings, RedirectRule
from wireguard.models import Peer, PeerAllowedIP from wireguard.models import Peer, PeerAllowedIP, WireGuardInstance
from django.utils import timezone
def get_peer_addresses(peers, include_networks): def get_peer_addresses(peers, include_networks):
addresses = [] addresses = []
# Indica se algum peer está completamente sem PeerAllowedIP
missing_ip = False
for peer in peers.all(): for peer in peers.all():
peer_ips = peer.peerallowedip_set.all().order_by('priority') peer_ips = peer.peerallowedip_set.all().order_by('priority')
if not include_networks: if not include_networks:
@ -13,17 +13,65 @@ def get_peer_addresses(peers, include_networks):
if peer_ips.exists(): if peer_ips.exists():
addresses.extend([f"{peer_ip.allowed_ip}/{peer_ip.netmask}" for peer_ip in peer_ips]) addresses.extend([f"{peer_ip.allowed_ip}/{peer_ip.netmask}" for peer_ip in peer_ips])
else: else:
missing_ip = True addresses.append(f"Missing IP for selected peer: {peer}")
return addresses, missing_ip return addresses
def generate_iptable_rules():
def reset_firewall_to_default():
for wireguard_instance in WireGuardInstance.objects.all():
wireguard_instance.pending_changes = True
wireguard_instance.legacy_firewall = False
wireguard_instance.post_up = ''
wireguard_instance.post_down = ''
wireguard_instance.save()
firewall_settings, firewall_settings_created = FirewallSettings.objects.get_or_create(name='global')
firewall_settings.pending_changes = True
firewall_settings.last_firewall_reset = timezone.now()
firewall_settings.allow_peer_to_peer = True
firewall_settings.allow_instance_to_instance = True
firewall_settings.wan_interface = 'eth0'
firewall_settings.default_forward_policy = 'drop'
firewall_settings.save()
FirewallRule.objects.all().delete()
RedirectRule.objects.all().delete()
FirewallRule.objects.create(
firewall_chain='postrouting', sort_order=0, out_interface=firewall_settings.wan_interface, rule_action='masquerade',
description='Masquerade traffic from VPN to WAN',
)
FirewallRule.objects.create(
firewall_chain='forward', sort_order=0, rule_action='accept', description='Allow established/related traffic',
state_established=True, state_related=True
)
FirewallRule.objects.create(
firewall_chain='forward', sort_order=1, rule_action='reject', description='Reject traffic to private networks exiting on WAN interface',
in_interface='wg+', out_interface=firewall_settings.wan_interface, destination_ip='10.0.0.0', destination_netmask=8
)
FirewallRule.objects.create(
firewall_chain='forward', sort_order=2, rule_action='reject', description='Reject traffic to private networks exiting on WAN interface',
in_interface='wg+', out_interface=firewall_settings.wan_interface, destination_ip='172.16.0.0', destination_netmask=12
)
FirewallRule.objects.create(
firewall_chain='forward', sort_order=3, rule_action='reject', description='Reject traffic to private networks exiting on WAN interface',
in_interface='wg+', out_interface=firewall_settings.wan_interface, destination_ip='192.168.0.0', destination_netmask=16
)
FirewallRule.objects.create(
firewall_chain='forward', sort_order=10, rule_action='accept', description='Allow traffic from VPN to WAN',
in_interface='wg+', out_interface=firewall_settings.wan_interface
)
return
def export_user_firewall():
iptables_rules = [] iptables_rules = []
rules = FirewallRule.objects.all().order_by('firewall_chain', 'sort_order') rules = FirewallRule.objects.all().order_by('firewall_chain', 'sort_order')
for rule in rules: for rule in rules:
source_addresses, source_missing_ip = get_peer_addresses(rule.source_peer, rule.source_peer_include_networks) source_addresses = get_peer_addresses(rule.source_peer, rule.source_peer_include_networks)
destination_addresses, destination_missing_ip = get_peer_addresses(rule.destination_peer, rule.destination_peer_include_networks) destination_addresses = get_peer_addresses(rule.destination_peer, rule.destination_peer_include_networks)
# Adiciona source_ip/destination_ip às listas, se definidos # Adiciona source_ip/destination_ip às listas, se definidos
if rule.source_ip: if rule.source_ip:
@ -41,19 +89,24 @@ def generate_iptable_rules():
for protocol in protocols: for protocol in protocols:
for source in source_addresses: for source in source_addresses:
for destination in destination_addresses: if source and "Missing IP for selected peer:" in source:
description = f" - {rule.description}" if rule.description else "" description = f" - {rule.description}" if rule.description else ""
comment = f"\n# {rule.sort_order} - {rule.uuid}{description}" iptables_rules.append(f"# {rule.sort_order} - {rule.uuid}{description} - {source}\n")
# Pula a geração de regra se um dos peers estiver faltando IP e for relevante para essa combinação
if (source is None and source_missing_ip) or (destination is None and destination_missing_ip):
iptables_rule = f"{comment} - Missing ip for selected peer\n"
iptables_rules.append(iptables_rule)
continue continue
comment += '\n'
for destination in destination_addresses:
if destination and "Missing IP for selected peer:" in destination:
description = f" - {rule.description}" if rule.description else ""
iptables_rules.append(f"# {rule.sort_order} - {rule.uuid}{description} - {destination}\n")
continue
description = f" - {rule.description}" if rule.description else ""
comment = f"# {rule.sort_order} - {rule.uuid}{description}\n"
if rule.firewall_chain == "forward": if rule.firewall_chain == "forward":
rule_base = "iptables -t filter -A FORWARD " rule_base = "iptables -t filter -A WGWADM_FORWARD "
elif rule.firewall_chain == "postrouting": elif rule.firewall_chain == "postrouting":
rule_base = "iptables -t nat -A POSTROUTING " rule_base = "iptables -t nat -A WGWADM_POSTROUTING "
else: else:
rule_base = f"#iptables -A {rule.firewall_chain.upper()} " rule_base = f"#iptables -A {rule.firewall_chain.upper()} "
rule_protocol = f"-p {protocol} " if protocol else "" rule_protocol = f"-p {protocol} " if protocol else ""
@ -61,6 +114,8 @@ def generate_iptable_rules():
rule_source = f"-s {source} " if source else "" rule_source = f"-s {source} " if source else ""
rule_destination = f"-d {destination} " if destination else "" rule_destination = f"-d {destination} " if destination else ""
rule_action = f"-j {rule.rule_action.upper()}" rule_action = f"-j {rule.rule_action.upper()}"
rule_in_interface = f"-i {rule.in_interface} " if rule.in_interface else ""
rule_out_interface = f"-o {rule.out_interface} " if rule.out_interface else ""
states = [] states = []
if rule.state_new: if rule.state_new:
@ -80,9 +135,83 @@ def generate_iptable_rules():
not_source = "! " if rule.not_source and source else "" not_source = "! " if rule.not_source and source else ""
not_destination = "! " if rule.not_destination and destination else "" not_destination = "! " if rule.not_destination and destination else ""
iptables_rule = f"{comment}{rule_base}{rule_in_interface}{rule_out_interface}{not_source}{rule_source}{not_destination}{rule_destination}{rule_state}{rule_protocol}{rule_destination_port}{rule_action}\n"
iptables_rule = f"{comment}{rule_base}{not_source}{rule_source}{not_destination}{rule_destination}{rule_state}{rule_protocol}{rule_destination_port}{rule_action}\n"
iptables_rules.append(iptables_rule) iptables_rules.append(iptables_rule)
return "".join(iptables_rules) return "".join(iptables_rules)
def generate_firewall_header():
firewall_settings, firewall_settings_created = FirewallSettings.objects.get_or_create(name='global')
header = f'''#!/bin/bash
# Description: Firewall rules for WireGuard_WebAdmin
# Do not edit this file directly. Use the web interface to manage firewall rules.
#
# This script was generated by WireGuard_WebAdmin on {timezone.now().strftime('%Y-%m-%d %H:%M:%S %Z')}
#
iptables -t nat -N WGWADM_POSTROUTING >> /dev/null 2>&1
iptables -t nat -N WGWADM_PREROUTING >> /dev/null 2>&1
iptables -t filter -N WGWADM_FORWARD >> /dev/null 2>&1
iptables -t nat -F WGWADM_POSTROUTING
iptables -t nat -F WGWADM_PREROUTING
iptables -t filter -F WGWADM_FORWARD
iptables -t nat -D POSTROUTING -j WGWADM_POSTROUTING >> /dev/null 2>&1
iptables -t nat -D PREROUTING -j WGWADM_PREROUTING >> /dev/null 2>&1
iptables -t filter -D FORWARD -j WGWADM_FORWARD >> /dev/null 2>&1
iptables -t nat -I POSTROUTING -j WGWADM_POSTROUTING
iptables -t nat -I PREROUTING -j WGWADM_PREROUTING
iptables -t filter -I FORWARD -j WGWADM_FORWARD
'''
return header
def generate_firewall_footer():
firewall_settings, firewall_settings_created = FirewallSettings.objects.get_or_create(name='global')
footer = '# The following rules come from Firewall settings\n'
footer += '# Default FORWARD policy\n'
footer += f'iptables -t filter -P FORWARD {firewall_settings.default_forward_policy.upper()}\n'
footer += '# Same instance Peer to Peer traffic\n'
for wireguard_instance in WireGuardInstance.objects.all().order_by('instance_id'):
footer += f'iptables -t filter -A WGWADM_FORWARD -i wg{wireguard_instance.instance_id} -o wg{wireguard_instance.instance_id} -j '
footer += 'ACCEPT\n' if firewall_settings.allow_peer_to_peer else "REJECT\n"
footer += '# Instance to Instance traffic\n'
footer += 'iptables -t filter -A WGWADM_FORWARD -i wg+ -o wg+ -j '
footer += 'ACCEPT\n' if firewall_settings.allow_instance_to_instance else "REJECT\n"
return footer
def generate_port_forward_firewall():
firewall_settings, firewall_settings_created = FirewallSettings.objects.get_or_create(name='global')
redirect_firewall = ''
wan_interface = firewall_settings.wan_interface
for redirect_rule in RedirectRule.objects.all().order_by('port'):
description = f" - {redirect_rule.description} " if redirect_rule.description else ""
rule_destination = redirect_rule.ip_address
if redirect_rule.peer:
peer_allowed_ip_address = PeerAllowedIP.objects.filter(peer=redirect_rule.peer, netmask=32, priority=0).first()
if peer_allowed_ip_address:
rule_destination = peer_allowed_ip_address.allowed_ip
if rule_destination:
rule_text = f"# {redirect_rule.port}/{redirect_rule.protocol} - {redirect_rule.uuid} - Port Forward Rule set{description}\n"
rule_text += f"iptables -t nat -A WGWADM_PREROUTING -p {redirect_rule.protocol} -d wireguard-webadmin -i {wan_interface} --dport {redirect_rule.port} -j DNAT --to-dest {rule_destination}:{redirect_rule.port}\n"
if redirect_rule.masquerade_source:
rule_text += f"iptables -t nat -A WGWADM_POSTROUTING -p {redirect_rule.protocol} -d {rule_destination} -o wg+ --dport {redirect_rule.port} -j MASQUERADE\n"
if redirect_rule.add_forward_rule:
rule_text += f"iptables -t filter -A WGWADM_FORWARD -p {redirect_rule.protocol} -d {rule_destination} -i {wan_interface} -o wg+ --dport {redirect_rule.port} -j ACCEPT\n"
redirect_firewall += rule_text
else:
rule_text = f"# {redirect_rule.port}/{redirect_rule.protocol} - {redirect_rule.uuid} - Port Forward Rule set{description} - Missing IP for selected peer: {redirect_rule.peer}\n"
redirect_firewall += rule_text
return redirect_firewall

View File

@ -6,11 +6,16 @@ from firewall.forms import RedirectRuleForm, FirewallRuleForm, FirewallSettingsF
from django.contrib import messages from django.contrib import messages
from wireguard.models import WireGuardInstance from wireguard.models import WireGuardInstance
from user_manager.models import UserAcl from user_manager.models import UserAcl
from firewall.tools import generate_iptable_rules from firewall.tools import export_user_firewall, generate_firewall_header, generate_firewall_footer, generate_port_forward_firewall, reset_firewall_to_default
from django.contrib.auth.decorators import login_required
from django.utils import timezone
@login_required
def view_redirect_rule_list(request): def view_redirect_rule_list(request):
wireguard_instances = WireGuardInstance.objects.all().order_by('instance_id') wireguard_instances = WireGuardInstance.objects.all().order_by('instance_id')
if wireguard_instances.filter(legacy_firewall=True).exists():
return redirect('/firewall/migration_required/')
if wireguard_instances.filter(pending_changes=True).exists(): if wireguard_instances.filter(pending_changes=True).exists():
pending_changes_warning = True pending_changes_warning = True
else: else:
@ -24,6 +29,7 @@ def view_redirect_rule_list(request):
return render(request, 'firewall/redirect_rule_list.html', context=context) return render(request, 'firewall/redirect_rule_list.html', context=context)
@login_required
def manage_redirect_rule(request): def manage_redirect_rule(request):
if not UserAcl.objects.filter(user=request.user).filter(user_level__gte=40).exists(): if not UserAcl.objects.filter(user=request.user).filter(user_level__gte=40).exists():
return render(request, 'access_denied.html', {'page_title': 'Access Denied'}) return render(request, 'access_denied.html', {'page_title': 'Access Denied'})
@ -59,8 +65,11 @@ def manage_redirect_rule(request):
return render(request, 'firewall/manage_redirect_rule.html', context=context) return render(request, 'firewall/manage_redirect_rule.html', context=context)
@login_required
def view_firewall_rule_list(request): def view_firewall_rule_list(request):
wireguard_instances = WireGuardInstance.objects.all().order_by('instance_id') wireguard_instances = WireGuardInstance.objects.all().order_by('instance_id')
if wireguard_instances.filter(legacy_firewall=True).exists():
return redirect('/firewall/migration_required/')
firewall_settings, firewall_settings_created = FirewallSettings.objects.get_or_create(name='global') firewall_settings, firewall_settings_created = FirewallSettings.objects.get_or_create(name='global')
current_chain = request.GET.get('chain', 'forward') current_chain = request.GET.get('chain', 'forward')
if current_chain not in ['forward', 'portforward', 'postrouting']: if current_chain not in ['forward', 'portforward', 'postrouting']:
@ -81,6 +90,7 @@ def view_firewall_rule_list(request):
return render(request, 'firewall/firewall_rule_list.html', context=context) return render(request, 'firewall/firewall_rule_list.html', context=context)
@login_required
def manage_firewall_rule(request): def manage_firewall_rule(request):
if not UserAcl.objects.filter(user=request.user).filter(user_level__gte=40).exists(): if not UserAcl.objects.filter(user=request.user).filter(user_level__gte=40).exists():
return render(request, 'access_denied.html', {'page_title': 'Access Denied'}) return render(request, 'access_denied.html', {'page_title': 'Access Denied'})
@ -96,6 +106,12 @@ def manage_firewall_rule(request):
firewall_settings.pending_changes = True firewall_settings.pending_changes = True
firewall_settings.save() firewall_settings.save()
instance.delete() instance.delete()
# Marking wireguard_instance as having pending changes, not the best way to do this, but it works for now.
# I will improve it later.
wireguard_instance = WireGuardInstance.objects.all().first()
if wireguard_instance:
wireguard_instance.pending_changes = True
wireguard_instance.save()
messages.success(request, 'Firewall rule deleted successfully') messages.success(request, 'Firewall rule deleted successfully')
else: else:
messages.warning(request, 'Error deleting Firewall rule|Confirmation did not match. Firewall rule was not deleted.') messages.warning(request, 'Error deleting Firewall rule|Confirmation did not match. Firewall rule was not deleted.')
@ -111,6 +127,12 @@ def manage_firewall_rule(request):
firewall_settings.save() firewall_settings.save()
form.save() form.save()
messages.success(request, 'Firewall rule saved successfully') messages.success(request, 'Firewall rule saved successfully')
# Marking wireguard_instance as having pending changes, not the best way to do this, but it works for now.
# I will improve it later.
wireguard_instance = WireGuardInstance.objects.all().first()
if wireguard_instance:
wireguard_instance.pending_changes = True
wireguard_instance.save()
return redirect('/firewall/rule_list/?chain=' + current_chain) return redirect('/firewall/rule_list/?chain=' + current_chain)
else: else:
form = FirewallRuleForm(instance=instance, current_chain=current_chain) form = FirewallRuleForm(instance=instance, current_chain=current_chain)
@ -132,6 +154,7 @@ def manage_firewall_rule(request):
return render(request, 'firewall/manage_firewall_rule.html', context=context) return render(request, 'firewall/manage_firewall_rule.html', context=context)
@login_required
def view_manage_firewall_settings(request): def view_manage_firewall_settings(request):
if not UserAcl.objects.filter(user=request.user).filter(user_level__gte=40).exists(): if not UserAcl.objects.filter(user=request.user).filter(user_level__gte=40).exists():
return render(request, 'access_denied.html', {'page_title': 'Access Denied'}) return render(request, 'access_denied.html', {'page_title': 'Access Denied'})
@ -162,12 +185,39 @@ def view_manage_firewall_settings(request):
return render(request, 'firewall/manage_firewall_settings.html', context=context) return render(request, 'firewall/manage_firewall_settings.html', context=context)
@login_required
def view_generate_iptables_script(request): def view_generate_iptables_script(request):
data = {'status': 'ok'} data = {'status': 'ok'}
firewall_rule_list = FirewallRule.objects.all().order_by('firewall_chain', 'sort_order') #firewall_header = generate_firewall_header()
for rule in firewall_rule_list: #port_forward_firewall = generate_port_forward_firewall()
print(str(rule.sort_order) + ' - ' + str(rule.uuid)) #user_firewall = export_user_firewall()
#firewall_footer = generate_firewall_footer()
#print(port_forward_firewall)
#print(firewall_header)
#print(user_firewall)
#print(firewall_footer)
rules_text = generate_iptable_rules()
print(rules_text)
return JsonResponse(data) return JsonResponse(data)
@login_required
def view_reset_firewall(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'})
if request.GET.get('confirmation') == 'delete all rules and reset firewall':
reset_firewall_to_default()
messages.success(request, 'VPN Firewall|Firewall reset to default successfully!')
else:
messages.warning(request, 'VPN Firewall|Firewall was not reset to default. Confirmation did not match.')
return redirect('/firewall/rule_list/')
@login_required
def view_firewall_migration_required(request):
if not WireGuardInstance.objects.filter(legacy_firewall=True).exists():
messages.warning(request, 'No Firewall Migration pending|No WireGuard instances with legacy firewall settings found.')
return redirect('/firewall/rule_list/')
if not UserAcl.objects.filter(user=request.user).filter(user_level__gte=40).exists():
return render(request, 'access_denied.html', {'page_title': 'Access Denied'})
return render(request, 'firewall/firewall_migration_required.html')

View File

@ -0,0 +1,47 @@
{% extends 'base.html' %}
{% block content %}
<div class="row">
<div class="col-md-6">
<form method="post">
{% csrf_token %}
<div class="card">
<div class="card-header">
<h3 class="card-title>">Firewall migration required</h3>
</div>
<div class="card-body">
<p>
I've upgraded our firewall's functionality. The new configuration tool enhances flexibility and power, granting complete control over your firewall via the web interface.<br><br>
Please note down your PostUp and PostDown rules before migrating; they will be erased in the process.<br><br>
Although the migration tool establishes a default rule set, you will have to manually input your specific rules.
</p>
<div class="card-footer">
<a href='javascript:void(0)' class='btn btn-outline-danger' data-command='delete' onclick='openCommandDialog(this)'>Reset firewall to default</a>
</div>
</div>
</form>
</div>
</div>
{% endblock %}
{% block custom_page_scripts %}
<script>
function openCommandDialog(element) {
var command = element.getAttribute('data-command');
var confirmation = prompt("Reseting the firewall to default will remove all rules and settings. Are you sure you want to continue?\n\nType 'delete all rules and reset firewall' to confirm. ");
if (confirmation) {
var url = "/firewall/reset_to_default/?confirmation=" + encodeURIComponent(confirmation);
window.location.href = url;
}
}
</script>
{% endblock %}

View File

@ -58,6 +58,7 @@
<div class="card-footer"> <div class="card-footer">
<button type="submit" class="btn btn-primary">Submit</button> <button type="submit" class="btn btn-primary">Submit</button>
<a class="btn btn-outline-secondary" href="{{ back_url }}">Back</a> <a class="btn btn-outline-secondary" href="{{ back_url }}">Back</a>
<a href='javascript:void(0)' class='btn btn-outline-danger' data-command='delete' onclick='openCommandDialog(this)'>Reset firewall to default</a>
</div> </div>
</div> </div>
</form> </form>
@ -67,3 +68,17 @@
</div> </div>
{% endblock %} {% endblock %}
{% block custom_page_scripts %}
<script>
function openCommandDialog(element) {
var command = element.getAttribute('data-command');
var confirmation = prompt("Reseting the firewall to default will remove all rules and settings. Are you sure you want to continue?\n\nType 'delete all rules and reset firewall' to confirm. ");
if (confirmation) {
var url = "/firewall/reset_to_default/?confirmation=" + encodeURIComponent(confirmation);
window.location.href = url;
}
}
</script>
{% endblock %}

View File

@ -103,12 +103,12 @@
<!-- Line 1: Post Up --> <!-- Line 1: Post Up -->
<div class="form-group"> <div class="form-group">
<label for="{{ form.post_up.id_for_label }}">{{ form.post_up.label }}</label> <label for="{{ form.post_up.id_for_label }}">{{ form.post_up.label }}</label>
<textarea class="form-control" id="{{ form.post_up.id_for_label }}" name="{{ form.post_up.html_name }}" placeholder="Post Up" style="height: 150px;">{{ form.post_up.value|default_if_none:'' }}</textarea> <textarea class="form-control" id="{{ form.post_up.id_for_label }}" name="{{ form.post_up.html_name }}" placeholder="Post Up" style="height: 150px;" readonly>{{ form.post_up.value|default_if_none:'' }}</textarea>
</div> </div>
<!-- Line 2: Post Down --> <!-- Line 2: Post Down -->
<div class="form-group"> <div class="form-group">
<label for="{{ form.post_down.id_for_label }}">{{ form.post_down.label }}</label> <label for="{{ form.post_down.id_for_label }}">{{ form.post_down.label }}</label>
<textarea class="form-control" id="{{ form.post_down.id_for_label }}" name="{{ form.post_down.html_name }}" placeholder="Post Down" style="height: 150px;">{{ form.post_down.value|default_if_none:'' }}</textarea> <textarea class="form-control" id="{{ form.post_down.id_for_label }}" name="{{ form.post_down.html_name }}" placeholder="Post Down" style="height: 150px;" readonly>{{ form.post_down.value|default_if_none:'' }}</textarea>
</div> </div>
</div> </div>
</div> </div>
@ -124,6 +124,14 @@
</div> </div>
</div> </div>
{% endblock %}
{% block custom_page_scripts %}
<script> <script>
function openCommandDialog(element) { function openCommandDialog(element) {
var command = element.getAttribute('data-command'); var command = element.getAttribute('data-command');
@ -135,38 +143,4 @@
} }
</script> </script>
{% endblock %}
{% block custom_page_scripts %}
<script>
document.addEventListener('DOMContentLoaded', function () {
var alertShown = false;
var fieldsToWatch = ['id_address', 'id_listen_port', 'id_instance_id', 'netmask'];
function showAlert() {
if (!alertShown) {
$(document).Toasts('create', {
class: 'bg-warning',
title: 'Action Required!',
body: 'When manually updating the "IP Address", "Netmask", "Listen Port", or "Instance ID", it is essential that you also manually review and update the "PostUp" and "PostDown" scripts to ensure that the new configuration matches your firewall.',
});
alertShown = true;
}
}
fieldsToWatch.forEach(function(fieldId) {
var field = document.getElementById(fieldId);
if (field) {
field.addEventListener('change', showAlert);
}
});
});
</script>
{% endblock %} {% endblock %}

View File

@ -30,6 +30,9 @@ class WireGuardInstanceForm(forms.ModelForm):
hostname = cleaned_data.get('hostname') hostname = cleaned_data.get('hostname')
address = cleaned_data.get('address') address = cleaned_data.get('address')
netmask = cleaned_data.get('netmask') netmask = cleaned_data.get('netmask')
post_up = cleaned_data.get('post_up')
post_down = cleaned_data.get('post_down')
peer_list_refresh_interval = cleaned_data.get('peer_list_refresh_interval') peer_list_refresh_interval = cleaned_data.get('peer_list_refresh_interval')
if peer_list_refresh_interval < 10: if peer_list_refresh_interval < 10:
raise forms.ValidationError('Peer List Refresh Interval must be at least 10 seconds') raise forms.ValidationError('Peer List Refresh Interval must be at least 10 seconds')
@ -46,5 +49,13 @@ class WireGuardInstanceForm(forms.ModelForm):
if current_network.overlaps(other_network): if current_network.overlaps(other_network):
raise forms.ValidationError(f"The network range {current_network} overlaps with another instance's network range {other_network}.") raise forms.ValidationError(f"The network range {current_network} overlaps with another instance's network range {other_network}.")
#if self.instance:
# if post_up or post_down:
# if self.instance.post_up != post_up or self.instance.post_down != post_down:
# raise forms.ValidationError('Post Up and Post Down cannot be changed, please go to Firewall page to make changes to the firewall.')
#else:
# if post_up or post_down:
# raise forms.ValidationError('Post Up and Post Down cannot be set, please go to Firewall page to make changes to the firewall.')
return cleaned_data return cleaned_data

View File

@ -0,0 +1,18 @@
# Generated by Django 5.0.2 on 2024-03-04 12:54
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('wireguard', '0018_wireguardinstance_legacy_firewall'),
]
operations = [
migrations.AlterField(
model_name='wireguardinstance',
name='legacy_firewall',
field=models.BooleanField(default=False),
),
]

View File

@ -59,7 +59,7 @@ class WireGuardInstance(models.Model):
dns_primary = models.GenericIPAddressField(unique=False, protocol='IPv4', default='1.1.1.1') dns_primary = models.GenericIPAddressField(unique=False, protocol='IPv4', default='1.1.1.1')
dns_secondary = models.GenericIPAddressField(unique=False, protocol='IPv4', default='1.0.0.1', blank=True, null=True) dns_secondary = models.GenericIPAddressField(unique=False, protocol='IPv4', default='1.0.0.1', blank=True, null=True)
pending_changes = models.BooleanField(default=True) pending_changes = models.BooleanField(default=True)
legacy_firewall = models.BooleanField(default=True) legacy_firewall = models.BooleanField(default=False)
created = models.DateTimeField(auto_now_add=True) created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True) updated = models.DateTimeField(auto_now=True)

View File

@ -31,25 +31,27 @@ def generate_instance_defaults():
instance_id = new_instance_id instance_id = new_instance_id
interface_name = f"wg{instance_id}" interface_name = f"wg{instance_id}"
post_up_script = ( #post_up_script = (
f"iptables -t nat -A POSTROUTING -s {network} -o eth0 -j MASQUERADE\n" # f"iptables -t nat -A POSTROUTING -s {network} -o eth0 -j MASQUERADE\n"
f"iptables -A INPUT -p udp -m udp --dport {port} -j ACCEPT\n" # f"iptables -A INPUT -p udp -m udp --dport {port} -j ACCEPT\n"
f"iptables -A FORWARD -i {interface_name} -o eth0 -d 10.0.0.0/8 -j REJECT\n" # f"iptables -A FORWARD -i {interface_name} -o eth0 -d 10.0.0.0/8 -j REJECT\n"
f"iptables -A FORWARD -i {interface_name} -o eth0 -d 172.16.0.0/12 -j REJECT\n" # f"iptables -A FORWARD -i {interface_name} -o eth0 -d 172.16.0.0/12 -j REJECT\n"
f"iptables -A FORWARD -i {interface_name} -o eth0 -d 192.168.0.0/16 -j REJECT\n" # f"iptables -A FORWARD -i {interface_name} -o eth0 -d 192.168.0.0/16 -j REJECT\n"
f"iptables -A FORWARD -i {interface_name} -j ACCEPT\n" # f"iptables -A FORWARD -i {interface_name} -j ACCEPT\n"
f"iptables -A FORWARD -o {interface_name} -j ACCEPT" # f"iptables -A FORWARD -o {interface_name} -j ACCEPT"
) #)
post_down_script = ( #post_down_script = (
f"iptables -t nat -D POSTROUTING -s {network} -o eth0 -j MASQUERADE\n" # f"iptables -t nat -D POSTROUTING -s {network} -o eth0 -j MASQUERADE\n"
f"iptables -D INPUT -p udp -m udp --dport {port} -j ACCEPT\n" # f"iptables -D INPUT -p udp -m udp --dport {port} -j ACCEPT\n"
f"iptables -D FORWARD -i {interface_name} -o eth0 -d 10.0.0.0/8 -j REJECT\n" # f"iptables -D FORWARD -i {interface_name} -o eth0 -d 10.0.0.0/8 -j REJECT\n"
f"iptables -D FORWARD -i {interface_name} -o eth0 -d 172.16.0.0/12 -j REJECT\n" # f"iptables -D FORWARD -i {interface_name} -o eth0 -d 172.16.0.0/12 -j REJECT\n"
f"iptables -D FORWARD -i {interface_name} -o eth0 -d 192.168.0.0/16 -j REJECT\n" # f"iptables -D FORWARD -i {interface_name} -o eth0 -d 192.168.0.0/16 -j REJECT\n"
f"iptables -D FORWARD -i {interface_name} -j ACCEPT\n" # f"iptables -D FORWARD -i {interface_name} -j ACCEPT\n"
f"iptables -D FORWARD -o {interface_name} -j ACCEPT" # f"iptables -D FORWARD -o {interface_name} -j ACCEPT"
) #)
post_up_script = ''
post_down_script = ''
return { return {
'name': '', 'name': '',

View File

@ -4,6 +4,7 @@ import qrcode
import subprocess import subprocess
from django.http import HttpResponse from django.http import HttpResponse
from django.shortcuts import redirect, get_object_or_404, render from django.shortcuts import redirect, get_object_or_404, render
from firewall.tools import generate_firewall_header, generate_firewall_footer, generate_port_forward_firewall, export_user_firewall
from user_manager.models import UserAcl from user_manager.models import UserAcl
from wireguard.models import WireGuardInstance, Peer, PeerAllowedIP from wireguard.models import WireGuardInstance, Peer, PeerAllowedIP
from firewall.models import RedirectRule from firewall.models import RedirectRule
@ -46,6 +47,19 @@ def generate_peer_config(peer_uuid):
] ]
return "\n".join(config_lines) return "\n".join(config_lines)
def export_firewall_configuration():
firewall_content = generate_firewall_header()
firewall_content += generate_port_forward_firewall()
firewall_content += export_user_firewall()
firewall_content += generate_firewall_footer()
firewall_path = "/etc/wireguard/wg-firewall.sh"
with open(firewall_path, "w") as firewall_file:
firewall_file.write(firewall_content)
subprocess.run(['chmod', '+x', firewall_path], check=True)
return
@login_required @login_required
def export_wireguard_configs(request): def export_wireguard_configs(request):
if not UserAcl.objects.filter(user=request.user).filter(user_level__gte=30).exists(): if not UserAcl.objects.filter(user=request.user).filter(user_level__gte=30).exists():
@ -53,7 +67,11 @@ def export_wireguard_configs(request):
instances = WireGuardInstance.objects.all() instances = WireGuardInstance.objects.all()
base_dir = "/etc/wireguard" base_dir = "/etc/wireguard"
export_firewall_configuration()
firewall_inserted = False
for instance in instances: for instance in instances:
if instance.legacy_firewall:
post_up_processed = clean_command_field(instance.post_up) if instance.post_up else "" post_up_processed = clean_command_field(instance.post_up) if instance.post_up else ""
post_down_processed = clean_command_field(instance.post_down) if instance.post_down else "" post_down_processed = clean_command_field(instance.post_down) if instance.post_down else ""
@ -82,6 +100,17 @@ def export_wireguard_configs(request):
post_up_processed += rule_text_up post_up_processed += rule_text_up
post_down_processed += rule_text_down post_down_processed += rule_text_down
pass
else:
post_down_processed = ''
if not firewall_inserted:
post_up_processed = '/etc/wireguard/wg-firewall.sh'
firewall_inserted = True
else:
post_up_processed = ''
config_lines = [ config_lines = [
"[Interface]", "[Interface]",
f"PrivateKey = {instance.private_key}", f"PrivateKey = {instance.private_key}",

View File

@ -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 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 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 api.views import wireguard_status, cron_check_updates, cron_update_peer_latest_handshake
from firewall.views import view_redirect_rule_list, manage_redirect_rule, view_firewall_rule_list, manage_firewall_rule, view_manage_firewall_settings, view_generate_iptables_script from firewall.views import view_redirect_rule_list, manage_redirect_rule, view_firewall_rule_list, manage_firewall_rule, view_manage_firewall_settings, view_generate_iptables_script, view_reset_firewall, view_firewall_migration_required
urlpatterns = [ urlpatterns = [
@ -52,4 +52,6 @@ urlpatterns = [
path('firewall/manage_firewall_rule/', manage_firewall_rule, name='manage_firewall_rule'), path('firewall/manage_firewall_rule/', manage_firewall_rule, name='manage_firewall_rule'),
path('firewall/firewall_settings/', view_manage_firewall_settings, name='firewall_settings'), path('firewall/firewall_settings/', view_manage_firewall_settings, name='firewall_settings'),
path('firewall/generate_firewall_script/', view_generate_iptables_script, name='generate_iptables_script'), path('firewall/generate_firewall_script/', view_generate_iptables_script, name='generate_iptables_script'),
path('firewall/reset_to_default/', view_reset_firewall, name='reset_firewall'),
path('firewall/migration_required/', view_firewall_migration_required, name='firewall_migration_required')
] ]