Add user permissions for reload and restart

This commit is contained in:
Eduardo Silva 2025-04-11 11:05:20 -03:00
parent b140362e86
commit a58d233546
9 changed files with 114 additions and 23 deletions

View File

@ -251,16 +251,26 @@
Your WireGuard settings have been modified. To apply these changes, please update the configuration and reload the WireGuard service. Your WireGuard settings have been modified. To apply these changes, please update the configuration and reload the WireGuard service.
</p> </p>
<p> <p>
<a href="/tools/export_wireguard_config/?action=update_and_restart" class="btn btn-secondary">Update and restart service</a> <a
<a href="/tools/export_wireguard_config/?action=update_and_reload" class="btn btn-secondary">Update and reload service</a> {% if user_acl.enable_restart %}
href="/tools/export_wireguard_config/?action=update_and_restart" class="btn btn-secondary"
{% else %}
href="#" class="btn btn-secondary disabled"
{% endif %}
>Update and restart service</a>
<a
{% if user_acl.enable_reload %}
href="/tools/export_wireguard_config/?action=update_and_reload" class="btn btn-secondary"
{% else %}
href="#" class="btn btn-secondary disabled"
{% endif %}
>Update and reload service</a>
</p> </p>
</div> </div>
{% endif %} {% endif %}
{% block content %}{% endblock %} {% block content %}{% endblock %}
</div><!-- /.container-fluid --> </div><!-- /.container-fluid -->
</section> </section>

View File

@ -1,15 +1,14 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block content %} {% block content %}
<table class="table table-striped"> <table class="table table-striped table-bordered">
<thead> <thead>
<tr> <tr>
<th>Username</th> <th>Username</th>
<th>User Level</th> <th>User Level</th>
<th>Peer Groups</th> <th>Peer Groups</th>
<th></th> <th colspan="4">Permissions</th>
<th></th> <th><i class="far fa-edit"></i></th>
<th></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -26,16 +25,29 @@
Any Any
{% endif %} {% endif %}
</td> </td>
<td style="width: 1%; white-space: nowrap;">
{% if user_acl.enable_restart %}
<i class="fas fa-power-off" title="Restart Enabled"></i>
{% endif %}
</td>
<td style="width: 1%; white-space: nowrap;">
{% if user_acl.enable_reload %}
<i class="fas fa-sync-alt" title="Reload Enabled"></i>
{% endif %}
</td>
<td style="width: 1%; white-space: nowrap;"> <td style="width: 1%; white-space: nowrap;">
{% if user_acl.enable_console %} {% if user_acl.enable_console %}
<i class="fas fa-terminal" title="Console Enabled"></i> <i class="fas fa-terminal" title="Console Enabled"></i>
{% endif %} {% endif %}
</td> </td>
<td style="width: 1%; white-space: nowrap;"> <td style="width: 1%; white-space: nowrap;">
{% if user_acl.enable_enhanced_filter %} {% if user_acl.enable_enhanced_filter %}
<i class="fas fa-eye-slash" title="Enhanced Filter Enabled"></i> <i class="fas fa-eye-slash" title="Enhanced Filter Enabled"></i>
{% endif %} {% endif %}
</td> </td>
<td style="width: 1%; white-space: nowrap;"> <td style="width: 1%; white-space: nowrap;">
<a href="/user/manage/?uuid={{ user_acl.uuid }}" ><i class="far fa-edit"></i></a> <a href="/user/manage/?uuid={{ user_acl.uuid }}" ><i class="far fa-edit"></i></a>
</td> </td>

View File

@ -7,8 +7,21 @@
<div class="card card-primary card-outline"> <div class="card card-primary card-outline">
<div class="card-header"> <div class="card-header">
<a href='/tools/export_wireguard_config/' class='btn btn-outline-primary'>Update Configuration</a> <a href='/tools/export_wireguard_config/' class='btn btn-outline-primary'>Update Configuration</a>
<a href='/tools/restart_wireguard/' class='btn btn-outline-primary'>Restart Wireguard service</a> <a
<a href='/tools/restart_wireguard/?mode=reload' class='btn btn-outline-primary'>Reload Wireguard service</a> {% if user_acl.enable_restart %}
href='/tools/restart_wireguard/' class='btn btn-outline-primary'
{% else %}
href='#' class='btn btn-outline-primary disabled'
{% endif %}
>Restart Wireguard service</a>
<a
{% if user_acl.enable_reload %}
href='/tools/restart_wireguard/?mode=reload' class='btn btn-outline-primary'
{% else %}
href='#' class='btn btn-outline-primary disabled'
{% endif %}
>Reload Wireguard service</a>
<div class="btn-group float-right" role="group" aria-label="Graph interval"> <div class="btn-group float-right" role="group" aria-label="Graph interval">
<a href="?period=6h" data-period="6h" class="btn btn-outline-primary">6h</a> <a href="?period=6h" data-period="6h" class="btn btn-outline-primary">6h</a>

View File

@ -1,11 +1,11 @@
from django import forms
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User
from .models import UserAcl
from django.core.exceptions import ValidationError
from wireguard.models import PeerGroup
from crispy_forms.helper import FormHelper from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, Row, Column, Submit, HTML from crispy_forms.layout import Column, HTML, Layout, Row, Submit
from django import forms
from django.contrib.auth.models import User
from django.core.exceptions import ValidationError
from wireguard.models import PeerGroup
from .models import UserAcl
class UserAclForm(forms.Form): class UserAclForm(forms.Form):
@ -13,6 +13,8 @@ class UserAclForm(forms.Form):
password1 = forms.CharField(widget=forms.PasswordInput, required=False, label="Password") password1 = forms.CharField(widget=forms.PasswordInput, required=False, label="Password")
password2 = forms.CharField(widget=forms.PasswordInput, required=False, label="Password confirmation") password2 = forms.CharField(widget=forms.PasswordInput, required=False, label="Password confirmation")
enable_console = forms.BooleanField(required=False, label="Enable Console") enable_console = forms.BooleanField(required=False, label="Enable Console")
enable_reload = forms.BooleanField(required=False, label="Enable Reload")
enable_restart = forms.BooleanField(required=False, label="Enable Restart")
enable_enhanced_filter = forms.BooleanField(required=False, label="Enable Enhanced Filter") enable_enhanced_filter = forms.BooleanField(required=False, label="Enable Enhanced Filter")
user_level = forms.ChoiceField(choices=UserAcl.user_level.field.choices, required=True, label="User Level") user_level = forms.ChoiceField(choices=UserAcl.user_level.field.choices, required=True, label="User Level")
peer_groups = forms.ModelMultipleChoiceField( peer_groups = forms.ModelMultipleChoiceField(
@ -30,14 +32,20 @@ class UserAclForm(forms.Form):
self.fields['username'].widget.attrs['readonly'] = True self.fields['username'].widget.attrs['readonly'] = True
self.fields['peer_groups'].initial = self.instance.useracl.peer_groups.all() self.fields['peer_groups'].initial = self.instance.useracl.peer_groups.all()
self.fields['enable_console'].initial = self.instance.useracl.enable_console self.fields['enable_console'].initial = self.instance.useracl.enable_console
self.fields['enable_reload'].initial = self.instance.useracl.enable_reload
self.fields['enable_restart'].initial = self.instance.useracl.enable_restart
self.fields['enable_enhanced_filter'].initial = self.instance.useracl.enable_enhanced_filter self.fields['enable_enhanced_filter'].initial = self.instance.useracl.enable_enhanced_filter
else: else:
self.fields['password1'].required = True self.fields['password1'].required = True
self.fields['password2'].required = True self.fields['password2'].required = True
self.fields['enable_console'].initial = True self.fields['enable_console'].initial = True
self.fields['enable_reload'].initial = True
self.fields['enable_restart'].initial = True
self.fields['enable_enhanced_filter'].initial = False self.fields['enable_enhanced_filter'].initial = False
self.fields['enable_console'].label = "Console" self.fields['enable_console'].label = "Console"
self.fields['enable_reload'].label = "Reload"
self.fields['enable_restart'].label = "Restart"
self.fields['enable_enhanced_filter'].label = "Enhanced Filter" self.fields['enable_enhanced_filter'].label = "Enhanced Filter"
self.helper = FormHelper() self.helper = FormHelper()
@ -73,6 +81,14 @@ class UserAclForm(forms.Form):
Column('enable_console', css_class='form-group col-md-12 mb-0'), Column('enable_console', css_class='form-group col-md-12 mb-0'),
css_class='form-row' css_class='form-row'
), ),
Row(
Column('enable_reload', css_class='form-group col-md-12 mb-0'),
css_class='form-row'
),
Row(
Column('enable_restart', css_class='form-group col-md-12 mb-0'),
css_class='form-row'
),
Row( Row(
Column('enable_enhanced_filter', css_class='form-group col-md-12 mb-0'), Column('enable_enhanced_filter', css_class='form-group col-md-12 mb-0'),
css_class='form-row' css_class='form-row'
@ -118,6 +134,8 @@ class UserAclForm(forms.Form):
user_level = self.cleaned_data['user_level'] user_level = self.cleaned_data['user_level']
peer_groups = self.cleaned_data.get('peer_groups', []) peer_groups = self.cleaned_data.get('peer_groups', [])
enable_console = self.cleaned_data.get('enable_console', False) enable_console = self.cleaned_data.get('enable_console', False)
enable_reload = self.cleaned_data.get('enable_reload', False)
enable_restart = self.cleaned_data.get('enable_restart', False)
enable_enhanced_filter = self.cleaned_data.get('enable_enhanced_filter', False) enable_enhanced_filter = self.cleaned_data.get('enable_enhanced_filter', False)
if self.instance: if self.instance:
@ -136,6 +154,8 @@ class UserAclForm(forms.Form):
defaults={ defaults={
'user_level': user_level, 'user_level': user_level,
'enable_console': enable_console, 'enable_console': enable_console,
'enable_reload': enable_reload,
'enable_restart': enable_restart,
'enable_enhanced_filter': enable_enhanced_filter 'enable_enhanced_filter': enable_enhanced_filter
} }
) )

View File

@ -0,0 +1,23 @@
# Generated by Django 5.2 on 2025-04-11 13:18
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('user_manager', '0005_useracl_enable_console_and_more'),
]
operations = [
migrations.AddField(
model_name='useracl',
name='enable_reload',
field=models.BooleanField(default=True),
),
migrations.AddField(
model_name='useracl',
name='enable_restart',
field=models.BooleanField(default=True),
),
]

View File

@ -1,6 +1,8 @@
from django.db import models
from django.contrib.auth.models import User
import uuid import uuid
from django.contrib.auth.models import User
from django.db import models
from wireguard.models import PeerGroup from wireguard.models import PeerGroup
@ -16,6 +18,8 @@ class UserAcl(models.Model):
peer_groups = models.ManyToManyField(PeerGroup, blank=True) peer_groups = models.ManyToManyField(PeerGroup, blank=True)
enable_console = models.BooleanField(default=True) enable_console = models.BooleanField(default=True)
enable_enhanced_filter = models.BooleanField(default=False) enable_enhanced_filter = models.BooleanField(default=False)
enable_reload = models.BooleanField(default=True)
enable_restart = models.BooleanField(default=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,9 +1,11 @@
from user_manager.models import UserAcl
from .models import WireGuardInstance from .models import WireGuardInstance
def pending_changes_warning(request): def pending_changes_warning(request):
user_acl = UserAcl.objects.filter(user=request.user).first()
if request.user.is_authenticated: if request.user.is_authenticated:
pending = WireGuardInstance.objects.filter(pending_changes=True).exists() pending = WireGuardInstance.objects.filter(pending_changes=True).exists()
else: else:
pending = False pending = False
return {'pending_changes_warning': pending} return {'pending_changes_warning': pending, 'user_acl': user_acl}

View File

@ -218,7 +218,8 @@ def download_config_or_qrcode(request):
@login_required @login_required
def restart_wireguard_interfaces(request): def restart_wireguard_interfaces(request):
if not UserAcl.objects.filter(user=request.user).filter(user_level__gte=30).exists(): user_acl = UserAcl.objects.filter(user=request.user).filter(user_level__gte=30).first()
if not user_acl:
return render(request, 'access_denied.html', {'page_title': 'Access Denied'}) return render(request, 'access_denied.html', {'page_title': 'Access Denied'})
mode = request.GET.get('mode', 'restart') mode = request.GET.get('mode', 'restart')
config_dir = "/etc/wireguard" config_dir = "/etc/wireguard"
@ -228,6 +229,9 @@ def restart_wireguard_interfaces(request):
if filename.endswith(".conf"): if filename.endswith(".conf"):
interface_name = filename[:-5] interface_name = filename[:-5]
if mode == "reload": if mode == "reload":
if not user_acl.enable_reload:
return render(request, 'access_denied.html', {'page_title': 'Access Denied'})
config_path = os.path.join(config_dir, filename) config_path = os.path.join(config_dir, filename)
with open(config_path, 'r') as f: with open(config_path, 'r') as f:
lines = f.readlines() lines = f.readlines()
@ -253,6 +257,9 @@ def restart_wireguard_interfaces(request):
interface_count += 1 interface_count += 1
else: else:
if not user_acl.enable_restart:
return render(request, 'access_denied.html', {'page_title': 'Access Denied'})
stop_command = f"wg-quick down {interface_name}" stop_command = f"wg-quick down {interface_name}"
stop_result = subprocess.run(stop_command, shell=True, capture_output=True, text=True) stop_result = subprocess.run(stop_command, shell=True, capture_output=True, text=True)
if stop_result.returncode != 0: if stop_result.returncode != 0:

View File

@ -136,6 +136,6 @@ STATICFILES_DIRS = [
DNS_CONFIG_FILE = '/etc/dnsmasq/wireguard_webadmin_dns.conf' DNS_CONFIG_FILE = '/etc/dnsmasq/wireguard_webadmin_dns.conf'
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
WIREGUARD_WEBADMIN_VERSION = 9963 WIREGUARD_WEBADMIN_VERSION = 9964
from wireguard_webadmin.production_settings import * from wireguard_webadmin.production_settings import *