mirror of
https://github.com/eduardogsilva/wireguard_webadmin.git
synced 2025-08-04 10:14:31 +02:00
Initial commit
This commit is contained in:
commit
ed55724808
2505 changed files with 1136655 additions and 0 deletions
0
wireguard/__init__.py
Normal file
0
wireguard/__init__.py
Normal file
23
wireguard/admin.py
Normal file
23
wireguard/admin.py
Normal file
|
@ -0,0 +1,23 @@
|
|||
from django.contrib import admin
|
||||
from .models import WireGuardInstance, Peer, PeerAllowedIP
|
||||
|
||||
|
||||
class WireGuardInstanceAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'instance_id', 'private_key', 'hostname', 'listen_port', 'address', 'netmask', 'post_up', 'post_down', 'persistent_keepalive', 'created', 'updated', 'uuid')
|
||||
search_fields = ('name', 'instance_id', 'private_key', 'hostname', 'listen_port', 'address', 'netmask', 'post_up', 'post_down', 'persistent_keepalive', 'created', 'updated', 'uuid')
|
||||
|
||||
admin.site.register(WireGuardInstance, WireGuardInstanceAdmin)
|
||||
|
||||
|
||||
class PeerAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'public_key', 'pre_shared_key', 'persistent_keepalive', 'wireguard_instance', 'created', 'updated', 'uuid')
|
||||
search_fields = ('name', 'public_key', 'pre_shared_key', 'persistent_keepalive', 'wireguard_instance', 'created', 'updated', 'uuid')
|
||||
|
||||
admin.site.register(Peer, PeerAdmin)
|
||||
|
||||
|
||||
class PeerAllowedIPAdmin(admin.ModelAdmin):
|
||||
list_display = ('peer', 'priority', 'allowed_ip', 'netmask', 'created', 'updated', 'uuid')
|
||||
search_fields = ('peer', 'priority', 'allowed_ip', 'netmask', 'created', 'updated', 'uuid')
|
||||
|
||||
admin.site.register(PeerAllowedIP, PeerAllowedIPAdmin)
|
6
wireguard/apps.py
Normal file
6
wireguard/apps.py
Normal file
|
@ -0,0 +1,6 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class WireguardConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'wireguard'
|
43
wireguard/forms.py
Normal file
43
wireguard/forms.py
Normal file
|
@ -0,0 +1,43 @@
|
|||
from django import forms
|
||||
from .models import WireGuardInstance, NETMASK_CHOICES
|
||||
from wgwadmlibrary.tools import is_valid_ip_or_hostname
|
||||
import ipaddress
|
||||
|
||||
class WireGuardInstanceForm(forms.ModelForm):
|
||||
name = forms.CharField(label='Display Name', required=False)
|
||||
instance_id = forms.IntegerField(label='Instance ID')
|
||||
private_key = forms.CharField(label='Private Key')
|
||||
hostname = forms.CharField(label='Public Address')
|
||||
listen_port = forms.IntegerField(label='Listen Port')
|
||||
address = forms.GenericIPAddressField(label='VPN IP Address')
|
||||
netmask = forms.ChoiceField(choices=NETMASK_CHOICES, label='Netmask')
|
||||
post_up = forms.CharField(label='Post Up', required=False)
|
||||
post_down = forms.CharField(label='Post Down', required=False)
|
||||
persistent_keepalive = forms.IntegerField(label='Persistent Keepalive')
|
||||
|
||||
class Meta:
|
||||
model = WireGuardInstance
|
||||
fields = [
|
||||
'name', 'instance_id', 'private_key', 'hostname', 'listen_port', 'address', 'netmask', 'post_up', 'post_down', 'persistent_keepalive'
|
||||
]
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super().clean()
|
||||
hostname = cleaned_data.get('hostname')
|
||||
address = cleaned_data.get('address')
|
||||
netmask = cleaned_data.get('netmask')
|
||||
|
||||
if not is_valid_ip_or_hostname(hostname):
|
||||
raise forms.ValidationError('Invalid hostname or IP Address')
|
||||
|
||||
current_network = ipaddress.ip_network(f"{address}/{netmask}", strict=False)
|
||||
all_other_instances = WireGuardInstance.objects.all()
|
||||
if self.instance:
|
||||
all_other_instances = all_other_instances.exclude(uuid=self.instance.uuid)
|
||||
for instance in all_other_instances:
|
||||
other_network = ipaddress.ip_network(f"{instance.address}/{instance.netmask}", strict=False)
|
||||
if current_network.overlaps(other_network):
|
||||
raise forms.ValidationError(f"The network range {current_network} overlaps with another instance's network range {other_network}.")
|
||||
|
||||
return cleaned_data
|
||||
|
63
wireguard/migrations/0001_initial.py
Normal file
63
wireguard/migrations/0001_initial.py
Normal file
|
@ -0,0 +1,63 @@
|
|||
# Generated by Django 5.0.1 on 2024-02-12 14:51
|
||||
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Peer',
|
||||
fields=[
|
||||
('name', models.CharField(blank=True, max_length=100, null=True)),
|
||||
('public_key', models.CharField(max_length=100)),
|
||||
('pre_shared_key', models.CharField(max_length=100)),
|
||||
('persistent_keepalive', models.IntegerField(default=25)),
|
||||
('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)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='WireGuardInstance',
|
||||
fields=[
|
||||
('name', models.CharField(blank=True, max_length=100, null=True)),
|
||||
('instance_id', models.PositiveIntegerField(default=0, unique=True)),
|
||||
('private_key', models.CharField(max_length=100)),
|
||||
('hostname', models.CharField(max_length=100)),
|
||||
('listen_port', models.IntegerField(default=51820, unique=True)),
|
||||
('address', models.GenericIPAddressField(protocol='IPv4', unique=True)),
|
||||
('netmask', models.IntegerField(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=24)),
|
||||
('post_up', models.TextField(blank=True, null=True)),
|
||||
('post_down', models.TextField(blank=True, null=True)),
|
||||
('persistent_keepalive', models.IntegerField(default=25)),
|
||||
('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)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='PeerAllowedIP',
|
||||
fields=[
|
||||
('priority', models.PositiveBigIntegerField(default=1)),
|
||||
('allowed_ip', models.GenericIPAddressField(protocol='IPv4')),
|
||||
('netmask', models.IntegerField(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)),
|
||||
('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)),
|
||||
('peer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='wireguard.peer')),
|
||||
],
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='peer',
|
||||
name='wireguard_instance',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='wireguard.wireguardinstance'),
|
||||
),
|
||||
]
|
18
wireguard/migrations/0002_peer_private_key.py
Normal file
18
wireguard/migrations/0002_peer_private_key.py
Normal file
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 5.0.1 on 2024-02-14 00:32
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('wireguard', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='peer',
|
||||
name='private_key',
|
||||
field=models.CharField(blank=True, max_length=100, null=True),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 5.0.1 on 2024-02-14 14:28
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('wireguard', '0002_peer_private_key'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='peerallowedip',
|
||||
name='missing_from_wireguard',
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
]
|
0
wireguard/migrations/__init__.py
Normal file
0
wireguard/migrations/__init__.py
Normal file
87
wireguard/models.py
Normal file
87
wireguard/models.py
Normal file
|
@ -0,0 +1,87 @@
|
|||
from django.db import models
|
||||
import uuid
|
||||
|
||||
NETMASK_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)'),
|
||||
)
|
||||
|
||||
|
||||
class WireGuardInstance(models.Model):
|
||||
name = models.CharField(max_length=100, blank=True, null=True)
|
||||
instance_id = models.PositiveIntegerField(unique=True, default=0)
|
||||
private_key = models.CharField(max_length=100)
|
||||
hostname = models.CharField(max_length=100)
|
||||
listen_port = models.IntegerField(default=51820, unique=True)
|
||||
address = models.GenericIPAddressField(unique=True, protocol='IPv4')
|
||||
netmask = models.IntegerField(default=24, choices=NETMASK_CHOICES)
|
||||
post_up = models.TextField(blank=True, null=True)
|
||||
post_down = models.TextField(blank=True, null=True)
|
||||
persistent_keepalive = models.IntegerField(default=25)
|
||||
|
||||
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):
|
||||
if self.name:
|
||||
return self.name
|
||||
else:
|
||||
return 'wg' + str(self.instance_id)
|
||||
|
||||
|
||||
class Peer(models.Model):
|
||||
name = models.CharField(max_length=100, blank=True, null=True)
|
||||
public_key = models.CharField(max_length=100)
|
||||
pre_shared_key = models.CharField(max_length=100)
|
||||
private_key = models.CharField(max_length=100, blank=True, null=True)
|
||||
persistent_keepalive = models.IntegerField(default=25)
|
||||
wireguard_instance = models.ForeignKey(WireGuardInstance, on_delete=models.CASCADE)
|
||||
|
||||
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):
|
||||
if self.name:
|
||||
return self.name
|
||||
else:
|
||||
return self.public_key
|
||||
|
||||
|
||||
class PeerAllowedIP(models.Model):
|
||||
peer = models.ForeignKey(Peer, on_delete=models.CASCADE)
|
||||
priority = models.PositiveBigIntegerField(default=1)
|
||||
allowed_ip = models.GenericIPAddressField(protocol='IPv4')
|
||||
netmask = models.IntegerField(default=32, choices=NETMASK_CHOICES)
|
||||
missing_from_wireguard = models.BooleanField(default=False)
|
||||
|
||||
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.allowed_ip) + '/' + str(self.netmask)
|
||||
|
3
wireguard/tests.py
Normal file
3
wireguard/tests.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
123
wireguard/views.py
Normal file
123
wireguard/views.py
Normal file
|
@ -0,0 +1,123 @@
|
|||
from django.shortcuts import render, get_object_or_404, redirect
|
||||
|
||||
from wireguard.forms import WireGuardInstanceForm
|
||||
from .models import WireGuardInstance
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib import messages
|
||||
from django.db import models
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
|
||||
def generate_instance_defaults():
|
||||
max_instance_id = WireGuardInstance.objects.all().aggregate(models.Max('instance_id'))['instance_id__max']
|
||||
new_instance_id = (max_instance_id + 1) if max_instance_id is not None else 0
|
||||
|
||||
max_listen_port = WireGuardInstance.objects.all().aggregate(models.Max('listen_port'))['listen_port__max']
|
||||
new_listen_port = (max_listen_port + 1) if max_listen_port is not None else 51820
|
||||
|
||||
new_private_key = subprocess.check_output('wg genkey', shell=True).decode('utf-8').strip()
|
||||
|
||||
new_address = f'10.188.{new_instance_id}.1'
|
||||
|
||||
address_parts = new_address.split('.')
|
||||
if len(address_parts) == 4:
|
||||
network = f"10.{address_parts[1]}.{address_parts[2]}.0/24"
|
||||
port = new_listen_port
|
||||
instance_id = new_instance_id
|
||||
interface_name = f"wg{instance_id}"
|
||||
|
||||
post_up_script = (
|
||||
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 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 192.168.0.0/16 -j REJECT\n"
|
||||
f"iptables -A FORWARD -i {interface_name} -j ACCEPT\n"
|
||||
f"iptables -A FORWARD -o {interface_name} -j ACCEPT"
|
||||
)
|
||||
|
||||
post_down_script = (
|
||||
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 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 192.168.0.0/16 -j REJECT\n"
|
||||
f"iptables -D FORWARD -i {interface_name} -j ACCEPT\n"
|
||||
f"iptables -D FORWARD -o {interface_name} -j ACCEPT"
|
||||
)
|
||||
|
||||
return {
|
||||
'name': '',
|
||||
'instance_id': new_instance_id,
|
||||
'listen_port': new_listen_port,
|
||||
'private_key': new_private_key,
|
||||
'address': new_address,
|
||||
'netmask': 24,
|
||||
'persistent_keepalive': 25,
|
||||
'hostname': 'myserver.example.com',
|
||||
'post_up': post_up_script,
|
||||
'post_down': post_down_script,
|
||||
}
|
||||
|
||||
|
||||
@login_required
|
||||
def view_welcome(request):
|
||||
page_title = 'Welcome'
|
||||
breadcrumb = {'level2': {'title': 'Place holder', 'href': '/blabla'}}
|
||||
context = {'page_title': page_title, 'breadcrumb': breadcrumb}
|
||||
return render(request, 'wireguard/welcome.html', context)
|
||||
|
||||
|
||||
@login_required
|
||||
def view_wireguard_status(request):
|
||||
page_title = 'WireGuard Status'
|
||||
context = {'page_title': page_title}
|
||||
return render(request, 'wireguard/wireguard_status.html', context)
|
||||
|
||||
|
||||
@login_required
|
||||
def view_wireguard_manage_instance(request):
|
||||
wireguard_instances = WireGuardInstance.objects.all().order_by('instance_id')
|
||||
if request.GET.get('uuid'):
|
||||
current_instance = get_object_or_404(WireGuardInstance, uuid=request.GET.get('uuid'))
|
||||
else:
|
||||
if request.GET.get('action') == 'create':
|
||||
current_instance = None
|
||||
else:
|
||||
current_instance = wireguard_instances.first()
|
||||
if current_instance:
|
||||
page_title = f'wg{current_instance.instance_id}'
|
||||
message_title = 'Update WireGuard Instance'
|
||||
if current_instance.name:
|
||||
page_title += f' ({current_instance.name})'
|
||||
if request.GET.get('action') == 'delete':
|
||||
message_title = 'Delete WireGuard Instance'
|
||||
if request.GET.get('confirmation') == 'delete wg' + str(current_instance.instance_id):
|
||||
if current_instance.peer_set.all().count() > 0:
|
||||
messages.warning(request, 'Error removing wg' +str(current_instance.instance_id) + '|Cannot delete WireGuard instance wg' + str(current_instance.instance_id) + '. There are still peers associated with this instance.')
|
||||
return redirect('/server/manage/?uuid=' + str(current_instance.uuid))
|
||||
current_instance.delete()
|
||||
messages.success(request, message_title + '|WireGuard instance wg' + str(current_instance.instance_id) + ' deleted successfully.')
|
||||
return redirect('/server/manage/')
|
||||
else:
|
||||
messages.warning(request, 'Invalid confirmation' + '|Please confirm deletion of WireGuard instance wg' + str(current_instance.instance_id))
|
||||
return redirect('/server/manage/?uuid=' + str(current_instance.uuid))
|
||||
|
||||
else:
|
||||
page_title = 'Create a new WireGuard Instance'
|
||||
message_title = 'New WireGuard Instance'
|
||||
|
||||
if request.method == 'POST':
|
||||
form = WireGuardInstanceForm(request.POST, instance=current_instance)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
messages.success(request, message_title + '|WireGuard instance wg' + str(form.instance.instance_id) + ' saved successfully.')
|
||||
return redirect('/server/manage/?uuid=' + str(form.instance.uuid))
|
||||
else:
|
||||
if not current_instance:
|
||||
form = WireGuardInstanceForm(initial=generate_instance_defaults())
|
||||
else:
|
||||
form = WireGuardInstanceForm(instance=current_instance)
|
||||
context = {'page_title': page_title, 'wireguard_instances': wireguard_instances, 'current_instance': current_instance, 'form': form}
|
||||
return render(request, 'wireguard/wireguard_manage_server.html', context)
|
Loading…
Add table
Add a link
Reference in a new issue