Implement config generator and other things
This commit is contained in:
parent
0de4709c30
commit
0b751b841a
6 changed files with 153 additions and 4 deletions
|
@ -22,13 +22,16 @@ class Network(models.Model):
|
|||
|
||||
class Device(models.Model):
|
||||
serial = models.CharField("Device Serial Number", max_length=12, unique=True)
|
||||
name = models.CharField("Common Name", max_length=100, blank=True, null=True)
|
||||
name = models.CharField("Common Name", max_length=100, default="", blank=True, null=True)
|
||||
organization = models.ForeignKey(Organization, on_delete=models.CASCADE)
|
||||
network = models.ForeignKey(Network, on_delete=models.SET_NULL, blank=True, null=True)
|
||||
curip = models.CharField("Current IP Address", max_length=15, blank=True, null=True)
|
||||
lasttime = models.DateTimeField("Last Received Heartbeat", blank=True, null=True)
|
||||
secret = models.CharField("Secret", default=getRandom, max_length=128)
|
||||
password = models.CharField("Device Password", default=getRandom, max_length=128)
|
||||
vpnconfig = models.TextField("VPN Configuration", blank=True, null=True, editable=False)
|
||||
reboot = models.BooleanField("Trigger Reboot", default=False)
|
||||
|
||||
def __str__(self):
|
||||
return "%s%s" % (self.serial, " (%s)" % self.curip if self.curip else "")
|
||||
|
||||
|
|
|
@ -37,6 +37,11 @@
|
|||
<li class="nav-item">
|
||||
<a class="nav-link" href="/devices">Devices</a>
|
||||
</li>
|
||||
{% if user.is_superuser %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/admin">Backend</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/accounts/logout">Logout</a>
|
||||
</li>
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
<th>Network</th>
|
||||
<th>Latest IP</th>
|
||||
<th>Secret</th>
|
||||
<th>Options</th>
|
||||
<th>Options {% if user.is_superuser %}<a href="/makedevice/" style="font-weight:bold;color:green;"><i class="fas fa-plus" title="Add Device"></i></a>{% endif %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
|
@ -25,7 +25,7 @@
|
|||
<td>{{ device.network }}</td>
|
||||
<td><div style="display:inline" id="{{ device.id }}-ip">{% if device.curip %}{{ device.curip }} (at {{ device.lasttime }}){% endif %}</div></td>
|
||||
<td>{{ device.secret }}</td>
|
||||
<td><a href="/devices/{{ device.id }}/edit"><i class="fas fa-edit"></i></a></td>
|
||||
<td><a href="/devices/{{ device.id }}/edit"><i class="fas fa-edit" title="Edit Device"></i></a> <a href="#"><i style="color: green;" onclick="askreboot({{ device.id }});" class="fas fa-sync" title="Reboot Device"></i></a>{% if user.is_superuser %} <a href="/devices/{{ device.id }}/download"><i class="fas fa-download" title="Download Configuration"></i></a> <a href="#"><i style="color: darkred;" onclick="askdelete({{ device.id }});" class="fas fa-trash-alt" title="Delete Device"></i></a>{% endif %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
|
|
|
@ -8,5 +8,9 @@ urlpatterns = [
|
|||
path('hosts', views.hosts, name='hosts'),
|
||||
path('devices/<int:device_id>/edit/', views.editdevice, name='editdevice'),
|
||||
path('devices/<int:device_id>/ping/', views.ping, name="ping"),
|
||||
path('heartbeat', views.heartbeat, name='heartbeat')
|
||||
path('devices/<int:device_id>/download/', views.getconfig, name="getconfig"),
|
||||
path('devices/<int:device_id>/delete/', views.deletedevice, name="deletedevice"),
|
||||
path('devices/<int:device_id>/reboot/', views.rebootdevice, name="rebootdevice"),
|
||||
path('heartbeat', views.heartbeat, name='heartbeat'),
|
||||
path('makedevice/', views.makedevice, name="makedevice")
|
||||
]
|
||||
|
|
129
manager/views.py
129
manager/views.py
|
@ -4,10 +4,20 @@ from django.contrib.auth.forms import AuthenticationForm
|
|||
from django.db.models import Q
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.utils import timezone
|
||||
from django.core.files import File
|
||||
from django.db.models.fields.files import FieldFile
|
||||
from .models import Device, Organization, Network
|
||||
|
||||
from distutils.dir_util import copy_tree
|
||||
|
||||
import glob
|
||||
import sys
|
||||
import subprocess
|
||||
import os
|
||||
import socket
|
||||
import tempfile
|
||||
import crypt
|
||||
import tarfile
|
||||
|
||||
def index(request):
|
||||
if request.user.is_authenticated:
|
||||
|
@ -112,3 +122,122 @@ def editdevice(request, device_id):
|
|||
|
||||
else:
|
||||
return redirect("/")
|
||||
|
||||
def getconfig(request, device_id):
|
||||
DEVICEDIR = "/opt/vpnmanager/device-config/"
|
||||
|
||||
if not request.user.is_superuser:
|
||||
return redirect("/")
|
||||
|
||||
device = get_object_or_404(Device, id=device_id)
|
||||
|
||||
tempdir = tempfile.TemporaryDirectory()
|
||||
|
||||
copy_tree(DEVICEDIR, tempdir.name)
|
||||
|
||||
# Write OpenVPN config
|
||||
|
||||
with open(tempdir.name + "/etc/openvpn/client.conf", "w") as vpnconf:
|
||||
vpnconf.write(device.vpnconfig)
|
||||
|
||||
# Write secret
|
||||
|
||||
with open(tempdir.name + "/etc/vpnsecret", "w") as secret:
|
||||
secret.write('SECRET="%s"' % device.secret)
|
||||
|
||||
# Write password
|
||||
|
||||
with open(tempdir.name + "/etc/shadow", "r") as shadow:
|
||||
password = crypt.crypt(device.password, crypt.mksalt(crypt.METHOD_MD5))
|
||||
shadowin = shadow.read()
|
||||
|
||||
with open(tempdir.name + "/etc/shadow", "w") as shadowout:
|
||||
shadowout.write(shadowin.replace("$PASSWORD", password))
|
||||
|
||||
# Write SSID
|
||||
|
||||
with open(tempdir.name + "/etc/config/wireless", "r") as wireless:
|
||||
wirein = wireless.read()
|
||||
|
||||
with open(tempdir.name + "/etc/config/wireless", "w") as wireout:
|
||||
wireout.write(wirein.replace("$SSID", device.serial))
|
||||
|
||||
# Generate .tar.gz file
|
||||
|
||||
with tarfile.open(tempdir.name + ".tar.gz", "w:gz") as tar:
|
||||
tar.add(tempdir.name, arcname=os.path.sep)
|
||||
|
||||
with open(tempdir.name + ".tar.gz", "rb") as download:
|
||||
response = HttpResponse(download.read(), content_type="application/tar+gzip")
|
||||
response['Content-Disposition'] = 'inline; filename=' + os.path.basename(device.serial + ".tar.gz")
|
||||
return response
|
||||
|
||||
def rebootdevice(request, device_id):
|
||||
if request.user.is_authenticated:
|
||||
device = None
|
||||
for organization in Organization.objects.filter(users=request.user):
|
||||
device = device or Device.objects.filter(id=device_id, organization=organization)
|
||||
|
||||
if not device:
|
||||
return redirect("/")
|
||||
|
||||
device[0].reboot = True
|
||||
device[0].save()
|
||||
|
||||
return redirect("/")
|
||||
|
||||
def deletedevice(request, device_id):
|
||||
if request.user.is_superuser:
|
||||
device = get_object_or_404(Device, id=device_id)
|
||||
device.delete()
|
||||
|
||||
return redirect("/")
|
||||
|
||||
def makedevice(request):
|
||||
CADIR = "/etc/openvpn/ca/"
|
||||
CONFIGDIR = "/etc/openvpn/client-configs/"
|
||||
BEFORE = os.getcwd()
|
||||
|
||||
device_serial = request.POST.get("serial", "")
|
||||
device_name = request.POST.get("name", "")
|
||||
device_organization = request.POST.get("organization", "")
|
||||
|
||||
if not request.user.is_superuser:
|
||||
return redirect("/")
|
||||
|
||||
if not device_serial:
|
||||
orga = Organization.objects.all()
|
||||
|
||||
return render(request, "manager/add.html",
|
||||
{
|
||||
"title": "Add Device",
|
||||
"organizations": orga,
|
||||
}
|
||||
)
|
||||
|
||||
if glob.glob(CADIR + "/keys/" + device_serial + "*"):
|
||||
return HttpResponse("This key already exists.")
|
||||
|
||||
os.chdir(CADIR)
|
||||
|
||||
if subprocess.call(CADIR + "/generate-key " + device_serial, shell=True):
|
||||
return HttpResponse("Something went wrong trying to generate the key.")
|
||||
|
||||
if glob.glob(CONFIGDIR + "/files/" + device_serial + "*"):
|
||||
return HttpResponse("This configuration file already exists.")
|
||||
|
||||
os.chdir(CONFIGDIR)
|
||||
|
||||
if subprocess.call(CONFIGDIR + "/make_config " + device_serial, shell=True):
|
||||
return HttpResponse("Something went wrong trying to generate the config file.")
|
||||
|
||||
os.chdir(BEFORE)
|
||||
|
||||
device = Device.objects.create(
|
||||
serial=device_serial,
|
||||
name=device_name,
|
||||
organization=Organization.objects.filter(id=device_organization)[0],
|
||||
vpnconfig = open(CONFIGDIR + "/files/" + device_serial + ".ovpn").read()
|
||||
)
|
||||
|
||||
return redirect("/")
|
||||
|
|
|
@ -29,3 +29,11 @@ function styleStatus(msg, device) {
|
|||
function updateStatus(device_id) {
|
||||
$.getJSON( "/devices/" + device_id + "/ping/", function(json) { styleStatus(json, device_id); });
|
||||
};
|
||||
|
||||
function askdelete(device_id) {
|
||||
if (confirm("Are you sure you want to delete this device?")) window.location.href = "/devices/" + device_id + "/delete";
|
||||
};
|
||||
|
||||
function askreboot(device_id) {
|
||||
if (confirm("Are you sure you want to reboot this device?")) window.location.href = "/devices/" + device_id + "/reboot";
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue